From 6b403788d6853312da7ebff7ac51b7004c0f9517 Mon Sep 17 00:00:00 2001 From: Josh Cunningham Date: Mon, 14 Sep 2020 09:30:49 -0700 Subject: [PATCH] Release new Rule templates (#249) * version bump * Rename MyLife Digital Rule file to match integration * npm run build --- package-lock.json | 2 +- package.json | 2 +- rules.json | 55 +++++++++++++++++++ ... => mylife-digital-progressive-consent.js} | 0 4 files changed, 57 insertions(+), 2 deletions(-) rename src/rules/{consentric-integration.js => mylife-digital-progressive-consent.js} (100%) diff --git a/package-lock.json b/package-lock.json index 1317300c..5a54f5d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "rules-templates", - "version": "0.11.2", + "version": "0.12.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9d6df696..1c71f4c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rules-templates", - "version": "0.11.2", + "version": "0.12.0", "description": "Auth0 Rules Repository", "main": "./rules", "scripts": { diff --git a/rules.json b/rules.json index 631d3195..f4004324 100644 --- a/rules.json +++ b/rules.json @@ -515,6 +515,61 @@ } ] }, + { + "name": "marketplace", + "templates": [ + { + "id": "caisson-id-check", + "title": "Caisson ID Check", + "overview": "Validate US driver's licenses and international passports in real time.", + "categories": [ + "marketplace" + ], + "description": "

Caisson Integration guide

", + "code": "async function caissonIDCheck(user, context, callback) {\n const { Auth0RedirectRuleUtilities } = require(\"@auth0/rule-utilities@0.1.0\");\n\n //copy off the config obj so we can use our own private key for session token signing.\n let caissonConf = JSON.parse(JSON.stringify(configuration));\n caissonConf.SESSION_TOKEN_SECRET = configuration.CAISSON_PRIVATE_KEY;\n\n const manager = {\n creds: {\n public_key: caissonConf.CAISSON_PUBLIC_KEY,\n private_key: caissonConf.CAISSON_PRIVATE_KEY,\n },\n /* prettier-ignore */\n debug: caissonConf.CAISSON_DEBUG && caissonConf.CAISSON_DEBUG.toLowerCase() === \"true\" ? true : false,\n idCheckFlags: {\n login_frequency_days: parseInt(\n caissonConf.CAISSON_LOGIN_FREQUENCY_DAYS,\n 10\n ),\n },\n caissonHosts: {\n idcheck: \"https://id.caisson.com\",\n api: \"https://api.caisson.com\",\n dashboard: \"https://www.caisson.com\",\n },\n axios: require(\"axios@0.19.2\"),\n util: new Auth0RedirectRuleUtilities(user, context, caissonConf),\n };\n\n /**\n * Toggleable logger. Set CAISSON_DEBUG in the Auth0 configuration to enable.\n *\n * @param {error} err\n */\n function dLog(err) {\n if (manager.debug) {\n console.log(err);\n }\n }\n\n /**\n * Helper function for converting milliseconds to days. Results rounded down.\n * @param {int} mils\n */\n function millisToDays(mils) {\n return Math.floor(mils / 1000 / 60 / 60 / 24);\n }\n\n /**\n * Creates Caisson specific session token and sets redirect url.\n */\n function setIDCheckRedirect() {\n const token = manager.util.createSessionToken({\n public_key: manager.creds.public_key,\n host: context.request.hostname,\n });\n\n //throws if redirects aren't allowed here.\n manager.util.doRedirect(`${manager.caissonHosts.idcheck}/auth0`, token); //throws\n }\n\n /**\n * Swaps the temp Caisson exchange token for an ID Check key.\n * https://www.caisson.com/docs/reference/api/#exchange-check-token-for-check-id\n * @param {string} t\n */\n async function exchangeToken() {\n try {\n let resp = await manager.axios.post(\n manager.caissonHosts.api + \"/v1/idcheck/exchangetoken\",\n { check_exchange_token: manager.util.queryParams.t },\n {\n headers: {\n Authorization: `Caisson ${manager.creds.private_key}`,\n },\n }\n );\n\n return resp.data.check_id;\n } catch (error) {\n let err = error;\n if (err.response && err.response.status === 401) {\n err = new UnauthorizedError(\n \"Invalid private key. See your API credentials at https://www.caisson.com/developer .\"\n );\n }\n throw err;\n }\n }\n\n /**\n * Fetches and validates ID Check results.\n * https://www.caisson.com/docs/reference/api/#get-an-id-check-result\n * @param {string} check_id\n */\n async function idCheckResults(check_id) {\n try {\n let resp = await manager.axios.get(\n manager.caissonHosts.api + \"/v1/idcheck\",\n {\n headers: {\n Authorization: `Caisson ${manager.creds.private_key}`,\n \"X-Caisson-CheckID\": check_id,\n },\n }\n );\n\n if (resp.data.error) {\n throw new Error(\n \"Error in Caisson ID Check: \" + JSON.stringify(resp.data)\n );\n }\n\n let results = {\n check_id: resp.data.check_id,\n auth0_id: resp.data.customer_id,\n timestamp: resp.data.checked_on,\n /* prettier-ignore */\n status: resp.data.confidence.document === \"high\" && resp.data.confidence.face === \"high\" ? \"passed\" : \"flagged\",\n };\n\n validateIDCheck(results); //throws if invalid\n\n return results;\n } catch (error) {\n let err = error;\n if (err.response && err.response.status === 401) {\n err = new UnauthorizedError(\n \"Invalid private key. See your API credentials at https://www.caisson.com/developer .\"\n );\n }\n\n throw err;\n }\n }\n\n /**\n * Validates Caisson ID Check results, ensuring the data is usable.\n * @param {object} results\n */\n function validateIDCheck(results) {\n const IDCheckTTL = 20 * 60 * 1000; //20 mins\n if (\n results.auth0_id !==\n user.user_id + \"__\" + manager.util.queryParams.state\n ) {\n throw new UnauthorizedError(\n \"ID mismatch. Caisson: %o, Auth0: %o\",\n results.auth0_id,\n user.user_id\n );\n } else if (Date.now() - Date.parse(results.timestamp) > IDCheckTTL) {\n throw new UnauthorizedError(\"ID Check too old.\");\n }\n }\n\n /**\n * Updates Caisson values on the Auth0 user object's app_metadata object.\n * @param {object} results\n */\n async function updateUser(results) {\n user.app_metadata = user.app_metadata || {};\n let caisson = user.app_metadata.caisson || {};\n\n caisson.idcheck_url =\n manager.caissonHosts.dashboard + \"/request/\" + results.check_id;\n caisson.status = results.status;\n caisson.last_check = Date.now();\n caisson.count = caisson.count ? caisson.count + 1 : 1;\n\n user.app_metadata.caisson = caisson;\n\n try {\n await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);\n } catch (err) {\n throw err;\n }\n }\n\n /**\n * ID Check is done, handle results.\n */\n if (manager.util.isRedirectCallback) {\n //is it our redirect?\n\n if (\n !manager.util.queryParams.caisson_flow ||\n parseInt(manager.util.queryParams.caisson_flow, 10) !== 1\n ) {\n //no, end it.\n return callback(null, user, context);\n }\n\n try {\n if (!manager.util.queryParams.t) {\n throw new Error(\"Missing Caisson exchange key\");\n }\n\n const check_id = await exchangeToken();\n const results = await idCheckResults(check_id);\n await updateUser(results);\n\n //deny the login if the ID Check is flagged\n if (results.status === \"flagged\") {\n throw new UnauthorizedError(\"ID Check flagged.\");\n }\n } catch (err) {\n dLog(err);\n return callback(err);\n }\n\n return callback(null, user, context);\n }\n\n /**\n * Else we're in the initial auth flow.\n * Perform ID Checks when appropriate.\n */\n\n try {\n if (isNaN(manager.idCheckFlags.login_frequency_days)) {\n //Do nothing. Skip if no preference is set.\n } else if (\n !user.app_metadata.caisson.last_check ||\n user.app_metadata.caisson.status !== \"passed\"\n ) {\n //Always perform the first ID Check or if the\n //last ID Check didn't pass.\n setIDCheckRedirect();\n } else if (\n manager.idCheckFlags.login_frequency_days >= 0 &&\n millisToDays(Date.now() - user.app_metadata.caisson.last_check) >=\n manager.idCheckFlags.login_frequency_days\n ) {\n //ID Check if the requisite number of days have passed since the last check.\n //Skip if we're only supposed to check once (login_frequency_days < -1).\n setIDCheckRedirect();\n }\n } catch (err) {\n dLog(err);\n return callback(err);\n }\n\n return callback(null, user, context);\n}" + }, + { + "id": "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": "

This configuration allows you to add an ID DataWeb verification workflow to the user’s first time login experience. This will allow your organization to ensure the user is really who they claim to be, aligning with KYC/AML requirements, and mitigating fraud attempts.

", + "code": "async function iddatawebVerificationWorkflow(user, context, callback) {\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 {\n IDDATAWEB_BASE_URL,\n IDDATAWEB_CLIENT_ID,\n IDDATAWEB_CLIENT_SECRET,\n IDDATAWEB_ALWAYS_VERIFY,\n } = configuration;\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": "Progressive Consent Capture", + "overview": "Uses a widget to capture missing consents and preferences at login to boost engagement and support compliance", + "categories": [ + "marketplace" + ], + "description": "

Consentric provides an integration to extend Auth0's universal login to include collection of consent as part of the authentication and sign up flows.

", + "code": "function consentricIntegration(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 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 = (!global.consentricApiToken) ||\n global.consentricApiToken.exp < new Date().getTime();\n\n if (consentricApiTokenNotValid) {\n try {\n // Exchange Credentials for Consentric Api Access token\n const { data: { expires_in, access_token } } = await consentricAuth\n .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\n } catch (error) {\n console.error('Unable to retrieve API Access token for Consentric. Please check that your credentials (CONSENTRIC_CLIENT_ID and CONSENTRIC_CLIENT_SECRET) are correct.');\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) { // 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 => 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 ({ userRef, apiAccessToken }) => {\n try {\n console.log(`Attempting to generate access token API for ${userRef}`);\n\n const { data: { token, expiryDate: exp } } = await consentricApi\n .post('/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\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 ((metadataUserToken) && moment(metadataUserToken.exp).subtract(1, \"days\").isAfter(moment())) 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(apiCredentials);\n\n // Persist the app_metadata update \n await auth0.users.updateAppMetadata(user.user_id, { consentric: generatedToken });\n\n return generatedToken;\n } catch (err) {\n console.error(`Issue loading Consentric User Access Token for user ${user.user_id} - ${err}`);\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 = CONSENTRIC_REDIRECT_URL + urlConnector + 'token=' + token;\n\n context.redirect = {\n url: redirectUrl\n };\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": "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": "

This rule adds a claim to the access token based on relationships the subject has in Scaled Access.

\n

Example of a resulting claim:

\n
\"https://example.com/relationships\": [\n   {\n     \"relationshipType\": \"is_admin_of\",\n     \"to\": {\n       \"id\": \"c7b134f9-28a2-4dcc-b345-affa18977ddf\",\n       \"type\": \"subscription\"\n     }\n   },\n   {\n     \"relationshipType\": \"is_sports_member_of\",\n     \"to\": {\n       \"id\": \"affa18977ddf-4dcc-b345-c7b134f9-28a2\",\n       \"type\": \"subscription\"\n     }\n   }\n]\n
\n

This is done through an API call to Scaled Access' Relationship Management API using a machine-to-machine token.\nMore info can be found at https://docs.scaledaccess.com/?path=integration-with-auth0

\n

A number of rule settings are required:

\n", + "code": "function scaledAccessAddRelationshipsClaim(user, context, callback) {\n const fetch = require(\"node-fetch\");\n const { URLSearchParams } = require('url');\n\n const getM2mToken = () => {\n if (global.scaledAccessM2mToken && global.scaledAccessM2mTokenExpiryInMillis > new Date().getTime() + 60000) {\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 = 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 })\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(accessToken, `actors/user/${user.user_id}/relationships`);\n };\n\n const addClaimToToken = (apiResponse) => {\n const claimName = configuration.SCALED_ACCESS_CUSTOMCLAIM || `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": "

Vouched

\n

ID Verification Process\n\"\"

\n

Rule Logic\n\"\"

", + "code": "async function vouchedVerification(user, context, callback) {\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://console.vouched.id';\n const idTokenClaim = 'https://vouched.id/is_verified';\n \n const getJob = async (apiKey, jobToken, apiUrl=defaultApiUrl) => {\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: {\n token: jobToken\n },\n });\n const items = response.data.items;\n if (items.length === 0) {\n throw new Error(`Unable to find Job with the following id: ${jobToken}`);\n }\n return items[0];\n };\n \n const createPacket = async (apiKey, publicKey, continueUrl, user, apiUrl=defaultApiUrl) => {\n const requestBody = {\n pk: publicKey,\n uid: user.user_id,\n continueUrl\n };\n \n if (user.given_name)\n requestBody.firstName = user.given_name;\n \n if (user.family_name)\n 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 job.request.properties\n .filter(prop => prop.name === \"uid\" && prop.value === userId)\n .length === 1;\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 || undefined;\n \n try {\n const jobToken = ruleUtils.queryParams.jobToken;\n if (ruleUtils.isRedirectCallback && jobToken) { \n // get job from API\n const job = await getJob(configuration.VOUCHED_API_KEY, jobToken, vouchedApiUrl);\n \n // check if job's user is the same as current user\n if (!isJobForUser(job, user.user_id)) {\n return callback(new Error(`The ID Verification results do not belong to this user.`));\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 getJob(configuration.VOUCHED_API_KEY, jobToken, vouchedApiUrl);\n \n // check if job's user is the same as current user\n if (!isJobForUser(mostRecentJob, user.user_id)) {\n return callback(new Error(`The ID Verification results do not belong to this user.`));\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(configuration.VOUCHED_API_KEY, \n configuration.VOUCHED_PUBLIC_KEY,\n `https://${context.request.hostname}/continue`,\n user);\n \n // user doesn't have a verification result, redirect to Vouched with packet\n ruleUtils.doRedirect(redirectToVerification(packetId));\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": "multifactor", "templates": [ diff --git a/src/rules/consentric-integration.js b/src/rules/mylife-digital-progressive-consent.js similarity index 100% rename from src/rules/consentric-integration.js rename to src/rules/mylife-digital-progressive-consent.js