-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
4 changed files
with
31 additions
and
16 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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", | ||
|
@@ -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}" | ||
} | ||
] | ||
}, | ||
|
@@ -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", | ||
|
Oops, something went wrong.