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.

\n

Optional configuration:

\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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\n\n

Optional configuration:

\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", + "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", + "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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\n\n

Optional configuration:

\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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\n\n

Optional configuration:

\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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\n\n

Optional configuration:

\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.

\n

Optional configuration:

\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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\n\n

Optional configuration:

\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", - "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", - "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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\n\n

Optional configuration:

\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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\n\n

Optional configuration:

\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.

\n

Required configuration (this Rule will be skipped if any of the below are not defined):

\n\n

Optional configuration:

\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';