UNPKG

api-signature

Version:

Express/Restify middleware to authenticate HTTP requests based on api key and signature

127 lines (114 loc) 4.53 kB
/** * @author Michael Piper <hello@zeroant.co> * MIT Licensed */ const { BadAuthenticationSchemeError, BadHeaderFormatError, ExpiredRequestError, MissingRequiredHeadersError, MissingRequiredSignatureParamsError, UnsupportedAlgorithmError, MissingRequiredSignatureHeadersError } = require('./errors'); module.exports = { /** * @function * @public * @description Parse the request and extract the signature parameters * @param {Object} req The request * @param {Object} options An object with options. * @param {Array} options.algorithms A list of available algorithms * @param {Number|null} [options.requestLifetime=300] The lifetime of a request in second (set to null to disable it) * @return {Object} Signature parameters * @throws {MissingRequiredHeadersError} * @throws {MissingRequiredSignatureParamsError} * @throws {BadAuthenticationSchemeError} */ parseRequest(req, options) { if (!req.headers || !req.headers.authorization) { throw new MissingRequiredHeadersError('authorization'); } const { algorithms, requestLifetime = 300, requiredHeaders=['date'] } = options; /* Check the authorization scheme */ let { authorization } = req.headers; const scheme = 'signature'; const prefix = authorization.substring(0, scheme.length).toLowerCase(); if (prefix !== scheme) { throw new BadAuthenticationSchemeError(); } /* Get the signature parameters */ authorization = authorization.substring(scheme.length).trim(); const parts = authorization.split(','); const signatureParams = {}; for (const part of parts) { const index = part.indexOf('="'); const key = part.substring(0, index).toLowerCase(); const value = part.substring(index + 2, part.length - 1); signatureParams[key] = value; } /* Check if the signature param exists */ const requiredParams = ['keyid', 'algorithm', 'signature']; const missingSignatureParams = []; for (const param of requiredParams) { if (!signatureParams[param.toLowerCase()]) { missingSignatureParams.push(param); } } if (missingSignatureParams.length > 0) { throw new MissingRequiredSignatureParamsError(...missingSignatureParams); } /* If "headers" param not exists use the date HTTP header by default */ signatureParams.headers = signatureParams.headers ? signatureParams.headers.toLowerCase().split(' ') : ['date']; /* Check if "required headers" param does not contain date then add it */ if (requiredHeaders.indexOf('date') === -1){ requiredHeaders.push('date'); } /* Check if "required headers" param not exists use the date HTTP header by default */ requiredHeaders.forEach( /** * * @param {string[]} requiredHeader * */ function (requiredHeader){ if(signatureParams.headers.indexOf(requiredHeader) === -1){ throw new MissingRequiredSignatureHeadersError(requiredHeader); } }); /* Check algoritm */ if (algorithms.indexOf(signatureParams.algorithm) === -1) { throw new UnsupportedAlgorithmError(...algorithms); } /* Check if the request if expired */ if (signatureParams.headers.indexOf('date') !== -1 && req.headers.date && requestLifetime) { /* Check if the request is not expired */ const currentDate = new Date().getTime(); const requestDate = Date.parse(req.headers.date); if (Number.isNaN(requestDate)) { throw new BadHeaderFormatError('date', '<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT'); } if (Math.abs(currentDate - requestDate) >= requestLifetime * 1000) { throw new ExpiredRequestError(); } } /* Create the signature string */ const missingRequiredHeaders = []; signatureParams.signingString = ''; signatureParams.headers.forEach((header, index, arr) => { if (header === '(request-target)') { signatureParams.signingString += `(request-target): ${req.method.toLowerCase()} ${req.path}`; } else if (req.headers[header]) { signatureParams.signingString += `${header}: ${req.headers[header]}`; } else { missingRequiredHeaders.push(header); } if (index < arr.length - 1) { signatureParams.signingString += '\n'; } }); if (missingRequiredHeaders.length > 0) { throw new MissingRequiredHeadersError(...missingRequiredHeaders); } return signatureParams; } };