Skip to content

Instantly share code, notes, and snippets.

@gregid
Created April 18, 2021 17:03
Show Gist options
  • Save gregid/e49f041ac5e8d857edec6cc6d2f0b19b to your computer and use it in GitHub Desktop.
Save gregid/e49f041ac5e8d857edec6cc6d2f0b19b to your computer and use it in GitHub Desktop.
Request with request-compose and http-signature-header. Draft Version 1
'use strict';
const crypto = require('crypto')
const {createAuthzHeader, createSignatureString} = require('http-signature-header');
const compose = require('request-compose');
const Request = compose.Request
const Response = compose.Response
const request = (spec) => compose(
Request.defaults(),
Request.url(spec.url),
split(spec),
addDigest(),
addHttpSignature(),
Request.send(),
Response.buffer(),
Response.string(),
Response.parse(),
)(spec)
const split = (spec) => ({options}) => new Promise((resolve, reject) => {
const { body } = spec
Object.assign(options, spec)
resolve({options, body})
})
const addDigest = () => ({options, body}) => new Promise((resolve, reject) => {
const { mastodonCompatible } = options;
if (!mastodonCompatible) resolve({options, body})
const digest = crypto.createHash('sha256')
.update(body)
.digest('base64')
options.headers = options.headers || {}
options.headers['Digest'] = `SHA-256=${digest}`
options.httpSignature.includeHeaders = options.httpSignature.includeHeaders || []
if (!options.httpSignature.includeHeaders.includes('digest')) {
options.httpSignature.includeHeaders.push('digest')
}
resolve({options, body})
})
const addHttpSignature = () => ({options, body}) => new Promise((resolve, reject) => {
const requestOptions = options
const authorizationHeaderName = options.httpSignature.authorizationHeaderName || 'Signature'
const algorithm = options.httpSignature.algorithm || 'rsa-sha256'
const previousHeaders = options.httpSignature.includeHeaders || []
const defaultHeaders = ['(request-target)', 'host', 'date']
const includeHeaders = defaultHeaders.concat(previousHeaders)
options.httpSignature.includeHeaders = includeHeaders
if (includeHeaders.includes('date') && !options.headers.date) {
options.headers.date = new Date().toUTCString()
}
const stringToSign = createSignatureString({includeHeaders, requestOptions})
let signer
switch (algorithm) {
case 'rsa-sha256':
signer = crypto.createSign('sha256');
break;
default:
signer = crypto.createSign('sha256');
break;
}
signer.update(stringToSign);
const signatureHash = signer.sign(options.httpSignature.key).toString('base64');
let authz = createAuthzHeader({
algorithm: algorithm,
includeHeaders: includeHeaders,
keyId: options.httpSignature.keyId,
signature: signatureHash
});
if (authorizationHeaderName.toLowerCase() === 'Signature'.toLowerCase()) {
authz = authz.substr('Signature '.length)
}
console.log('authz', authz)
options.headers[authorizationHeaderName] = authz
console.log('Headers:', options.headers)
resolve({options, body})
})
/* Example Use
const reqSpec = {
method: 'POST',
url: 'https://example.org/test',
headers: {
'Content-Type': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
},
mastodonCompatible: true,
httpSignature: {
key: '-----BEGIN RSA PRIVATE KEY-----MIIEpAIB...',
keyId: 'key-1',
},
body: 'some body here'
}
const {res, body} = await request(reqSpec)
*/
module.exports = request;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment