Skip to content

Commit

Permalink
Release v0.16.0 (#271)
Browse files Browse the repository at this point in the history
* Format, test, and compile

* 0.16.0
  • Loading branch information
joshcanhelp authored Feb 9, 2021
1 parent b18a945 commit 3eb2f32
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 16 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.15.0",
"version": "0.16.0",
"description": "Auth0 Rules Repository",
"main": "./rules",
"scripts": {
Expand Down
14 changes: 12 additions & 2 deletions rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@
"multifactor"
],
"description": "<p>This rule will challenge for a second authentication factor on request (step up) when\nacr_values = 'http://schemas.openid.net/pape/policies/2007/06/multi-factor' is sent in\nthe request. Before the challenge is made, 'context.authentication.methods' is checked\nto determine when the user has already successfully completed a challenge in the\ncurrent session.</p>",
"code": "function guardianMultifactorStepUpAuthentication(user, context, callback) {\n // This rule initiates multi-factor authenticaiton as a second factor\n // whenever the request contains the following value:\n //\n // acr_values = 'http://schemas.openid.net/pape/policies/2007/06/multi-factor'\n //\n // and multi-factor authentication has not already been completed in the\n // current session/\n\n if (\n context.request.query.acr_values ===\n 'http://schemas.openid.net/pape/policies/2007/06/multi-factor' &&\n !context.authentication.methods.some((method) => method.name === 'mfa')\n ) {\n context.multifactor = {\n provider: 'any',\n allowRememberBrowser: false\n };\n }\n\n callback(null, user, context);\n}"
"code": "function guardianMultifactorStepUpAuthentication(user, context, callback) {\n // This rule initiates multi-factor authenticaiton as a second factor\n // whenever the request contains the following value:\n //\n // acr_values = 'http://schemas.openid.net/pape/policies/2007/06/multi-factor'\n //\n // and multi-factor authentication has not already been completed in the\n // current session/\n\n const isMfa =\n context.request.query.acr_values ===\n 'http://schemas.openid.net/pape/policies/2007/06/multi-factor';\n\n let authMethods = [];\n if (context.authentication && Array.isArray(context.authentication.methods)) {\n authMethods = context.authentication.methods;\n }\n\n if (isMfa && !authMethods.some((method) => method.name === 'mfa')) {\n context.multifactor = {\n provider: 'any',\n allowRememberBrowser: false\n };\n }\n\n callback(null, user, context);\n}"
},
{
"id": "guardian-multifactor",
Expand Down Expand Up @@ -258,7 +258,7 @@
"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 requireMfaOncePerSession(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}"
"code": "function requireMfaOncePerSession(user, context, callback) {\n let authMethods = [];\n if (context.authentication && Array.isArray(context.authentication.methods)) {\n authMethods = context.authentication.methods;\n }\n\n const completedMfa = !!authMethods.find((method) => method.name === 'mfa');\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 Expand Up @@ -636,6 +636,16 @@
"description": "<p>Please see the <a href=\"https://marketplace.auth0.com/integrations/caisson-id-check\">Caisson integration</a> for more information and detailed installation instructions.</p>\n<p><strong>Required configuration</strong> (this Rule will be skipped if any of the below are not defined):</p>\n<ul>\n<li><code>CAISSON_PUBLIC_KEY</code> Found on the Caisson Developer tab above</li>\n<li><code>CAISSON_PRIVATE_KEY</code> Found on the Caisson Developer tab above</li>\n<li><code>CAISSON_LOGIN_FREQUENCY_DAYS</code> Set to \"-1\" to check ID on registration only, \"0\" to check on all logins, and another positive integer for a minimum number of days between ID checks</li>\n</ul>\n<p><strong>Optional configuration:</strong></p>\n<ul>\n<li><code>CAISSON_DEBUG</code> Set to \"true\" to log errors in the console</li>\n</ul>",
"code": "async function caissonIDCheck(user, context, callback) {\n if (\n !configuration.CAISSON_PUBLIC_KEY ||\n !configuration.CAISSON_PRIVATE_KEY ||\n !configuration.CAISSON_LOGIN_FREQUENCY_DAYS\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const { Auth0RedirectRuleUtilities } = require('@auth0/[email protected]');\n\n //copy off the config obj so we can use our own private key for session token signing.\n let caissonConf = JSON.parse(JSON.stringify(configuration));\n caissonConf.SESSION_TOKEN_SECRET = configuration.CAISSON_PRIVATE_KEY;\n\n const manager = {\n creds: {\n public_key: caissonConf.CAISSON_PUBLIC_KEY,\n private_key: caissonConf.CAISSON_PRIVATE_KEY\n },\n /* prettier-ignore */\n debug: caissonConf.CAISSON_DEBUG && caissonConf.CAISSON_DEBUG.toLowerCase() === \"true\" ? true : false,\n idCheckFlags: {\n login_frequency_days: parseInt(\n caissonConf.CAISSON_LOGIN_FREQUENCY_DAYS,\n 10\n )\n },\n caissonHosts: {\n idcheck: 'https://id.caisson.com',\n api: 'https://api.caisson.com',\n dashboard: 'https://www.caisson.com'\n },\n axios: require('[email protected]'),\n util: new Auth0RedirectRuleUtilities(user, context, caissonConf)\n };\n\n user.app_metadata = user.app_metadata || {};\n user.app_metadata.caisson = user.app_metadata.caisson || {};\n const caisson = user.app_metadata.caisson;\n\n /**\n * Toggleable logger. Set CAISSON_DEBUG in the Auth0 configuration to enable.\n *\n * @param {error} err\n */\n function dLog(err) {\n if (manager.debug) {\n console.log(err);\n }\n }\n\n /**\n * Helper function for converting milliseconds to days. Results rounded down.\n * @param {int} mils\n */\n function millisToDays(mils) {\n return Math.floor(mils / 1000 / 60 / 60 / 24);\n }\n\n /**\n * Creates Caisson specific session token and sets redirect url.\n */\n function setIDCheckRedirect() {\n const token = manager.util.createSessionToken({\n public_key: manager.creds.public_key,\n host: context.request.hostname\n });\n\n //throws if redirects aren't allowed here.\n manager.util.doRedirect(`${manager.caissonHosts.idcheck}/auth0`, token); //throws\n }\n\n /**\n * Swaps the temp Caisson exchange token for an ID Check key.\n * https://www.caisson.com/docs/reference/api/#exchange-check-token-for-check-id\n * @param {string} t\n */\n async function exchangeToken() {\n try {\n let resp = await manager.axios.post(\n manager.caissonHosts.api + '/v1/idcheck/exchangetoken',\n { check_exchange_token: manager.util.queryParams.t },\n {\n headers: {\n Authorization: `Caisson ${manager.creds.private_key}`\n }\n }\n );\n\n return resp.data.check_id;\n } catch (error) {\n let err = error;\n if (err.response && err.response.status === 401) {\n err = new UnauthorizedError(\n 'Invalid private key. See your API credentials at https://www.caisson.com/developer .'\n );\n }\n throw err;\n }\n }\n\n /**\n * Fetches and validates ID Check results.\n * https://www.caisson.com/docs/reference/api/#get-an-id-check-result\n * @param {string} check_id\n */\n async function idCheckResults(check_id) {\n try {\n let resp = await manager.axios.get(\n manager.caissonHosts.api + '/v1/idcheck',\n {\n headers: {\n Authorization: `Caisson ${manager.creds.private_key}`,\n 'X-Caisson-CheckID': check_id\n }\n }\n );\n\n if (resp.data.error) {\n throw new Error(\n 'Error in Caisson ID Check: ' + JSON.stringify(resp.data)\n );\n }\n\n let results = {\n check_id: resp.data.check_id,\n auth0_id: resp.data.customer_id,\n timestamp: resp.data.checked_on,\n /* prettier-ignore */\n status: resp.data.confidence.document === \"high\" && resp.data.confidence.face === \"high\" ? \"passed\" : \"flagged\"\n };\n\n validateIDCheck(results); //throws if invalid\n\n return results;\n } catch (error) {\n let err = error;\n if (err.response && err.response.status === 401) {\n err = new UnauthorizedError(\n 'Invalid private key. See your API credentials at https://www.caisson.com/developer .'\n );\n }\n\n throw err;\n }\n }\n\n /**\n * Validates Caisson ID Check results, ensuring the data is usable.\n * @param {object} results\n */\n function validateIDCheck(results) {\n const IDCheckTTL = 20 * 60 * 1000; //20 mins\n if (\n results.auth0_id !==\n user.user_id + '__' + manager.util.queryParams.state\n ) {\n throw new UnauthorizedError(\n 'ID mismatch. Caisson: %o, Auth0: %o',\n results.auth0_id,\n user.user_id\n );\n } else if (Date.now() - Date.parse(results.timestamp) > IDCheckTTL) {\n throw new UnauthorizedError('ID Check too old.');\n }\n }\n\n /**\n * Updates Caisson values on the Auth0 user object's app_metadata object.\n * @param {object} results\n */\n async function updateUser(results) {\n caisson.idcheck_url =\n manager.caissonHosts.dashboard + '/request/' + results.check_id;\n caisson.status = results.status;\n caisson.last_check = Date.now();\n caisson.count = caisson.count ? caisson.count + 1 : 1;\n\n try {\n await auth0.users.updateAppMetadata(user.user_id, { caisson });\n } catch (err) {\n throw err;\n }\n }\n\n /**\n * ID Check is done, handle results.\n */\n if (manager.util.isRedirectCallback) {\n //is it our redirect?\n\n if (\n !manager.util.queryParams.caisson_flow ||\n parseInt(manager.util.queryParams.caisson_flow, 10) !== 1\n ) {\n //no, end it.\n return callback(null, user, context);\n }\n\n try {\n if (!manager.util.queryParams.t) {\n throw new Error('Missing Caisson exchange key');\n }\n\n const check_id = await exchangeToken();\n const results = await idCheckResults(check_id);\n await updateUser(results);\n\n //deny the login if the ID Check is flagged\n if (results.status === 'flagged') {\n throw new UnauthorizedError('ID Check flagged.');\n }\n } catch (err) {\n dLog(err);\n return callback(err);\n }\n\n return callback(null, user, context);\n }\n\n /**\n * Else we're in the initial auth flow.\n * Perform ID Checks when appropriate.\n */\n\n try {\n if (isNaN(manager.idCheckFlags.login_frequency_days)) {\n //Do nothing. Skip if no preference is set.\n } else if (!caisson.last_check || caisson.status !== 'passed') {\n //Always perform the first ID Check or if the\n //last ID Check didn't pass.\n setIDCheckRedirect();\n } else if (\n manager.idCheckFlags.login_frequency_days >= 0 &&\n millisToDays(Date.now() - caisson.last_check) >=\n manager.idCheckFlags.login_frequency_days\n ) {\n //ID Check if the requisite number of days have passed since the last check.\n //Skip if we're only supposed to check once (login_frequency_days < -1).\n setIDCheckRedirect();\n }\n } catch (err) {\n dLog(err);\n return callback(err);\n }\n\n return callback(null, user, context);\n}"
},
{
"id": "eva-voice-biometric",
"title": "EVA Voice Biometric connector",
"overview": "EVA Voice Biometric connector rule for Auth0 enables voice enrolment and verification as a second factor",
"categories": [
"marketplace"
],
"description": "<pre><code>all configuration items are optional:\nAURAYA_URL = optional. EVA endpoint, typically: https://eva-web.mydomain.com/server/oauth\nAURAYA_CLIENT_ID = optional. JWT client id on the EVA server (and this server)\nAURAYA_CLIENT_SECRET = optional. JWT client secret on the EVA server (and this server)\nAURAYA_ISSUER = optional. this app (or \"issuer\")\n\nAURAYA_RANDOM_DIGITS = optional. true|false whether to prompt for random digits\nAURAYA_COMMON_DIGITS = optional. true|false whether to prompt for common digits\nAURAYA_PERSONAL_DIGITS = optional. a user.user_metadata property that contains digits such as phone_number\nAURAYA_COMMON_DIGITS_PROMPT = optional. a digit string to prompt for common digits (e.g '987654321')\nAURAYA_PERSONAL_DIGITS_PROMPT = optional. a string to prompt for personal digits (e.g 'your cell number')\n\nAURAYA_DEBUG = optional. if set, controls detailed debug output\n</code></pre>",
"code": "function evaVoiceBiometric(user, context, callback) {\n const debug = typeof configuration.AURAYA_DEBUG !== 'undefined';\n if (debug) {\n console.log(user);\n console.log(context);\n console.log(configuration);\n }\n\n const eva_url =\n configuration.AURAYA_URL ||\n 'https://eval-eva-web.aurayasystems.com/server/oauth';\n const clientSecret =\n configuration.AURAYA_CLIENT_SECRET ||\n 'o4X0LFKi2caP5ipUwaF4B27cZmfOIh0JXnqmfiC4mHkVskSzbp72Emk3AB6';\n const clientId = configuration.AURAYA_CLIENT_ID || 'auraya';\n const issuer = configuration.AURAYA_ISSUER || 'issuer';\n\n // Prepare user's enrolment status\n user.user_metadata = user.user_metadata || {};\n user.user_metadata.auraya_eva = user.user_metadata.auraya_eva || {};\n\n // User has initiated a login and is prompted to use voice biometrics\n // Send user's information and query params in a JWT to avoid tampering\n function createToken(user) {\n const options = {\n expiresInMinutes: 2,\n audience: clientId,\n issuer: issuer\n };\n\n return jwt.sign(user, clientSecret, options);\n }\n\n if (context.protocol === 'redirect-callback') {\n // user was redirected to the /continue endpoint with correct state parameter value\n\n var options = {\n //subject: user.user_id, // validating the subject is nice to have but not strictly necessary\n jwtid: user.jti // unlike state, this value can't be spoofed by DNS hacking or inspecting the payload\n };\n\n const payload = jwt.verify(\n context.request.body.token,\n clientSecret,\n options\n );\n if (debug) {\n console.log(payload);\n }\n\n if (payload.reason === 'enrolment_succeeded') {\n user.user_metadata.auraya_eva.status = 'enrolled';\n\n console.log('Biometric user successfully enrolled');\n // persist the user_metadata update\n auth0.users\n .updateUserMetadata(user.user_id, user.user_metadata)\n .then(function () {\n callback(null, user, context);\n })\n .catch(function (err) {\n callback(err);\n });\n\n return;\n }\n\n if (payload.reason !== 'verification_accepted') {\n // logic to detect repeatedly rejected attempts could go here\n // and update the eva.status accordingly (perhaps with 'blocked')\n console.log(`Biometric rejection reason: ${payload.reason}`);\n return callback(new UnauthorizedError(payload.reason), user, context);\n }\n\n // verification accepted\n console.log('Biometric verification accepted');\n return callback(null, user, context);\n }\n\n const url = require('[email protected]');\n user.jti = uuid.v4();\n user.user_metadata.auraya_eva.status =\n user.user_metadata.auraya_eva.status || 'initial';\n const mode =\n user.user_metadata.auraya_eva.status === 'initial' ? 'enrol' : 'verify';\n\n // returns property of the user.user_metadata object, typically \"phone_number\"\n // default is '', (server skips this prompt)\n const personalDigits =\n typeof configuration.AURAYA_PERSONAL_DIGITS === 'undefined'\n ? ''\n : user.user_metadata[configuration.AURAYA_PERSONAL_DIGITS];\n\n // default value for these is 'true'\n const commonDigits = configuration.AURAYA_COMMON_DIGITS || 'true';\n const randomDigits = configuration.AURAYA_RANDOM_DIGITS || 'true';\n\n // default value for these is '' (the server default)\n const commonDigitsPrompt = configuration.AURAYA_COMMON_DIGITS_PROMPT || ''; // 123456789\n const personalDigitsPrompt =\n configuration.AURAYA_PERSONAL_DIGITS_PROMPT || ''; // 'your phone number'\n\n const token = createToken({\n sub: user.user_id,\n jti: user.jti,\n oauth: {\n state: '', // not used in token, only in the GET request\n callbackURL: url.format({\n protocol: 'https',\n hostname: context.request.hostname,\n pathname: '/continue'\n }),\n nonce: user.jti // performs same function as jti\n },\n biometric: {\n id: user.user_id, // email - can be used for identities that cross IdP boundaries\n mode: mode,\n personalDigits: personalDigits,\n personalDigitsPrompt: personalDigitsPrompt,\n commonDigits: commonDigits,\n commonDigitsPrompt: commonDigitsPrompt,\n randomDigits: randomDigits\n }\n });\n\n context.redirect = {\n url: `${eva_url}?token=${token}`\n };\n\n return callback(null, user, context);\n}"
},
{
"id": "iddataweb-verification-workflow",
"title": "ID DataWeb Verification Workflow",
Expand Down
Loading

0 comments on commit 3eb2f32

Please sign in to comment.