Full Example
API Signed Request Examples
const buffer = require('buffer');
const crypto = require('crypto');
/* Message Signing Helpers */
/**
* Convert a string to an array buffer
* @param {string} str
* @returns {string}
*/
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
/**
* Convert a string to base64-url safe encoding
* @param {string} str
* @returns {string}
*/
function base64UrlEncode(str) {
return Buffer.from(str).toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
/**
* Import a private key and return a CryptoKey
* @param {string} privateKeyDer base64 encoded private key der
* @returns {Promise<CryptoKey>}
*/
async function getPrivateKeyFromDer(privKeyDer) {
const decoded = Buffer.from(privKeyDer, 'base64').toString('binary');
const buf = str2ab(decoded);
return crypto.subtle.importKey('pkcs8', buf, {
name: 'ECDSA',
namedCurve: 'P-256',
}, true, ['sign']);
}
/**
* Sign a message with a private CryptoKey
* @param {CryptoKey} privateKey
* @param {string} message The message to sign
* @returns {string} The signed message
*/
async function signMessage(privateKey, message) {
return crypto.subtle.sign(
{
name: 'ECDSA',
hash: 'SHA-256'
},
privateKey,
new TextEncoder().encode(message)
);
}
/**
* Create a request signature
* @param {CryptoKey} privateKey
* @param {string} date HTTP header date value
* @param {string} method HTTP method value
* @param {string} path HTTP path
* @param {string} [queryString] HTTP path query string (? not included)
*/
async function createRequestSignature({
privateKey,
date,
method,
path,
queryString = undefined
}) {
const nonce = Date.now().toString();
const sigParts = [
method.toUpperCase(),
path,
queryString,
date,
nonce
].filter(str => str && str.length !== 0);
const signature = await signMessage(privateKey, sigParts.join('\n'));
return base64UrlEncode(signature) + '.' + base64UrlEncode(nonce);
}
/* HTTP Request */
const API_URL = 'https://int.quadrata.com';
const API_KEY = '...';
/**
* Send a signed request
* @param {CryptoKey} privateKey
* @param {string} method HTTP method value
* @param {string} path HTTP path
* @param {string} [queryString] HTTP path query string (? not included)
* @param {object} [body] HTTP body payload
* @param {object} [extraHeaders] Optional headers to send with the request
* @returns {Promise<Response|void>}
*/
async function makeRequest({
privateKey,
method,
path,
queryString = undefined,
body = undefined,
extraHeaders = undefined
}) {
const date = (new Date()).toUTCString();
const signature = await createRequestSignature({
privateKey,
method,
path,
queryString,
date
});
const headers = {
...extraHeaders,
'Authorization': `Basic ${Buffer.from(API_KEY).toString('base64')}`,
'Date': date,
'Signature': signature
};
let urlPath = `${API_URL}${path}`;
if (queryString && queryString.length !== 0) {
urlPath += `?${queryString}`;
}
const response = await fetch(urlPath, {
method,
headers,
body: body ? JSON.stringify(body) : undefined
});
if (!response.ok) {
console.error(response);
return;
}
return response.json();
}
/**
* Base64 Encoded Private Key DER
* @type {string}
*/
const privateKeyDer = '...';
/**
* @type {CryptoKey}
*/
const privateKey = await getPrivateKeyFromDer(privateKeyDer);
/**
* @type {string}
*/
const walletAddress = '...';
// make the request
const json = await makeRequest({
method: 'GET',
privateKey: privateKey,
path: `/api/v1/privacy/grants/${encodeURIComponent(walletAddress)}`,
});
Last updated