Skip to content

Commit 9f818bf

Browse files
author
Anthony Bouch
committed
Initial commit
A hapi-auth-signature authentication scheme plugin (based on the layout and docs for the hapi-auth-basic plugin).
0 parents  commit 9f818bf

File tree

6 files changed

+220
-0
lines changed

6 files changed

+220
-0
lines changed

.gitignore

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.idea
2+
*.iml
3+
npm-debug.log
4+
dump.rdb
5+
node_modules
6+
results.tap
7+
results.xml
8+
npm-shrinkwrap.json
9+
config.json
10+
.DS_Store
11+
*/.DS_Store
12+
*/*/.DS_Store
13+
._*
14+
*/._*
15+
*/*/._*
16+
coverage.*
17+
lib-cov
18+
*.swp
19+
*.swo
20+
*.swn

LICENSE

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Copyright (c) 2014, Anthony Bouch
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
* Redistributions of source code must retain the above copyright
7+
notice, this list of conditions and the following disclaimer.
8+
* Redistributions in binary form must reproduce the above copyright
9+
notice, this list of conditions and the following disclaimer in the
10+
documentation and/or other materials provided with the distribution.
11+
* The names of any contributors may not be used to endorse or promote
12+
products derived from this software without specific prior written
13+
permission.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
19+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
### hapi-auth-signature
2+
3+
Signature authentication scheme that wraps the [Joyent Signature Authentication Scheme](https://github.com/joyent/node-http-signature) and requires validating signed authorization header.
4+
5+
- `validateFunc` - (required) an api key id and secret key lookup validation function with the signature `function(keyId, callback)` where:
6+
- `parsedHeader` - [http-signature](https://github.com/joyent/node-http-signature) parsed header including the key id and signature.
7+
- `callback` - a callback function with the signature `function(err, isValid, credentials)` where:
8+
- `err` - an internal error.
9+
- `isValid` - `true` if the signature is verified, otherwise `false`.
10+
- `credentials` - a credentials object passed back to the application in `request.auth.credentials`. Typically, `credentials` are only
11+
included when `isValid` is `true`, but there are cases when the application needs to know who tried to authenticate even when it fails
12+
(e.g. with authentication mode `'try'`).
13+
14+
The validation function shown below is based on an hmac scheme with a key identifier and secret key stored in a user record. [http-signature](https://github.com/joyent/node-http-signature) supports the following algorithms:
15+
16+
* rsa-sha1
17+
* rsa-sha256
18+
* rsa-sha512
19+
* dsa-sha1
20+
* hmac-sha1
21+
* hmac-sha256
22+
* hmac-sha512
23+
24+
25+
```javascript
26+
27+
var HttpSignature = require('http-signature');
28+
29+
var users = [
30+
{
31+
id: '1,
32+
username: 'john',
33+
apikeyid: '18KF2FGK6807ZQA945R2',
34+
secretkey: '46573e78ce9df4f2d9ae93afd5f5c281', // 'secret'
35+
}
36+
];
37+
38+
var validate = function (parsedHeader, callback) {
39+
40+
var keyId = parsedHeader.keyId;
41+
var credentials {};
42+
var secretKey;
43+
users.forEach(function(user, index) {
44+
if (user.apikeyid === keyId) {
45+
secretKey = user.secretkey;
46+
credentials = {id: user.id, username: user.username};
47+
}
48+
}
49+
50+
if (!secretKey) {
51+
return callback(null, false);
52+
}
53+
54+
if(HttpSignature.verifySignature(parsedHeader, user.secretkey)) {
55+
callback(null, true, credentials);
56+
} else {
57+
callback(null, false);
58+
};
59+
};
60+
61+
server.pack.register(require('hapi-auth-signature'), function (err) {
62+
63+
server.auth.strategy('hmac', 'signature', { validateFunc: validate });
64+
server.route({ method: 'GET', path: '/', config: { auth: 'hmac' } });
65+
});
66+
67+
```

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./lib');

lib/index.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Load modules
2+
3+
var Boom = require('boom');
4+
var Hoek = require('hoek');
5+
var HttpSignature = require('http-signature');
6+
7+
8+
// Declare internals
9+
10+
var internals = {};
11+
12+
13+
exports.register = function (plugin, options, next) {
14+
15+
plugin.auth.scheme('signature', internals.implementation);
16+
next();
17+
};
18+
19+
exports.register.attributes = {
20+
pkg: require('../package.json')
21+
};
22+
23+
24+
internals.implementation = function (server, options) {
25+
26+
Hoek.assert(options, 'Missing signature auth strategy options');
27+
Hoek.assert(typeof options.validateFunc === 'function', 'options.validateFunc must be a valid function in signature scheme');
28+
29+
var settings = Hoek.clone(options);
30+
31+
var scheme = {
32+
authenticate: function (request, reply) {
33+
34+
var req = request.raw.req;
35+
if (!req.headers.authorization) {
36+
return reply(Boom.unauthorized(null, 'Signature'));
37+
}
38+
39+
var parsed = HttpSignature.parseRequest(req);
40+
if (!parsed) {
41+
return reply(Boom.unauthorized('HTTP authentication header missing signature', 'Signature'));
42+
}
43+
44+
settings.validateFunc(parsed, function (err, isValid, credentials) {
45+
46+
credentials = credentials || null;
47+
48+
if (err) {
49+
return reply(err, { credentials: credentials, log: { tags: ['auth', 'signature'], data: err } });
50+
}
51+
52+
if (!isValid) {
53+
return reply(Boom.unauthorized('Bad signature', 'Signature'), { credentials: credentials });
54+
}
55+
56+
if (!credentials ||
57+
typeof credentials !== 'object') {
58+
59+
return reply(Boom.badImplementation('Bad credentials object received for Signature auth validation'), { log: { tags: ['auth', 'credentials'] } });
60+
}
61+
62+
// Authenticated
63+
64+
return reply(null, { credentials: credentials });
65+
});
66+
}
67+
};
68+
69+
return scheme;
70+
};

package.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "hapi-auth-signature",
3+
"description": "Signature authentication plugin that wraps https://github.com/joyent/node-http-signature",
4+
"version": "1.0.0",
5+
"author": "Anthony Bouch <tony@58bits.com> (http://www.58bits.com)",
6+
"repository": "git://github.com/58bits/hapi-auth-signature",
7+
"main": "index",
8+
"keywords": [
9+
"hapi",
10+
"plugin",
11+
"auth",
12+
"signature"
13+
],
14+
"engines": {
15+
"node": ">=0.10.30"
16+
},
17+
"dependencies": {
18+
"boom": "2.x.x",
19+
"hoek": "2.x.x",
20+
"http-signature": "^0.10.0"
21+
},
22+
"peerDependencies": {
23+
"hapi": ">=2.x.x"
24+
},
25+
"devDependencies": {
26+
"hapi": "6.x.x",
27+
"lab": "3.x.x"
28+
},
29+
"scripts": {
30+
"test": "make test-cov"
31+
},
32+
"licenses": [
33+
{
34+
"type": "BSD",
35+
"url": "http://github.com/58bits/hapi-auth-signature/raw/master/LICENSE"
36+
}
37+
]
38+
}

0 commit comments

Comments
 (0)