UNPKG

wix-auth-hmac

Version:

A Node library for signing URL's using HMAC signing for Wix projects

274 lines (256 loc) 7.32 kB
/** * @file core library for connecting to Wix REST APIs * @author David Zuckerman <davidz@wix.com> * */ var crypto = require('crypto'); var urlLib = require('url'); var _ = require('lodash-node'); function signData(mode, key, data, sanitize) { var hmac = crypto.createHmac(mode.value(), key); return sanitize ? toBase64Safe(hmac.update(data).digest('base64')) : hmac.update(data).digest('base64'); } function toBase64Safe(out, encoding) { if (out instanceof Buffer) { return toBase64Safe(out.toString((encoding !== undefined ? encoding : 'base64'))); } return out.replace(/\+/g, '-').replace(/\//g, '_'); } function removeBase64Padding(b64) { return b64.replace('=', '') } function WixPaths() { this.paths = []; } WixPaths.prototype = { withSegment: function (segment) { if (segment && segment !== null && segment.length > 0) { this.paths.push(segment); } return this; }, toString: function () { return this.paths.join('/'); } }; function WixParameters(copy) { if (copy !== undefined && copy instanceof WixParameters) { this.params = copy.params.slice(0) } else { this.params = []; } } WixParameters.prototype = { withParameter: function (name, value) { var normalizedValue; if (Array.isArray(value)) { normalizedValue = value.join(','); } else if ('string' === typeof value) { normalizedValue = value.trim(); } else { normalizedValue = value; } this.params.push({param: name, value: normalizedValue}); return this; }, getParameters: function () { return this.params; }, withParameters: function (params) { this.params = this.params.concat(params); return this; }, hasParameters : function() { return this.params.length > 0; }, toQueryString: function () { return _.reduce(this.params, function (queryOut, element) { return queryOut + ((queryOut.length > 0) ? '&' : '') + element.param + '=' + element.value; }, ''); }, toHeaderMap: function () { if (this.params.length === 0) { return null; } var r = {}; for (var i = 0; i < this.params.length; i++) { r[this.params[i].param] = this.params[i].value; } return r; } }; function Scheme(m) { this.value = function() { return m; } } var Algorithms = { "SHA256" : new Scheme("sha256"), "SHA1" : new Scheme("sha1") }; var Options = { WITH_PARAM_VALUES : "withParameterValues", PATH_PRIORITY : "pathPriority", HMAC_SCHEMA : "hmacSchema", WEBSAFE_B64 : "websafeBase64", PAD_B64 : "padB64", TRAILING_NEWLINE : "trailingNewline" } function initFromOptions(options, param, defaultValue) { return (options !== undefined && options[param] !== undefined) ? options[param] : defaultValue; } function HMACAuthRequest(url, verb, path, secretKey, options) { this.paths = new WixPaths(); this.headers = new WixParameters(); this.queryParams = new WixParameters(); this.key = secretKey; this.verb = verb; this.path = path; this.postData = null; this.url = url; this.options(Options.WITH_PARAM_VALUES, initFromOptions(options, Options.WITH_PARAM_VALUES, false)); this.options(Options.PATH_PRIORITY, initFromOptions(options, Options.PATH_PRIORITY, true)); this.options(Options.HMAC_SCHEMA, initFromOptions(options, Options.HMAC_SCHEMA, Algorithms.SHA256)); this.options(Options.WEBSAFE_B64, initFromOptions(options, Options.WEBSAFE_B64, true)); this.options(Options.TRAILING_NEWLINE, initFromOptions(options, Options.TRAILING_NEWLINE, false)); this.options(Options.PAD_B64, initFromOptions(options, Options.PAD_B64, false)); } HMACAuthRequest.prototype = { options : function(key, value) { if(key === Options.HMAC_SCHEMA) { if(!(value instanceof Scheme)) { throw "Bad HMAC scheme"; } this.cryptMode = value; } else if(key === Options.WITH_PARAM_VALUES) { this.withValues = value; } else if(key === Options.PATH_PRIORITY) { this.pathFirst = value; } else if(key === Options.WEBSAFE_B64) { this.sanitizeB64 = value; } else if(key === Options.TRAILING_NEWLINE) { this.trailingNewline = value; } else if(key === Options.PAD_B64) { this.padB64Output = value; } return this; }, withPostData: function (data) { this.postData = data; return this; }, asHeaders: function (headerPrefix) { this.paramMode = 'header'; this.headerPrefix = headerPrefix; return this; }, asQueryParams: function () { this.paramMode = 'query'; }, isHeaderMode: function () { return this.paramMode === 'header'; }, isQueryMode: function () { return this.paramMode === 'query'; }, withPathSegment: function (segment) { this.paths.withSegment(segment); return this; }, withQueryParam: function (key, value) { this.queryParams.withParameter(key, value); return this; }, withHeader: function(key, value) { this.headers.withParameter(key, value); return this; }, getHeaders: function () { var signingHeaders = new WixParameters(this.headers); var standardHeaders = new WixParameters(this.headers); if (this.isHeaderMode()) { var that = this; _.remove(signingHeaders.params, function(p) { if(p.param.length > that.headerPrefix.length) { return p.param.substring(0, that.headerPrefix.length) !== that.headerPrefix; } return false; }); } if (this.postData !== undefined && this.postData !== null) { standardHeaders.withParameter('Content-Length', JSON.stringify(this.postData).length). withParameter('Content-Type', 'application/json'); } return {signing: signingHeaders, all: standardHeaders}; }, getQueryParams: function () { return this.queryParams; }, calculateSignature: function () { var headers = this.getHeaders().signing.getParameters(); var parameters = this.getQueryParams().params.concat(headers); parameters.sort(function (a, b) { if (a.param < b.param) return -1; if (a.param > b.param) return 1; return 0; }); var pathString = urlLib.parse(this.path + this.paths.toString()).pathname; var out = this.verb + "\n"; if(this.pathFirst) { out += pathString + "\n" } if(this.withValues) { out += _.reduce(parameters, function(result, value) { result.push(value.param + ":" + value.value); return result; }, []).join('\n'); } else { out += _.pluck(parameters, 'value').join('\n'); } if (this.postData) { out += "\n" + this.postData; } if(!this.pathFirst) { out += "\n" + pathString } if(this.trailingNewline) { out += "\n"; } var sig = signData(this.cryptMode, this.key, out, this.sanitizeB64); if(!this.padB64Output) { sig = removeBase64Padding(sig); } return sig; }, toRequestAuth : function(signature) { return signature; }, toHttpsOptions: function (signatureKey) { var sig = this.calculateSignature(); var headers = this.getHeaders(); var query = this.getQueryParams(); var needsAuth = query; if (this.isHeaderMode()) { needsAuth = headers.all; } needsAuth.withParameter(signatureKey, this.toRequestAuth(sig)); var path = this.path + this.paths.toString(); if(query.hasParameters()) { path += '?' + query.toQueryString(); } return { host: this.url, path: path, method: this.verb, headers: headers.all.toHeaderMap() }; } }; module.exports = { HMACAuthRequest : HMACAuthRequest, Utils : { toWebSafeB64 : toBase64Safe, signData : signData }, Algorithms : Algorithms, Options : Options };