зеркало из https://github.com/mozilla/hawk.git
Add nonce support, closes #6
This commit is contained in:
Родитель
ad756f1dbc
Коммит
1f60f2ce83
63
lib/index.js
63
lib/index.js
|
@ -7,7 +7,9 @@ var Err = require('./error');
|
|||
|
||||
// Declare internals
|
||||
|
||||
var internals = {};
|
||||
var internals = {
|
||||
randomSource: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
};
|
||||
|
||||
|
||||
// Hawk authentication
|
||||
|
@ -41,9 +43,12 @@ var internals = {};
|
|||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* nonceFunc - optional nonce validation function. The function signature is function(nonce, ts, callback)
|
||||
* where 'callback' must be called using the signature function(err).
|
||||
*/
|
||||
|
||||
exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
|
||||
|
@ -51,9 +56,14 @@ exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
|
|||
var callback = (arg2 ? arg2 : arg1);
|
||||
var options = (arg2 ? arg1 : {});
|
||||
|
||||
// Default options
|
||||
|
||||
options.hostHeaderName = (options.hostHeaderName ? options.hostHeaderName.toLowerCase() : 'host');
|
||||
options.nonceFunc = options.nonceFunc || function (nonce, ts, callback) { return callback(); };
|
||||
|
||||
// Check required HTTP headers: host, authentication
|
||||
|
||||
var hostHeader = (options.hostHeaderName ? req.headers[options.hostHeaderName.toLowerCase()] : req.headers.host);
|
||||
var hostHeader = req.headers[options.hostHeaderName];
|
||||
if (!hostHeader) {
|
||||
return callback(Err.badRequest('Missing Host header'), null, null);
|
||||
}
|
||||
|
@ -76,6 +86,7 @@ exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
|
|||
|
||||
if (!attributes.id ||
|
||||
!attributes.ts ||
|
||||
!attributes.nonce ||
|
||||
!attributes.mac) {
|
||||
|
||||
return callback(Err.badRequest('Missing attributes'), null, attributes.ext);
|
||||
|
@ -120,21 +131,30 @@ exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
|
|||
|
||||
// Calculate MAC
|
||||
|
||||
var mac = exports.calculateMAC(credentials.key, credentials.algorithm, attributes.ts, req.method, req.url, host, port, attributes.ext);
|
||||
var mac = exports.calculateMAC(credentials.key, credentials.algorithm, attributes.ts, attributes.nonce, req.method, req.url, host, port, attributes.ext);
|
||||
if (mac !== attributes.mac) {
|
||||
return callback(Err.unauthorized('Bad mac'), credentials, attributes.ext);
|
||||
}
|
||||
|
||||
// Successful authentication
|
||||
// Check nonce
|
||||
|
||||
return callback(null, credentials, attributes.ext);
|
||||
options.nonceFunc(attributes.nonce, attributes.ts, function (err) {
|
||||
|
||||
if (err) {
|
||||
return callback(Err.unauthorized('Invalid nonce'), credentials, attributes.ext);
|
||||
}
|
||||
|
||||
// Successful authentication
|
||||
|
||||
return callback(null, credentials, attributes.ext);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Calculate the request MAC
|
||||
|
||||
exports.calculateMAC = function (key, algorithm, timestamp, method, uri, host, port, ext) {
|
||||
exports.calculateMAC = function (key, algorithm, timestamp, nonce, method, uri, host, port, ext) {
|
||||
|
||||
// Parse request URI
|
||||
|
||||
|
@ -143,6 +163,7 @@ exports.calculateMAC = function (key, algorithm, timestamp, method, uri, host, p
|
|||
// Construct normalized req string
|
||||
|
||||
var normalized = timestamp + '\n' +
|
||||
nonce + '\n' +
|
||||
method.toUpperCase() + '\n' +
|
||||
url.pathname + (url.search || '') + '\n' +
|
||||
host.toLowerCase() + '\n' +
|
||||
|
@ -184,7 +205,7 @@ exports.parseHeader = function (header) {
|
|||
|
||||
var attributes = {};
|
||||
|
||||
var attributesRegex = /(id|ts|ext|mac)="([^"\\]*)"\s*(?:,\s*|$)/g;
|
||||
var attributesRegex = /(id|ts|nonce|ext|mac)="([^"\\]*)"\s*(?:,\s*|$)/g;
|
||||
var verify = headerParts[2].replace(attributesRegex, function ($0, $1, $2) {
|
||||
|
||||
if (attributes[$1] === undefined) {
|
||||
|
@ -207,7 +228,7 @@ exports.parseHeader = function (header) {
|
|||
* credentials is an object with the following keys: 'id, 'key', 'algorithm'.
|
||||
*/
|
||||
|
||||
exports.getAuthorizationHeader = function (credentials, method, uri, host, port, ext, timestamp) {
|
||||
exports.getAuthorizationHeader = function (credentials, method, uri, host, port, ext, timestamp, nonce) {
|
||||
|
||||
// Check request
|
||||
|
||||
|
@ -222,7 +243,8 @@ exports.getAuthorizationHeader = function (credentials, method, uri, host, port,
|
|||
// Calculate signature
|
||||
|
||||
timestamp = timestamp || Math.floor(((new Date()).getTime() / 1000));
|
||||
var mac = exports.calculateMAC(credentials.key, credentials.algorithm, timestamp, method, uri, host, port, ext);
|
||||
nonce = nonce || exports.randomString(6);
|
||||
var mac = exports.calculateMAC(credentials.key, credentials.algorithm, timestamp, nonce, method, uri, host, port, ext);
|
||||
|
||||
if (!mac) {
|
||||
return '';
|
||||
|
@ -230,7 +252,22 @@ exports.getAuthorizationHeader = function (credentials, method, uri, host, port,
|
|||
|
||||
// Construct header
|
||||
|
||||
var header = 'Hawk id="' + credentials.id + '", ts="' + timestamp + (ext ? '", ext="' + ext : '') + '", mac="' + mac + '"';
|
||||
var header = 'Hawk id="' + credentials.id + '", ts="' + timestamp + '", nonce="' + nonce + (ext ? '", ext="' + ext : '') + '", mac="' + mac + '"';
|
||||
return header;
|
||||
};
|
||||
|
||||
|
||||
// Generate a random string of given size (not for crypto)
|
||||
|
||||
exports.randomString = function (size) {
|
||||
|
||||
var result = [];
|
||||
|
||||
var len = internals.randomSource.length;
|
||||
for (var i = 0; i < size; ++i) {
|
||||
result.push(internals.randomSource[Math.floor(Math.random() * len)]);
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "hawk",
|
||||
"description": "HTTP Hawk Authentication Scheme",
|
||||
"version": "0.0.8",
|
||||
"version": "0.1.0",
|
||||
"author": "Eran Hammer <eran@hueniverse.com> (http://hueniverse.com)",
|
||||
"contributors": [],
|
||||
"repository": "git://github.com/hueniverse/hawk",
|
||||
|
|
129
test/index.js
129
test/index.js
|
@ -40,7 +40,7 @@ describe('Hawk', function () {
|
|||
|
||||
credentialsFunc('123456', function (err, credentials) {
|
||||
|
||||
req.headers.authorization = Hawk.getAuthorizationHeader(credentials, req.method, req.url, 'example.com', 8080, 'some-app-data', 1353809207);
|
||||
req.headers.authorization = Hawk.getAuthorizationHeader(credentials, req.method, req.url, 'example.com', 8080, 'some-app-data');
|
||||
|
||||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
|
||||
|
||||
|
@ -64,7 +64,7 @@ describe('Hawk', function () {
|
|||
|
||||
credentialsFunc('123456', function (err, credentials) {
|
||||
|
||||
req.headers.authorization = Hawk.getAuthorizationHeader(credentials, req.method, req.url, 'example.com', 8080, 'some-app-data', 1353809207);
|
||||
req.headers.authorization = Hawk.getAuthorizationHeader(credentials, req.method, req.url, 'example.com', 8080, 'some-app-data');
|
||||
req.url = '/something/else';
|
||||
|
||||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
|
||||
|
@ -82,7 +82,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="1", ts="1353788437", mac="lDdDLlWQhgcxTvYgzzLo3EZExog=", ext="hello"',
|
||||
authorization: 'Hawk id="1", ts="1353788437", nonce="k3j4h2", mac="qrP6b5tiS2CO330rpjUEym/USBM=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -101,7 +101,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="ZPa2zWC3WUAYXrwPzJ3DpF54xjQ2ZDLe8GF1ny6JJFI=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -116,6 +116,44 @@ describe('Hawk', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should fail on a replay', function (done) {
|
||||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="ZPa2zWC3WUAYXrwPzJ3DpF54xjQ2ZDLe8GF1ny6JJFI=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
url: '/resource/4?filter=a'
|
||||
};
|
||||
|
||||
var memoryCache = {};
|
||||
var options = {
|
||||
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, ext) {
|
||||
|
||||
expect(err).to.not.exist;
|
||||
expect(credentials.user).to.equal('steve');
|
||||
|
||||
Hawk.authenticate(req, credentialsFunc, options, function (err, credentials, ext) {
|
||||
|
||||
expect(err).to.exist;
|
||||
expect(err.toResponse().payload.message).to.equal('Invalid nonce');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail on an invalid authentication header: wrong scheme', function (done) {
|
||||
|
||||
var req = {
|
||||
|
@ -157,7 +195,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"'
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"'
|
||||
},
|
||||
method: 'GET',
|
||||
url: '/resource/4?filter=a'
|
||||
|
@ -171,11 +209,68 @@ describe('Hawk', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should fail on an missing authorization attribute', function (done) {
|
||||
it('should fail on an missing authorization attribute (id)', function (done) {
|
||||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
url: '/resource/4?filter=a'
|
||||
};
|
||||
|
||||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
|
||||
|
||||
expect(err).to.exist;
|
||||
expect(err.toResponse().payload.message).to.equal('Missing attributes');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail on an missing authorization attribute (ts)', function (done) {
|
||||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
url: '/resource/4?filter=a'
|
||||
};
|
||||
|
||||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
|
||||
|
||||
expect(err).to.exist;
|
||||
expect(err.toResponse().payload.message).to.equal('Missing attributes');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail on an missing authorization attribute (nonce)', function (done) {
|
||||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
url: '/resource/4?filter=a'
|
||||
};
|
||||
|
||||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
|
||||
|
||||
expect(err).to.exist;
|
||||
expect(err.toResponse().payload.message).to.equal('Missing attributes');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail on an missing authorization attribute (mac)', function (done) {
|
||||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -194,7 +289,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", x="3", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", x="3", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -232,7 +327,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080:90'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -251,7 +346,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -275,7 +370,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -299,7 +394,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -329,7 +424,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -360,7 +455,7 @@ describe('Hawk', function () {
|
|||
|
||||
var req = {
|
||||
headers: {
|
||||
authorization: 'Hawk id="123", ts="1353788437", mac="/qwS4UjfVWMcU4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcU4jlr7T/wuKe3dKijvTvSos=", ext="hello"',
|
||||
host: 'example.com:8080'
|
||||
},
|
||||
method: 'GET',
|
||||
|
@ -391,7 +486,7 @@ describe('Hawk', function () {
|
|||
|
||||
it('should return an empty value on unknown algorithm', function (done) {
|
||||
|
||||
expect(Hawk.calculateMAC('dasdfasdf', 'hmac-sha-0', Date.now() / 1000, 'GET', '/resource/something', 'example.com', 8080)).to.equal('');
|
||||
expect(Hawk.calculateMAC('dasdfasdf', 'hmac-sha-0', Date.now() / 1000, 'k3k4j5', 'GET', '/resource/something', 'example.com', 8080)).to.equal('');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -406,8 +501,8 @@ describe('Hawk', function () {
|
|||
algorithm: 'hmac-sha-256'
|
||||
};
|
||||
|
||||
var header = Hawk.getAuthorizationHeader(credentials, 'POST', '/somewhere/over/the/rainbow', 'example.net', 443, 'Bazinga!', 1353809207);
|
||||
expect(header).to.equal('Hawk id="123456", ts="1353809207", ext="Bazinga!", mac="LYUkYKYkQsQstqNQHcnAzDXce0oHsmS049rv4EalMb8="');
|
||||
var header = Hawk.getAuthorizationHeader(credentials, 'POST', '/somewhere/over/the/rainbow', 'example.net', 443, 'Bazinga!', 1353809207, 'Ygvqdz');
|
||||
expect(header).to.equal('Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="Bazinga!", mac="qSK1cZEkqPwE2ttBX8QSXxO+NE3epFMu4tyVpGKjdnU="');
|
||||
done();
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче