Skip to content

Commit

Permalink
[IPS-169] Add authentication object to Rule's context (#199)
Browse files Browse the repository at this point in the history
* [IPS-169] Add authentication object to Rule's context

* Rename rule

* 0.8.0

* Build and bump version
  • Loading branch information
FadyMak authored and shawnmclean committed Feb 28, 2019
1 parent 858004e commit 5009fb1
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 4 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rules-templates",
"version": "0.7.4",
"version": "0.8.0",
"description": "Auth0 Rules Repository",
"main": "./rules",
"scripts": {
Expand Down
12 changes: 11 additions & 1 deletion rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"access control"
],
"description": "<p>This rule adds a Roles field to the user based on some pattern.</p>",
"code": "function (user, context, callback) {\n\n // Roles should only be set to verified users.\n if (!user.email || !user.email_verified) {\n return callback(null, user, context);\n }\n\n user.app_metadata = user.app_metadata || {};\n // You can add a Role based on what you want\n // In this case I check domain\n const addRolesToUser = function(user) {\n const endsWith = '@example.com';\n\n if (user.email && (user.email.substring(user.email.length - endsWith.length, user.email.length) === endsWith)) {\n return ['admin']\n }\n return ['user'];\n };\n\n const roles = addRolesToUser(user);\n\n user.app_metadata.roles = roles;\n auth0.users.updateAppMetadata(user.user_id, user.app_metadata)\n .then(function() {\n context.idToken['https://example.com/roles'] = user.app_metadata.roles;\n callback(null, user, context);\n })\n .catch(function (err) {\n callback(err);\n });\n}"
"code": "function (user, context, callback) {\n\n // Roles should only be set to verified users.\n if (!user.email || !user.email_verified) {\n return callback(null, user, context);\n }\n\n user.app_metadata = user.app_metadata || {};\n // You can add a Role based on what you want\n // In this case I check domain\n const addRolesToUser = function (user) {\n const endsWith = '@example.com';\n\n if (user.email && (user.email.substring(user.email.length - endsWith.length, user.email.length) === endsWith)) {\n return ['admin'];\n }\n return ['user'];\n };\n\n const roles = addRolesToUser(user);\n\n user.app_metadata.roles = roles;\n auth0.users.updateAppMetadata(user.user_id, user.app_metadata)\n .then(function () {\n context.idToken['https://example.com/roles'] = user.app_metadata.roles;\n callback(null, user, context);\n })\n .catch(function (err) {\n callback(err);\n });\n}"
},
{
"id": "simple-domain-whitelist",
Expand Down Expand Up @@ -520,6 +520,16 @@
],
"description": "<p>This rule is used to trigger multifactor authentication when a condition is met.</p>",
"code": "function (user, context, callback) {\n /*\n You can trigger MFA conditionally by checking:\n 1. Client ID:\n context.clientID === 'REPLACE_WITH_YOUR_CLIENT_ID'\n 2. User metadata:\n user.user_metadata.use_mfa\n */\n\n // if (<condition>) {\n context.multifactor = {\n provider: 'any',\n\n // optional, defaults to true. Set to false to force authentication every time.\n // See https://auth0.com/docs/multifactor-authentication/custom#change-the-frequency-of-authentication-requests for details\n allowRememberBrowser: false\n };\n //}\n\n callback(null, user, context);\n}"
},
{
"id": "require-mfa-once-per-session",
"title": "Require MFA once per session",
"overview": "Require multifactor authentication only once per session",
"categories": [
"multifactor"
],
"description": "<p>This rule can be used to avoid prompting a user for multifactor authentication if they have successfully completed MFA in their current session.</p>\n<p>This is particularly useful when performing silent authentication (<code>prompt=none</code>) to renew short-lived access tokens in a SPA (Single Page Application) during the duration of a user's session without having to rely on setting <code>allowRememberBrowser</code> to <code>true</code>.</p>",
"code": "function (user, context, callback) {\n const completedMfa = !!context.authentication.methods.find(\n (method) => method.name === 'mfa'\n );\n \n if (completedMfa) {\n return callback(null, user, context);\n }\n \n context.multifactor = {\n provider: 'any',\n allowRememberBrowser: false\n };\n \n callback(null, user, context);\n}"
}
]
},
Expand Down
28 changes: 28 additions & 0 deletions src/rules/require-mfa-once-per-session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @title Require MFA once per session
* @overview Require multifactor authentication only once per session
* @gallery true
* @category multifactor
*
* This rule can be used to avoid prompting a user for multifactor authentication if they have successfully completed MFA in their current session.
*
* This is particularly useful when performing silent authentication (`prompt=none`) to renew short-lived access tokens in a SPA (Single Page Application) during the duration of a user's session without having to rely on setting `allowRememberBrowser` to `true`.
*
*/

function (user, context, callback) {
const completedMfa = !!context.authentication.methods.find(
(method) => method.name === 'mfa'
);

if (completedMfa) {
return callback(null, user, context);
}

context.multifactor = {
provider: 'any',
allowRememberBrowser: false
};

callback(null, user, context);
}
68 changes: 68 additions & 0 deletions test/rules/require-mfa-once-per-session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict';

const loadRule = require('../utils/load-rule');
const ContextBuilder = require('../utils/contextBuilder');
const RequestBuilder = require('../utils/requestBuilder');
const AuthenticationBuilder = require('../utils/authenticationBuilder');

const ruleName = 'require-mfa-once-per-session';

describe(ruleName, () => {
let context;
let rule;
let user;

describe('With only a login prompt completed', () => {
beforeEach(() => {
rule = loadRule(ruleName);

const request = new RequestBuilder().build();
const authentication = new AuthenticationBuilder().build();
context = new ContextBuilder()
.withRequest(request)
.withAuthentication(authentication)
.build();
});

it('should set a multifactor provider', (done) => {
rule(user, context, (err, u, c) => {
expect(c.multifactor.provider).toBe('any');
expect(c.multifactor.allowRememberBrowser).toBe(false);

done();
});
});
});

describe('With a login and MFA prompt completed', () => {
beforeEach(() => {
rule = loadRule(ruleName);

const request = new RequestBuilder().build();
const authentication = new AuthenticationBuilder()
.withMethods([
{
name: 'pwd',
timestamp: 1434454643024
},
{
name: 'mfa',
timestamp: 1534454643881
}
])
.build();
context = new ContextBuilder()
.withRequest(request)
.withAuthentication(authentication)
.build();
});

it('should not set a multifactor provider', (done) => {
rule(user, context, (err, u, c) => {
expect(c.multifactor).toBe(undefined);

done();
});
});
});
});
23 changes: 23 additions & 0 deletions test/utils/authenticationBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

class AuthenticationBuilder {
constructor() {
this.authentication = {
methods: [
{
name: 'pwd',
timestamp: 1434454643024
}
]
}
}
withMethods(methods) {
this.authentication.methods = methods;
return this;
}
build() {
return this.authentication;
}
}

module.exports = AuthenticationBuilder;
7 changes: 6 additions & 1 deletion test/utils/contextBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class ContextBuilder {
accessToken: {},
idToken: {},
sessionID: 'jYA5wG...BNT5Bak',
request: {}
request: {},
authentication: {}
};
this.context.request = new RequestBuilder().build();
}
Expand Down Expand Up @@ -67,6 +68,10 @@ class ContextBuilder {
this.context.stats = stats;
return this;
}
withAuthentication(authentication) {
this.context.authentication = authentication;
return this;
}
build() {
return this.context;
}
Expand Down

0 comments on commit 5009fb1

Please sign in to comment.