Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

hapi-inferred-scopes

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hapi-inferred-scopes - npm Package Compare versions

Comparing version 1.2.0 to 3.0.0

9

.eslintrc.js

@@ -11,2 +11,3 @@ module.exports = {

"sourceType": "script",
"ecmaVersion": 2018,
"ecmaFeatures": {

@@ -18,2 +19,5 @@ "modules": false,

rules: {
"strict": [2, "global"],
"indent": ["error", 2],
"linebreak-style": 0,
"max-len": [2, 120, 2, {

@@ -25,7 +29,10 @@ ignoreUrls: true,

"no-restricted-syntax": [0],
"no-multi-assign": 0,
"no-return-assign": 0,
"strict": [2, "global"],
"global-require": 0,
"padded-blocks": 0,
"no-underscore-dangle": 0
"no-underscore-dangle": 0,
"no-param-reassign": 0
}
};

@@ -11,5 +11,13 @@ {

"name": "Debug Tests",
"program": "${workspaceRoot}/test/inferredScope.spec.js"
"program": "${workspaceRoot}/node_modules/@hapi/lab/bin/lab",
"args": ["test"],
"skipFiles": [
"<node_internals>/**/*.js"
],
"cwd": "${workspaceRoot}",
"runtimeExecutable": null,
"outputCapture": "std",
"protocol": "inspector"
}
]
}

119

lib/index.js
'use strict';
const deep = require('deep-get-set');
const Boom = require('boom');
const Joi = require('joi');
const Boom = require('@hapi/boom');
const Joi = require('@hapi/joi');

@@ -11,8 +11,8 @@ const internals = {};

scopeDelimiter: Joi.string().default(':'),
scopeAccessor: Joi.func().default(request => request.auth.credentials.scope)
scopeAccessor: Joi.function()
});
internals.isSegMatchAll = s => s === '*';
internals.isSegMatchAll = (s) => s === '*';
internals.isSegRegex = s => s.startsWith('/') && s.endsWith('/');
internals.isSegRegex = (s) => s.startsWith('/') && s.endsWith('/');

