diff options
Diffstat (limited to 'deps/npm/node_modules/hawk/lib/client.js')
-rwxr-xr-x | deps/npm/node_modules/hawk/lib/client.js | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/deps/npm/node_modules/hawk/lib/client.js b/deps/npm/node_modules/hawk/lib/client.js new file mode 100755 index 0000000000..13bd77b359 --- /dev/null +++ b/deps/npm/node_modules/hawk/lib/client.js @@ -0,0 +1,391 @@ +'use strict'; + +// Load modules + +const Url = require('url'); +const Hoek = require('hoek'); +const Cryptiles = require('cryptiles'); +const Crypto = require('./crypto'); +const Utils = require('./utils'); + + +// Declare internals + +const internals = {}; + + +// Generate an Authorization header for a given request + +/* + uri: 'http://example.com/resource?a=b' or object from Url.parse() + method: HTTP verb (e.g. 'GET', 'POST') + options: { + + // Required + + credentials: { + id: 'dh37fgj492je', + key: 'aoijedoaijsdlaksjdl', + algorithm: 'sha256' // 'sha1', 'sha256' + }, + + // Optional + + ext: 'application-specific', // Application specific data sent via the ext attribute + timestamp: Date.now() / 1000, // A pre-calculated timestamp in seconds + nonce: '2334f34f', // A pre-generated nonce + localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided) + payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided) + contentType: 'application/json', // Payload content-type (ignored if hash provided) + hash: 'U4MKKSmiVxk37JCCrAVIjV=', // Pre-calculated payload hash + app: '24s23423f34dx', // Oz application id + dlg: '234sz34tww3sd' // Oz delegated-by application id + } +*/ + +exports.header = function (uri, method, options) { + + const result = { + field: '', + artifacts: {} + }; + + // Validate inputs + + if (!uri || (typeof uri !== 'string' && typeof uri !== 'object') || + !method || typeof method !== 'string' || + !options || typeof options !== 'object') { + + result.err = 'Invalid argument type'; + return result; + } + + // Application time + + const timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec); + + // Validate credentials + + const credentials = options.credentials; + if (!credentials || + !credentials.id || + !credentials.key || + !credentials.algorithm) { + + result.err = 'Invalid credential object'; + return result; + } + + if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { + result.err = 'Unknown algorithm'; + return result; + } + + // Parse URI + + if (typeof uri === 'string') { + uri = Url.parse(uri); + } + + // Calculate signature + + const artifacts = { + ts: timestamp, + nonce: options.nonce || Cryptiles.randomString(6), + method, + resource: uri.pathname + (uri.search || ''), // Maintain trailing '?' + host: uri.hostname, + port: uri.port || (uri.protocol === 'http:' ? 80 : 443), + hash: options.hash, + ext: options.ext, + app: options.app, + dlg: options.dlg + }; + + result.artifacts = artifacts; + + // Calculate payload hash + + if (!artifacts.hash && + (options.payload || options.payload === '')) { + + artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType); + } + + const mac = Crypto.calculateMac('header', credentials, artifacts); + + // Construct header + + const hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''; // Other falsey values allowed + let header = 'Hawk id="' + credentials.id + + '", ts="' + artifacts.ts + + '", nonce="' + artifacts.nonce + + (artifacts.hash ? '", hash="' + artifacts.hash : '') + + (hasExt ? '", ext="' + Hoek.escapeHeaderAttribute(artifacts.ext) : '') + + '", mac="' + mac + '"'; + + if (artifacts.app) { + header = header + ', app="' + artifacts.app + + (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"'; + } + + result.field = header; + + return result; +}; + + +// Validate server response + +/* + res: node's response object + artifacts: object received from header().artifacts + options: { + payload: optional payload received + required: specifies if a Server-Authorization header is required. Defaults to 'false' + } +*/ + +exports.authenticate = function (res, credentials, artifacts, options, callback) { + + artifacts = Hoek.clone(artifacts); + options = options || {}; + + let wwwAttributes = null; + let serverAuthAttributes = null; + + const finalize = function (err) { + + if (callback) { + const headers = { + 'www-authenticate': wwwAttributes, + 'server-authorization': serverAuthAttributes + }; + + return callback(err, headers); + } + + return !err; + }; + + if (res.headers['www-authenticate']) { + + // Parse HTTP WWW-Authenticate header + + wwwAttributes = Utils.parseAuthorizationHeader(res.headers['www-authenticate'], ['ts', 'tsm', 'error']); + if (wwwAttributes instanceof Error) { + wwwAttributes = null; + return finalize(new Error('Invalid WWW-Authenticate header')); + } + + // Validate server timestamp (not used to update clock since it is done via the SNPT client) + + if (wwwAttributes.ts) { + const tsm = Crypto.calculateTsMac(wwwAttributes.ts, credentials); + if (tsm !== wwwAttributes.tsm) { + return finalize(new Error('Invalid server timestamp hash')); + } + } + } + + // Parse HTTP Server-Authorization header + + if (!res.headers['server-authorization'] && + !options.required) { + + return finalize(); + } + + serverAuthAttributes = Utils.parseAuthorizationHeader(res.headers['server-authorization'], ['mac', 'ext', 'hash']); + if (serverAuthAttributes instanceof Error) { + serverAuthAttributes = null; + return finalize(new Error('Invalid Server-Authorization header')); + } + + artifacts.ext = serverAuthAttributes.ext; + artifacts.hash = serverAuthAttributes.hash; + + const mac = Crypto.calculateMac('response', credentials, artifacts); + if (mac !== serverAuthAttributes.mac) { + return finalize(new Error('Bad response mac')); + } + + if (!options.payload && + options.payload !== '') { + + return finalize(); + } + + if (!serverAuthAttributes.hash) { + return finalize(new Error('Missing response hash attribute')); + } + + const calculatedHash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, res.headers['content-type']); + if (calculatedHash !== serverAuthAttributes.hash) { + return finalize(new Error('Bad response payload mac')); + } + + return finalize(); +}; + + +// Generate a bewit value for a given URI + +/* + uri: 'http://example.com/resource?a=b' or object from Url.parse() + options: { + + // Required + + credentials: { + id: 'dh37fgj492je', + key: 'aoijedoaijsdlaksjdl', + algorithm: 'sha256' // 'sha1', 'sha256' + }, + ttlSec: 60 * 60, // TTL in seconds + + // Optional + + ext: 'application-specific', // Application specific data sent via the ext attribute + localtimeOffsetMsec: 400 // Time offset to sync with server time + }; +*/ + +exports.getBewit = function (uri, options) { + + // Validate inputs + + if (!uri || + (typeof uri !== 'string' && typeof uri !== 'object') || + !options || + typeof options !== 'object' || + !options.ttlSec) { + + return ''; + } + + options.ext = (options.ext === null || options.ext === undefined ? '' : options.ext); // Zero is valid value + + // Application time + + const now = Utils.now(options.localtimeOffsetMsec); + + // Validate credentials + + const credentials = options.credentials; + if (!credentials || + !credentials.id || + !credentials.key || + !credentials.algorithm) { + + return ''; + } + + if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { + return ''; + } + + // Parse URI + + if (typeof uri === 'string') { + uri = Url.parse(uri); + } + + // Calculate signature + + const exp = Math.floor(now / 1000) + options.ttlSec; + const mac = Crypto.calculateMac('bewit', credentials, { + ts: exp, + nonce: '', + method: 'GET', + resource: uri.pathname + (uri.search || ''), // Maintain trailing '?' + host: uri.hostname, + port: uri.port || (uri.protocol === 'http:' ? 80 : 443), + ext: options.ext + }); + + // Construct bewit: id\exp\mac\ext + + const bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext; + return Hoek.base64urlEncode(bewit); +}; + + +// Generate an authorization string for a message + +/* + host: 'example.com', + port: 8000, + message: '{"some":"payload"}', // UTF-8 encoded string for body hash generation + options: { + + // Required + + credentials: { + id: 'dh37fgj492je', + key: 'aoijedoaijsdlaksjdl', + algorithm: 'sha256' // 'sha1', 'sha256' + }, + + // Optional + + timestamp: Date.now() / 1000, // A pre-calculated timestamp in seconds + nonce: '2334f34f', // A pre-generated nonce + localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided) + } +*/ + +exports.message = function (host, port, message, options) { + + // Validate inputs + + if (!host || typeof host !== 'string' || + !port || typeof port !== 'number' || + message === null || message === undefined || typeof message !== 'string' || + !options || typeof options !== 'object') { + + return null; + } + + // Application time + + const timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec); + + // Validate credentials + + const credentials = options.credentials; + if (!credentials || + !credentials.id || + !credentials.key || + !credentials.algorithm) { + + // Invalid credential object + return null; + } + + if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { + return null; + } + + // Calculate signature + + const artifacts = { + ts: timestamp, + nonce: options.nonce || Cryptiles.randomString(6), + host, + port, + hash: Crypto.calculatePayloadHash(message, credentials.algorithm) + }; + + // Construct authorization + + const result = { + id: credentials.id, + ts: artifacts.ts, + nonce: artifacts.nonce, + hash: artifacts.hash, + mac: Crypto.calculateMac('message', credentials, artifacts) + }; + + return result; +}; |