hawk
Advanced tools
Comparing version 0.12.0 to 0.12.1
@@ -0,0 +0,0 @@ /* |
@@ -200,1 +200,87 @@ // Load modules | ||
// Generate a bewit value for a given URI | ||
/* | ||
* credentials is an object with the following keys: 'id, 'key', 'algorithm'. | ||
* options is an object with the following optional keys: 'ext', 'localtimeOffsetMsec' | ||
*/ | ||
/* | ||
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 | ||
var now = Utils.now() + (options.localtimeOffsetMsec || 0); | ||
// Validate credentials | ||
var 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 | ||
var exp = Math.floor(now / 1000) + options.ttlSec; | ||
var 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 | ||
var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext; | ||
return Utils.base64urlEncode(bewit); | ||
}; | ||
@@ -7,6 +7,10 @@ // Export sub-modules | ||
exports.client = require('./client'); | ||
exports.uri = require('./uri'); | ||
exports.crypto = require('./crypto'); | ||
exports.utils = require('./utils'); | ||
exports.uri = { | ||
authenticate: exports.server.authenticateBewit, | ||
getBewit: exports.client.getBewit | ||
}; | ||
@@ -84,6 +84,8 @@ // Load modules | ||
callback = Utils.nextTick(callback); | ||
// Default options | ||
options.nonceFunc = options.nonceFunc || function (nonce, ts, callback) { return callback(); }; // No validation | ||
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds | ||
options.nonceFunc = options.nonceFunc || function (nonce, ts, nonceCallback) { return nonceCallback(); }; // No validation | ||
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds | ||
@@ -288,1 +290,137 @@ // Application time | ||
}; | ||
/* | ||
* Arguments and options are the same as index.js with the exception that the only supported options are: | ||
* 'hostHeaderName', 'localtimeOffsetMsec' | ||
*/ | ||
exports.authenticateBewit = function (req, credentialsFunc, options, callback) { | ||
callback = Utils.nextTick(callback); | ||
// Application time | ||
var now = Utils.now() + (options.localtimeOffsetMsec || 0); | ||
// Convert node Http request object to a request configuration object | ||
var request = Utils.parseRequest(req, options); | ||
if (request instanceof Error) { | ||
return callback(Boom.badRequest(request.message)); | ||
} | ||
// Extract bewit | ||
// 1 2 3 4 | ||
var resource = request.url.match(/^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/); | ||
if (!resource) { | ||
return callback(Boom.unauthorized(null, 'Hawk')); | ||
} | ||
// Bewit not empty | ||
if (!resource[3]) { | ||
return callback(Boom.unauthorized('Empty bewit', 'Hawk')); | ||
} | ||
// Verify method is GET | ||
if (request.method !== 'GET' && | ||
request.method !== 'HEAD') { | ||
return callback(Boom.unauthorized('Invalid method', 'Hawk')); | ||
} | ||
// No other authentication | ||
if (request.authorization) { | ||
return callback(Boom.badRequest('Multiple authentications', 'Hawk')); | ||
} | ||
// Parse bewit | ||
var bewitString = Utils.base64urlDecode(resource[3]); | ||
if (bewitString instanceof Error) { | ||
return callback(Boom.badRequest('Invalid bewit encoding')); | ||
} | ||
// Bewit format: id\exp\mac\ext ('\' is used because it is a reserved header attribute character) | ||
var bewitParts = bewitString.split('\\'); | ||
if (!bewitParts || | ||
bewitParts.length !== 4) { | ||
return callback(Boom.badRequest('Invalid bewit structure')); | ||
} | ||
var bewit = { | ||
id: bewitParts[0], | ||
exp: parseInt(bewitParts[1], 10), | ||
mac: bewitParts[2], | ||
ext: bewitParts[3] || '' | ||
}; | ||
if (!bewit.id || | ||
!bewit.exp || | ||
!bewit.mac) { | ||
return callback(Boom.badRequest('Missing bewit attributes')); | ||
} | ||
// Construct URL without bewit | ||
var url = resource[1]; | ||
if (resource[4]) { | ||
url += resource[2] + resource[4]; | ||
} | ||
// Check expiration | ||
if (bewit.exp * 1000 <= now) { | ||
return callback(Boom.unauthorized('Access expired', 'Hawk'), null, bewit); | ||
} | ||
// Fetch Hawk credentials | ||
credentialsFunc(bewit.id, function (err, credentials) { | ||
if (err) { | ||
return callback(err, credentials || null, bewit.ext); | ||
} | ||
if (!credentials) { | ||
return callback(Boom.unauthorized('Unknown credentials', 'Hawk'), null, bewit); | ||
} | ||
if (!credentials.key || | ||
!credentials.algorithm) { | ||
return callback(Boom.internal('Invalid credentials'), credentials, bewit); | ||
} | ||
if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { | ||
return callback(Boom.internal('Unknown algorithm'), credentials, bewit); | ||
} | ||
// Calculate MAC | ||
var mac = Crypto.calculateMac('bewit', credentials, { | ||
ts: bewit.exp, | ||
nonce: '', | ||
method: 'GET', | ||
resource: url, | ||
host: request.host, | ||
port: request.port, | ||
ext: bewit.ext | ||
}); | ||
if (!Cryptiles.fixedTimeComparison(mac, bewit.mac)) { | ||
return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials, bewit); | ||
} | ||
// Successful authentication | ||
return callback(null, credentials, bewit); | ||
}); | ||
}; |
{ | ||
"name": "hawk", | ||
"description": "HTTP Hawk Authentication Scheme", | ||
"version": "0.12.0", | ||
"version": "0.12.1", | ||
"author": "Eran Hammer <eran@hueniverse.com> (http://hueniverse.com)", | ||
@@ -6,0 +6,0 @@ "contributors": [], |
@@ -6,3 +6,3 @@ ![hawk Logo](https://raw.github.com/hueniverse/hawk/master/images/hawk.png) | ||
Current version: **0.12.0** | ||
Current version: **0.12** | ||
@@ -9,0 +9,0 @@ [![Build Status](https://secure.travis-ci.org/hueniverse/hawk.png)](http://travis-ci.org/hueniverse/hawk) |
@@ -0,0 +0,0 @@ // Load modules |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
257945
24
2992