@@ -22,10 +22,10 @@ internals.segmentProcessingCommands = {

test: internals.isSegRegex,
process: seg => seg
process: (seg) => seg
},
isMatchAll: {
test: internals.isSegMatchAll,
process: seg => seg
process: (seg) => seg
},
isComputable: {
test: seg => !(internals.isSegRegex(seg) || internals.isSegMatchAll(seg)),
test: (seg) => !(internals.isSegRegex(seg) || internals.isSegMatchAll(seg)),
process: (seg, request) => {

@@ -60,5 +60,5 @@ const matches = seg.match(/{(.*)}/g) || [];

const match = zip.filter(z => {
const match = zip.filter((z) => {
const seg2Exists = z[1];
const defferedSegsAreEqual = () => (
const deferredSegsAreEqual = () => (
internals.isSegRegex(z[0])

@@ -68,6 +68,6 @@ ? new RegExp(z[0].slice(1, -1)).test(z[1])

);
return seg2Exists && defferedSegsAreEqual();
return seg2Exists && deferredSegsAreEqual();
});
return match.map(m => m[1]).join(delimiter) === effectiveScope2;
return match.map((m) => m[1]).join(delimiter) === effectiveScope2;
};

@@ -77,4 +77,4 @@

const effectiveScopes = scopes.reduce((es, s1) => {
const inferredExists = scopes.some(s2 =>
s1 !== s2 && internals.isScope2Inferred(s1, s2, settings.scopeDelimiter)
const inferredExists = scopes.some(
(s2) => s1 !== s2 && internals.isScope2Inferred(s1, s2, settings.scopeDelimiter)
);

@@ -97,11 +97,12 @@

const filters = {
required: rScopes.filter(r => r.startsWith('+')).map(s => s.slice(1)),
forbidden: rScopes.filter(r => r.startsWith('!')).map(s => s.slice(1)),
optional: rScopes.filter(r => !(r.startsWith('+') || r.startsWith('!')))
required: rScopes.filter((r) => r.startsWith('+')).map((s) => s.slice(1)),
forbidden: rScopes.filter((r) => r.startsWith('!')).map((s) => s.slice(1)),
optional: rScopes.filter((r) => !(r.startsWith('+') || r.startsWith('!')))
};
const ensureOnlyOneMatchAll = segs => {
const ensureOnlyOneMatchAll = (segs) => {
if (!segs.some(internals.isSegMatchAll)) {
return segs;
}
const firstMatchAll = segs.find(internals.isSegMatchAll);

@@ -112,15 +113,13 @@ return segs.filter((_, i) => i <= segs.indexOf(firstMatchAll));

Object.keys(filters).forEach((type) => (
filters[type] = filters[type].map((rs) =>
ensureOnlyOneMatchAll(rs.split(delimiter))
.map(seg => {
filters[type] = filters[type].map((rs) => ensureOnlyOneMatchAll(rs.split(delimiter))
.map((seg) => {
const commands = internals.segmentProcessingCommands;
const command = Object.keys(commands).find(key => commands[key].test(seg));
const command = Object.keys(commands).find((key) => commands[key].test(seg));
return commands[command].process(seg, request);
})
.join(delimiter)
)
.join(delimiter))
));
const scopesEqualOrInferred = s1 => effectiveScopes.some(s2 =>
s1 === s2 || internals.isScope2Inferred(s1, s2, settings.scopeDelimiter)
const scopesEqualOrInferred = (s1) => effectiveScopes.some(
(s2) => s1 === s2 || internals.isScope2Inferred(s1, s2, settings.scopeDelimiter)
);

@@ -149,2 +148,3 @@

}
const prev = s.shift();

@@ -163,49 +163,42 @@ map(s, o[prev]);

internals.inferredScope = (settings) =>
(request, reply) => {
const inferredScopeConfig = request.route.settings.plugins.inferredScope;
internals.inferredScope = (settings) => (request, h) => {
const inferredScopeConfig = request.route.settings.plugins.inferredScope;
if (!inferredScopeConfig) {
return reply.continue();
}
if (!inferredScopeConfig) {
return h.continue;
}
const scope = settings.scopeAccessor(request);
if (!scope) {
return reply(Boom.forbidden('Insufficient scope'));
}
const scope = settings.scopeAccessor(request);
if (!scope) {
return Boom.forbidden('Insufficient scope');
}
if (!(inferredScopeConfig instanceof Array)) {
return reply(Boom.forbidden('Unknown scopes'));
}
if (!(inferredScopeConfig instanceof Array)) {
return Boom.forbidden('Unknown scopes');
}
const effectiveScopes = internals.getEffectiveScopes(scope, settings);
const effectiveScopes = internals.getEffectiveScopes(scope, settings);
// eslint-disable-next-line no-param-reassign
request.auth.artifacts = request.auth.artifacts || {};
// eslint-disable-next-line no-param-reassign
request.auth.artifacts.scopeContext = internals.createScopeContext(
effectiveScopes,
settings
);
request.auth.artifacts = request.auth.artifacts || {};
request.auth.artifacts.scopeContext = internals.createScopeContext(
effectiveScopes,
settings
);
if (!internals.hasInferredScope(request, effectiveScopes, settings)) {
return reply(Boom.forbidden('Insufficient scope'));
}
return reply.continue();
};
exports.register = (plugin, options, next) => {
const results = Joi.validate(options, internals.schema);
if (results.error) {
return next(results.error);
if (!internals.hasInferredScope(request, effectiveScopes, settings)) {
return Boom.forbidden('Insufficient scope');
}
plugin.ext('onPostAuth', internals.inferredScope(results.value));
return next();
return h.continue;
};
exports.register.attributes = {
name: 'hapi-inferred-scopes',
version: '1.0.0'
exports.plugin = {
register: (server, options) => {
const validatedOptions = Joi.attempt(options, internals.schema);
if (!validatedOptions.scopeAccessor) {
validatedOptions.scopeAccessor = (request) => request.auth.credentials.scope;
}
server.ext('onPostAuth', internals.inferredScope(validatedOptions));
},
pkg: require('../package.json')
};
{
"name": "hapi-inferred-scopes",
"version": "1.2.0",
"version": "3.0.0",
"description": "infer route access from grouped scopes",
"main": "lib/index.js",
"scripts": {
"test": "test/inferredScope.spec.js"
"coverage": "lab -C --reporter html test > coverage.html",
"lint": "node ./node_modules/.bin/eslint lib",
"test": "node ./node_modules/@hapi/lab/bin/lab test"
},

@@ -16,11 +18,26 @@ "repository": {

],
"author": "daniel ross",
"author": {
"name": "Daniel Ross",
"email": "danielglennross@gmail.com"
},
"contributors": [
{
"name": "Matthew Bates",
"email": "matthew.bates.117@gmail.com"
}
],
"license": "ISC",
"devDependencies": {
"code": "^4.0.0",
"eslint": "^2.9.0",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.7.0",
"hapi": "^16.1.0",
"lab": "^13.0.1"
"@hapi/code": "^7.0.0",
"@hapi/eslint-config-hapi": "^12.3.0",
"@hapi/eslint-plugin-hapi": "^4.3.4",
"@hapi/hapi": "^18.0.0",
"@hapi/lab": "^21.0.0",
"eslint": "^6.1.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.14.3",
"eslint-plugin-react-hooks": "^1.7.0"
},

@@ -32,6 +49,6 @@ "bugs": {

"dependencies": {
"boom": "^4.2.0",
"deep-get-set": "^1.1.0",
"joi": "^10.2.2"
"@hapi/boom": "^8.0.0",
"@hapi/joi": "^16.0.0",
"deep-get-set": "^1.1.0"
}
}
# hapi-inferred-scopes
Hapi out-the-box scopes are powerful, but what if we wanted to manage scopes like resources which have sub-resources and infer whether a request is permitted?

@@ -6,5 +7,7 @@ `hapi-inferred-scopes` allows us to mark routes with finely grained scopes, however still permit access for credentials that infer parent scopes.

## Install
`npm install --save hapi-inferred-scopes`
## Usage
```javascript

@@ -19,20 +22,18 @@ // the following option values are the defaults

server.register({
register: require('hapi-inferred-scopes'),
await server.register({
plugin: require('hapi-inferred-scopes'),
options
}, (err) => {
server.route({
method: 'GET',
path: '/user/email',
config: {
auth: 'session',
plugins: {
inferredScope: ['user:email'] // make this as granular as possible
},
handler: (request, reply) =>
reply(request.auth.artifacts.scopeContext).code(200)
}
});
});
server.route({
method: 'GET',
path: '/user/email',
options: {
auth: 'session',
plugins: {
inferredScope: ['user:email'] // make this as granular as possible
},
handler: (request, h) =>
h.response(request.auth.artifacts.scopeContext).code(200)
}
});

@@ -42,2 +43,3 @@ ```

## Example
Say within our app we identify the following scope groups:

@@ -48,3 +50,3 @@

We can now guard a route with the most specific scope we'd like a request auth credentials to have.
We can now guard a route with the most specific scope we'd like a request auth credentials to have.
For example, a route which accesses a user's account we can guard with `user:account`.

@@ -60,7 +62,7 @@ An auth credential that has scope `user:account` is allowed to access this route, along with any credential that has the parent scope `user` - this is inferred.

For dynamic expressions, surround the expression in `{...}` like so:
For dynamic expressions, surround the expression in `{...}` like so:
- `{params.username`
- `user-{params.username}`
- `user:{params.accountType}`(child scope is a dynaimc expression)
- `user:{params.accountType}`(child scope is a dynamic expression)

@@ -73,4 +75,4 @@ For regular expressions, surround the expression in `/.../` like so:

A wild card operator: match all `*` is supported.
This operator will match any scope or subsequent subscopes, including absent ones. For example, `user:*` will match:
A wild card operator: match all `*` is supported.
This operator will match any scope or subsequent sub-scopes, including absent ones. For example, `user:*` will match:

@@ -81,3 +83,3 @@ - `user`

In addition, a `scopeContext` is created and accessible on `request.auth.arifacts.scopeContext`. This is an object representing the hierarchy of grouped scopes.
In addition, a `scopeContext` is created and accessible on `request.auth.artifacts.scopeContext`. This is an object representing the hierarchy of grouped scopes.
`scopeContext` can be inspected to make any further decisions regarding scopes during a request's life-cycle.

@@ -84,0 +86,0 @@ For example a credential with scopes: `['user:account:read', 'user:profile', 'admin']` will have the following `scopeContext`:

'use strict';
const Code = require('code');
const Hapi = require('hapi');
const Lab = require('lab');
const Hapi = require('@hapi/hapi');
const expect = Code.expect;
const lab = Lab.script();
const describe = lab.describe;
const it = lab.it;
const { expect } = require('@hapi/code');
const setAuthSchemeWithScope = (scope) =>
() => ({
authenticate(request, reply) {
return reply.continue({ credentials: { scope } });
}
});
const { describe, it } = exports.lab = require('@hapi/lab').script();
const setAuthSchemeWithScope = (scope) => () => ({
authenticate(request, h) {
return h.authenticated({ credentials: { scope } });
}
});
describe('explicit scope', () => {
it('assigns scopeContext to auth artifacts', (done) => {
it('assigns scopeContext to auth artifacts', async () => {
const server = new Hapi.Server();
server.connection();

@@ -28,27 +23,23 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope([]));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/artifacts',
config: {
auth: 'scope',
plugins: {
inferredScope: []
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/artifacts',
options: {
auth: 'scope',
plugins: {
inferredScope: []
},
handler: (request) => request.auth.artifacts
}
});
server.inject('/artifacts', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
done();
});
});
const res = await server.inject('/artifacts');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
});
it('builds scopeContext object', (done) => {
it('builds scopeContext object', async () => {
const server = new Hapi.Server();
server.connection();

@@ -58,29 +49,25 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1', 'scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/artifacts',
config: {
auth: 'scope',
plugins: {
inferredScope: []
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/artifacts',
config: {
auth: 'scope',
plugins: {
inferredScope: []
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/artifacts', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope1).to.exist();
expect(res.result.scopeContext.scope2).to.exist();
done();
});
});
const res = await server.inject('/artifacts');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope1).to.exist();
expect(res.result.scopeContext.scope2).to.exist();
});
it('authenticates on matching an explicit single scope', (done) => {
it('authenticates on matching an explicit single scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -90,26 +77,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/singlescope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/singlescope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/singlescope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/singlescope');
expect(res.statusCode).to.equal(200);
});
it('authenticates on matching explicit multiple scopes', (done) => {
it('authenticates on matching explicit multiple scopes', async () => {
const server = new Hapi.Server();
server.connection();

@@ -119,26 +102,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1', 'scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/multiplescopes',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope1', 'scope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/multiplescopes',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope1', 'scope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/multiplescopes', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/multiplescopes');
expect(res.statusCode).to.equal(200);
});
it('authenticates on matching at least one explicit scope', (done) => {
it('authenticates on matching at least one explicit scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -148,26 +127,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1', 'scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/eitherscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/eitherscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/eitherscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/eitherscope');
expect(res.statusCode).to.equal(200);
});
it('authenticates when the route is configured with no scopes (empty array)', (done) => {
it('authenticates when the route is configured with no scopes (empty array)', async () => {
const server = new Hapi.Server();
server.connection();

@@ -177,26 +152,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1', 'scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/emptyscopes',
config: {
auth: 'scope',
plugins: {
inferredScope: []
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/emptyscopes',
config: {
auth: 'scope',
plugins: {
inferredScope: []
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/emptyscopes', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/emptyscopes');
expect(res.statusCode).to.equal(200);
});
it('authenticates a required scope', (done) => {
it('authenticates a required scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -206,26 +177,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1', 'scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/requiredscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/requiredscope');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticates when a required scope is missing', (done) => {
it('fails to authenticates when a required scope is missing', async () => {
const server = new Hapi.Server();
server.connection();

@@ -235,26 +202,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/requiredscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/requiredscope');
expect(res.statusCode).to.equal(403);
});
it('authenticates a missing forbidden scope', (done) => {
it('authenticates a missing forbidden scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -264,26 +227,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope1', 'scope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope1', 'scope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/forbiddenscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/forbiddenscope');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticates when a forbidden scope exists', (done) => {
it('fails to authenticates when a forbidden scope exists', async () => {
const server = new Hapi.Server();
server.connection();

@@ -293,26 +252,23 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1', 'scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/forbiddenscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/forbiddenscope');
expect(res.statusCode).to.equal(403);
});
it('dynamically binds request data to scopes', (done) => {
it('dynamically binds request data to scopes', async () => {
const server = new Hapi.Server();
server.connection();

@@ -322,25 +278,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/groupedscope/{scope}',
config: {
auth: 'scope',
plugins: {
inferredScope: ['{params.scope}']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/groupedscope/{scope}',
config: {
auth: 'scope',
plugins: {
inferredScope: ['{params.scope}']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/groupedscope/scope', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
done();
});
});
const res = await server.inject('/groupedscope/scope');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
});
});
'use strict';
const Code = require('code');
const Hapi = require('hapi');
const Lab = require('lab');
const Hapi = require('@hapi/hapi');
const expect = Code.expect;
const lab = Lab.script();
const describe = lab.describe;
const it = lab.it;
const { expect } = require('@hapi/code');
const setAuthSchemeWithScope = (scope) =>
() => ({
authenticate(request, reply) {
return reply.continue({ credentials: { scope } });
}
});
const { describe, it } = exports.lab = require('@hapi/lab').script();
const setAuthSchemeWithScope = (scope) => () => ({
authenticate(request, h) {
return h.authenticated({ credentials: { scope } });
}
});
describe('inferred scope', () => {
it('authenticates on matching a grouped scope', (done) => {
it('authenticates on matching a grouped scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -28,26 +23,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/groupedscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/groupedscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/groupedscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/groupedscope');
expect(res.statusCode).to.equal(200);
});
it('authenticates on matching a grouped scope and explicit scopes exist', (done) => {
it('authenticates on matching a grouped scope and explicit scopes exist', async () => {
const server = new Hapi.Server();
server.connection();

@@ -57,26 +48,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope', 'scope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/inferredexplicit',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/inferredexplicit',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/inferredexplicit', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/inferredexplicit');
expect(res.statusCode).to.equal(200);
});
it('authenticates on matching a single inferred scope', (done) => {
it('authenticates on matching a single inferred scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -86,26 +73,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/singleinferred',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/singleinferred',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/singleinferred', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/singleinferred');
expect(res.statusCode).to.equal(200);
});
it('builds scopeContext on matching a single inferred scope', (done) => {
it('builds scopeContext on matching a single inferred scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -115,28 +98,24 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/scopecontext',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/scopecontext',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/scopecontext', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
done();
});
});
const res = await server.inject('/scopecontext');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
});
it('authenticates and reduces extraneous scopes to common inferred scope', (done) => {
it('authenticates and reduces extraneous scopes to common inferred scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -146,29 +125,25 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope', 'scope:subscope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/reduces',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/reduces',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/reduces', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
expect(res.result.scopeContext.scope.subscope).to.not.exist();
done();
});
});
const res = await server.inject('/reduces');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
expect(res.result.scopeContext.scope.subscope).to.not.exist();
});
it('authenticates on matching a twice nested inferred scope', (done) => {
it('authenticates on matching a twice nested inferred scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -178,26 +153,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/twicenested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1:subscope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/twicenested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1:subscope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/twicenested', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/twicenested');
expect(res.statusCode).to.equal(200);
});
it('builds scopeContext on matching a twice nested inferred scope', (done) => {
it('builds scopeContext on matching a twice nested inferred scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -207,28 +178,24 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/twicenested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1:subscope2']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/twicenested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1:subscope2']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/twicenested', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope.subscope1).to.exist();
done();
});
});
const res = await server.inject('/twicenested');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope.subscope1).to.exist();
});
it('authenticates on matching a grouped scope on a twice nested inferred scope', (done) => {
it('authenticates on matching a grouped scope on a twice nested inferred scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -238,26 +205,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1:subscope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/twicenested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1:subscope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/twicenested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1:subscope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/twicenested', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/twicenested');
expect(res.statusCode).to.equal(200);
});
it('builds scopeContext on matching a 3x nested inferred scope', (done) => {
it('builds scopeContext on matching a 3x nested inferred scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -267,29 +230,25 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1:subscope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/threetimesnested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1:subscope2']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/threetimesnested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1:subscope2']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/threetimesnested', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope.subscope1).to.exist();
expect(res.result.scopeContext.scope.subscope1.subscope2).to.exist();
done();
});
});
const res = await server.inject('/threetimesnested');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope.subscope1).to.exist();
expect(res.result.scopeContext.scope.subscope1.subscope2).to.exist();
});
it('authenticates and reduces extraneous 3x grouped scopes to common inferred scope', (done) => {
it('authenticates and reduces extraneous 3x grouped scopes to common inferred scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -299,30 +258,26 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1', 'scope:subscope1:subscope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/threetimesnested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/threetimesnested',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/threetimesnested', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
expect(res.result.scopeContext.scope.subscope1).to.exist();
expect(res.result.scopeContext.scope.subscope1.subscope2).to.not.exist();
done();
});
});
const res = await server.inject('/threetimesnested');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
expect(res.result.scopeContext.scope.subscope1).to.exist();
expect(res.result.scopeContext.scope.subscope1.subscope2).to.not.exist();
});
it('builds scopeContext on inferred and explicit scopes', (done) => {
it('builds scopeContext on inferred and explicit scopes', async () => {
const server = new Hapi.Server();
server.connection();

@@ -332,67 +287,59 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1:subscope1', 'scope1:subscope2', 'scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/mixed',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope1:subscope1']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/mixed',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope1:subscope1']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/mixed', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope1).to.exist();
expect(res.result.scopeContext.scope1.subscope1).to.exist();
expect(res.result.scopeContext.scope1.subscope2).to.exist();
expect(res.result.scopeContext.scope2).to.exist();
done();
});
});
const res = await server.inject('/mixed');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope1).to.exist();
expect(res.result.scopeContext.scope1.subscope1).to.exist();
expect(res.result.scopeContext.scope1.subscope2).to.exist();
expect(res.result.scopeContext.scope2).to.exist();
});
it('builds scopeContext on inferred and explicit reduced scopes', (done) => {
it('builds scopeContext on inferred and explicit reduced scopes', async () => {
const server = new Hapi.Server();
server.connection();
server.auth.scheme('scopeTest', setAuthSchemeWithScope(
['scope1:subscope1', 'scope1:subscope2', 'scope2', 'scope2:subscope2'])
);
['scope1:subscope1', 'scope1:subscope2', 'scope2', 'scope2:subscope2']
));
server.auth.strategy('scope', 'scopeTest');
server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/mixed',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope1:subscope1']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/mixed',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope1:subscope1']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/mixed', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope1).to.exist();
expect(res.result.scopeContext.scope1.subscope1).to.exist();
expect(res.result.scopeContext.scope1.subscope2).to.exist();
expect(res.result.scopeContext.scope2).to.exist();
expect(res.result.scopeContext.scope2.subscope2).to.not.exist();
done();
});
});
const res = await server.inject('/mixed');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope1).to.exist();
expect(res.result.scopeContext.scope1.subscope1).to.exist();
expect(res.result.scopeContext.scope1.subscope2).to.exist();
expect(res.result.scopeContext.scope2).to.exist();
expect(res.result.scopeContext.scope2.subscope2).to.not.exist();
});
it('fails to authenticate when single group scope is too granular', (done) => {
it('fails to authenticate when single group scope is too granular', async () => {
const server = new Hapi.Server();
server.connection();

@@ -402,26 +349,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/invalidscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/invalidscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/invalidscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/invalidscope');
expect(res.statusCode).to.equal(403);
});
it('fails to authenticate when multiple group scope is too granular', (done) => {
it('fails to authenticate when multiple group scope is too granular', async () => {
const server = new Hapi.Server();
server.connection();

@@ -431,26 +374,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1:subscope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/invalidscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/invalidscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:subscope1']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/invalidscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/invalidscope');
expect(res.statusCode).to.equal(403);
});
it('authenticates a required scope', (done) => {
it('authenticates a required scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -460,26 +399,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/requiredscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/requiredscope');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticates when a required scope is missing', (done) => {
it('fails to authenticates when a required scope is missing', async () => {
const server = new Hapi.Server();
server.connection();

@@ -489,26 +424,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope([]));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/requiredscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/requiredscope');
expect(res.statusCode).to.equal(403);
});
it('authenticates a required nested scope', (done) => {
it('authenticates a required nested scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -518,26 +449,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/requiredscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/requiredscope');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticates when a required nested scope is missing', (done) => {
it('fails to authenticates when a required nested scope is missing', async () => {
const server = new Hapi.Server();
server.connection();

@@ -547,26 +474,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope([]));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/requiredscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/requiredscope');
expect(res.statusCode).to.equal(403);
});
it('authenticates a missing forbidden scope', (done) => {
it('authenticates a missing forbidden scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -576,26 +499,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope([]));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/forbiddenscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/forbiddenscope');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticates when a forbidden scope exists', (done) => {
it('fails to authenticates when a forbidden scope exists', async () => {
const server = new Hapi.Server();
server.connection();

@@ -605,26 +524,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/forbiddenscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/forbiddenscope');
expect(res.statusCode).to.equal(403);
});
it('authenticates a missing forbidden nested scope', (done) => {
it('authenticates a missing forbidden nested scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -634,26 +549,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope([]));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope:subscope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope:subscope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/forbiddenscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/forbiddenscope');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticates when a forbidden nested scope exists', (done) => {
it('fails to authenticates when a forbidden nested scope exists', async () => {
const server = new Hapi.Server();
server.connection();

@@ -663,26 +574,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope:subscope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope:subscope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/forbiddenscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/forbiddenscope');
expect(res.statusCode).to.equal(403);
});
it('fails to authenticate an inferred scope where the route specifies a nested forbidden scope', (done) => {
it('fails to authenticate an inferred scope where the route specifies a nested forbidden scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -692,26 +599,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope:subscope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope:subscope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/forbiddenscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/forbiddenscope');
expect(res.statusCode).to.equal(403);
});
it('authenticates an inferred scope where the route specifies a nested required scope', (done) => {
it('authenticates an inferred scope where the route specifies a nested required scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -721,26 +624,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope:subscope']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope:subscope']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/requiredscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/requiredscope');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticate an inferred scope where the route specifies a twice nested forbidden scope', (done) => {
it('fails to authenticate an inferred scope where the route specifies a twice nested forbidden scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -750,26 +649,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope:subscope1:subscope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/forbiddenscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['!scope:subscope1:subscope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/forbiddenscope', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/forbiddenscope');
expect(res.statusCode).to.equal(403);
});
it('authenticates an inferred scope where the route specifies a twice nested required scope', (done) => {
it('authenticates an inferred scope where the route specifies a twice nested required scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -779,26 +674,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope:subscope1:subscope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/requiredscope',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope:subscope1:subscope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/requiredscope', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/requiredscope');
expect(res.statusCode).to.equal(200);
});
it('authenticates with a requied scope and a missing forbidden scope', (done) => {
it('authenticates with a required scope and a missing forbidden scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -808,26 +699,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/mixture',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1', '!scope2:subscope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/mixture',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1', '!scope2:subscope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/mixture', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/mixture');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticates with an inferred required scope and a forbidden scope', (done) => {
it('fails to authenticates with an inferred required scope and a forbidden scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -837,26 +724,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1', 'scope2:subscope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/mixture',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1', '!scope2:subscope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/mixture',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1', '!scope2:subscope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/mixture', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/mixture');
expect(res.statusCode).to.equal(403);
});
it('fails to authenticates with a required scope and an inferred forbidden scope', (done) => {
it('fails to authenticates with a required scope and an inferred forbidden scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -866,26 +749,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1:subscope1', 'scope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/mixture',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1', '!scope2:subscope2']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/mixture',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1', '!scope2:subscope2']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/mixture', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/mixture');
expect(res.statusCode).to.equal(403);
});
it('authenticates with a required scope and a forbidden scope missing', (done) => {
it('authenticates with a required scope and a forbidden scope missing', async () => {
const server = new Hapi.Server();
server.connection();

@@ -895,26 +774,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope1:subscope1', 'scope2:subscope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/mixture',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1', '!scope2:subscope3']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/mixture',
config: {
auth: 'scope',
plugins: {
inferredScope: ['+scope1:subscope1', '!scope2:subscope3']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/mixture', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/mixture');
expect(res.statusCode).to.equal(200);
});
it('dynamically binds request data to grouped scopes', (done) => {
it('dynamically binds request data to grouped scopes', async () => {
const server = new Hapi.Server();
server.connection();

@@ -924,29 +799,25 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/groupedscope/{scope}/{subscope}',
config: {
auth: 'scope',
plugins: {
inferredScope: ['{params.scope}:{params.subscope}']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/groupedscope/{scope}/{subscope}',
config: {
auth: 'scope',
plugins: {
inferredScope: ['{params.scope}:{params.subscope}']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/groupedscope/scope/subscope', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
expect(res.result.scopeContext.scope.subscope).to.exist();
done();
});
});
const res = await server.inject('/groupedscope/scope/subscope');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
expect(res.result.scopeContext.scope.subscope).to.exist();
});
it('authenticates with a regex scope', (done) => {
it('authenticates with a regex scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -956,26 +827,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/regex',
config: {
auth: 'scope',
plugins: {
inferredScope: ['/.*/']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/regex',
config: {
auth: 'scope',
plugins: {
inferredScope: ['/.*/']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/regex', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/regex');
expect(res.statusCode).to.equal(200);
});
it('authenticates with a partial regex nested scope', (done) => {
it('authenticates with a partial regex nested scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -985,26 +852,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/regex',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:/sub.*/']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/regex',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:/sub.*/']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/regex', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/regex');
expect(res.statusCode).to.equal(200);
});
it('authenticates with a regex nested scope', (done) => {
it('authenticates with a regex nested scope', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1014,26 +877,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/regex',
config: {
auth: 'scope',
plugins: {
inferredScope: ['/.*/:/.*/']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/regex',
config: {
auth: 'scope',
plugins: {
inferredScope: ['/.*/:/.*/']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/regex', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/regex');
expect(res.statusCode).to.equal(200);
});
it('fails to authenticate when a nested scope is missing', (done) => {
it('fails to authenticate when a nested scope is missing', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1043,26 +902,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/regex',
config: {
auth: 'scope',
plugins: {
inferredScope: ['/.*/']
},
handler: (request, reply) => reply().code(200)
}
});
server.route({
method: 'GET',
path: '/regex',
config: {
auth: 'scope',
plugins: {
inferredScope: ['/.*/']
},
handler: (request, h) => h.response().code(200)
}
});
server.inject('/regex', (res) => {
expect(res.statusCode).to.equal(403);
done();
});
});
const res = await server.inject('/regex');
expect(res.statusCode).to.equal(403);
});
it('authenticates using regex and dynamic scopes', (done) => {
it('authenticates using regex and dynamic scopes', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1072,30 +927,26 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1:subscope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/groupedscope/{scope}/{subscope1}',
config: {
auth: 'scope',
plugins: {
inferredScope: ['{params.scope}:{params.subscope1}:/sub.*/']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/groupedscope/{scope}/{subscope1}',
config: {
auth: 'scope',
plugins: {
inferredScope: ['{params.scope}:{params.subscope1}:/sub.*/']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/groupedscope/scope/subscope1', (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
expect(res.result.scopeContext.scope.subscope1).to.exist();
expect(res.result.scopeContext.scope.subscope1.subscope2).to.exist();
done();
});
});
const res = await server.inject('/groupedscope/scope/subscope1');
expect(res.statusCode).to.equal(200);
expect(res.result.scopeContext).to.exist();
expect(res.result.scopeContext.scope).to.exist();
expect(res.result.scopeContext.scope.subscope1).to.exist();
expect(res.result.scopeContext.scope.subscope1.subscope2).to.exist();
});
it('authenticates using a match all operator', (done) => {
it('authenticates using a match all operator', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1105,26 +956,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['*']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['*']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/matchall', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/matchall');
expect(res.statusCode).to.equal(200);
});
it('authenticates using a match all operator when a subscope doesn\'t exist', (done) => {
it('authenticates using a match all operator when a subscope doesn\'t exist', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1134,26 +981,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:*']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:*']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/matchall', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/matchall');
expect(res.statusCode).to.equal(200);
});
it('authenticates using a nested match all operator', (done) => {
it('authenticates using a nested match all operator', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1163,26 +1006,23 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:*']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:*']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/matchall', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/matchall');
expect(res.statusCode).to.equal(200);
});
it('authenticates using a twice nested match all operator', (done) => {
it('authenticates using a twice nested match all operator', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1192,26 +1032,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1:subscope2']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:*']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:*']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/matchall', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/matchall');
expect(res.statusCode).to.equal(200);
});
it('authenticates using the first match all operator discovered', (done) => {
it('authenticates using the first match all operator discovered', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1221,26 +1057,22 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope:subscope1']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:*:*']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['scope:*:*']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/matchall', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/matchall');
expect(res.statusCode).to.equal(200);
});
it('authenticates and ignores any scopes after the match all operator', (done) => {
it('authenticates and ignores any scopes after the match all operator', async () => {
const server = new Hapi.Server();
server.connection();

@@ -1250,23 +1082,19 @@ server.auth.scheme('scopeTest', setAuthSchemeWithScope(['scope']));

server.register(require('../'), () => {
await server.register(require('../'));
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['*:scope']
},
handler: (request, reply) => reply(request.auth.artifacts).code(200)
}
});
server.route({
method: 'GET',
path: '/matchall',
config: {
auth: 'scope',
plugins: {
inferredScope: ['*:scope']
},
handler: (request, h) => h.response(request.auth.artifacts).code(200)
}
});
server.inject('/matchall', (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
const res = await server.inject('/matchall');
expect(res.statusCode).to.equal(200);
});
});
'use strict';
const Code = require('code');
const Hapi = require('hapi');
const Lab = require('lab');
const Hapi = require('@hapi/hapi');
const expect = Code.expect;
const lab = Lab.script();
const describe = lab.describe;
const it = lab.it;
const { expect } = require('@hapi/code');
const { describe, it } = exports.lab = require('@hapi/lab').script();
describe('register', () => {
it('passes with default options', (done) => {
it('passes with default options', async () => {
const server = new Hapi.Server();
server.connection();
let exception = null;
server.register(require('../'), (err) => {
expect(err).to.not.exist();
done();
});
try {
await server.register(require('../'));
} catch (e) {
exception = e;
}
expect(exception).to.be.null();
});
it('passes with configured options', (done) => {
it('passes with configured options', async () => {
const server = new Hapi.Server();
server.connection();
const options = {
scopeDelimiter: ';',
scopeAccessor: request => request.auth.credentials.scope
scopeAccessor: (request) => request.auth.credentials.scope
};
server.register({
register: require('../'),
options
}, (err) => {
expect(err).to.not.exist();
done();
});
let exception = null;
try {
await server.register({
plugin: require('../'),
options
});
} catch (e) {
exception = e;
}
expect(exception).to.be.null();
});
it('fails with invalid options', (done) => {
it('fails with invalid options', async () => {
const server = new Hapi.Server();
server.connection();

@@ -50,11 +51,15 @@ const options = {

server.register({
register: require('../'),
options
}, (err) => {
expect(err).to.exist();
done();
});
let exception = null;
try {
await server.register({
plugin: require('../'),
options
});
} catch (e) {
exception = e;
}
expect(exception).to.not.be.null();
expect(exception.message).to.equal('"invalidProperty" is not allowed');
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc