Comparing version 0.9.0 to 0.10.0
@@ -13,3 +13,3 @@ // Load modules | ||
dh37fgj492je: { | ||
id: 'dh37fgj492je', // Required by Hawk.getAuthorizationHeader | ||
id: 'dh37fgj492je', // Required by Hawk.client.header | ||
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', | ||
@@ -35,6 +35,12 @@ algorithm: 'sha256', | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
Hawk.server.authenticate(req, credentialsFunc, {}, function (err, credentials, artifacts) { | ||
res.writeHead(!err ? 200 : 401, { 'Content-Type': 'text/plain' }); | ||
res.end(!err ? 'Hello ' + credentials.user + ' ' + attributes.ext : 'Shoosh!'); | ||
var payload = (!err ? 'Hello ' + credentials.user + ' ' + artifacts.ext : 'Shoosh!'); | ||
var headers = { | ||
'Content-Type': 'text/plain', | ||
'Authorization': Hawk.server.header(artifacts, { payload: payload, contentType: 'text/plain' }) | ||
}; | ||
res.writeHead(!err ? 200 : 401, headers); | ||
res.end(payload); | ||
}); | ||
@@ -56,2 +62,3 @@ }; | ||
var header = Hawk.client.header('http://127.0.0.1:8000/resource/1?b=1&a=2', 'GET', { credentials: internals.credentials.dh37fgj492je, ext: 'and welcome!' }); | ||
var options = { | ||
@@ -61,3 +68,3 @@ uri: 'http://127.0.0.1:8000/resource/1?b=1&a=2', | ||
headers: { | ||
authorization: Hawk.getAuthorizationHeader('http://127.0.0.1:8000/resource/1?b=1&a=2', 'GET', { credentials: internals.credentials.dh37fgj492je, ext: 'and welcome!' }) | ||
authorization: header.field | ||
} | ||
@@ -70,3 +77,4 @@ }; | ||
console.log(response.statusCode + ': ' + body); | ||
var isValid = Hawk.client.authenticate(response, header.artifacts, { payload: body }); | ||
console.log(response.statusCode + ': ' + body + (isValid ? ' (valid)' : ' (invalid)')); | ||
process.exit(0); | ||
@@ -73,0 +81,0 @@ }); |
@@ -5,2 +5,3 @@ // Load modules | ||
var Url = require('url'); | ||
var Utils = require('./utils'); | ||
@@ -26,4 +27,4 @@ | ||
/* | ||
options = { | ||
type: 'header', // 'header', 'bewit' | ||
type: 'header' // 'header', 'bewit', 'response' | ||
options: { | ||
credentials: { | ||
@@ -37,3 +38,3 @@ key: 'aoijedoaijsdlaksjdl', | ||
port: 8080, | ||
timestamp: 1357718381034, | ||
ts: 1357718381034, | ||
nonce: 'd3d345f', | ||
@@ -44,8 +45,8 @@ hash: 'U4MKKSmiVxk37JCCrAVIjV/OhB3y+NdwoCr6RShbVkE=', | ||
dlg: 'd8djwekds9cj' // Delegated by application id (Oz), requires options.app | ||
}; | ||
} | ||
*/ | ||
exports.calculateMac = function (options) { | ||
exports.calculateMac = function (type, options) { | ||
var normalized = exports.generateNormalizedString(options); | ||
var normalized = exports.generateNormalizedString(type, options); | ||
@@ -58,6 +59,6 @@ var hmac = Crypto.createHmac(options.credentials.algorithm, options.credentials.key).update(normalized); | ||
exports.generateNormalizedString = function (options) { | ||
exports.generateNormalizedString = function (type, options) { | ||
var normalized = 'hawk.' + exports.headerVersion + '.' + options.type + '\n' + | ||
options.timestamp + '\n' + | ||
var normalized = 'hawk.' + exports.headerVersion + '.' + type + '\n' + | ||
options.ts + '\n' + | ||
options.nonce + '\n' + | ||
@@ -85,7 +86,19 @@ options.method.toUpperCase() + '\n' + | ||
exports.calculateHash = function (payload, algorithm) { | ||
exports.calculateHash = function (payload, algorithm, contentType) { | ||
var hash = Crypto.createHash(algorithm); | ||
var digest = hash.update(payload).digest('base64'); | ||
return digest; | ||
hash.update('hawk.' + exports.headerVersion + '.payload\n'); | ||
hash.update(Utils.parseContentType(contentType) + '\n'); | ||
hash.update(payload || ''); | ||
hash.update('\n'); | ||
return hash.digest('base64'); | ||
}; | ||
exports.calculateTsMac = function (ts, credentials) { | ||
var hash = Crypto.createHash(credentials.algorithm); | ||
hash.update('hawk.' + exports.headerVersion + '.ts\n' + ts + '\n'); | ||
return hash.digest('base64'); | ||
}; | ||
376
lib/index.js
@@ -1,373 +0,11 @@ | ||
// Load modules | ||
var Url = require('url'); | ||
var Boom = require('boom'); | ||
var Cryptiles = require('cryptiles'); | ||
var Sntp = require('sntp'); | ||
var Crypto = require('./crypto'); | ||
var Utils = require('./utils'); | ||
var Uri = require('./uri'); | ||
// Declare internals | ||
var internals = {}; | ||
// Export sub-modules | ||
exports.crypto = Crypto; | ||
exports.error = exports.Error = Boom; | ||
exports.sntp = Sntp; | ||
exports.uri = Uri; | ||
exports.utils = Utils; | ||
exports.error = exports.Error = require('boom'); | ||
exports.sntp = require('sntp'); | ||
exports.server = require('./server'); | ||
exports.client = require('./client'); | ||
exports.uri = require('./uri'); | ||
exports.crypto = require('./crypto'); | ||
exports.utils = require('./utils'); | ||
// Hawk authentication | ||
/* | ||
* req - node's HTTP request object or an object as follows: | ||
* | ||
* var request = { | ||
* method: 'GET', | ||
* url: '/resource/4?a=1&b=2', | ||
* host: 'example.com', | ||
* port: 8080, | ||
* authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE="' | ||
* }; | ||
* | ||
* credentialsFunc - required function to lookup the set of Hawk credentials based on the provided credentials id. | ||
* The credentials include the MAC key, MAC algorithm, and other attributes (such as username) | ||
* needed by the application. This function is the equivalent of verifying the username and | ||
* password in Basic authentication. | ||
* | ||
* var credentialsFunc = function (id, callback) { | ||
* | ||
* // Lookup credentials in database | ||
* db.lookup(id, function (err, item) { | ||
* | ||
* if (err || !item) { | ||
* return callback(err); | ||
* } | ||
* | ||
* var credentials = { | ||
* // Required | ||
* key: item.key, | ||
* algorithm: item.algorithm, | ||
* // Application specific | ||
* user: item.user | ||
* }; | ||
* | ||
* return callback(null, credentials); | ||
* }); | ||
* }; | ||
* | ||
* options: | ||
* | ||
* hostHeaderName - optional header field name, used to override the default 'Host' header when used | ||
* behind a cache of a proxy. Apache2 changes the value of the 'Host' header while preserving | ||
* the original (which is what the module must verify) in the 'x-forwarded-host' header field. | ||
* Only used when passed a node Http.ServerRequest object. | ||
* | ||
* nonceFunc - optional nonce validation function. The function signature is function(nonce, ts, callback) | ||
* where 'callback' must be called using the signature function(err). | ||
* | ||
* timestampSkewSec - optional number of seconds of permitted clock skew for incoming timestamps. Defaults to 60 seconds. | ||
* Provides a +/- skew which means actual allowed window is double the number of seconds. | ||
* | ||
* localtimeOffsetMsec - optional local clock time offset express in a number of milliseconds (positive or negative). | ||
* Defaults to 0. | ||
* | ||
* payload - optional payload for validation. The client calculates the hash value and includes it via the 'hash' | ||
* header attribute. The server always ensures the value provided has been included in the request | ||
* MAC. When this option is provided, it validates the hash value itself. Validation is done by calculating | ||
* a hash value over the entire payload (assuming it has already be normalized to the same format and | ||
* encoding used by the client to calculate the hash on request). If the payload is not available at the time | ||
* of authentication, the validatePayload() method can be used by passing it the credentials and | ||
* attributes.hash returned in the authenticate callback. | ||
*/ | ||
exports.authenticate = function (req, credentialsFunc, options, callback) { | ||
// Default options | ||
options.nonceFunc = options.nonceFunc || function (nonce, ts, callback) { return callback(); }; // No validation | ||
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds | ||
// 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)); | ||
} | ||
// Parse HTTP Authorization header | ||
if (!request.authorization) { | ||
return callback(Boom.unauthorized(null, 'Hawk', { ts: now })); | ||
} | ||
var headerParts = request.authorization.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something] | ||
if (!headerParts) { | ||
return callback(Boom.badRequest('Invalid header syntax')); | ||
} | ||
var scheme = headerParts[1]; | ||
if (scheme.toLowerCase() !== 'hawk') { | ||
return callback(Boom.unauthorized(null, 'Hawk', { ts: now })); | ||
} | ||
var attributesString = headerParts[2]; | ||
if (!attributesString) { | ||
return callback(Boom.badRequest('Invalid header syntax')); | ||
} | ||
var attributes = {}; | ||
var errorMessage = ''; | ||
var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) { | ||
// Check valid attribute names | ||
if (['id', 'ts', 'nonce', 'hash', 'ext', 'mac', 'app', 'dlg'].indexOf($1) === -1) { | ||
errorMessage = 'Unknown attribute: ' + $1; | ||
return; | ||
} | ||
// Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9 | ||
if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) { | ||
errorMessage = 'Bad attribute value: ' + $1; | ||
return; | ||
} | ||
// Check for duplicates | ||
if (attributes.hasOwnProperty($1)) { | ||
errorMessage = 'Duplicate attribute: ' + $1; | ||
return; | ||
} | ||
attributes[$1] = $2; | ||
return ''; | ||
}); | ||
if (verify !== '') { | ||
return callback(Boom.badRequest(errorMessage || 'Bad header format')); | ||
} | ||
// Verify required header attributes | ||
if (!attributes.id || | ||
!attributes.ts || | ||
!attributes.nonce || | ||
!attributes.mac) { | ||
return callback(Boom.badRequest('Missing attributes'), null, attributes); | ||
} | ||
// Check timestamp staleness | ||
if (Math.abs((attributes.ts * 1000) - now) > (options.timestampSkewSec * 1000)) { | ||
return callback(Boom.unauthorized('Stale timestamp', 'Hawk', { ts: now }), null, attributes); | ||
} | ||
// Fetch Hawk credentials | ||
credentialsFunc(attributes.id, function (err, credentials) { | ||
if (err) { | ||
return callback(err, credentials || null, attributes); | ||
} | ||
if (!credentials) { | ||
return callback(Boom.unauthorized('Unknown credentials', 'Hawk'), null, attributes); | ||
} | ||
if (!credentials.key || | ||
!credentials.algorithm) { | ||
return callback(Boom.internal('Invalid credentials'), credentials, attributes); | ||
} | ||
if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { | ||
return callback(Boom.internal('Unknown algorithm'), credentials, attributes); | ||
} | ||
// Calculate MAC | ||
var mac = Crypto.calculateMac({ | ||
type: 'header', | ||
credentials: credentials, | ||
timestamp: attributes.ts, | ||
nonce: attributes.nonce, | ||
method: request.method, | ||
resource: request.url,///////////////////////////////////////////////// | ||
host: request.host, | ||
port: request.port, | ||
hash: attributes.hash, | ||
ext: attributes.ext, | ||
app: attributes.app, | ||
dlg: attributes.dlg | ||
}); | ||
if (!Cryptiles.fixedTimeComparison(mac, attributes.mac)) { | ||
return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials, attributes); | ||
} | ||
// Check payload hash | ||
if (options.payload !== null && | ||
options.payload !== undefined) { // '' is valid | ||
if (!attributes.hash) { | ||
return callback(Boom.unauthorized('Missing required payload hash', 'Hawk'), credentials, attributes); | ||
} | ||
var hash = Crypto.calculateHash(options.payload, credentials.algorithm); | ||
if (!Cryptiles.fixedTimeComparison(hash, attributes.hash)) { | ||
return callback(Boom.unauthorized('Bad payload hash', 'Hawk'), credentials, attributes); | ||
} | ||
} | ||
// Check nonce | ||
options.nonceFunc(attributes.nonce, attributes.ts, function (err) { | ||
if (err) { | ||
return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials, attributes); | ||
} | ||
// Successful authentication | ||
return callback(null, credentials, attributes); | ||
}); | ||
}); | ||
}; | ||
// 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(), // A pre-calculated timestamp | ||
none: '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 | ||
app: '24s23423f34dx', // Oz application id | ||
dlg: '234sz34tww3sd' // Oz delegated-by application id | ||
}; | ||
*/ | ||
exports.getAuthorizationHeader = function (uri, method, options) { | ||
// Validate inputs | ||
if (!uri || | ||
(typeof uri !== 'string' && typeof uri !== 'object') || | ||
!method || | ||
typeof method !== 'string' || | ||
!options || | ||
typeof options !== 'object') { | ||
return ''; | ||
} | ||
// Application time | ||
var timestamp = options.timestamp || Math.floor((Utils.now() + (options.localtimeOffsetMsec || 0)) / 1000) | ||
// Validate credentials | ||
var credentials = options.credentials; | ||
if (!credentials || | ||
!credentials.id || | ||
!credentials.key || | ||
!credentials.algorithm) { | ||
// Invalid credential object | ||
return ''; | ||
} | ||
if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { | ||
return ''; | ||
} | ||
// Calculate payload hash | ||
var hash = null; | ||
if (options.payload !== null && | ||
options.payload !== undefined) { | ||
hash = Crypto.calculateHash(options.payload, credentials.algorithm); | ||
} | ||
// Parse URI | ||
if (typeof uri === 'string') { | ||
uri = Url.parse(uri); | ||
} | ||
// Calculate signature | ||
var artifacts = { | ||
type: 'header', | ||
credentials: credentials, | ||
timestamp: timestamp, | ||
nonce: options.nonce || Cryptiles.randomString(6), | ||
method: method, | ||
resource: uri.pathname + (uri.search || ''), // Maintain trailing '?' | ||
host: uri.hostname, | ||
port: uri.port || (uri.protocol === 'http' ? 80 : 443), | ||
hash: hash, | ||
ext: options.ext, | ||
app: options.app, | ||
dlg: options.dlg | ||
}; | ||
var mac = Crypto.calculateMac(artifacts); | ||
// Construct header | ||
var hasExt = options.ext !== null && options.ext !== undefined && options.ext !== ''; // Other falsey values allowed | ||
var header = 'Hawk id="' + credentials.id + | ||
'", ts="' + artifacts.timestamp + | ||
'", nonce="' + artifacts.nonce + | ||
(hash ? '", hash="' + hash : '') + | ||
(hasExt ? '", ext="' + Utils.escapeHeaderAttribute(options.ext) : '') + | ||
'", mac="' + mac + '"'; | ||
if (options.app) { | ||
header += ', app="' + options.app + | ||
(options.dlg ? '", dlg="' + options.dlg : '') + '"'; | ||
} | ||
return header; | ||
}; | ||
// Validate payload hash | ||
exports.validatePayload = function (payload, credentials, hash) { | ||
var calculatedHash = Crypto.calculateHash(payload, credentials.algorithm); | ||
return Cryptiles.fixedTimeComparison(calculatedHash, hash); | ||
}; | ||
@@ -130,6 +130,5 @@ // Load modules | ||
var mac = Crypto.calculateMac({ | ||
type: 'bewit', | ||
var mac = Crypto.calculateMac('bewit', { | ||
credentials: credentials, | ||
timestamp: bewit.exp, | ||
ts: bewit.exp, | ||
nonce: '', | ||
@@ -223,6 +222,5 @@ method: 'GET', | ||
var exp = Math.floor(now / 1000) + options.ttlSec; | ||
var mac = Crypto.calculateMac({ | ||
type: 'bewit', | ||
var mac = Crypto.calculateMac('bewit', { | ||
credentials: credentials, | ||
timestamp: exp, | ||
ts: exp, | ||
nonce: '', | ||
@@ -229,0 +227,0 @@ method: 'GET', |
@@ -5,2 +5,3 @@ // Load modules | ||
var Sntp = require('sntp'); | ||
var Boom = require('boom'); | ||
@@ -62,2 +63,14 @@ | ||
// Parse Content-Type header content | ||
exports.parseContentType = function (header) { | ||
if (!header) { | ||
return ''; | ||
} | ||
return header.split(';')[0].trim().toLowerCase(); | ||
}; | ||
// Convert node's to request configuration object | ||
@@ -78,3 +91,3 @@ | ||
var req = { | ||
var request = { | ||
method: req.method, | ||
@@ -84,6 +97,7 @@ url: req.url, | ||
port: host.port, | ||
authorization: req.headers.authorization | ||
authorization: req.headers.authorization, | ||
contentType: req.headers['content-type'] || '' | ||
}; | ||
return req; | ||
return request; | ||
}; | ||
@@ -97,1 +111,62 @@ | ||
// Parse Hawk HTTP Authorization header | ||
exports.parseAuthorizationHeader = function (header, keys) { | ||
keys = keys || ['id', 'ts', 'nonce', 'hash', 'ext', 'mac', 'app', 'dlg']; | ||
if (!header) { | ||
return Boom.unauthorized(null, 'Hawk'); | ||
} | ||
var headerParts = header.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something] | ||
if (!headerParts) { | ||
return Boom.badRequest('Invalid header syntax'); | ||
} | ||
var scheme = headerParts[1]; | ||
if (scheme.toLowerCase() !== 'hawk') { | ||
return Boom.unauthorized(null, 'Hawk'); | ||
} | ||
var attributesString = headerParts[2]; | ||
if (!attributesString) { | ||
return Boom.badRequest('Invalid header syntax'); | ||
} | ||
var attributes = {}; | ||
var errorMessage = ''; | ||
var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) { | ||
// Check valid attribute names | ||
if (keys.indexOf($1) === -1) { | ||
errorMessage = 'Unknown attribute: ' + $1; | ||
return; | ||
} | ||
// Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9 | ||
if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) { | ||
errorMessage = 'Bad attribute value: ' + $1; | ||
return; | ||
} | ||
// Check for duplicates | ||
if (attributes.hasOwnProperty($1)) { | ||
errorMessage = 'Duplicate attribute: ' + $1; | ||
return; | ||
} | ||
attributes[$1] = $2; | ||
return ''; | ||
}); | ||
if (verify !== '') { | ||
return Boom.badRequest(errorMessage || 'Bad header format'); | ||
} | ||
return attributes; | ||
}; |
{ | ||
"name": "hawk", | ||
"description": "HTTP Hawk Authentication Scheme", | ||
"version": "0.9.0", | ||
"version": "0.10.0", | ||
"author": "Eran Hammer <eran@hueniverse.com> (http://hueniverse.com)", | ||
@@ -6,0 +6,0 @@ "contributors": [], |
@@ -6,4 +6,6 @@ ![hawk Logo](https://raw.github.com/hueniverse/hawk/master/images/hawk.png) | ||
Current version: **0.9.0** | ||
Current version: **0.10.0** | ||
Last protocol change: **0.10.0** (changed payload hash method) | ||
[![Build Status](https://secure.travis-ci.org/hueniverse/hawk.png)](http://travis-ci.org/hueniverse/hawk) | ||
@@ -120,9 +122,25 @@ | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
// Authenticate incoming request | ||
res.writeHead(!err ? 200 : 401, { 'Content-Type': 'text/plain' }); | ||
res.end(!err ? 'Hello ' + credentials.user : 'Shoosh!'); | ||
Hawk.server.authenticate(req, credentialsFunc, {}, function (err, credentials, artifacts) { | ||
// Prepare response | ||
var payload = (!err ? 'Hello ' + credentials.user + ' ' + artifacts.ext : 'Shoosh!'); | ||
var headers = { 'Content-Type': 'text/plain' }; | ||
// Generate Authorization response header | ||
var header = Hawk.server.header(artifacts, { payload: payload, contentType: headers['Content-Type'] }); | ||
headers.Authorization = header; | ||
// Send the response back | ||
res.writeHead(!err ? 200 : 401, headers); | ||
res.end(payload); | ||
}); | ||
}; | ||
// Start server | ||
Http.createServer(handler).listen(8000, 'example.com'); | ||
@@ -146,15 +164,26 @@ ``` | ||
// Send authenticated request | ||
// Request options | ||
var options = { | ||
var requestOptions = { | ||
uri: 'http://example.com:8000/resource/1?b=1&a=2', | ||
method: 'GET', | ||
headers: { | ||
authorization: Hawk.getAuthorizationHeader('http://example.com:8000/resource/1?b=1&a=2', 'GET', { credentials: credentials, ext: 'some-app-data' }) | ||
} | ||
headers: {} | ||
}; | ||
Request(options, function (error, response, body) { | ||
// Generate Authorization request header | ||
console.log(response.statusCode + ': ' + body); | ||
var header = Hawk.client.header('http://example.com:8000/resource/1?b=1&a=2', 'GET', { credentials: credentials, ext: 'some-app-data' }); | ||
requestOptions.headers.Authorization = header.field; | ||
// Send authenticated request | ||
Request(requestOptions, function (error, response, body) { | ||
// Authenticate the server's response | ||
var isValid = Hawk.client.authenticate(response, header.artifacts, { payload: body }); | ||
// Output results | ||
console.log(response.statusCode + ': ' + body + (isValid ? ' (valid)' : ' (invalid)')); | ||
}); | ||
@@ -240,8 +269,28 @@ ``` | ||
**Hawk** provides optional payload validation. When generating the authentication header, the client calculates a payload hash | ||
using the specified hash algorithm. The hash is calculated over the request payload prior to any content encoding (the exact | ||
representation requirements should be specified by the server for payloads other than simple single-part ascii to ensure interoperability): | ||
using the specified hash algorithm. The hash is calculated over the concatenated value of: | ||
* `hawk.1.payload` | ||
* the content-type in lowercase, without any parameters (e.g. `application/json`) | ||
* the request payload prior to any content encoding (the exact representation requirements should be specified by the server for payloads other than simple single-part ascii to ensure interoperability) | ||
Payload: `Thank you for flying Hawk` | ||
Hash (sha256): `CBbyqZ/H0rd6nKdg3O9FS5uiQZ5NmgcXUPLut9heuyo=` | ||
For example: | ||
* Payload: `Thank you for flying Hawk` | ||
* Content Type: `text/plain` | ||
* Hash (sha256): `Yi9LfIIFRtBEPt74PVmbTF/xVAwPn7ub15ePICfgnuY=` | ||
Results in the following input to the payload hash function (newline separated values): | ||
``` | ||
hawk.1.payload | ||
text/plain | ||
Thank you for flying Hawk | ||
``` | ||
Which produces the following hash value: | ||
``` | ||
Yi9LfIIFRtBEPt74PVmbTF/xVAwPn7ub15ePICfgnuY= | ||
``` | ||
The client constructs the normalized request string (newline separated values): | ||
@@ -257,3 +306,3 @@ | ||
8000 | ||
CBbyqZ/H0rd6nKdg3O9FS5uiQZ5NmgcXUPLut9heuyo= | ||
Yi9LfIIFRtBEPt74PVmbTF/xVAwPn7ub15ePICfgnuY= | ||
some-app-ext-data | ||
@@ -269,3 +318,3 @@ | ||
Host: example.com:8000 | ||
Authorization: Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", hash="CBbyqZ/H0rd6nKdg3O9FS5uiQZ5NmgcXUPLut9heuyo=", ext="some-app-ext-data", mac="D0pHf7mKEh55AxFZ+qyiJ/fVE8uL0YgkoJjOMcOhVQU=" | ||
Authorization: Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", hash="Yi9LfIIFRtBEPt74PVmbTF/xVAwPn7ub15ePICfgnuY=", ext="some-app-ext-data", mac="aSe1DERmZuRl3pI36/9BdZmnErTw3sNzOOAUlfeKjVw=" | ||
``` | ||
@@ -477,3 +526,3 @@ | ||
[.NET](https://github.com/pcibraro/hawknet), and [JAVA](https://github.com/wealdtech/hawk) libraries available. The full list | ||
is maintained [here](https://github.com/hueniverse/hawk/issues?labels=port). Please add an issue is you are working on another | ||
is maintained [here](https://github.com/hueniverse/hawk/issues?labels=port). Please add an issue if you are working on another | ||
port. A cross-platform test-suite is in the works. | ||
@@ -480,0 +529,0 @@ |
@@ -25,4 +25,3 @@ // Load modules | ||
expect(Hawk.crypto.generateNormalizedString({ | ||
type: 'header', | ||
expect(Hawk.crypto.generateNormalizedString('header', { | ||
credentials: { | ||
@@ -32,3 +31,3 @@ key: 'dasdfasdf', | ||
}, | ||
timestamp: 1357747017, | ||
ts: 1357747017, | ||
nonce: 'k3k4j5', | ||
@@ -46,4 +45,3 @@ method: 'GET', | ||
expect(Hawk.crypto.generateNormalizedString({ | ||
type: 'header', | ||
expect(Hawk.crypto.generateNormalizedString('header', { | ||
credentials: { | ||
@@ -53,3 +51,3 @@ key: 'dasdfasdf', | ||
}, | ||
timestamp: 1357747017, | ||
ts: 1357747017, | ||
nonce: 'k3k4j5', | ||
@@ -68,4 +66,3 @@ method: 'GET', | ||
expect(Hawk.crypto.generateNormalizedString({ | ||
type: 'header', | ||
expect(Hawk.crypto.generateNormalizedString('header', { | ||
credentials: { | ||
@@ -75,3 +72,3 @@ key: 'dasdfasdf', | ||
}, | ||
timestamp: 1357747017, | ||
ts: 1357747017, | ||
nonce: 'k3k4j5', | ||
@@ -78,0 +75,0 @@ method: 'GET', |
@@ -43,10 +43,10 @@ // Load modules | ||
req.authorization = Hawk.getAuthorizationHeader(Url.parse('http://example.com:8080/resource/4?filter=a'), req.method, { credentials: credentials, ext: 'some-app-data' }); | ||
req.authorization = Hawk.client.header(Url.parse('http://example.com:8080/resource/4?filter=a'), req.method, { credentials: credentials, ext: 'some-app-data' }).field; | ||
expect(req.authorization).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
Hawk.server.authenticate(req, credentialsFunc, {}, function (err, credentials, artifacts) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
expect(attributes.ext).to.equal('some-app-data'); | ||
expect(artifacts.ext).to.equal('some-app-data'); | ||
done(); | ||
@@ -60,17 +60,34 @@ }); | ||
var req = { | ||
method: 'GET', | ||
method: 'POST', | ||
url: '/resource/4?filter=a', | ||
headers: { | ||
host: 'example.com:8080' | ||
host: 'example.com:8080', | ||
'content-type': 'text/plain;x=y' | ||
} | ||
}; | ||
var payload = 'some not so random text'; | ||
credentialsFunc('123456', function (err, credentials) { | ||
req.headers.authorization = Hawk.getAuthorizationHeader('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, ext: 'some-app-data' }); | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
var reqHeader = Hawk.client.header('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, ext: 'some-app-data', payload: payload, contentType: req.headers['content-type'] }); | ||
req.headers.authorization = reqHeader.field; | ||
Hawk.server.authenticate(req, credentialsFunc, {}, function (err, credentials, artifacts) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
expect(attributes.ext).to.equal('some-app-data'); | ||
expect(artifacts.ext).to.equal('some-app-data'); | ||
expect(Hawk.server.authenticatePayload(payload, credentials, artifacts.hash, req.headers['content-type'])).to.equal(true); | ||
var res = { | ||
headers: { | ||
'content-type': 'text/plain' | ||
} | ||
}; | ||
res.headers.authorization = Hawk.server.header(artifacts, { payload: 'some reply', contentType: 'text/plain', ext: 'response-specific' }); | ||
expect(res.headers.authorization).to.exist; | ||
expect(Hawk.client.authenticate(res, artifacts, { payload: 'some reply' })).to.equal(true); | ||
done(); | ||
@@ -92,8 +109,8 @@ }); | ||
req.authorization = Hawk.getAuthorizationHeader('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, payload: 'hola!', ext: 'some-app-data' }); | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
req.authorization = Hawk.client.header('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, payload: 'hola!', ext: 'some-app-data' }).field; | ||
Hawk.server.authenticate(req, credentialsFunc, {}, function (err, credentials, artifacts) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
expect(attributes.ext).to.equal('some-app-data'); | ||
expect(artifacts.ext).to.equal('some-app-data'); | ||
done(); | ||
@@ -115,10 +132,10 @@ }); | ||
req.authorization = Hawk.getAuthorizationHeader('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, payload: 'hola!', ext: 'some-app-data' }); | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
req.authorization = Hawk.client.header('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, payload: 'hola!', ext: 'some-app-data' }).field; | ||
Hawk.server.authenticate(req, credentialsFunc, {}, function (err, credentials, artifacts) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
expect(attributes.ext).to.equal('some-app-data'); | ||
expect(Hawk.validatePayload('hola!', credentials, attributes.hash)).to.be.true; | ||
expect(Hawk.validatePayload('hello!', credentials, attributes.hash)).to.be.false; | ||
expect(artifacts.ext).to.equal('some-app-data'); | ||
expect(Hawk.server.authenticatePayload('hola!', credentials, artifacts.hash)).to.be.true; | ||
expect(Hawk.server.authenticatePayload('hello!', credentials, artifacts.hash)).to.be.false; | ||
done(); | ||
@@ -140,10 +157,10 @@ }); | ||
req.authorization = Hawk.getAuthorizationHeader('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, ext: 'some-app-data', app: 'asd23ased', dlg: '23434szr3q4d' }); | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
req.authorization = Hawk.client.header('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, ext: 'some-app-data', app: 'asd23ased', dlg: '23434szr3q4d' }).field; | ||
Hawk.server.authenticate(req, credentialsFunc, {}, function (err, credentials, artifacts) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
expect(attributes.ext).to.equal('some-app-data'); | ||
expect(attributes.app).to.equal('asd23ased'); | ||
expect(attributes.dlg).to.equal('23434szr3q4d'); | ||
expect(artifacts.ext).to.equal('some-app-data'); | ||
expect(artifacts.app).to.equal('asd23ased'); | ||
expect(artifacts.dlg).to.equal('23434szr3q4d'); | ||
done(); | ||
@@ -165,4 +182,4 @@ }); | ||
req.authorization = Hawk.getAuthorizationHeader('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, payload: 'hola!', ext: 'some-app-data' }); | ||
Hawk.authenticate(req, credentialsFunc, { payload: 'byebye!' }, function (err, credentials, attributes) { | ||
req.authorization = Hawk.client.header('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, payload: 'hola!', ext: 'some-app-data' }).field; | ||
Hawk.server.authenticate(req, credentialsFunc, { payload: 'byebye!' }, function (err, credentials, artifacts) { | ||
@@ -187,6 +204,6 @@ expect(err).to.exist; | ||
req.authorization = Hawk.getAuthorizationHeader('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, ext: 'some-app-data' }); | ||
req.authorization = Hawk.client.header('http://example.com:8080/resource/4?filter=a', req.method, { credentials: credentials, ext: 'some-app-data' }).field; | ||
req.url = '/something/else'; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
Hawk.server.authenticate(req, credentialsFunc, {}, function (err, credentials, artifacts) { | ||
@@ -199,626 +216,2 @@ expect(err).to.exist; | ||
}); | ||
describe('#authenticate', function () { | ||
it('should parse a valid authentication header (sha1)', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="1", ts="1353788437", nonce="k3j4h2", mac="zy79QQ5/EYFmQqutVnYb73gAc/U=", ext="hello"', | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
done(); | ||
}); | ||
}); | ||
it('should parse a valid authentication header (sha256)', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/1?b=1&a=2', | ||
host: 'example.com', | ||
port: 8000, | ||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", mac="m8r1rHbXN6NgO+KIIhjO7sFRyd78RNGVUwehe8Cp2dU=", ext="some-app-data"', | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353832234000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
done(); | ||
}); | ||
}); | ||
it('should parse a valid authentication header (POST with payload)', function (done) { | ||
var req = { | ||
method: 'POST', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123456", ts="1357926341", nonce="1AwuJD", hash="qAiXIVv+yjDATneWxZP2YCTa9aHRgQdnH9b3Wc+o3dg=", ext="some-app-data", mac="UeYcj5UoTVaAWXNvJfLVia7kU3VabxCqrccXP8sUGC4="', | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1357926341000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on missing hash', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/1?b=1&a=2', | ||
host: 'example.com', | ||
port: 8000, | ||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", mac="m8r1rHbXN6NgO+KIIhjO7sFRyd78RNGVUwehe8Cp2dU=", ext="some-app-data"', | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { payload: 'body', localtimeOffsetMsec: 1353832234000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Missing required payload hash'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on a stale timestamp', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/1?b=1&a=2', | ||
host: 'example.com', | ||
port: 8000, | ||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", mac="m8r1rHbXN6NgO+KIIhjO7sFRyd78RNGVUwehe8Cp2dU=", ext="some-app-data"', | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Stale timestamp'); | ||
var header = err.response.headers['WWW-Authenticate']; | ||
var ts = header.match(/^Hawk ts\=\"(\d+)\"\, error=\"Stale timestamp\"$/); | ||
var now = Hawk.utils.now(); | ||
expect(parseInt(ts[1], 10)).to.be.within(now - 1, now + 1); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on a replay', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="bXx7a7p1h9QYQNZ8x7QhvDQym8ACgab4m3lVSFn4DBw=", ext="hello"', | ||
}; | ||
var memoryCache = {}; | ||
var options = { | ||
localtimeOffsetMsec: 1353788437000 - Hawk.utils.now(), | ||
nonceFunc: function (nonce, ts, callback) { | ||
if (memoryCache[nonce]) { | ||
return callback(new Error()); | ||
} | ||
memoryCache[nonce] = true; | ||
return callback(); | ||
} | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, options, function (err, credentials, attributes) { | ||
expect(err).to.not.exist; | ||
expect(credentials.user).to.equal('steve'); | ||
Hawk.authenticate(req, credentialsFunc, options, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Invalid nonce'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should fail on an invalid authentication header: wrong scheme', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Basic asdasdasdasd' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an invalid authentication header: no scheme', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: '!@#' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Invalid header syntax'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an missing authorization header', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080 | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.isMissing).to.equal(true); | ||
var header = err.response.headers['WWW-Authenticate']; | ||
var ts = header.match(/^Hawk ts\=\"(\d+)\"$/); | ||
var now = Hawk.utils.now(); | ||
expect(parseInt(ts[1], 10)).to.be.within(now - 1, now + 1); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an missing host header', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
headers: { | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
} | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Invalid Host header'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an missing authorization attribute (id)', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Missing attributes'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an missing authorization attribute (ts)', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Missing attributes'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an missing authorization attribute (nonce)', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Missing attributes'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an missing authorization attribute (mac)', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Missing attributes'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an unknown authorization attribute', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", x="3", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Unknown attribute: x'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an bad authorization header format', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123\\", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Bad header format'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an bad authorization attribute value', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="\t", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Bad attribute value: id'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an empty authorization attribute value', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Bad attribute value: id'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on duplicated authorization attribute key', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", id="456", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Duplicate attribute: id'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an invalid authorization header format', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Invalid header syntax'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an bad host header (missing host)', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
headers: { | ||
host: ':8080', | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
} | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Invalid Host header'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an bad host header (pad port)', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
headers: { | ||
host: 'example.com:something', | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
} | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Invalid Host header'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on credentialsFunc error', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
var credentialsFunc = function (id, callback) { | ||
return callback(new Error('Unknown user')); | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('Unknown user'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on missing credentials', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
var credentialsFunc = function (id, callback) { | ||
return callback(null, null); | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Unknown credentials'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on invalid credentials', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
var credentialsFunc = function (id, callback) { | ||
var credentials = { | ||
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', | ||
user: 'steve' | ||
}; | ||
return callback(null, credentials); | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('Invalid credentials'); | ||
expect(err.response.payload.message).to.equal('An internal server error occurred'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on unknown credentials algorithm', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
var credentialsFunc = function (id, callback) { | ||
var credentials = { | ||
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', | ||
algorithm: 'hmac-sha-0', | ||
user: 'steve' | ||
}; | ||
return callback(null, credentials); | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('Unknown algorithm'); | ||
expect(err.response.payload.message).to.equal('An internal server error occurred'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on unknown bad mac', function (done) { | ||
var req = { | ||
method: 'GET', | ||
url: '/resource/4?filter=a', | ||
host: 'example.com', | ||
port: 8080, | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcU4jlr7T/wuKe3dKijvTvSos=", ext="hello"' | ||
}; | ||
var credentialsFunc = function (id, callback) { | ||
var credentials = { | ||
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', | ||
algorithm: 'sha256', | ||
user: 'steve' | ||
}; | ||
return callback(null, credentials); | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Hawk.utils.now() }, function (err, credentials, attributes) { | ||
expect(err).to.exist; | ||
expect(err.response.payload.message).to.equal('Bad mac'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#getAuthorizationHeader', function () { | ||
it('should return a valid authorization header (sha1)', function (done) { | ||
var credentials = { | ||
id: '123456', | ||
key: '2983d45yun89q', | ||
algorithm: 'sha1' | ||
}; | ||
var header = Hawk.getAuthorizationHeader('https://example.net/somewhere/over/the/rainbow', 'POST', { credentials: credentials, ext: 'Bazinga!', timestamp: 1353809207, nonce: 'Ygvqdz', payload: 'something to write about' }); | ||
expect(header).to.equal('Hawk id="123456", ts="1353809207", nonce="Ygvqdz", hash="eQJ6qAuxoMrLdTMb5IJiv04W4F4=", ext="Bazinga!", mac="Ti2SMCBfDGp4DLoOw2OpFjOs+nI="'); | ||
done(); | ||
}); | ||
it('should return a valid authorization header (sha256)', function (done) { | ||
var credentials = { | ||
id: '123456', | ||
key: '2983d45yun89q', | ||
algorithm: 'sha256' | ||
}; | ||
var header = Hawk.getAuthorizationHeader('https://example.net/somewhere/over/the/rainbow', 'POST', { credentials: credentials, ext: 'Bazinga!', timestamp: 1353809207, nonce: 'Ygvqdz', payload: 'something to write about' }); | ||
expect(header).to.equal('Hawk id="123456", ts="1353809207", nonce="Ygvqdz", hash="Yz+K6hTiKD4IVEckK1yPIBdb/gh4LdtWwpXvM776Edg=", ext="Bazinga!", mac="Uk1EHe77nOiAo4Hgm8Qio21+MtU7jEcVSIaqw21Yy48="'); | ||
done(); | ||
}); | ||
it('should return an empty authorization header on missing options', function (done) { | ||
var header = Hawk.getAuthorizationHeader('https://example.net/somewhere/over/the/rainbow', 'POST'); | ||
expect(header).to.equal(''); | ||
done(); | ||
}); | ||
it('should return an empty authorization header on invalid credentials', function (done) { | ||
var credentials = { | ||
key: '2983d45yun89q', | ||
algorithm: 'sha256' | ||
}; | ||
var header = Hawk.getAuthorizationHeader('https://example.net/somewhere/over/the/rainbow', 'POST', { credentials: credentials, ext: 'Bazinga!', timestamp: 1353809207 }); | ||
expect(header).to.equal(''); | ||
done(); | ||
}); | ||
it('should return an empty authorization header on invalid algorithm', function (done) { | ||
var credentials = { | ||
id: '123456', | ||
key: '2983d45yun89q', | ||
algorithm: 'hmac-sha-0' | ||
}; | ||
var header = Hawk.getAuthorizationHeader('https://example.net/somewhere/over/the/rainbow', 'POST', { credentials: credentials, payload: 'something, anything!', ext: 'Bazinga!', timestamp: 1353809207 }); | ||
expect(header).to.equal(''); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -39,3 +39,3 @@ // Load modules | ||
var header = Hawk.getAuthorizationHeader('http://example.com:8000/resource/1?b=1&a=2', 'GET', options); | ||
var header = Hawk.client.header('http://example.com:8000/resource/1?b=1&a=2', 'GET', options).field; | ||
@@ -48,6 +48,5 @@ expect(header).to.equal('Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE="'); | ||
var normalized = Hawk.crypto.generateNormalizedString({ | ||
type: 'header', | ||
var normalized = Hawk.crypto.generateNormalizedString('header', { | ||
credentials: credentials, | ||
timestamp: options.timestamp, | ||
ts: options.timestamp, | ||
nonce: options.nonce, | ||
@@ -67,8 +66,9 @@ method: 'GET', | ||
payloadOptions.payload = 'Thank you for flying Hawk'; | ||
payloadOptions.contentType = 'text/plain'; | ||
it('should generate a header protocol example (with payload)', function (done) { | ||
var header = Hawk.getAuthorizationHeader('http://example.com:8000/resource/1?b=1&a=2', 'POST', payloadOptions); | ||
var header = Hawk.client.header('http://example.com:8000/resource/1?b=1&a=2', 'POST', payloadOptions).field; | ||
expect(header).to.equal('Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", hash="CBbyqZ/H0rd6nKdg3O9FS5uiQZ5NmgcXUPLut9heuyo=", ext="some-app-ext-data", mac="D0pHf7mKEh55AxFZ+qyiJ/fVE8uL0YgkoJjOMcOhVQU="'); | ||
expect(header).to.equal('Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", hash="Yi9LfIIFRtBEPt74PVmbTF/xVAwPn7ub15ePICfgnuY=", ext="some-app-ext-data", mac="aSe1DERmZuRl3pI36/9BdZmnErTw3sNzOOAUlfeKjVw="'); | ||
done(); | ||
@@ -79,6 +79,5 @@ }); | ||
var normalized = Hawk.crypto.generateNormalizedString({ | ||
type: 'header', | ||
var normalized = Hawk.crypto.generateNormalizedString('header', { | ||
credentials: credentials, | ||
timestamp: options.timestamp, | ||
ts: options.timestamp, | ||
nonce: options.nonce, | ||
@@ -89,7 +88,7 @@ method: 'POST', | ||
port: 8000, | ||
hash: Hawk.crypto.calculateHash(payloadOptions.payload, credentials.algorithm), | ||
hash: Hawk.crypto.calculateHash(payloadOptions.payload, credentials.algorithm, payloadOptions.contentType), | ||
ext: options.ext | ||
}); | ||
expect(normalized).to.equal('hawk.1.header\n1353832234\nj4h3g2\nPOST\n/resource?a=1&b=2\nexample.com\n8000\nCBbyqZ/H0rd6nKdg3O9FS5uiQZ5NmgcXUPLut9heuyo=\nsome-app-ext-data\n'); | ||
expect(normalized).to.equal('hawk.1.header\n1353832234\nj4h3g2\nPOST\n/resource?a=1&b=2\nexample.com\n8000\nYi9LfIIFRtBEPt74PVmbTF/xVAwPn7ub15ePICfgnuY=\nsome-app-ext-data\n'); | ||
done(); | ||
@@ -96,0 +95,0 @@ }); |
@@ -143,4 +143,3 @@ // Load modules | ||
var ext = 'some-app-data'; | ||
var mac = Hawk.crypto.calculateMac({ | ||
type: 'bewit', | ||
var mac = Hawk.crypto.calculateMac('bewit', { | ||
credentials: credentials, | ||
@@ -147,0 +146,0 @@ timestamp: exp, |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
203032
23
2016
576
3