react / wstein / node_modules / jest-cli / node_modules / jsdom / node_modules / request / node_modules / hawk / lib / client.js
81146 views// Load modules12var Url = require('url');3var Hoek = require('hoek');4var Cryptiles = require('cryptiles');5var Crypto = require('./crypto');6var Utils = require('./utils');789// Declare internals1011var internals = {};121314// Generate an Authorization header for a given request1516/*17uri: 'http://example.com/resource?a=b' or object from Url.parse()18method: HTTP verb (e.g. 'GET', 'POST')19options: {2021// Required2223credentials: {24id: 'dh37fgj492je',25key: 'aoijedoaijsdlaksjdl',26algorithm: 'sha256' // 'sha1', 'sha256'27},2829// Optional3031ext: 'application-specific', // Application specific data sent via the ext attribute32timestamp: Date.now(), // A pre-calculated timestamp33nonce: '2334f34f', // A pre-generated nonce34localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided)35payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided)36contentType: 'application/json', // Payload content-type (ignored if hash provided)37hash: 'U4MKKSmiVxk37JCCrAVIjV=', // Pre-calculated payload hash38app: '24s23423f34dx', // Oz application id39dlg: '234sz34tww3sd' // Oz delegated-by application id40}41*/4243exports.header = function (uri, method, options) {4445var result = {46field: '',47artifacts: {}48};4950// Validate inputs5152if (!uri || (typeof uri !== 'string' && typeof uri !== 'object') ||53!method || typeof method !== 'string' ||54!options || typeof options !== 'object') {5556result.err = 'Invalid argument type';57return result;58}5960// Application time6162var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);6364// Validate credentials6566var credentials = options.credentials;67if (!credentials ||68!credentials.id ||69!credentials.key ||70!credentials.algorithm) {7172result.err = 'Invalid credential object';73return result;74}7576if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {77result.err = 'Unknown algorithm';78return result;79}8081// Parse URI8283if (typeof uri === 'string') {84uri = Url.parse(uri);85}8687// Calculate signature8889var artifacts = {90ts: timestamp,91nonce: options.nonce || Cryptiles.randomString(6),92method: method,93resource: uri.pathname + (uri.search || ''), // Maintain trailing '?'94host: uri.hostname,95port: uri.port || (uri.protocol === 'http:' ? 80 : 443),96hash: options.hash,97ext: options.ext,98app: options.app,99dlg: options.dlg100};101102result.artifacts = artifacts;103104// Calculate payload hash105106if (!artifacts.hash &&107(options.payload || options.payload === '')) {108109artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);110}111112var mac = Crypto.calculateMac('header', credentials, artifacts);113114// Construct header115116var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''; // Other falsey values allowed117var header = 'Hawk id="' + credentials.id +118'", ts="' + artifacts.ts +119'", nonce="' + artifacts.nonce +120(artifacts.hash ? '", hash="' + artifacts.hash : '') +121(hasExt ? '", ext="' + Hoek.escapeHeaderAttribute(artifacts.ext) : '') +122'", mac="' + mac + '"';123124if (artifacts.app) {125header += ', app="' + artifacts.app +126(artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"';127}128129result.field = header;130131return result;132};133134135// Validate server response136137/*138res: node's response object139artifacts: object received from header().artifacts140options: {141payload: optional payload received142required: specifies if a Server-Authorization header is required. Defaults to 'false'143}144*/145146exports.authenticate = function (res, credentials, artifacts, options) {147148artifacts = Hoek.clone(artifacts);149options = options || {};150151if (res.headers['www-authenticate']) {152153// Parse HTTP WWW-Authenticate header154155var attributes = Utils.parseAuthorizationHeader(res.headers['www-authenticate'], ['ts', 'tsm', 'error']);156if (attributes instanceof Error) {157return false;158}159160// Validate server timestamp (not used to update clock since it is done via the SNPT client)161162if (attributes.ts) {163var tsm = Crypto.calculateTsMac(attributes.ts, credentials);164if (tsm !== attributes.tsm) {165return false;166}167}168}169170// Parse HTTP Server-Authorization header171172if (!res.headers['server-authorization'] &&173!options.required) {174175return true;176}177178var attributes = Utils.parseAuthorizationHeader(res.headers['server-authorization'], ['mac', 'ext', 'hash']);179if (attributes instanceof Error) {180return false;181}182183artifacts.ext = attributes.ext;184artifacts.hash = attributes.hash;185186var mac = Crypto.calculateMac('response', credentials, artifacts);187if (mac !== attributes.mac) {188return false;189}190191if (!options.payload &&192options.payload !== '') {193194return true;195}196197if (!attributes.hash) {198return false;199}200201var calculatedHash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, res.headers['content-type']);202return (calculatedHash === attributes.hash);203};204205206// Generate a bewit value for a given URI207208/*209uri: 'http://example.com/resource?a=b' or object from Url.parse()210options: {211212// Required213214credentials: {215id: 'dh37fgj492je',216key: 'aoijedoaijsdlaksjdl',217algorithm: 'sha256' // 'sha1', 'sha256'218},219ttlSec: 60 * 60, // TTL in seconds220221// Optional222223ext: 'application-specific', // Application specific data sent via the ext attribute224localtimeOffsetMsec: 400 // Time offset to sync with server time225};226*/227228exports.getBewit = function (uri, options) {229230// Validate inputs231232if (!uri ||233(typeof uri !== 'string' && typeof uri !== 'object') ||234!options ||235typeof options !== 'object' ||236!options.ttlSec) {237238return '';239}240241options.ext = (options.ext === null || options.ext === undefined ? '' : options.ext); // Zero is valid value242243// Application time244245var now = Utils.now(options.localtimeOffsetMsec);246247// Validate credentials248249var credentials = options.credentials;250if (!credentials ||251!credentials.id ||252!credentials.key ||253!credentials.algorithm) {254255return '';256}257258if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {259return '';260}261262// Parse URI263264if (typeof uri === 'string') {265uri = Url.parse(uri);266}267268// Calculate signature269270var exp = Math.floor(now / 1000) + options.ttlSec;271var mac = Crypto.calculateMac('bewit', credentials, {272ts: exp,273nonce: '',274method: 'GET',275resource: uri.pathname + (uri.search || ''), // Maintain trailing '?'276host: uri.hostname,277port: uri.port || (uri.protocol === 'http:' ? 80 : 443),278ext: options.ext279});280281// Construct bewit: id\exp\mac\ext282283var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext;284return Hoek.base64urlEncode(bewit);285};286287288// Generate an authorization string for a message289290/*291host: 'example.com',292port: 8000,293message: '{"some":"payload"}', // UTF-8 encoded string for body hash generation294options: {295296// Required297298credentials: {299id: 'dh37fgj492je',300key: 'aoijedoaijsdlaksjdl',301algorithm: 'sha256' // 'sha1', 'sha256'302},303304// Optional305306timestamp: Date.now(), // A pre-calculated timestamp307nonce: '2334f34f', // A pre-generated nonce308localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided)309}310*/311312exports.message = function (host, port, message, options) {313314// Validate inputs315316if (!host || typeof host !== 'string' ||317!port || typeof port !== 'number' ||318message === null || message === undefined || typeof message !== 'string' ||319!options || typeof options !== 'object') {320321return null;322}323324// Application time325326var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);327328// Validate credentials329330var credentials = options.credentials;331if (!credentials ||332!credentials.id ||333!credentials.key ||334!credentials.algorithm) {335336// Invalid credential object337return null;338}339340if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {341return null;342}343344// Calculate signature345346var artifacts = {347ts: timestamp,348nonce: options.nonce || Cryptiles.randomString(6),349host: host,350port: port,351hash: Crypto.calculatePayloadHash(message, credentials.algorithm)352};353354// Construct authorization355356var result = {357id: credentials.id,358ts: artifacts.ts,359nonce: artifacts.nonce,360hash: artifacts.hash,361mac: Crypto.calculateMac('message', credentials, artifacts)362};363364return result;365};366367368369370371