Skip to content
This repository was archived by the owner on May 2, 2020. It is now read-only.

Commit 5c3677e

Browse files
committedMay 3, 2016
initial commit
1 parent 69b191a commit 5c3677e

File tree

5 files changed

+236
-1
lines changed

5 files changed

+236
-1
lines changed
 

‎.travis.yml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
sudo: false
2+
language: node_js
3+
node_js:
4+
- "4"
5+
- "5"
6+
- "6"

‎README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
# nuisance
2-
hapi authentication requiring multiple strategies
2+
3+
[![Current Version](https://img.shields.io/npm/v/nuisance.svg)](https://www.npmjs.org/package/nuisance)
4+
[![Build Status via Travis CI](https://travis-ci.org/continuationlabs/nuisance.svg?branch=master)](https://travis-ci.org/continuationlabs/nuisance)
5+
![Dependencies](http://img.shields.io/david/continuationlabs/nuisance.svg)
6+
7+
[![belly-button-style](https://cdn.rawgit.com/continuationlabs/belly-button/master/badge.svg)](https://github.com/continuationlabs/belly-button)
8+
9+
`nuisance` is a hapi plugin that allows multiple authentication strategies to be aggregated into a single strategy. hapi allows you specify multiple strategies on a route, however this approach only requires that a single strategy is successful. `nuisance`, on the other hand, requires that **all** of the strategies are successful.

‎lib/index.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
3+
const Assert = require('assert');
4+
const Boom = require('boom');
5+
const Insync = require('insync');
6+
7+
8+
module.exports.register = function (server, options, next) {
9+
server.auth.scheme('nuisance', function nuisanceScheme (server, options) {
10+
Assert.strictEqual(Array.isArray(options.strategies), true);
11+
12+
return {
13+
authenticate (request, reply) {
14+
const credentials = {};
15+
16+
Insync.each(options.strategies, function eachIterator (strategy, next) {
17+
server.auth.test(strategy, request, function testCb (err, creds) {
18+
credentials[strategy] = creds;
19+
next(err);
20+
});
21+
}, function eachCb (err) {
22+
if (err) {
23+
return reply(Boom.unauthorized());
24+
}
25+
26+
reply.continue({ credentials });
27+
});
28+
}
29+
};
30+
});
31+
32+
next();
33+
};
34+
35+
36+
module.exports.register.attributes = {
37+
pkg: require('../package.json')
38+
};

‎package.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "nuisance",
3+
"version": "0.1.0",
4+
"description": "hapi authentication requiring multiple strategies",
5+
"author": "Continuation Labs <contact@continuation.io> (http://continuation.io/)",
6+
"main": "lib/index.js",
7+
"homepage": "https://github.com/cjihrig/nuisance",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/cjihrig/nuisance"
11+
},
12+
"bugs": {
13+
"url": "https://github.com/cjihrig/nuisance/issues"
14+
},
15+
"license": "MIT",
16+
"scripts": {
17+
"lint": "belly-button -f",
18+
"test": "npm run lint && lab -v -a code -c"
19+
},
20+
"engines": {
21+
"node": ">=4.0.0"
22+
},
23+
"dependencies": {
24+
"boom": "3.x.x",
25+
"insync": "2.x.x"
26+
},
27+
"devDependencies": {
28+
"belly-button": "2.x.x",
29+
"code": "2.x.x",
30+
"hapi": "13.x.x",
31+
"lab": "10.x.x"
32+
},
33+
"keywords": [
34+
"hapi",
35+
"plugin",
36+
"auth",
37+
"authentication",
38+
"scheme",
39+
"strategy"
40+
]
41+
}

‎test/index.js

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
'use strict';
2+
3+
const Boom = require('boom');
4+
const Code = require('code');
5+
const Hapi = require('hapi');
6+
const Lab = require('lab');
7+
const Plugin = require('../lib');
8+
9+
const lab = exports.lab = Lab.script();
10+
const expect = Code.expect;
11+
const describe = lab.describe;
12+
const it = lab.it;
13+
14+
function prepareServer (callback) {
15+
const server = new Hapi.Server();
16+
17+
server.connection();
18+
server.register([Plugin], (err) => {
19+
if (err) {
20+
return callback(err);
21+
}
22+
23+
server.auth.scheme('test', function (server, options) {
24+
return {
25+
authenticate (request, reply) {
26+
const header = request.headers[options.header];
27+
28+
if (header === options.value) {
29+
return reply.continue({
30+
credentials: { [options.header]: options.value }
31+
});
32+
}
33+
34+
reply(Boom.unauthorized());
35+
}
36+
};
37+
});
38+
39+
server.auth.strategy('fooAuth', 'test', { header: 'foo', value: 42 });
40+
server.auth.strategy('barAuth', 'test', { header: 'bar', value: 53 });
41+
server.auth.strategy('bazAuth', 'test', { header: 'baz', value: 64 });
42+
43+
server.auth.strategy('fooOnly', 'nuisance', {
44+
strategies: ['fooAuth']
45+
});
46+
server.auth.strategy('fooBar', 'nuisance', {
47+
strategies: ['fooAuth', 'barAuth']
48+
});
49+
server.auth.strategy('fooBarBaz', 'nuisance', {
50+
strategies: ['fooAuth', 'barAuth', 'bazAuth']
51+
});
52+
53+
server.route([
54+
{
55+
method: 'GET',
56+
path: '/foo',
57+
config: {
58+
auth: 'fooOnly',
59+
handler: function (request, reply) {
60+
reply(request.auth);
61+
}
62+
}
63+
},
64+
{
65+
method: 'GET',
66+
path: '/foo/bar',
67+
config: {
68+
auth: 'fooBar',
69+
handler: function (request, reply) {
70+
reply(request.auth);
71+
}
72+
}
73+
},
74+
{
75+
method: 'GET',
76+
path: '/foo/bar/baz',
77+
config: {
78+
auth: 'fooBarBaz',
79+
handler: function (request, reply) {
80+
reply(request.auth);
81+
}
82+
}
83+
}
84+
]);
85+
86+
callback(null, server);
87+
});
88+
}
89+
90+
describe('Nuisance', () => {
91+
it('authenticates multiple strategies at once', (done) => {
92+
prepareServer((err, server) => {
93+
expect(err).to.not.exist();
94+
95+
server.inject({
96+
method: 'GET',
97+
url: '/foo/bar/baz',
98+
headers: {
99+
foo: 42,
100+
bar: 53,
101+
baz: 64
102+
}
103+
}, (res) => {
104+
expect(res.statusCode).to.equal(200);
105+
expect(res.result).to.deep.include({
106+
isAuthenticated: true,
107+
credentials: {
108+
fooAuth: { foo: 42 },
109+
barAuth: { bar: 53 },
110+
bazAuth: { baz: 64 }
111+
},
112+
strategy: 'fooBarBaz',
113+
mode: 'required',
114+
error: null
115+
});
116+
done();
117+
});
118+
});
119+
});
120+
121+
it('authenticates fails if any strategies fail', (done) => {
122+
prepareServer((err, server) => {
123+
expect(err).to.not.exist();
124+
125+
server.inject({
126+
method: 'GET',
127+
url: '/foo/bar/baz',
128+
headers: {
129+
foo: 42,
130+
bar: 53,
131+
baz: 63 // fails
132+
}
133+
}, (res) => {
134+
expect(res.statusCode).to.equal(401);
135+
expect(res.result).to.deep.equal({
136+
statusCode: 401,
137+
error: 'Unauthorized'
138+
});
139+
done();
140+
});
141+
});
142+
});
143+
});

0 commit comments

Comments
 (0)