diff --git a/package-lock.json b/package-lock.json
index 5c67da18..d5e915eb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "rules-templates",
- "version": "0.17.0",
+ "version": "0.18.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index f7b74ac6..cc9f56c7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rules-templates",
- "version": "0.17.0",
+ "version": "0.18.0",
"description": "Auth0 Rules Repository",
"main": "./rules",
"scripts": {
diff --git a/rules.json b/rules.json
index 9f30e7e3..303699c9 100644
--- a/rules.json
+++ b/rules.json
@@ -468,6 +468,118 @@
}
]
},
+ {
+ "name": "marketplace",
+ "templates": [
+ {
+ "id": "arengu-progressive-profiling",
+ "title": "Arengu Progressive Profiling",
+ "overview": "Capture new users' information in your authentication flows.",
+ "categories": [
+ "marketplace"
+ ],
+ "code": "async function arenguCompleteUserProfile(user, context, callback) {\n if (\n !configuration.SESSION_TOKEN_SECRET ||\n !configuration.ARENGU_PROFILE_FORM_URL\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const {\n Auth0RedirectRuleUtilities,\n Auth0UserUpdateUtilities\n } = require('@auth0/rule-utilities@0.2.0');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n const userUtils = new Auth0UserUpdateUtilities(user, auth0);\n\n function validateSessionToken() {\n try {\n return ruleUtils.validateSessionToken();\n } catch (error) {\n callback(error);\n }\n }\n\n // Modify your login criteria to your needs\n function isLogin() {\n const loginCount = configuration.ARENGU_PROFILE_LOGIN_COUNT || 2;\n return context.stats.loginsCount > parseInt(loginCount, 10);\n }\n\n function isEmptyUserMeta(key) {\n return (\n userUtils.getUserMeta(key) === undefined ||\n userUtils.getUserMeta(key) === null ||\n userUtils.getUserMeta(key).length === 0\n );\n }\n\n function isProfileIncomplete() {\n // Add your required user_medata keys\n return isEmptyUserMeta('job_title') || isEmptyUserMeta('company_name');\n }\n\n if (ruleUtils.isRedirectCallback && ruleUtils.queryParams.session_token) {\n const decodedToken = validateSessionToken();\n const customClaims = decodedToken.other;\n\n for (const [key, value] of Object.entries(customClaims)) {\n userUtils.setUserMeta(key, value);\n }\n\n try {\n await userUtils.updateUserMeta();\n\n return callback(null, user, context);\n } catch (error) {\n return callback(error);\n }\n }\n\n if (isLogin() && isProfileIncomplete()) {\n ruleUtils.doRedirect(configuration.ARENGU_PROFILE_FORM_URL);\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": "
Please see the EVA Voice Biometrics integration for more information and detailed installation instructions.
\nOptional configuration:
\n\nAURAYA_URL
EVA endpoint, typically: https://eva-web.mydomain.com/server/oauth \nAURAYA_CLIENT_ID
JWT client id on the EVA server (and this server) \nAURAYA_CLIENT_SECRET
JWT client secret on the EVA server (and this server) \nAURAYA_ISSUER
This app (or \"issuer\") \nAURAYA_RANDOM_DIGITS
Set to \"true\" to prompt for random digits or \"false\" not to \nAURAYA_COMMON_DIGITS
Set to \"true\" to prompt for common digits or \"false\" not to \nAURAYA_PERSONAL_DIGITS
A user.usermetadata property that contains digits such as phonenumber \nAURAYA_COMMON_DIGITS_PROMPT
A digit string to prompt for common digits (e.g '987654321') \nAURAYA_PERSONAL_DIGITS_PROMPT
A string to prompt for personal digits (e.g 'your cell number') \nAURAYA_DEBUG
Set to \"true\" to log errors in the console \n
",
+ "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('url@0.10.3');\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\n let personalDigits = '';\n if (typeof configuration.AURAYA_PERSONAL_DIGITS !== 'undefined') {\n personalDigits = user.user_metadata[configuration.AURAYA_PERSONAL_DIGITS];\n }\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",
+ "overview": "Verify your user's identity in 180+ countries with ID DataWeb's adaptive Verification Workflows.",
+ "categories": [
+ "marketplace"
+ ],
+ "description": "Please see the ID DataWeb integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nIDDATAWEB_BASE_URL
Indicates the ID DataWeb environment. The default value is Pre-production - https://prod2.iddataweb.com/prod-axn
- where all testing and POCs should take place. To switch to production, change the URL to https://prod2.iddataweb.com/prod-axn
\nIDDATAWEB_CLIENT_ID
Identifies your specific verification workflow and user experience. Get this from the ID DataWeb’s AXN Admin console. \nIDDATAWEB_CLIENT_SECRET
Authenticates your specific verification workflow and user experience. Get this from ID DataWeb’s AXN Admin console. \n
\nOptional configuration:
\n\nIDDATAWEB_ALWAYS_VERIFY
Controls if users are verified each time they login, or just initially. We recommend \"true\" (verify the user on every login) for testing, not set (verify once, then not again) for production. \n
",
+ "code": "async function iddatawebVerificationWorkflow(user, context, callback) {\n const {\n IDDATAWEB_BASE_URL,\n IDDATAWEB_CLIENT_ID,\n IDDATAWEB_CLIENT_SECRET,\n IDDATAWEB_ALWAYS_VERIFY\n } = configuration;\n\n if (!IDDATAWEB_BASE_URL || !IDDATAWEB_CLIENT_ID || !IDDATAWEB_CLIENT_SECRET) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const { Auth0RedirectRuleUtilities } = require('@auth0/rule-utilities@0.1.0');\n const axiosClient = require('axios@0.19.2');\n const url = require('url');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n const idwBasicAuth = Buffer.from(\n IDDATAWEB_CLIENT_ID + ':' + IDDATAWEB_CLIENT_SECRET\n ).toString('base64');\n\n const idwTokenNamepsace = 'https://iddataweb.com/';\n const idwTokenEndpoint = `${IDDATAWEB_BASE_URL}/axn/oauth2/token`;\n const idwAuthorizeEndpoint = `${IDDATAWEB_BASE_URL}/axn/oauth2/authorize`;\n const auth0ContinueUrl = `https://${context.request.hostname}/continue`;\n\n let iddataweb = (user.app_metadata && user.app_metadata.iddataweb) || {};\n iddataweb.verificationResult = iddataweb.verificationResult || {};\n\n // if the user is already verified and we don't need to check, exit\n if (\n iddataweb.verificationResult.policyDecision === 'approve' &&\n IDDATAWEB_ALWAYS_VERIFY !== 'true'\n ) {\n console.log('user ' + user.user_id + ' has been previously verified.');\n return callback(null, user, context);\n }\n\n // if coming back from redirect - get token, make policy decision, and update user metadata.\n if (ruleUtils.isRedirectCallback) {\n console.log('code from IDW: ' + ruleUtils.queryParams.code);\n\n const formParams = new url.URLSearchParams({\n grant_type: 'authorization_code',\n code: ruleUtils.queryParams.code,\n redirect_uri: auth0ContinueUrl\n });\n\n const headers = {\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'Cache-Control': 'no-cache',\n Authorization: `Basic ${idwBasicAuth}`\n };\n\n let decodedToken;\n try {\n const tokenResponse = await axiosClient.post(\n idwTokenEndpoint,\n formParams.toString(),\n { headers }\n );\n\n if (tokenResponse.data.error) {\n throw new Error(tokenResponse.data.error_description);\n }\n\n decodedToken = jwt.decode(tokenResponse.data.id_token);\n } catch (error) {\n return callback(error);\n }\n\n //check issuer, audience and experiation of ID DataWeb Token\n if (\n decodedToken.iss !== IDDATAWEB_BASE_URL ||\n decodedToken.aud !== IDDATAWEB_CLIENT_ID\n ) {\n return callback(new Error('ID token invalid.'));\n }\n\n console.log('policy decision: ' + decodedToken.policyDecision);\n console.log('score: ' + decodedToken.idwTrustScore);\n console.log('IDW transaction ID: ' + decodedToken.jti);\n\n // once verification is complete, update user's metadata in Auth0.\n //this could be used for downstream application authorization,\n //or mapping access to levels of assurance.\n iddataweb.verificationResult = {\n policyDecision: decodedToken.policyDecision,\n transactionid: decodedToken.jti,\n iat: decodedToken.iat\n };\n\n try {\n auth0.users.updateAppMetadata(user.user_id, { iddataweb });\n } catch (error) {\n return callback(error);\n }\n\n //include ID DataWeb results in Auth0 ID Token\n context.idToken[idwTokenNamepsace + 'policyDecision'] =\n decodedToken.policyDecision;\n context.idToken[idwTokenNamepsace + 'transactionId'] = decodedToken.jti;\n context.idToken[idwTokenNamepsace + 'iat'] = decodedToken.iat;\n\n return callback(null, user, context);\n }\n\n // ... otherwise, redirect for verification.\n\n let idwRedirectUrl =\n idwAuthorizeEndpoint +\n '?client_id=' +\n IDDATAWEB_CLIENT_ID +\n '&redirect_uri=' +\n auth0ContinueUrl +\n '&scope=openid+country.US&response_type=code';\n\n if (ruleUtils.canRedirect) {\n context.redirect = {\n url: idwRedirectUrl\n };\n }\n\n return callback(null, user, context);\n}"
+ },
+ {
+ "id": "incognia-authentication",
+ "title": "Incognia Authentication Rule",
+ "overview": "Verify if the device logging in is at a trusted location.",
+ "categories": [
+ "marketplace"
+ ],
+ "code": "async function incogniaAuthenticationRule(user, context, callback) {\n const _ = require('lodash@4.17.19');\n\n const { IncogniaAPI } = require('@incognia/api@1.0.0');\n\n const { INCOGNIA_CLIENT_ID, INCOGNIA_CLIENT_SECRET } = configuration;\n\n if (!INCOGNIA_CLIENT_ID || !INCOGNIA_CLIENT_SECRET) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const installationId = _.get(\n context,\n 'request.query.incognia_installation_id'\n );\n if (!installationId) {\n console.log('Missing installation_id. Skipping.');\n return callback(null, user, context);\n }\n\n const accountId = _.get(user, 'user_id');\n if (!accountId) {\n console.log('Missing user_id. Skipping.');\n return callback(null, user, context);\n }\n\n let incogniaAPI;\n if (global.incogniaAPI) {\n incogniaAPI = global.incogniaAPI;\n } else {\n incogniaAPI = new IncogniaAPI({\n clientId: INCOGNIA_CLIENT_ID,\n clientSecret: INCOGNIA_CLIENT_SECRET\n });\n global.incogniaAPI = incogniaAPI;\n }\n\n try {\n const loginAssessment = await incogniaAPI.registerLoginAssessment({\n installationId: installationId,\n accountId: accountId\n });\n\n // Incognia's risk assessment will be in a namespaced claim so it can be used in other rules\n // for skipping/prompting MFA or in the mobile app itself to decide whether the user should be\n // redirected to step-up auth for example.\n context.idToken['https://www.incognia.com/assessment'] =\n loginAssessment.riskAssessment;\n } catch (error) {\n console.log('Error calling Incognia API for a new login.');\n return callback(error);\n }\n\n return callback(null, user, context);\n}"
+ },
+ {
+ "id": "incognia-onboarding",
+ "title": "Incognia Onboarding Rule",
+ "overview": "Verify if the device location behavior matches the address declared during onboarding.",
+ "categories": [
+ "marketplace"
+ ],
+ "code": "async function incogniaOnboardingRule(user, context, callback) {\n const _ = require('lodash@4.17.19');\n\n const { IncogniaAPI } = require('@incognia/api@1.0.0');\n const { Auth0UserUpdateUtilities } = require('@auth0/rule-utilities@0.2.0');\n\n const {\n INCOGNIA_CLIENT_ID,\n INCOGNIA_CLIENT_SECRET,\n INCOGNIA_HOME_ADDRESS_PROP\n } = configuration;\n\n if (!INCOGNIA_CLIENT_ID || !INCOGNIA_CLIENT_SECRET) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const installationId = _.get(\n context,\n 'request.query.incognia_installation_id'\n );\n if (!installationId) {\n console.log('Missing installation_id. Skipping.');\n return callback(null, user, context);\n }\n\n // User home address should be set using Auth0's Signup API for example. If the home address is\n // not in 'user_metadata.home_address', please specify the path of the field inside the user\n // object where the home address is through the INCOGNIA_HOME_ADDRESS_PROP configuration.\n const homeAddressProp =\n INCOGNIA_HOME_ADDRESS_PROP || 'user_metadata.home_address';\n const homeAddress = _.get(user, homeAddressProp);\n if (!homeAddress) {\n console.log('Missing user home address. Skipping.');\n return callback(null, user, context);\n }\n\n const userUtils = new Auth0UserUpdateUtilities(user, auth0, 'incognia');\n\n const status = userUtils.getAppMeta('status');\n // This rule was previously run and calculated the assessment successfully.\n if (status && status !== 'pending') {\n console.log(\n 'Assessment is already calculated or is unevaluable. Skipping.'\n );\n return callback(null, user, context);\n }\n\n let incogniaAPI;\n if (global.incogniaAPI) {\n incogniaAPI = global.incogniaAPI;\n } else {\n incogniaAPI = new IncogniaAPI({\n clientId: INCOGNIA_CLIENT_ID,\n clientSecret: INCOGNIA_CLIENT_SECRET\n });\n global.incogniaAPI = incogniaAPI;\n }\n\n let onboardingAssessment;\n const signupId = userUtils.getAppMeta('signup_id');\n // The rule was previously run, but Incognia could not assess the signup.\n if (signupId) {\n try {\n onboardingAssessment = await incogniaAPI.getOnboardingAssessment(\n signupId\n );\n } catch (error) {\n console.log('Error calling Incognia API for signup previously submitted');\n return callback(error);\n }\n // This is the first time the rule is being run with all necessary arguments.\n } else {\n try {\n onboardingAssessment = await incogniaAPI.registerOnboardingAssessment({\n installationId: installationId,\n addressLine: homeAddress\n });\n } catch (error) {\n console.log('Error calling Incognia API for new signup submission');\n return callback(error);\n }\n }\n\n /*\n * Updates the status in the metadata now that the assessment was calculated. If the new\n * assessment is valid, the status will go to evaluated and this rule won't be executed again.\n * If Incognia still doesn't know how to assess the signup, it will try to calculate it again up\n * to 48 hours after the first try.\n */\n const firstAssessmentAt = userUtils.getAppMeta('first_assessment_at');\n let newStatus;\n if (onboardingAssessment.riskAssessment !== 'unknown_risk') {\n newStatus = 'evaluated';\n } else if (!firstAssessmentAt) {\n newStatus = 'pending';\n } else {\n const firstAssessmentAge =\n Math.round(Date.now() / 1000) - firstAssessmentAt;\n // 48 hours limit.\n if (firstAssessmentAge > 172800) {\n newStatus = 'unevaluable';\n } else {\n newStatus = 'pending';\n }\n }\n\n const updatedMetadata = {\n status: newStatus,\n first_assessment_at: firstAssessmentAt || Math.round(Date.now() / 1000),\n signup_id: onboardingAssessment.id,\n assessment: onboardingAssessment\n };\n\n try {\n userUtils.setAppMeta('incognia', updatedMetadata);\n await userUtils.updateAppMeta();\n } catch (error) {\n console.log('Error calling Auth0 management API');\n return callback(error);\n }\n\n return callback(null, user, context);\n}"
+ },
+ {
+ "id": "mylife-digital-progressive-consent",
+ "title": "Consentric Progressive Consent",
+ "overview": "Uses a widget to capture missing consents and preferences at login to boost engagement and support compliance",
+ "categories": [
+ "marketplace"
+ ],
+ "description": "Please see the MyLife Digital integration for more information and detailed installation instructions.\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nCONSENTRIC_AUTH_HOST
The URL to authenticate against for your Consentric API token, like https://sandbox-consentric.eu.auth0.com
\nCONSENTRIC_API_HOST
The Consentric API host URL, like https://sandbox.consentric.io
\nCONSENTRIC_CLIENT_ID
The Consentric ClientId issued to you \nCONSENTRIC_CLIENT_SECRET
The Consentric ClientSecret issued to you \nCONSENTRIC_AUDIENCE
The name of the Consentric API being called, like https://sandbox.consentric.io
\nCONSENTRIC_APPLICATION_ID
The Consentric ApplicationId issued to you \nCONSENTRIC_REDIRECT_URL
The URL of the page containing the Progressive widget \n
",
+ "code": "function consentricProgressiveConsent(user, context, callback) {\n const axios = require('axios@0.19.2');\n const moment = require('moment@2.11.2');\n const { Auth0RedirectRuleUtilities } = require('@auth0/rule-utilities@0.1.0');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n const asMilliSeconds = (seconds) => seconds * 1000;\n\n const {\n CONSENTRIC_AUTH_HOST,\n CONSENTRIC_API_HOST,\n CONSENTRIC_AUDIENCE,\n CONSENTRIC_CLIENT_ID,\n CONSENTRIC_CLIENT_SECRET,\n CONSENTRIC_APPLICATION_ID,\n CONSENTRIC_REDIRECT_URL\n } = configuration;\n\n if (\n !CONSENTRIC_AUTH_HOST ||\n !CONSENTRIC_API_HOST ||\n !CONSENTRIC_AUDIENCE ||\n !CONSENTRIC_CLIENT_ID ||\n !CONSENTRIC_CLIENT_SECRET ||\n !CONSENTRIC_APPLICATION_ID ||\n !CONSENTRIC_REDIRECT_URL\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const consentricAuth = axios.create({\n baseURL: CONSENTRIC_AUTH_HOST,\n timeout: 1000\n });\n\n const consentricApi = axios.create({\n baseURL: CONSENTRIC_API_HOST,\n timeout: 1000\n });\n\n // Returns Consentric API Access Token (JWT) from either the global cache or generates it anew from clientId and secret\n const getConsentricApiAccessToken = async () => {\n const consentricApiTokenNotValid =\n !global.consentricApiToken || global.consentricApiToken.exp < Date.now();\n\n if (consentricApiTokenNotValid) {\n try {\n // Exchange Credentials for Consentric Api Access token\n const {\n data: { expires_in, access_token }\n } = await consentricAuth.post('/oauth/token', {\n grant_type: 'client_credentials',\n client_id: CONSENTRIC_CLIENT_ID,\n client_secret: CONSENTRIC_CLIENT_SECRET,\n audience: CONSENTRIC_AUDIENCE,\n applicationId: CONSENTRIC_APPLICATION_ID\n });\n\n const expiryInMs = new Date().getTime() + asMilliSeconds(expires_in);\n const auth = {\n jwt: access_token,\n exp: expiryInMs\n };\n\n // Persist API Access token in global properties\n global.consentricApiToken = auth;\n } catch (error) {\n console.error(\n 'Unable to retrieve API Access token for Consentric. Please check that your credentials (CONSENTRIC_CLIENT_ID and CONSENTRIC_CLIENT_SECRET) are correct.'\n );\n throw error;\n }\n }\n\n return global.consentricApiToken;\n };\n\n // Creates Citizen Record in Consentric with Auth0 Id\n const createCitizen = ({ userRef, apiAccessToken }) => {\n console.log(`Upserting Consentric Citizen record for ${userRef}`);\n const data = {\n applicationId: CONSENTRIC_APPLICATION_ID,\n externalRef: userRef\n };\n\n return consentricApi\n .post('/v1/citizens', data, {\n headers: {\n Authorization: 'Bearer ' + apiAccessToken\n }\n })\n .catch((err) => {\n if (err.response.status !== 409) {\n // 409 indicates Citizen with given reference already exists in Consentric\n console.error(err);\n throw err;\n }\n });\n };\n\n // Function to retrieve Consentric User Token from User Metadata\n const getConsentricUserTokenFromMetadata = (user) =>\n user.app_metadata && user.app_metadata.consentric;\n\n // Generates On Demand Consentric User Token for the given User using the API Access Token\n const generateConsentricUserAccessToken = async ({\n userRef,\n apiAccessToken\n }) => {\n try {\n console.log(`Attempting to generate access token API for ${userRef}`);\n\n const {\n data: { token, expiryDate: exp }\n } = await consentricApi.post(\n '/v1/access-tokens/tokens',\n {\n applicationId: CONSENTRIC_APPLICATION_ID,\n externalRef: userRef,\n expiryDate: moment().add(3, 'months').toISOString()\n },\n {\n headers: {\n Authorization: 'Bearer ' + apiAccessToken\n }\n }\n );\n\n return {\n token,\n exp\n };\n } catch (err) {\n console.error(err);\n throw err;\n }\n };\n\n const loadConsentricUserAccessToken = async ({ user }) => {\n try {\n const metadataUserToken = getConsentricUserTokenFromMetadata(user);\n if (\n metadataUserToken &&\n moment(metadataUserToken.exp).subtract(1, 'days').isAfter(moment())\n )\n return metadataUserToken;\n\n const { jwt: apiAccessToken } = await getConsentricApiAccessToken();\n const apiCredentials = {\n userRef: user.user_id,\n apiAccessToken\n };\n\n // Create Citizen with Auth0 UserId\n await createCitizen(apiCredentials);\n\n // Generate an On Demand Access Token for the created citizen\n const generatedToken = await generateConsentricUserAccessToken(\n apiCredentials\n );\n\n // Persist the app_metadata update\n await auth0.users.updateAppMetadata(user.user_id, {\n consentric: generatedToken\n });\n\n return generatedToken;\n } catch (err) {\n console.error(\n `Issue loading Consentric User Access Token for user ${user.user_id} - ${err}`\n );\n throw err;\n }\n };\n\n const initConsentricFlow = async () => {\n try {\n const { token } = await loadConsentricUserAccessToken({ user });\n const urlConnector = CONSENTRIC_REDIRECT_URL.includes('?') ? '&' : '?';\n const redirectUrl =\n CONSENTRIC_REDIRECT_URL + urlConnector + 'token=' + token;\n\n context.redirect = {\n url: redirectUrl\n };\n } catch (err) {\n console.error(`CONSENTRIC RULE ABORTED: ${err}`);\n }\n return callback(null, user, context);\n };\n\n if (ruleUtils.canRedirect) {\n return initConsentricFlow();\n } else {\n // Run after Redirect or Silent Auth\n return callback(null, user, context);\n }\n}"
+ },
+ {
+ "id": "netlify-role-management",
+ "title": "Netlify Role Management",
+ "overview": "Adds a default role if the user doesn't have any yet and attaches roles to the ID Token.",
+ "categories": [
+ "marketplace"
+ ],
+ "description": "Optional configuration:
\n\nDEFAULT_ROLE_NAME
- name of the default role to be given to a user \nDEFAULT_ROLE_ID
- id of the role to be given to a user \nCUSTOM_CLAIMS_NAMESPACE
- namespace for adding custom claims to ID Token \n
",
+ "code": "async function netlifyRoleManagement(user, context, callback) {\n const ManagementClient = require('auth0@2.30.0').ManagementClient;\n\n const namespace =\n configuration.CUSTOM_CLAIMS_NAMESPACE || 'https://netlify-integration.com';\n const assignedRoles = (context.authorization || {}).roles || [];\n const defaultRoleName = configuration.DEFAULT_ROLE_NAME;\n const defaultRoleId = configuration.DEFAULT_ROLE_ID;\n\n //give default role if the user doesn't already have any roles assigned\n if (\n (!assignedRoles || assignedRoles.length === 0) &&\n defaultRoleName &&\n defaultRoleId\n ) {\n try {\n const management = new ManagementClient({\n token: auth0.accessToken,\n domain: auth0.domain\n });\n await management.assignRolestoUser(\n { id: user.user_id },\n { roles: [defaultRoleId] }\n );\n } catch (ex) {\n console.error('Failed to add default role to user', ex);\n } finally {\n assignedRoles.push(defaultRoleName);\n }\n }\n\n context.idToken[namespace + '/roles'] = assignedRoles;\n return callback(null, user, context);\n}"
+ },
+ {
+ "id": "onetrust-consent-management",
+ "title": "OneTrust Consent Management",
+ "overview": "Enhance Auth0 user profiles with consent, opt-ins and communication preferences data.",
+ "categories": [
+ "marketplace"
+ ],
+ "description": "Please see the OneTrust integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nONETRUST_REQUEST_INFORMATION
Your OneTrust Collection Point API token \nONETRUST_CONSENT_API_URL
Your OneTrust Collection Point API URL \nONETRUST_PURPOSE_ID
Your OneTrust Collection Point Purpose ID \n
\nOptional configuration:
\n\nONETRUST_SKIP_IF_NO_EMAIL
If set to \"true\" then the Rule will be skipped if there is no email address. Otherwise the Rule will fail with an error. \n
",
+ "code": "/* global configuration */\nasync function oneTrustConsentManagement(user, context, callback) {\n const axios = require('axios@0.19.2');\n\n const {\n ONETRUST_REQUEST_INFORMATION,\n ONETRUST_CONSENT_API_URL,\n ONETRUST_PURPOSE_ID\n } = configuration;\n\n if (\n !ONETRUST_REQUEST_INFORMATION ||\n !ONETRUST_CONSENT_API_URL ||\n !ONETRUST_PURPOSE_ID\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const skipIfNoEmail = configuration.ONETRUST_SKIP_IF_NO_EMAIL === 'true';\n\n user.app_metadata = user.app_metadata || {};\n let onetrust = user.app_metadata.onetrust || {};\n\n if (onetrust.receipt) {\n console.log('User has a Collection Point receipt. Skipping.');\n return callback(null, user, context);\n }\n\n if (!user.email) {\n if (skipIfNoEmail) {\n console.log('User has no email address. Skipping.');\n return callback(null, user, context);\n }\n return callback(new Error('An email address is required.'));\n }\n\n try {\n const response = await axios.post(ONETRUST_CONSENT_API_URL, {\n identifier: user.email,\n requestInformation: ONETRUST_REQUEST_INFORMATION,\n purposes: [{ Id: ONETRUST_PURPOSE_ID }]\n });\n onetrust.receipt = response.data.receipt;\n } catch (error) {\n console.log('Error calling the Collection Point.');\n return callback(error);\n }\n\n try {\n await auth0.users.updateAppMetadata(user.user_id, { onetrust });\n } catch (error) {\n console.log('Error updating user app_metadata.');\n return callback(error);\n }\n\n return callback(null, user, context);\n}"
+ },
+ {
+ "id": "onfido-idv",
+ "title": "Onfido Identity Verification",
+ "overview": "Redirect to your Onfido IDV Application for Identity Verification during login.",
+ "categories": [
+ "marketplace"
+ ],
+ "description": "Please see the Onfido integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nSESSION_TOKEN_SECRET
Long, random string, should match on Onfido app side. \nONFIDO_API_TOKEN
Your Onfido API Token \nONFIDO_REGION
The supported Onfido region your tenant is operating in \nONFIDO_ID_VERIFICATION_URL
URL to receive the redirect \n
",
+ "code": "/* global configuration */\nasync function onfidoIdentityVerification(user, context, callback) {\n if (\n !configuration.SESSION_TOKEN_SECRET ||\n !configuration.ONFIDO_API_TOKEN ||\n !configuration.ONFIDO_REGION ||\n !configuration.ONFIDO_ID_VERIFICATION_URL\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n // using auth0 rule-utilities to make sure our rule is efficient in the pipeline\n const { Auth0RedirectRuleUtilities } = require('@auth0/rule-utilities@0.1.0');\n // requiring Onfido's node SDK for making the calls easier to Onfido's service.\n const { Onfido, Region } = require('@onfido/api@1.5.1');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n // creating a claim namespace for adding the Onfido IDV check results back to the ID Token\n const claimNamespace = 'https://claims.onfido.com/';\n\n // creating a new Onfido client, the region here is where your Onfido instance is located. Possible values are EU for Europe, US for United States, and CA for Canada.\n const onfidoClient = new Onfido({\n apiToken: configuration.ONFIDO_API_TOKEN,\n region: Region[configuration.ONFIDO_REGION] || Region.EU\n });\n\n user.app_metadata = user.app_metadata || {};\n user.app_metadata.onfido = user.app_metadata.onfido || {};\n\n if (\n ruleUtils.isRedirectCallback &&\n ruleUtils.queryParams.session_token &&\n 'true' === ruleUtils.queryParams.onfido_idv\n ) {\n // User is back from the Onfido experience and has a session token to validate and assign to user meta\n\n // Validating session token and extracting payload for check results\n let payload;\n try {\n payload = ruleUtils.validateSessionToken();\n } catch (error) {\n return callback(error);\n }\n\n // assigning check status and result to the app_metadata so the downstream application can decided what to do next\n // note, in the example integration, the Onfido app returns after 30 seconds even if the check is still in progress\n // If this claim status is still in_progress it is recommended the downstream application recheck for completion or implement the Onfido Webhook: https://documentation.onfido.com/#webhooks\n // Additionally, you can place these items into the idToken claim with custom claims as needed as shown\n const onfido = {\n check_result: payload.checkResult,\n check_status: payload.checkStatus,\n applicant_id: payload.applicant\n };\n try {\n await auth0.users.updateAppMetadata(user.user_id, onfido);\n } catch (error) {\n callback(error);\n }\n\n user.app_metadata.onfido = onfido;\n\n context.idToken[claimNamespace + 'check_result'] = payload.checkResult;\n context.idToken[claimNamespace + 'check_status'] = payload.checkStatus;\n context.idToken[claimNamespace + 'applicant_id'] = payload.applicant;\n\n return callback(null, user, context);\n }\n\n if (ruleUtils.canRedirect && !user.app_metadata.onfido.check_status) {\n // if the user has not already been redirected and check_status is empty, we will create the applicant and redirect to the Onfido implementation.\n let applicant;\n try {\n applicant = await onfidoClient.applicant.create({\n // these values do not need to match what is on the document for IDV, but if Data Comparison on Onfido's side is tuned on, these values will flag\n // if Auth0 contains these values in the app_metadata or on the user object you can map them here as needed. You could also pass them in as query_string variables\n firstName: !user.given_name ? 'anon' : user.given_name,\n lastName: !user.family_name ? 'anon' : user.family_name,\n email: !user.email ? 'anon@example.com' : user.email\n });\n\n // create the session token with the applicant id as a custom claim\n const sessionToken = ruleUtils.createSessionToken({\n applicant: applicant.id\n });\n // redirect to Onfido implementation with sessionToken\n ruleUtils.doRedirect(\n configuration.ONFIDO_ID_VERIFICATION_URL,\n sessionToken\n );\n return callback(null, user, context);\n } catch (error) {\n return callback(error);\n }\n }\n return callback(null, user, context);\n}"
+ },
+ {
+ "id": "scaled-access-relationships-claim",
+ "title": "Scaled Access relationship-based claims",
+ "overview": "Adds a claim based on the relationships the subject has in Scaled Access",
+ "categories": [
+ "marketplace"
+ ],
+ "description": "Please see the Scaled Access integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nSCALED_ACCESS_AUDIENCE
The identifier of the Auth0 API \nSCALED_ACCESS_CLIENTID
The Client ID of the Auth0 machine-to-machine application. \nSCALED_ACCESS_CLIENTSECRET
The Client secret of the Auth0 machine-to-machine application. \nSCALED_ACCESS_BASEURL
The base URL for the Relationship Management API. \nSCALED_ACCESS_TENANT
Your tenant code provided by Scaled Access. \n
\nOptional configuration:
\n\nSCALED_ACCESS_CUSTOMCLAIM
A namespaced ID token claim (defaults to https://scaledaccess.com/relationships
) \n
",
+ "code": "function scaledAccessAddRelationshipsClaim(user, context, callback) {\n if (\n !configuration.SCALED_ACCESS_AUDIENCE ||\n !configuration.SCALED_ACCESS_CLIENTID ||\n !configuration.SCALED_ACCESS_CLIENTSECRET ||\n !configuration.SCALED_ACCESS_BASEURL ||\n !configuration.SCALED_ACCESS_TENANT\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const fetch = require('node-fetch');\n const { URLSearchParams } = require('url');\n\n const getM2mToken = () => {\n if (\n global.scaledAccessM2mToken &&\n global.scaledAccessM2mTokenExpiryInMillis > new Date().getTime() + 60000\n ) {\n return Promise.resolve(global.scaledAccessM2mToken);\n } else {\n const tokenUrl = `https://${context.request.hostname}/oauth/token`;\n return fetch(tokenUrl, {\n method: 'POST',\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: configuration.SCALED_ACCESS_CLIENTID,\n client_secret: configuration.SCALED_ACCESS_CLIENTSECRET,\n audience: configuration.SCALED_ACCESS_AUDIENCE,\n scope: 'pg:tenant:admin'\n })\n })\n .then((response) => {\n if (!response.ok) {\n return response.text().then((error) => {\n console.error('Failed to obtain m2m token from ' + tokenUrl);\n throw Error(error);\n });\n } else {\n return response.json();\n }\n })\n .then(({ access_token, expires_in }) => {\n global.scaledAccessM2mToken = access_token;\n global.scaledAccessM2mTokenExpiryInMillis =\n new Date().getTime() + expires_in * 1000;\n return access_token;\n });\n }\n };\n\n const callRelationshipManagementApi = async (accessToken, path) => {\n const url = `${configuration.SCALED_ACCESS_BASEURL}/${configuration.SCALED_ACCESS_TENANT}/${path}`;\n return fetch(url, {\n method: 'GET',\n headers: {\n Authorization: 'Bearer ' + accessToken,\n 'Content-Type': 'application/json'\n }\n }).then(async (response) => {\n if (response.status === 404) {\n return [];\n } else if (!response.ok) {\n return response.text().then((error) => {\n console.error('Failed to call relationship management API', url);\n throw Error(error);\n });\n } else {\n return response.json();\n }\n });\n };\n\n const getRelationships = (accessToken) => {\n return callRelationshipManagementApi(\n accessToken,\n `actors/user/${user.user_id}/relationships`\n );\n };\n\n const addClaimToToken = (apiResponse) => {\n const claimName =\n configuration.SCALED_ACCESS_CUSTOMCLAIM ||\n `https://scaledaccess.com/relationships`;\n context.accessToken[claimName] = apiResponse.map((relationship) => ({\n relationshipType: relationship.relationshipType,\n to: relationship.to\n }));\n };\n\n getM2mToken()\n .then(getRelationships)\n .then(addClaimToToken)\n .then(() => {\n callback(null, user, context);\n })\n .catch((err) => {\n console.error(err);\n console.log('Using configuration: ', JSON.stringify(configuration));\n callback(null, user, context); // fail gracefully, token just won't have extra claim\n });\n}"
+ },
+ {
+ "id": "vouched-verification",
+ "title": "Vouched Verification",
+ "overview": "Verify a person's identity using Vouched.",
+ "categories": [
+ "marketplace"
+ ],
+ "description": "Please see the Vouched integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nVOUCHED_API_KEY
Your Private Key located in Vouched Dashboard \nVOUCHED_PUBLIC_KEY
Your Public Key located in Vouched Dashboard \n
\nOptional configuration:
\n\nVOUCHED_API_URL
Your Vouched API URL; leave blank unless instructed by your Vouched rep \nVOUCHED_ID_TOKEN_CLAIM
Set a https://vouchedid/is_verified
claim in the ID token with results \nVOUCHED_VERIFICATION_OPTIONAL
Set to \"true\" to succeed even if verification fails \n
",
+ "code": "async function vouchedVerification(user, context, callback) {\n if (!configuration.VOUCHED_API_KEY || !configuration.VOUCHED_PUBLIC_KEY) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n /* ----------- START helpers ----------- */\n const axios = require('axios');\n const url = require('url');\n const { Auth0RedirectRuleUtilities } = require('@auth0/rule-utilities@0.1.0');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n const defaultApiUrl = 'https://verify.vouched.id/api';\n const defaultUiUrl = 'https://i.vouched.id';\n const idTokenClaim = 'https://vouched.id/is_verified';\n\n const getJobByToken = async (apiKey, jobToken, apiUrl) => {\n return getJob(apiKey, { token: jobToken }, apiUrl);\n };\n\n const getJobById = async (apiKey, jobId, apiUrl) => {\n return getJob(apiKey, { id: jobId }, apiUrl);\n };\n\n const getJob = async (apiKey, params, apiUrl) => {\n const response = await axios({\n headers: {\n 'X-Api-Key': apiKey,\n 'Content-Type': 'application/json'\n },\n baseURL: apiUrl,\n url: '/jobs',\n params: params\n });\n const items = response.data.items;\n if (items.length === 0) {\n throw new Error(\n `Unable to find Job with the following params: ${JSON.stringify(\n params\n )}`\n );\n }\n return items[0];\n };\n\n const createPacket = async (\n apiKey,\n publicKey,\n continueUrl,\n user,\n apiUrl = defaultApiUrl\n ) => {\n const requestBody = {\n pk: publicKey,\n uid: user.user_id,\n continueUrl\n };\n\n if (user.given_name) requestBody.firstName = user.given_name;\n\n if (user.family_name) requestBody.lastName = user.family_name;\n\n const response = await axios({\n method: 'post',\n headers: {\n 'X-Api-Key': apiKey,\n 'Content-Type': 'application/json'\n },\n baseURL: apiUrl,\n url: '/packet/auth0',\n data: requestBody\n });\n const data = response.data;\n if (data.errors) {\n throw new Error(`${data.errors[0].message}`);\n }\n return data.id;\n };\n\n const isJobForUser = (job, userId) => {\n try {\n return (\n job.request.properties.filter(\n (prop) => prop.name === 'uid' && prop.value === userId\n ).length === 1\n );\n } catch (e) {\n return false;\n }\n };\n\n const extractResults = (job) => {\n const { id, status, reviewSuccess, result } = job;\n return {\n id,\n status,\n reviewSuccess,\n result\n };\n };\n\n const isJobVerified = (job) => {\n try {\n return job.result.success || job.reviewSuccess;\n } catch (e) {\n return false;\n }\n };\n\n const redirectToVerification = (packetId, baseUrl = defaultUiUrl) => {\n const redirectUrl = new url.URL(`${baseUrl}/auth0`);\n redirectUrl.searchParams.append('id', packetId);\n return redirectUrl.href;\n };\n\n /* ----------- END helpers ----------- */\n\n user.app_metadata = user.app_metadata || {};\n const vouchedApiUrl = configuration.VOUCHED_API_URL || defaultApiUrl;\n\n try {\n const jobToken = ruleUtils.queryParams.jobToken;\n if (ruleUtils.isRedirectCallback && jobToken) {\n // get job from API\n const job = await getJobByToken(\n configuration.VOUCHED_API_KEY,\n jobToken,\n vouchedApiUrl\n );\n\n // check if job's user is the same as current user\n if (!isJobForUser(job, user.user_id)) {\n return callback(\n new Error(`The ID Verification results do not belong to this user.`)\n );\n }\n\n // update app metadata w/ results\n user.app_metadata.vouched = extractResults(job);\n await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);\n }\n\n const vouchedResults = user.app_metadata.vouched;\n if (vouchedResults) {\n if (!isJobVerified(vouchedResults)) {\n // user failed id verification\n const mostRecentJob = await getJobById(\n configuration.VOUCHED_API_KEY,\n vouchedResults.id,\n vouchedApiUrl\n );\n\n // check if job's user is the same as current user\n if (!isJobForUser(mostRecentJob, user.user_id)) {\n return callback(\n new Error(`The ID Verification results do not belong to this user.`)\n );\n }\n\n // user is now verified, update app metadata\n if (isJobVerified(mostRecentJob)) {\n user.app_metadata.vouched = extractResults(mostRecentJob);\n await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);\n } else {\n // user failed verification check and doesn't have an override\n if (configuration.VOUCHED_ID_TOKEN_CLAIM === 'true') {\n context.idToken[idTokenClaim] = false;\n }\n if (configuration.VOUCHED_VERIFICATION_OPTIONAL === 'true') {\n return callback(null, user, context);\n }\n\n return callback(new Error(`This user's ID cannot be verified.`));\n }\n }\n } else {\n // create Auth0 packet to securely pass info to Vouched\n const packetId = await createPacket(\n configuration.VOUCHED_API_KEY,\n configuration.VOUCHED_PUBLIC_KEY,\n `https://${context.request.hostname}/continue`,\n user\n );\n\n // user doesn't have a verification result, redirect to Vouched with packet\n if (ruleUtils.canRedirect) {\n context.redirect = { url: redirectToVerification(packetId) };\n }\n return callback(null, user, context);\n }\n } catch (e) {\n return callback(e);\n }\n\n if (configuration.VOUCHED_ID_TOKEN_CLAIM === 'true') {\n context.idToken[idTokenClaim] = true;\n }\n\n return callback(null, user, context);\n}"
+ }
+ ]
+ },
{
"name": "webhook",
"templates": [
@@ -623,91 +735,6 @@
}
]
},
- {
- "name": "marketplace",
- "templates": [
- {
- "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": "Please see the EVA Voice Biometrics integration for more information and detailed installation instructions.
\nOptional configuration:
\n\nAURAYA_URL
EVA endpoint, typically: https://eva-web.mydomain.com/server/oauth \nAURAYA_CLIENT_ID
JWT client id on the EVA server (and this server) \nAURAYA_CLIENT_SECRET
JWT client secret on the EVA server (and this server) \nAURAYA_ISSUER
This app (or \"issuer\") \nAURAYA_RANDOM_DIGITS
Set to \"true\" to prompt for random digits or \"false\" not to \nAURAYA_COMMON_DIGITS
Set to \"true\" to prompt for common digits or \"false\" not to \nAURAYA_PERSONAL_DIGITS
A user.usermetadata property that contains digits such as phonenumber \nAURAYA_COMMON_DIGITS_PROMPT
A digit string to prompt for common digits (e.g '987654321') \nAURAYA_PERSONAL_DIGITS_PROMPT
A string to prompt for personal digits (e.g 'your cell number') \nAURAYA_DEBUG
Set to \"true\" to log errors in the console \n
",
- "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('url@0.10.3');\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\n let personalDigits = '';\n if (typeof configuration.AURAYA_PERSONAL_DIGITS !== 'undefined') {\n personalDigits = user.user_metadata[configuration.AURAYA_PERSONAL_DIGITS];\n }\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",
- "overview": "Verify your user's identity in 180+ countries with ID DataWeb's adaptive Verification Workflows.",
- "categories": [
- "marketplace"
- ],
- "description": "Please see the ID DataWeb integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nIDDATAWEB_BASE_URL
Indicates the ID DataWeb environment. The default value is Pre-production - https://prod2.iddataweb.com/prod-axn
- where all testing and POCs should take place. To switch to production, change the URL to https://prod2.iddataweb.com/prod-axn
\nIDDATAWEB_CLIENT_ID
Identifies your specific verification workflow and user experience. Get this from the ID DataWeb’s AXN Admin console. \nIDDATAWEB_CLIENT_SECRET
Authenticates your specific verification workflow and user experience. Get this from ID DataWeb’s AXN Admin console. \n
\nOptional configuration:
\n\nIDDATAWEB_ALWAYS_VERIFY
Controls if users are verified each time they login, or just initially. We recommend \"true\" (verify the user on every login) for testing, not set (verify once, then not again) for production. \n
",
- "code": "async function iddatawebVerificationWorkflow(user, context, callback) {\n const {\n IDDATAWEB_BASE_URL,\n IDDATAWEB_CLIENT_ID,\n IDDATAWEB_CLIENT_SECRET,\n IDDATAWEB_ALWAYS_VERIFY\n } = configuration;\n\n if (!IDDATAWEB_BASE_URL || !IDDATAWEB_CLIENT_ID || !IDDATAWEB_CLIENT_SECRET) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const { Auth0RedirectRuleUtilities } = require('@auth0/rule-utilities@0.1.0');\n const axiosClient = require('axios@0.19.2');\n const url = require('url');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n const idwBasicAuth = Buffer.from(\n IDDATAWEB_CLIENT_ID + ':' + IDDATAWEB_CLIENT_SECRET\n ).toString('base64');\n\n const idwTokenNamepsace = 'https://iddataweb.com/';\n const idwTokenEndpoint = `${IDDATAWEB_BASE_URL}/axn/oauth2/token`;\n const idwAuthorizeEndpoint = `${IDDATAWEB_BASE_URL}/axn/oauth2/authorize`;\n const auth0ContinueUrl = `https://${context.request.hostname}/continue`;\n\n let iddataweb = (user.app_metadata && user.app_metadata.iddataweb) || {};\n iddataweb.verificationResult = iddataweb.verificationResult || {};\n\n // if the user is already verified and we don't need to check, exit\n if (\n iddataweb.verificationResult.policyDecision === 'approve' &&\n IDDATAWEB_ALWAYS_VERIFY !== 'true'\n ) {\n console.log('user ' + user.user_id + ' has been previously verified.');\n return callback(null, user, context);\n }\n\n // if coming back from redirect - get token, make policy decision, and update user metadata.\n if (ruleUtils.isRedirectCallback) {\n console.log('code from IDW: ' + ruleUtils.queryParams.code);\n\n const formParams = new url.URLSearchParams({\n grant_type: 'authorization_code',\n code: ruleUtils.queryParams.code,\n redirect_uri: auth0ContinueUrl\n });\n\n const headers = {\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'Cache-Control': 'no-cache',\n Authorization: `Basic ${idwBasicAuth}`\n };\n\n let decodedToken;\n try {\n const tokenResponse = await axiosClient.post(\n idwTokenEndpoint,\n formParams.toString(),\n { headers }\n );\n\n if (tokenResponse.data.error) {\n throw new Error(tokenResponse.data.error_description);\n }\n\n decodedToken = jwt.decode(tokenResponse.data.id_token);\n } catch (error) {\n return callback(error);\n }\n\n //check issuer, audience and experiation of ID DataWeb Token\n if (\n decodedToken.iss !== IDDATAWEB_BASE_URL ||\n decodedToken.aud !== IDDATAWEB_CLIENT_ID\n ) {\n return callback(new Error('ID token invalid.'));\n }\n\n console.log('policy decision: ' + decodedToken.policyDecision);\n console.log('score: ' + decodedToken.idwTrustScore);\n console.log('IDW transaction ID: ' + decodedToken.jti);\n\n // once verification is complete, update user's metadata in Auth0.\n //this could be used for downstream application authorization,\n //or mapping access to levels of assurance.\n iddataweb.verificationResult = {\n policyDecision: decodedToken.policyDecision,\n transactionid: decodedToken.jti,\n iat: decodedToken.iat\n };\n\n try {\n auth0.users.updateAppMetadata(user.user_id, { iddataweb });\n } catch (error) {\n return callback(error);\n }\n\n //include ID DataWeb results in Auth0 ID Token\n context.idToken[idwTokenNamepsace + 'policyDecision'] =\n decodedToken.policyDecision;\n context.idToken[idwTokenNamepsace + 'transactionId'] = decodedToken.jti;\n context.idToken[idwTokenNamepsace + 'iat'] = decodedToken.iat;\n\n return callback(null, user, context);\n }\n\n // ... otherwise, redirect for verification.\n\n let idwRedirectUrl =\n idwAuthorizeEndpoint +\n '?client_id=' +\n IDDATAWEB_CLIENT_ID +\n '&redirect_uri=' +\n auth0ContinueUrl +\n '&scope=openid+country.US&response_type=code';\n\n if (ruleUtils.canRedirect) {\n context.redirect = {\n url: idwRedirectUrl\n };\n }\n\n return callback(null, user, context);\n}"
- },
- {
- "id": "mylife-digital-progressive-consent",
- "title": "Consentric Progressive Consent",
- "overview": "Uses a widget to capture missing consents and preferences at login to boost engagement and support compliance",
- "categories": [
- "marketplace"
- ],
- "description": "Please see the MyLife Digital integration for more information and detailed installation instructions.\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nCONSENTRIC_AUTH_HOST
The URL to authenticate against for your Consentric API token, like https://sandbox-consentric.eu.auth0.com
\nCONSENTRIC_API_HOST
The Consentric API host URL, like https://sandbox.consentric.io
\nCONSENTRIC_CLIENT_ID
The Consentric ClientId issued to you \nCONSENTRIC_CLIENT_SECRET
The Consentric ClientSecret issued to you \nCONSENTRIC_AUDIENCE
The name of the Consentric API being called, like https://sandbox.consentric.io
\nCONSENTRIC_APPLICATION_ID
The Consentric ApplicationId issued to you \nCONSENTRIC_REDIRECT_URL
The URL of the page containing the Progressive widget \n
",
- "code": "function consentricProgressiveConsent(user, context, callback) {\n const axios = require('axios@0.19.2');\n const moment = require('moment@2.11.2');\n const { Auth0RedirectRuleUtilities } = require('@auth0/rule-utilities@0.1.0');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n const asMilliSeconds = (seconds) => seconds * 1000;\n\n const {\n CONSENTRIC_AUTH_HOST,\n CONSENTRIC_API_HOST,\n CONSENTRIC_AUDIENCE,\n CONSENTRIC_CLIENT_ID,\n CONSENTRIC_CLIENT_SECRET,\n CONSENTRIC_APPLICATION_ID,\n CONSENTRIC_REDIRECT_URL\n } = configuration;\n\n if (\n !CONSENTRIC_AUTH_HOST ||\n !CONSENTRIC_API_HOST ||\n !CONSENTRIC_AUDIENCE ||\n !CONSENTRIC_CLIENT_ID ||\n !CONSENTRIC_CLIENT_SECRET ||\n !CONSENTRIC_APPLICATION_ID ||\n !CONSENTRIC_REDIRECT_URL\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const consentricAuth = axios.create({\n baseURL: CONSENTRIC_AUTH_HOST,\n timeout: 1000\n });\n\n const consentricApi = axios.create({\n baseURL: CONSENTRIC_API_HOST,\n timeout: 1000\n });\n\n // Returns Consentric API Access Token (JWT) from either the global cache or generates it anew from clientId and secret\n const getConsentricApiAccessToken = async () => {\n const consentricApiTokenNotValid =\n !global.consentricApiToken || global.consentricApiToken.exp < Date.now();\n\n if (consentricApiTokenNotValid) {\n try {\n // Exchange Credentials for Consentric Api Access token\n const {\n data: { expires_in, access_token }\n } = await consentricAuth.post('/oauth/token', {\n grant_type: 'client_credentials',\n client_id: CONSENTRIC_CLIENT_ID,\n client_secret: CONSENTRIC_CLIENT_SECRET,\n audience: CONSENTRIC_AUDIENCE,\n applicationId: CONSENTRIC_APPLICATION_ID\n });\n\n const expiryInMs = new Date().getTime() + asMilliSeconds(expires_in);\n const auth = {\n jwt: access_token,\n exp: expiryInMs\n };\n\n // Persist API Access token in global properties\n global.consentricApiToken = auth;\n } catch (error) {\n console.error(\n 'Unable to retrieve API Access token for Consentric. Please check that your credentials (CONSENTRIC_CLIENT_ID and CONSENTRIC_CLIENT_SECRET) are correct.'\n );\n throw error;\n }\n }\n\n return global.consentricApiToken;\n };\n\n // Creates Citizen Record in Consentric with Auth0 Id\n const createCitizen = ({ userRef, apiAccessToken }) => {\n console.log(`Upserting Consentric Citizen record for ${userRef}`);\n const data = {\n applicationId: CONSENTRIC_APPLICATION_ID,\n externalRef: userRef\n };\n\n return consentricApi\n .post('/v1/citizens', data, {\n headers: {\n Authorization: 'Bearer ' + apiAccessToken\n }\n })\n .catch((err) => {\n if (err.response.status !== 409) {\n // 409 indicates Citizen with given reference already exists in Consentric\n console.error(err);\n throw err;\n }\n });\n };\n\n // Function to retrieve Consentric User Token from User Metadata\n const getConsentricUserTokenFromMetadata = (user) =>\n user.app_metadata && user.app_metadata.consentric;\n\n // Generates On Demand Consentric User Token for the given User using the API Access Token\n const generateConsentricUserAccessToken = async ({\n userRef,\n apiAccessToken\n }) => {\n try {\n console.log(`Attempting to generate access token API for ${userRef}`);\n\n const {\n data: { token, expiryDate: exp }\n } = await consentricApi.post(\n '/v1/access-tokens/tokens',\n {\n applicationId: CONSENTRIC_APPLICATION_ID,\n externalRef: userRef,\n expiryDate: moment().add(3, 'months').toISOString()\n },\n {\n headers: {\n Authorization: 'Bearer ' + apiAccessToken\n }\n }\n );\n\n return {\n token,\n exp\n };\n } catch (err) {\n console.error(err);\n throw err;\n }\n };\n\n const loadConsentricUserAccessToken = async ({ user }) => {\n try {\n const metadataUserToken = getConsentricUserTokenFromMetadata(user);\n if (\n metadataUserToken &&\n moment(metadataUserToken.exp).subtract(1, 'days').isAfter(moment())\n )\n return metadataUserToken;\n\n const { jwt: apiAccessToken } = await getConsentricApiAccessToken();\n const apiCredentials = {\n userRef: user.user_id,\n apiAccessToken\n };\n\n // Create Citizen with Auth0 UserId\n await createCitizen(apiCredentials);\n\n // Generate an On Demand Access Token for the created citizen\n const generatedToken = await generateConsentricUserAccessToken(\n apiCredentials\n );\n\n // Persist the app_metadata update\n await auth0.users.updateAppMetadata(user.user_id, {\n consentric: generatedToken\n });\n\n return generatedToken;\n } catch (err) {\n console.error(\n `Issue loading Consentric User Access Token for user ${user.user_id} - ${err}`\n );\n throw err;\n }\n };\n\n const initConsentricFlow = async () => {\n try {\n const { token } = await loadConsentricUserAccessToken({ user });\n const urlConnector = CONSENTRIC_REDIRECT_URL.includes('?') ? '&' : '?';\n const redirectUrl =\n CONSENTRIC_REDIRECT_URL + urlConnector + 'token=' + token;\n\n context.redirect = {\n url: redirectUrl\n };\n } catch (err) {\n console.error(`CONSENTRIC RULE ABORTED: ${err}`);\n }\n return callback(null, user, context);\n };\n\n if (ruleUtils.canRedirect) {\n return initConsentricFlow();\n } else {\n // Run after Redirect or Silent Auth\n return callback(null, user, context);\n }\n}"
- },
- {
- "id": "netlify-role-management",
- "title": "Netlify Role Management",
- "overview": "Adds a default role if the user doesn't have any yet and attaches roles to the ID Token.",
- "categories": [
- "marketplace"
- ],
- "description": "Optional configuration:
\n\nDEFAULT_ROLE_NAME
- name of the default role to be given to a user \nDEFAULT_ROLE_ID
- id of the role to be given to a user \nCUSTOM_CLAIMS_NAMESPACE
- namespace for adding custom claims to ID Token \n
",
- "code": "async function netlifyRoleManagement(user, context, callback) {\n const ManagementClient = require('auth0@2.30.0').ManagementClient;\n\n const namespace =\n configuration.CUSTOM_CLAIMS_NAMESPACE || 'https://netlify-integration.com';\n const assignedRoles = (context.authorization || {}).roles || [];\n const defaultRoleName = configuration.DEFAULT_ROLE_NAME;\n const defaultRoleId = configuration.DEFAULT_ROLE_ID;\n\n //give default role if the user doesn't already have any roles assigned\n if (\n (!assignedRoles || assignedRoles.length === 0) &&\n defaultRoleName &&\n defaultRoleId\n ) {\n try {\n const management = new ManagementClient({\n token: auth0.accessToken,\n domain: auth0.domain\n });\n await management.assignRolestoUser(\n { id: user.user_id },\n { roles: [defaultRoleId] }\n );\n } catch (ex) {\n console.error('Failed to add default role to user', ex);\n } finally {\n assignedRoles.push(defaultRoleName);\n }\n }\n\n context.idToken[namespace + '/roles'] = assignedRoles;\n return callback(null, user, context);\n}"
- },
- {
- "id": "onetrust-consent-management",
- "title": "OneTrust Consent Management",
- "overview": "Enhance Auth0 user profiles with consent, opt-ins and communication preferences data.",
- "categories": [
- "marketplace"
- ],
- "description": "Please see the OneTrust integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nONETRUST_REQUEST_INFORMATION
Your OneTrust Collection Point API token \nONETRUST_CONSENT_API_URL
Your OneTrust Collection Point API URL \nONETRUST_PURPOSE_ID
Your OneTrust Collection Point Purpose ID \n
\nOptional configuration:
\n\nONETRUST_SKIP_IF_NO_EMAIL
If set to \"true\" then the Rule will be skipped if there is no email address. Otherwise the Rule will fail with an error. \n
",
- "code": "/* global configuration */\nasync function oneTrustConsentManagement(user, context, callback) {\n const axios = require('axios@0.19.2');\n\n const {\n ONETRUST_REQUEST_INFORMATION,\n ONETRUST_CONSENT_API_URL,\n ONETRUST_PURPOSE_ID\n } = configuration;\n\n if (\n !ONETRUST_REQUEST_INFORMATION ||\n !ONETRUST_CONSENT_API_URL ||\n !ONETRUST_PURPOSE_ID\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const skipIfNoEmail = configuration.ONETRUST_SKIP_IF_NO_EMAIL === 'true';\n\n user.app_metadata = user.app_metadata || {};\n let onetrust = user.app_metadata.onetrust || {};\n\n if (onetrust.receipt) {\n console.log('User has a Collection Point receipt. Skipping.');\n return callback(null, user, context);\n }\n\n if (!user.email) {\n if (skipIfNoEmail) {\n console.log('User has no email address. Skipping.');\n return callback(null, user, context);\n }\n return callback(new Error('An email address is required.'));\n }\n\n try {\n const response = await axios.post(ONETRUST_CONSENT_API_URL, {\n identifier: user.email,\n requestInformation: ONETRUST_REQUEST_INFORMATION,\n purposes: [{ Id: ONETRUST_PURPOSE_ID }]\n });\n onetrust.receipt = response.data.receipt;\n } catch (error) {\n console.log('Error calling the Collection Point.');\n return callback(error);\n }\n\n try {\n await auth0.users.updateAppMetadata(user.user_id, { onetrust });\n } catch (error) {\n console.log('Error updating user app_metadata.');\n return callback(error);\n }\n\n return callback(null, user, context);\n}"
- },
- {
- "id": "onfido-idv",
- "title": "Onfido Identity Verification",
- "overview": "Redirect to your Onfido IDV Application for Identity Verification during login.",
- "categories": [
- "marketplace"
- ],
- "description": "Please see the Onfido integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nSESSION_TOKEN_SECRET
Long, random string, should match on Onfido app side. \nONFIDO_API_TOKEN
Your Onfido API Token \nONFIDO_REGION
The supported Onfido region your tenant is operating in \nONFIDO_ID_VERIFICATION_URL
URL to receive the redirect \n
",
- "code": "/* global configuration */\nasync function onfidoIdentityVerification(user, context, callback) {\n if (\n !configuration.SESSION_TOKEN_SECRET ||\n !configuration.ONFIDO_API_TOKEN ||\n !configuration.ONFIDO_REGION ||\n !configuration.ONFIDO_ID_VERIFICATION_URL\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n // using auth0 rule-utilities to make sure our rule is efficient in the pipeline\n const { Auth0RedirectRuleUtilities } = require('@auth0/rule-utilities@0.1.0');\n // requiring Onfido's node SDK for making the calls easier to Onfido's service.\n const { Onfido, Region } = require('@onfido/api@1.5.1');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n // creating a claim namespace for adding the Onfido IDV check results back to the ID Token\n const claimNamespace = 'https://claims.onfido.com/';\n\n // creating a new Onfido client, the region here is where your Onfido instance is located. Possible values are EU for Europe, US for United States, and CA for Canada.\n const onfidoClient = new Onfido({\n apiToken: configuration.ONFIDO_API_TOKEN,\n region: Region[configuration.ONFIDO_REGION] || Region.EU\n });\n\n user.app_metadata = user.app_metadata || {};\n user.app_metadata.onfido = user.app_metadata.onfido || {};\n\n if (\n ruleUtils.isRedirectCallback &&\n ruleUtils.queryParams.session_token &&\n 'true' === ruleUtils.queryParams.onfido_idv\n ) {\n // User is back from the Onfido experience and has a session token to validate and assign to user meta\n\n // Validating session token and extracting payload for check results\n let payload;\n try {\n payload = ruleUtils.validateSessionToken();\n } catch (error) {\n return callback(error);\n }\n\n // assigning check status and result to the app_metadata so the downstream application can decided what to do next\n // note, in the example integration, the Onfido app returns after 30 seconds even if the check is still in progress\n // If this claim status is still in_progress it is recommended the downstream application recheck for completion or implement the Onfido Webhook: https://documentation.onfido.com/#webhooks\n // Additionally, you can place these items into the idToken claim with custom claims as needed as shown\n const onfido = {\n check_result: payload.checkResult,\n check_status: payload.checkStatus,\n applicant_id: payload.applicant\n };\n try {\n await auth0.users.updateAppMetadata(user.user_id, onfido);\n } catch (error) {\n callback(error);\n }\n\n user.app_metadata.onfido = onfido;\n\n context.idToken[claimNamespace + 'check_result'] = payload.checkResult;\n context.idToken[claimNamespace + 'check_status'] = payload.checkStatus;\n context.idToken[claimNamespace + 'applicant_id'] = payload.applicant;\n\n return callback(null, user, context);\n }\n\n if (ruleUtils.canRedirect && !user.app_metadata.onfido.check_status) {\n // if the user has not already been redirected and check_status is empty, we will create the applicant and redirect to the Onfido implementation.\n let applicant;\n try {\n applicant = await onfidoClient.applicant.create({\n // these values do not need to match what is on the document for IDV, but if Data Comparison on Onfido's side is tuned on, these values will flag\n // if Auth0 contains these values in the app_metadata or on the user object you can map them here as needed. You could also pass them in as query_string variables\n firstName: !user.given_name ? 'anon' : user.given_name,\n lastName: !user.family_name ? 'anon' : user.family_name,\n email: !user.email ? 'anon@example.com' : user.email\n });\n\n // create the session token with the applicant id as a custom claim\n const sessionToken = ruleUtils.createSessionToken({\n applicant: applicant.id\n });\n // redirect to Onfido implementation with sessionToken\n ruleUtils.doRedirect(\n configuration.ONFIDO_ID_VERIFICATION_URL,\n sessionToken\n );\n return callback(null, user, context);\n } catch (error) {\n return callback(error);\n }\n }\n return callback(null, user, context);\n}"
- },
- {
- "id": "scaled-access-relationships-claim",
- "title": "Scaled Access relationship-based claims",
- "overview": "Adds a claim based on the relationships the subject has in Scaled Access",
- "categories": [
- "marketplace"
- ],
- "description": "Please see the Scaled Access integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nSCALED_ACCESS_AUDIENCE
The identifier of the Auth0 API \nSCALED_ACCESS_CLIENTID
The Client ID of the Auth0 machine-to-machine application. \nSCALED_ACCESS_CLIENTSECRET
The Client secret of the Auth0 machine-to-machine application. \nSCALED_ACCESS_BASEURL
The base URL for the Relationship Management API. \nSCALED_ACCESS_TENANT
Your tenant code provided by Scaled Access. \n
\nOptional configuration:
\n\nSCALED_ACCESS_CUSTOMCLAIM
A namespaced ID token claim (defaults to https://scaledaccess.com/relationships
) \n
",
- "code": "function scaledAccessAddRelationshipsClaim(user, context, callback) {\n if (\n !configuration.SCALED_ACCESS_AUDIENCE ||\n !configuration.SCALED_ACCESS_CLIENTID ||\n !configuration.SCALED_ACCESS_CLIENTSECRET ||\n !configuration.SCALED_ACCESS_BASEURL ||\n !configuration.SCALED_ACCESS_TENANT\n ) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n const fetch = require('node-fetch');\n const { URLSearchParams } = require('url');\n\n const getM2mToken = () => {\n if (\n global.scaledAccessM2mToken &&\n global.scaledAccessM2mTokenExpiryInMillis > new Date().getTime() + 60000\n ) {\n return Promise.resolve(global.scaledAccessM2mToken);\n } else {\n const tokenUrl = `https://${context.request.hostname}/oauth/token`;\n return fetch(tokenUrl, {\n method: 'POST',\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: configuration.SCALED_ACCESS_CLIENTID,\n client_secret: configuration.SCALED_ACCESS_CLIENTSECRET,\n audience: configuration.SCALED_ACCESS_AUDIENCE,\n scope: 'pg:tenant:admin'\n })\n })\n .then((response) => {\n if (!response.ok) {\n return response.text().then((error) => {\n console.error('Failed to obtain m2m token from ' + tokenUrl);\n throw Error(error);\n });\n } else {\n return response.json();\n }\n })\n .then(({ access_token, expires_in }) => {\n global.scaledAccessM2mToken = access_token;\n global.scaledAccessM2mTokenExpiryInMillis =\n new Date().getTime() + expires_in * 1000;\n return access_token;\n });\n }\n };\n\n const callRelationshipManagementApi = async (accessToken, path) => {\n const url = `${configuration.SCALED_ACCESS_BASEURL}/${configuration.SCALED_ACCESS_TENANT}/${path}`;\n return fetch(url, {\n method: 'GET',\n headers: {\n Authorization: 'Bearer ' + accessToken,\n 'Content-Type': 'application/json'\n }\n }).then(async (response) => {\n if (response.status === 404) {\n return [];\n } else if (!response.ok) {\n return response.text().then((error) => {\n console.error('Failed to call relationship management API', url);\n throw Error(error);\n });\n } else {\n return response.json();\n }\n });\n };\n\n const getRelationships = (accessToken) => {\n return callRelationshipManagementApi(\n accessToken,\n `actors/user/${user.user_id}/relationships`\n );\n };\n\n const addClaimToToken = (apiResponse) => {\n const claimName =\n configuration.SCALED_ACCESS_CUSTOMCLAIM ||\n `https://scaledaccess.com/relationships`;\n context.accessToken[claimName] = apiResponse.map((relationship) => ({\n relationshipType: relationship.relationshipType,\n to: relationship.to\n }));\n };\n\n getM2mToken()\n .then(getRelationships)\n .then(addClaimToToken)\n .then(() => {\n callback(null, user, context);\n })\n .catch((err) => {\n console.error(err);\n console.log('Using configuration: ', JSON.stringify(configuration));\n callback(null, user, context); // fail gracefully, token just won't have extra claim\n });\n}"
- },
- {
- "id": "vouched-verification",
- "title": "Vouched Verification",
- "overview": "Verify a person's identity using Vouched.",
- "categories": [
- "marketplace"
- ],
- "description": "Please see the Vouched integration for more information and detailed installation instructions.
\nRequired configuration (this Rule will be skipped if any of the below are not defined):
\n\nVOUCHED_API_KEY
Your Private Key located in Vouched Dashboard \nVOUCHED_PUBLIC_KEY
Your Public Key located in Vouched Dashboard \n
\nOptional configuration:
\n\nVOUCHED_API_URL
Your Vouched API URL; leave blank unless instructed by your Vouched rep \nVOUCHED_ID_TOKEN_CLAIM
Set a https://vouchedid/is_verified
claim in the ID token with results \nVOUCHED_VERIFICATION_OPTIONAL
Set to \"true\" to succeed even if verification fails \n
",
- "code": "async function vouchedVerification(user, context, callback) {\n if (!configuration.VOUCHED_API_KEY || !configuration.VOUCHED_PUBLIC_KEY) {\n console.log('Missing required configuration. Skipping.');\n return callback(null, user, context);\n }\n\n /* ----------- START helpers ----------- */\n const axios = require('axios');\n const url = require('url');\n const { Auth0RedirectRuleUtilities } = require('@auth0/rule-utilities@0.1.0');\n\n const ruleUtils = new Auth0RedirectRuleUtilities(\n user,\n context,\n configuration\n );\n\n const defaultApiUrl = 'https://verify.vouched.id/api';\n const defaultUiUrl = 'https://i.vouched.id';\n const idTokenClaim = 'https://vouched.id/is_verified';\n\n const getJobByToken = async (apiKey, jobToken, apiUrl) => {\n return getJob(apiKey, { token: jobToken }, apiUrl);\n };\n\n const getJobById = async (apiKey, jobId, apiUrl) => {\n return getJob(apiKey, { id: jobId }, apiUrl);\n };\n\n const getJob = async (apiKey, params, apiUrl) => {\n const response = await axios({\n headers: {\n 'X-Api-Key': apiKey,\n 'Content-Type': 'application/json'\n },\n baseURL: apiUrl,\n url: '/jobs',\n params: params\n });\n const items = response.data.items;\n if (items.length === 0) {\n throw new Error(\n `Unable to find Job with the following params: ${JSON.stringify(\n params\n )}`\n );\n }\n return items[0];\n };\n\n const createPacket = async (\n apiKey,\n publicKey,\n continueUrl,\n user,\n apiUrl = defaultApiUrl\n ) => {\n const requestBody = {\n pk: publicKey,\n uid: user.user_id,\n continueUrl\n };\n\n if (user.given_name) requestBody.firstName = user.given_name;\n\n if (user.family_name) requestBody.lastName = user.family_name;\n\n const response = await axios({\n method: 'post',\n headers: {\n 'X-Api-Key': apiKey,\n 'Content-Type': 'application/json'\n },\n baseURL: apiUrl,\n url: '/packet/auth0',\n data: requestBody\n });\n const data = response.data;\n if (data.errors) {\n throw new Error(`${data.errors[0].message}`);\n }\n return data.id;\n };\n\n const isJobForUser = (job, userId) => {\n try {\n return (\n job.request.properties.filter(\n (prop) => prop.name === 'uid' && prop.value === userId\n ).length === 1\n );\n } catch (e) {\n return false;\n }\n };\n\n const extractResults = (job) => {\n const { id, status, reviewSuccess, result } = job;\n return {\n id,\n status,\n reviewSuccess,\n result\n };\n };\n\n const isJobVerified = (job) => {\n try {\n return job.result.success || job.reviewSuccess;\n } catch (e) {\n return false;\n }\n };\n\n const redirectToVerification = (packetId, baseUrl = defaultUiUrl) => {\n const redirectUrl = new url.URL(`${baseUrl}/auth0`);\n redirectUrl.searchParams.append('id', packetId);\n return redirectUrl.href;\n };\n\n /* ----------- END helpers ----------- */\n\n user.app_metadata = user.app_metadata || {};\n const vouchedApiUrl = configuration.VOUCHED_API_URL || defaultApiUrl;\n\n try {\n const jobToken = ruleUtils.queryParams.jobToken;\n if (ruleUtils.isRedirectCallback && jobToken) {\n // get job from API\n const job = await getJobByToken(\n configuration.VOUCHED_API_KEY,\n jobToken,\n vouchedApiUrl\n );\n\n // check if job's user is the same as current user\n if (!isJobForUser(job, user.user_id)) {\n return callback(\n new Error(`The ID Verification results do not belong to this user.`)\n );\n }\n\n // update app metadata w/ results\n user.app_metadata.vouched = extractResults(job);\n await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);\n }\n\n const vouchedResults = user.app_metadata.vouched;\n if (vouchedResults) {\n if (!isJobVerified(vouchedResults)) {\n // user failed id verification\n const mostRecentJob = await getJobById(\n configuration.VOUCHED_API_KEY,\n vouchedResults.id,\n vouchedApiUrl\n );\n\n // check if job's user is the same as current user\n if (!isJobForUser(mostRecentJob, user.user_id)) {\n return callback(\n new Error(`The ID Verification results do not belong to this user.`)\n );\n }\n\n // user is now verified, update app metadata\n if (isJobVerified(mostRecentJob)) {\n user.app_metadata.vouched = extractResults(mostRecentJob);\n await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);\n } else {\n // user failed verification check and doesn't have an override\n if (configuration.VOUCHED_ID_TOKEN_CLAIM === 'true') {\n context.idToken[idTokenClaim] = false;\n }\n if (configuration.VOUCHED_VERIFICATION_OPTIONAL === 'true') {\n return callback(null, user, context);\n }\n\n return callback(new Error(`This user's ID cannot be verified.`));\n }\n }\n } else {\n // create Auth0 packet to securely pass info to Vouched\n const packetId = await createPacket(\n configuration.VOUCHED_API_KEY,\n configuration.VOUCHED_PUBLIC_KEY,\n `https://${context.request.hostname}/continue`,\n user\n );\n\n // user doesn't have a verification result, redirect to Vouched with packet\n if (ruleUtils.canRedirect) {\n context.redirect = { url: redirectToVerification(packetId) };\n }\n return callback(null, user, context);\n }\n } catch (e) {\n return callback(e);\n }\n\n if (configuration.VOUCHED_ID_TOKEN_CLAIM === 'true') {\n context.idToken[idTokenClaim] = true;\n }\n\n return callback(null, user, context);\n}"
- }
- ]
- },
{
"name": "guardian",
"templates": [
diff --git a/src/rules/arengu-progressive-profiling.js b/src/rules/arengu-progressive-profiling.js
index ff78b9f4..df7fadca 100644
--- a/src/rules/arengu-progressive-profiling.js
+++ b/src/rules/arengu-progressive-profiling.js
@@ -1,11 +1,11 @@
/**
- * @title Arengu Progressive Profiling
+ * @title Arengu Progressive Profiling
* @overview Capture new users' information in your authentication flows.
* @gallery true
* @category marketplace
*/
- async function arenguCompleteUserProfile(user, context, callback) {
+async function arenguCompleteUserProfile(user, context, callback) {
if (
!configuration.SESSION_TOKEN_SECRET ||
!configuration.ARENGU_PROFILE_FORM_URL
@@ -17,7 +17,7 @@
const {
Auth0RedirectRuleUtilities,
Auth0UserUpdateUtilities
- } = require("@auth0/rule-utilities@0.2.0");
+ } = require('@auth0/rule-utilities@0.2.0');
const ruleUtils = new Auth0RedirectRuleUtilities(
user,
@@ -42,14 +42,16 @@
}
function isEmptyUserMeta(key) {
- return userUtils.getUserMeta(key) === undefined ||
+ return (
+ userUtils.getUserMeta(key) === undefined ||
userUtils.getUserMeta(key) === null ||
- userUtils.getUserMeta(key).length === 0;
+ userUtils.getUserMeta(key).length === 0
+ );
}
function isProfileIncomplete() {
// Add your required user_medata keys
- return isEmptyUserMeta('job_title') || isEmptyUserMeta('company_name');
+ return isEmptyUserMeta('job_title') || isEmptyUserMeta('company_name');
}
if (ruleUtils.isRedirectCallback && ruleUtils.queryParams.session_token) {
@@ -74,4 +76,4 @@
}
return callback(null, user, context);
-}
\ No newline at end of file
+}
diff --git a/src/rules/incognia-authentication.js b/src/rules/incognia-authentication.js
index abdd9528..c8559bd9 100644
--- a/src/rules/incognia-authentication.js
+++ b/src/rules/incognia-authentication.js
@@ -17,7 +17,10 @@ async function incogniaAuthenticationRule(user, context, callback) {
return callback(null, user, context);
}
- const installationId = _.get(context, 'request.query.incognia_installation_id');
+ const installationId = _.get(
+ context,
+ 'request.query.incognia_installation_id'
+ );
if (!installationId) {
console.log('Missing installation_id. Skipping.');
return callback(null, user, context);
@@ -49,7 +52,8 @@ async function incogniaAuthenticationRule(user, context, callback) {
// Incognia's risk assessment will be in a namespaced claim so it can be used in other rules
// for skipping/prompting MFA or in the mobile app itself to decide whether the user should be
// redirected to step-up auth for example.
- context.idToken["https://www.incognia.com/assessment"] = loginAssessment.riskAssessment;
+ context.idToken['https://www.incognia.com/assessment'] =
+ loginAssessment.riskAssessment;
} catch (error) {
console.log('Error calling Incognia API for a new login.');
return callback(error);
diff --git a/src/rules/incognia-onboarding.js b/src/rules/incognia-onboarding.js
index 0e5914a9..fe45afa8 100644
--- a/src/rules/incognia-onboarding.js
+++ b/src/rules/incognia-onboarding.js
@@ -11,14 +11,21 @@ async function incogniaOnboardingRule(user, context, callback) {
const { IncogniaAPI } = require('@incognia/api@1.0.0');
const { Auth0UserUpdateUtilities } = require('@auth0/rule-utilities@0.2.0');
- const { INCOGNIA_CLIENT_ID, INCOGNIA_CLIENT_SECRET, INCOGNIA_HOME_ADDRESS_PROP } = configuration;
+ const {
+ INCOGNIA_CLIENT_ID,
+ INCOGNIA_CLIENT_SECRET,
+ INCOGNIA_HOME_ADDRESS_PROP
+ } = configuration;
if (!INCOGNIA_CLIENT_ID || !INCOGNIA_CLIENT_SECRET) {
console.log('Missing required configuration. Skipping.');
return callback(null, user, context);
}
- const installationId = _.get(context, 'request.query.incognia_installation_id');
+ const installationId = _.get(
+ context,
+ 'request.query.incognia_installation_id'
+ );
if (!installationId) {
console.log('Missing installation_id. Skipping.');
return callback(null, user, context);
@@ -27,7 +34,8 @@ async function incogniaOnboardingRule(user, context, callback) {
// User home address should be set using Auth0's Signup API for example. If the home address is
// not in 'user_metadata.home_address', please specify the path of the field inside the user
// object where the home address is through the INCOGNIA_HOME_ADDRESS_PROP configuration.
- const homeAddressProp = INCOGNIA_HOME_ADDRESS_PROP || 'user_metadata.home_address';
+ const homeAddressProp =
+ INCOGNIA_HOME_ADDRESS_PROP || 'user_metadata.home_address';
const homeAddress = _.get(user, homeAddressProp);
if (!homeAddress) {
console.log('Missing user home address. Skipping.');
@@ -39,7 +47,9 @@ async function incogniaOnboardingRule(user, context, callback) {
const status = userUtils.getAppMeta('status');
// This rule was previously run and calculated the assessment successfully.
if (status && status !== 'pending') {
- console.log('Assessment is already calculated or is unevaluable. Skipping.');
+ console.log(
+ 'Assessment is already calculated or is unevaluable. Skipping.'
+ );
return callback(null, user, context);
}
@@ -59,12 +69,14 @@ async function incogniaOnboardingRule(user, context, callback) {
// The rule was previously run, but Incognia could not assess the signup.
if (signupId) {
try {
- onboardingAssessment = await incogniaAPI.getOnboardingAssessment(signupId);
+ onboardingAssessment = await incogniaAPI.getOnboardingAssessment(
+ signupId
+ );
} catch (error) {
console.log('Error calling Incognia API for signup previously submitted');
return callback(error);
}
- // This is the first time the rule is being run with all necessary arguments.
+ // This is the first time the rule is being run with all necessary arguments.
} else {
try {
onboardingAssessment = await incogniaAPI.registerOnboardingAssessment({
@@ -90,7 +102,8 @@ async function incogniaOnboardingRule(user, context, callback) {
} else if (!firstAssessmentAt) {
newStatus = 'pending';
} else {
- const firstAssessmentAge = Math.round(Date.now() / 1000) - firstAssessmentAt;
+ const firstAssessmentAge =
+ Math.round(Date.now() / 1000) - firstAssessmentAt;
// 48 hours limit.
if (firstAssessmentAge > 172800) {
newStatus = 'unevaluable';