From f702d7b7abea0f8ce7dd9a21b1ff512ae3ef67f3 Mon Sep 17 00:00:00 2001 From: John Eke Date: Fri, 26 Jan 2024 13:27:13 -0500 Subject: [PATCH] chore: add actions templates --- .../code.js | 36 ++++++ .../manifest.yaml | 15 +++ templates/adaptive-mfa-POST_LOGIN/code.js | 40 +++++++ .../adaptive-mfa-POST_LOGIN/manifest.yaml | 10 ++ templates/add-attribute-POST_LOGIN/code.js | 34 ++++++ .../add-attribute-POST_LOGIN/manifest.yaml | 15 +++ templates/add-country-POST_LOGIN/code.js | 34 ++++++ .../add-country-POST_LOGIN/manifest.yaml | 15 +++ .../code.js | 27 +++++ .../manifest.yaml | 15 +++ .../code.js | 75 ++++++++++++ .../manifest.yaml | 22 ++++ .../code.js | 70 +++++++++++ .../manifest.yaml | 22 ++++ .../code.js | 52 ++++++++ .../manifest.yaml | 20 ++++ .../code.js | 70 +++++++++++ .../manifest.yaml | 22 ++++ .../code.js | 70 +++++++++++ .../manifest.yaml | 21 ++++ .../code.js | 47 ++++++++ .../manifest.yaml | 13 ++ .../code.js | 113 ++++++++++++++++++ .../manifest.yaml | 26 ++++ .../code.js | 97 +++++++++++++++ .../manifest.yaml | 26 ++++ .../code.js | 25 ++++ .../manifest.yaml | 9 ++ templates/email-verified-POST_LOGIN/code.js | 23 ++++ .../email-verified-POST_LOGIN/manifest.yaml | 9 ++ .../code.js | 43 +++++++ .../manifest.yaml | 23 ++++ .../code.js | 48 ++++++++ .../manifest.yaml | 17 +++ .../code.js | 34 ++++++ .../manifest.yaml | 16 +++ .../code.js | 23 ++++ .../manifest.yaml | 15 +++ .../code.js | 30 +++++ .../manifest.yaml | 15 +++ .../ip-address-allowlist-POST_LOGIN/code.js | 30 +++++ .../manifest.yaml | 15 +++ .../code.js | 25 ++++ .../manifest.yaml | 14 +++ .../mfa-require-enrollment-POST_LOGIN/code.js | 23 ++++ .../manifest.yaml | 22 ++++ .../code.js | 33 +++++ .../manifest.yaml | 15 +++ templates/querystring-POST_LOGIN/code.js | 47 ++++++++ .../querystring-POST_LOGIN/manifest.yaml | 17 +++ .../code.js | 28 +++++ .../manifest.yaml | 15 +++ templates/role-creation-POST_LOGIN/code.js | 52 ++++++++ .../role-creation-POST_LOGIN/manifest.yaml | 22 ++++ .../saml-attribute-mapping-POST_LOGIN/code.js | 43 +++++++ .../manifest.yaml | 10 ++ .../saml-configuration-POST_LOGIN/code.js | 48 ++++++++ .../manifest.yaml | 26 ++++ .../code.js | 58 +++++++++ .../manifest.yaml | 15 +++ .../code.js | 42 +++++++ .../manifest.yaml | 15 +++ .../code.js | 38 ++++++ .../manifest.yaml | 15 +++ .../simple-user-allowlist-POST_LOGIN/code.js | 38 ++++++ .../manifest.yaml | 15 +++ .../code.js | 49 ++++++++ .../manifest.yaml | 15 +++ .../code.js | 49 ++++++++ .../manifest.yaml | 15 +++ .../code.js | 46 +++++++ .../manifest.yaml | 16 +++ .../code.js | 45 +++++++ .../manifest.yaml | 17 +++ .../code.js | 71 +++++++++++ .../manifest.yaml | 21 ++++ .../code.js | 61 ++++++++++ .../manifest.yaml | 21 ++++ templates/soap-webservice-POST_LOGIN/code.js | 77 ++++++++++++ .../soap-webservice-POST_LOGIN/manifest.yaml | 25 ++++ .../code.js | 61 ++++++++++ .../manifest.yaml | 21 ++++ .../code.js | 61 ++++++++++ .../manifest.yaml | 21 ++++ templates/track-consent-POST_LOGIN/code.js | 32 +++++ .../track-consent-POST_LOGIN/manifest.yaml | 15 +++ 86 files changed, 2797 insertions(+) create mode 100644 templates/active-directory-groups-POST_LOGIN/code.js create mode 100644 templates/active-directory-groups-POST_LOGIN/manifest.yaml create mode 100644 templates/adaptive-mfa-POST_LOGIN/code.js create mode 100644 templates/adaptive-mfa-POST_LOGIN/manifest.yaml create mode 100644 templates/add-attribute-POST_LOGIN/code.js create mode 100644 templates/add-attribute-POST_LOGIN/manifest.yaml create mode 100644 templates/add-country-POST_LOGIN/code.js create mode 100644 templates/add-country-POST_LOGIN/manifest.yaml create mode 100644 templates/add-email-to-access-token-POST_LOGIN/code.js create mode 100644 templates/add-email-to-access-token-POST_LOGIN/manifest.yaml create mode 100644 templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE/code.js create mode 100644 templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml create mode 100644 templates/add-persistence-attribute-POST_CHANGE_PASSWORD/code.js create mode 100644 templates/add-persistence-attribute-POST_CHANGE_PASSWORD/manifest.yaml create mode 100644 templates/add-persistence-attribute-POST_LOGIN/code.js create mode 100644 templates/add-persistence-attribute-POST_LOGIN/manifest.yaml create mode 100644 templates/add-persistence-attribute-POST_USER_REGISTRATION/code.js create mode 100644 templates/add-persistence-attribute-POST_USER_REGISTRATION/manifest.yaml create mode 100644 templates/add-persistence-attribute-SEND_PHONE_MESSAGE/code.js create mode 100644 templates/add-persistence-attribute-SEND_PHONE_MESSAGE/manifest.yaml create mode 100644 templates/check-last-password-reset-POST_LOGIN/code.js create mode 100644 templates/check-last-password-reset-POST_LOGIN/manifest.yaml create mode 100644 templates/creates-lead-salesforce-POST_LOGIN/code.js create mode 100644 templates/creates-lead-salesforce-POST_LOGIN/manifest.yaml create mode 100644 templates/creates-lead-salesforce-POST_USER_REGISTRATION/code.js create mode 100644 templates/creates-lead-salesforce-POST_USER_REGISTRATION/manifest.yaml create mode 100644 templates/email-verified-PASSWORD_RESET_POST_CHALLENGE/code.js create mode 100644 templates/email-verified-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml create mode 100644 templates/email-verified-POST_LOGIN/code.js create mode 100644 templates/email-verified-POST_LOGIN/manifest.yaml create mode 100644 templates/guardian-multifactor-authorization-extension-POST_LOGIN/code.js create mode 100644 templates/guardian-multifactor-authorization-extension-POST_LOGIN/manifest.yaml create mode 100644 templates/guardian-multifactor-ip-range-POST_LOGIN/code.js create mode 100644 templates/guardian-multifactor-ip-range-POST_LOGIN/manifest.yaml create mode 100644 templates/guardian-multifactor-stepup-authentication-POST_LOGIN/code.js create mode 100644 templates/guardian-multifactor-stepup-authentication-POST_LOGIN/manifest.yaml create mode 100644 templates/ip-address-allowlist-CREDENTIALS_EXCHANGE/code.js create mode 100644 templates/ip-address-allowlist-CREDENTIALS_EXCHANGE/manifest.yaml create mode 100644 templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE/code.js create mode 100644 templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml create mode 100644 templates/ip-address-allowlist-POST_LOGIN/code.js create mode 100644 templates/ip-address-allowlist-POST_LOGIN/manifest.yaml create mode 100644 templates/ip-address-allowlist-PRE_USER_REGISTRATION/code.js create mode 100644 templates/ip-address-allowlist-PRE_USER_REGISTRATION/manifest.yaml create mode 100644 templates/mfa-require-enrollment-POST_LOGIN/code.js create mode 100644 templates/mfa-require-enrollment-POST_LOGIN/manifest.yaml create mode 100644 templates/querystring-PASSWORD_RESET_POST_CHALLENGE/code.js create mode 100644 templates/querystring-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml create mode 100644 templates/querystring-POST_LOGIN/code.js create mode 100644 templates/querystring-POST_LOGIN/manifest.yaml create mode 100644 templates/require-mfa-once-per-session-POST_LOGIN/code.js create mode 100644 templates/require-mfa-once-per-session-POST_LOGIN/manifest.yaml create mode 100644 templates/role-creation-POST_LOGIN/code.js create mode 100644 templates/role-creation-POST_LOGIN/manifest.yaml create mode 100644 templates/saml-attribute-mapping-POST_LOGIN/code.js create mode 100644 templates/saml-attribute-mapping-POST_LOGIN/manifest.yaml create mode 100644 templates/saml-configuration-POST_LOGIN/code.js create mode 100644 templates/saml-configuration-POST_LOGIN/manifest.yaml create mode 100644 templates/simple-domain-allowlist-POST_LOGIN/code.js create mode 100644 templates/simple-domain-allowlist-POST_LOGIN/manifest.yaml create mode 100644 templates/simple-domain-allowlist-PRE_USER_REGISTRATION/code.js create mode 100644 templates/simple-domain-allowlist-PRE_USER_REGISTRATION/manifest.yaml create mode 100644 templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE/code.js create mode 100644 templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml create mode 100644 templates/simple-user-allowlist-POST_LOGIN/code.js create mode 100644 templates/simple-user-allowlist-POST_LOGIN/manifest.yaml create mode 100644 templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE/code.js create mode 100644 templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml create mode 100644 templates/simple-user-allowlist-for-app-POST_LOGIN/code.js create mode 100644 templates/simple-user-allowlist-for-app-POST_LOGIN/manifest.yaml create mode 100644 templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE/code.js create mode 100644 templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml create mode 100644 templates/simple-user-allowlist-on-a-connection-POST_LOGIN/code.js create mode 100644 templates/simple-user-allowlist-on-a-connection-POST_LOGIN/manifest.yaml create mode 100644 templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE/code.js create mode 100644 templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml create mode 100644 templates/soap-webservice-POST_CHANGE_PASSWORD/code.js create mode 100644 templates/soap-webservice-POST_CHANGE_PASSWORD/manifest.yaml create mode 100644 templates/soap-webservice-POST_LOGIN/code.js create mode 100644 templates/soap-webservice-POST_LOGIN/manifest.yaml create mode 100644 templates/soap-webservice-POST_USER_REGISTRATION/code.js create mode 100644 templates/soap-webservice-POST_USER_REGISTRATION/manifest.yaml create mode 100644 templates/soap-webservice-SEND_PHONE_MESSAGE/code.js create mode 100644 templates/soap-webservice-SEND_PHONE_MESSAGE/manifest.yaml create mode 100644 templates/track-consent-POST_LOGIN/code.js create mode 100644 templates/track-consent-POST_LOGIN/manifest.yaml diff --git a/templates/active-directory-groups-POST_LOGIN/code.js b/templates/active-directory-groups-POST_LOGIN/code.js new file mode 100644 index 0000000..c752d6f --- /dev/null +++ b/templates/active-directory-groups-POST_LOGIN/code.js @@ -0,0 +1,36 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/active-directory-groups-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // ensure that the allowed group is configured + const groupAllowed = event.secrets.ALLOWED_GROUP; + if (!groupAllowed) { + return api.access.deny('Invalid configuration'); + } + + // get the users groups + let groups = event.user.groups || []; + if (!Array.isArray(groups)) { + groups = [groups]; + } + + // if the allowed group is not one of the users, deny access + if (!groups.includes(groupAllowed)) { + return api.access.deny('Access denied'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/active-directory-groups-POST_LOGIN/manifest.yaml b/templates/active-directory-groups-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..31bff0d --- /dev/null +++ b/templates/active-directory-groups-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: 'fd8e30ba-02e2-4648-8d89-78bc10425ab4' +name: 'Check if a user belongs to an active directory group.' +description: 'Check if a user belongs to an AD group and if not, deny access.' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/active-directory-groups-POST_LOGIN' +notes: | + **Secrets** + + * `ALLOWED_GROUP` - the name of the allowed group. +useCases: + - 'ACCESS_CONTROL' diff --git a/templates/adaptive-mfa-POST_LOGIN/code.js b/templates/adaptive-mfa-POST_LOGIN/code.js new file mode 100644 index 0000000..73cb330 --- /dev/null +++ b/templates/adaptive-mfa-POST_LOGIN/code.js @@ -0,0 +1,40 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/adaptive-mfa-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // Decide which confidence scores should trigger MFA, for more information refer to + // https://auth0.com/docs/secure/multi-factor-authentication/adaptive-mfa/customize-adaptive-mfa#confidence-scores + const promptConfidences = ['low', 'medium']; + + // Example condition: prompt MFA only based on the NewDevice + // confidence level, this will prompt for MFA when a user is logging in + // from an unknown device. + const confidence = + event.authentication?.riskAssessment?.assessments?.NewDevice + ?.confidence; + const shouldPromptMfa = + confidence && promptConfidences.includes(confidence); + + // It only makes sense to prompt for MFA when the user has at least one + // enrolled MFA factor. + const canPromptMfa = + event.user.multifactor && event.user.multifactor.length > 0; + if (shouldPromptMfa && canPromptMfa) { + api.multifactor.enable('any', { allowRememberBrowser: true }); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/adaptive-mfa-POST_LOGIN/manifest.yaml b/templates/adaptive-mfa-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..5be6ea6 --- /dev/null +++ b/templates/adaptive-mfa-POST_LOGIN/manifest.yaml @@ -0,0 +1,10 @@ +id: 'd2a3e1d7-e2ef-4681-8192-cdcac078b7df' +name: 'Trigger multi-factor authentication when a condition is met' +public: true +description: 'A POST_LOGIN action to trigger multifactor authentication based on risk assessment and if the user is enrolled in at least one factor.' +triggers: ['POST_LOGIN'] +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/adaptive-mfa-POST_LOGIN' +useCases: + - 'MULTIFACTOR' diff --git a/templates/add-attribute-POST_LOGIN/code.js b/templates/add-attribute-POST_LOGIN/code.js new file mode 100644 index 0000000..5e3fd17 --- /dev/null +++ b/templates/add-attribute-POST_LOGIN/code.js @@ -0,0 +1,34 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/add-attribute-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // ensure the connection secret is valid + if (!event.secrets.CONNECTION_NAME) { + return api.access.deny('Invalid configuration'); + } + + // ensure the claim name secret is valid + if (!event.secrets.CLAIM_NAME) { + return api.access.deny('Invalid configuration'); + } + + // add an additional claim conditionally + if (event.connection.name === event.secrets.CONNECTION_NAME) { + api.idToken.setCustomClaim(event.secrets.CLAIM_NAME, true); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/add-attribute-POST_LOGIN/manifest.yaml b/templates/add-attribute-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..f0ded71 --- /dev/null +++ b/templates/add-attribute-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: '06975b69-6433-4918-98a1-3779f084b21d' +name: 'Add an attribute to the user' +description: 'Add an attribute to the user only for the login transaction. This is useful for cases where you want to enrich the user information for a specific application.' +public: true +triggers: ['POST_LOGIN'] +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/add-attribute-POST_LOGIN' +useCases: + - 'ENRICH_PROFILE' +notes: | + **Secrets** + + * `CONNECTION_NAME` - the name of the connection from which users will have a custom claim added. For example, `Username-Password-Authentication`. + * `CLAIM_NAME` - the name of the custom claim to be added. diff --git a/templates/add-country-POST_LOGIN/code.js b/templates/add-country-POST_LOGIN/code.js new file mode 100644 index 0000000..bc49541 --- /dev/null +++ b/templates/add-country-POST_LOGIN/code.js @@ -0,0 +1,34 @@ +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/add-country-POST_LOGIN --- +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + let namespace = event.secrets.ID_TOKEN_NAMESPACE || ''; + if (namespace && !namespace.endsWith('/')) { + namespace += '/'; + } + + if (event.request.geoip) { + api.idToken.setCustomClaim( + namespace + 'country', + event.request.geoip.countryName + ); + api.idToken.setCustomClaim( + namespace + 'timezone', + event.request.geoip.timeZone + ); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/add-country-POST_LOGIN/manifest.yaml b/templates/add-country-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..2eafa60 --- /dev/null +++ b/templates/add-country-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: 'c8d3367c-dcdb-41e1-a303-4063d2a1d124' +name: 'Add country to User Profile' +description: "This action template adds a `country` attribute to the user's id token based on their ip address." +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/add-country-POST_LOGIN' +notes: | + **Optional Secrets** + + * `ID_TOKEN_NAMESPACE` - An optional namespace for the custom claim + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/add-email-to-access-token-POST_LOGIN/code.js b/templates/add-email-to-access-token-POST_LOGIN/code.js new file mode 100644 index 0000000..0218103 --- /dev/null +++ b/templates/add-email-to-access-token-POST_LOGIN/code.js @@ -0,0 +1,27 @@ +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/add-email-to-access-token-POST_LOGIN --- +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // This action adds the authenticated user's email address to the access token. + + let namespace = event.secrets.NAMESPACE || ''; + if (namespace && !namespace.endsWith('/')) { + namespace += '/'; + } + + api.accessToken.setCustomClaim(namespace + 'email', event.user.email); +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/add-email-to-access-token-POST_LOGIN/manifest.yaml b/templates/add-email-to-access-token-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..28503e5 --- /dev/null +++ b/templates/add-email-to-access-token-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: 'f246bbc7-6676-4a30-9706-bd328e7f9ac4' +name: 'Add Email to Access Token' +description: 'Add the users email as one of the fields in the access token' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/add-email-to-access-token-POST_LOGIN' +notes: | + **Optional Secrets** + + * `NAMESPACE` - optional namespace for the access token field, for example `https://acme-inc.com` would result in an access token that looks like: `{ ... "https://acme-inc.com/email": "user@acme-email.com" ... }` + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE/code.js b/templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE/code.js new file mode 100644 index 0000000..29bfead --- /dev/null +++ b/templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE/code.js @@ -0,0 +1,75 @@ +const { ManagementClient } = require('auth0'); + +const HTTP_TIMEOUT = 1000; // 1s + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE --- +/** + * Handler that will be called during the execution of a Password Reset / Post Challenge Flow. + * + * @param {Event} event - Details about the post challenge request. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +exports.onExecutePostChallenge = async (event, api) => { + if (!event.secrets.METADATA_KEY) { + return api.access.deny('missing metadata key'); + } + + const metadataKey = event.secrets.METADATA_KEY; + + if (!event.secrets.METADATA_DEFAULT_VALUE) { + return api.access.deny('missing metadata default value'); + } + + const metadataValue = event.user.user_metadata[metadataKey]; + + // quit early if metadata is already set + if (metadataValue) { + return; + } + + const metadataDefaultValue = event.secrets.METADATA_DEFAULT_VALUE; + + if (!event.secrets.CLIENT_ID) { + return api.access.deny('missing client id'); + } + + const clientId = event.secrets.CLIENT_ID; + + if (!event.secrets.CLIENT_SECRET) { + return api.access.deny('missing client secret'); + } + + const clientSecret = event.secrets.CLIENT_SECRET; + + if (!event.secrets.TENANT_DOMAIN) { + return api.access.deny('missing tenant domain'); + } + + const domain = event.secrets.TENANT_DOMAIN; + + const management = new ManagementClient({ + domain, + clientId, + clientSecret, + httpTimeout: HTTP_TIMEOUT, + }); + + await management.users.update( + { id: event.user.user_id }, + { + user_metadata: { + [metadataKey]: metadataValue || metadataDefaultValue, + }, + } + ); +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostChallenge function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +// exports.onContinuePostChallenge = async (event, api) => { +// }; diff --git a/templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml b/templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml new file mode 100644 index 0000000..ce30acd --- /dev/null +++ b/templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml @@ -0,0 +1,22 @@ +id: 'a66b279a-067c-414b-93e8-c35698f8f379' +name: 'Add Persistent Attributes to the User' +description: 'Set any preference value to a user (using `user_metadata`).' +public: true +triggers: + - 'PASSWORD_RESET_POST_CHALLENGE' +runtime: 'node18' +modules: + - name: 'auth0' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-PASSWORD_RESET_POST_CHALLENGE' +notes: | + **Required Secrets** + + * `METADATA_KEY` - key to be used to store the user preference, for example: 'favorite_color' + * `METADATA_DEFAULT_VALUE` - value to be stored for the user preference in the event that it does not already exist, for example: 'blue' + * `CLIENT_ID` - client id for an application that is permitted to update your users + * `CLIENT_SECRET` - corresponding client secret for `CLIENT_ID`, you'll find this in the settings alongside `CLIENT_ID` + * `TENANT_DOMAIN` - corresponding tenant domain for `CLIENT_ID`, you'll find this in the settings alongside `CLIENT_ID` + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/add-persistence-attribute-POST_CHANGE_PASSWORD/code.js b/templates/add-persistence-attribute-POST_CHANGE_PASSWORD/code.js new file mode 100644 index 0000000..9482fbb --- /dev/null +++ b/templates/add-persistence-attribute-POST_CHANGE_PASSWORD/code.js @@ -0,0 +1,70 @@ +const { ManagementClient } = require('auth0'); + +const HTTP_TIMEOUT = 1000; // 1s + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-POST_CHANGE_PASSWORD --- +/** + * Handler that will be called during the execution of a PostChangePassword flow. + * + * @param {Event} event - Details about the user and the context in which the change password is happening. + * @param {PostChangePasswordAPI} api - Methods and utilities to help change the behavior after a user changes their password. + */ +exports.onExecutePostChangePassword = async (event, api) => { + if (!event.secrets.METADATA_KEY) { + console.log('missing event.secrets.METADATA_KEY'); + return; + } + + const metadataKey = event.secrets.METADATA_KEY; + + if (!event.secrets.METADATA_DEFAULT_VALUE) { + console.log('missing event.secrets.METADATA_DEFAULT_VALUE'); + return; + } + + const metadataValue = event.user.user_metadata[metadataKey]; + + // quit early if metadata is already set + if (metadataValue) { + return; + } + + const metadataDefaultValue = event.secrets.METADATA_DEFAULT_VALUE; + + if (!event.secrets.CLIENT_ID) { + console.log('missing event.secrets.CLIENT_ID'); + return; + } + + const clientId = event.secrets.CLIENT_ID; + + if (!event.secrets.CLIENT_SECRET) { + console.log('missing event.secrets.CLIENT_SECRET'); + return; + } + + const clientSecret = event.secrets.CLIENT_SECRET; + + if (!event.secrets.TENANT_DOMAIN) { + console.log('missing event.secrets.TENANT_DOMAIN'); + return; + } + + const domain = event.secrets.TENANT_DOMAIN; + + const management = new ManagementClient({ + domain, + clientId, + clientSecret, + httpTimeout: HTTP_TIMEOUT, + }); + + await management.users.update( + { id: event.user.user_id }, + { + user_metadata: { + [metadataKey]: metadataValue || metadataDefaultValue, + }, + } + ); +}; diff --git a/templates/add-persistence-attribute-POST_CHANGE_PASSWORD/manifest.yaml b/templates/add-persistence-attribute-POST_CHANGE_PASSWORD/manifest.yaml new file mode 100644 index 0000000..cf70454 --- /dev/null +++ b/templates/add-persistence-attribute-POST_CHANGE_PASSWORD/manifest.yaml @@ -0,0 +1,22 @@ +id: '7ebea5e9-cb1b-49fe-81b7-fba87e62e9bc' +name: 'Add Persistent Attributes to the User' +description: 'Set any preference value to a user (using `user_metadata`).' +public: true +triggers: + - 'POST_CHANGE_PASSWORD' +runtime: 'node18' +modules: + - name: 'auth0' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-POST_CHANGE_PASSWORD' +notes: | + **Required Secrets** + + * `METADATA_KEY` - key to be used to store the user preference, for example: 'favorite_color' + * `METADATA_DEFAULT_VALUE` - value to be stored for the user preference in the event that it does not already exist, for example: 'blue' + * `CLIENT_ID` - client id for an application that is permitted to update your users + * `CLIENT_SECRET` - corresponding client secret for `CLIENT_ID`, you'll find this in the settings alongside `CLIENT_ID` + * `TENANT_DOMAIN` - corresponding tenant domain for `CLIENT_ID`, you'll find this in the settings alongside `CLIENT_ID` + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/add-persistence-attribute-POST_LOGIN/code.js b/templates/add-persistence-attribute-POST_LOGIN/code.js new file mode 100644 index 0000000..265636d --- /dev/null +++ b/templates/add-persistence-attribute-POST_LOGIN/code.js @@ -0,0 +1,52 @@ +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-POST_LOGIN --- +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + if (!event.secrets.METADATA_KEY) { + return api.access.deny('missing metadata key'); + } + + const metadataKey = event.secrets.METADATA_KEY; + + if (!event.secrets.METADATA_DEFAULT_VALUE) { + return api.access.deny('missing metadata default value'); + } + + const metadataValue = event.user.user_metadata[metadataKey]; + + // quit early if metadata is already set + if (metadataValue) { + return; + } + + const metadataDefaultValue = event.secrets.METADATA_DEFAULT_VALUE; + + api.user.setUserMetadata( + metadataKey, + metadataValue || metadataDefaultValue + ); + + let namespace = event.secrets.ID_TOKEN_NAMESPACE || ''; + if (namespace && !namespace.endsWith('/')) { + namespace += '/'; + } + + api.idToken.setCustomClaim( + namespace + metadataKey, + metadataValue || metadataDefaultValue + ); +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/add-persistence-attribute-POST_LOGIN/manifest.yaml b/templates/add-persistence-attribute-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..4d342f7 --- /dev/null +++ b/templates/add-persistence-attribute-POST_LOGIN/manifest.yaml @@ -0,0 +1,20 @@ +id: 'e590705c-1e59-4746-acab-8b0bdb329b4f' +name: 'Add Persistent Attributes to the User' +description: 'Set any preference value to a user (using `user_metadata`).' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-POST_LOGIN' +notes: | + **Required Secrets** + + * `METADATA_KEY` - key to be used to store the user preference, for example: 'favorite_color' + * `METADATA_DEFAULT_VALUE` - value to be stored for the user preference in the event that it does not already exist, for example: 'blue' + + **Optional Secrets** + + * `ID_TOKEN_NAMESPACE` - An optional namespace to set the preference via a custom claim + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/add-persistence-attribute-POST_USER_REGISTRATION/code.js b/templates/add-persistence-attribute-POST_USER_REGISTRATION/code.js new file mode 100644 index 0000000..eea9ad1 --- /dev/null +++ b/templates/add-persistence-attribute-POST_USER_REGISTRATION/code.js @@ -0,0 +1,70 @@ +const { ManagementClient } = require('auth0'); + +const HTTP_TIMEOUT = 1000; // 1s + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-POST_USER_REGISTRATION --- +/** + * Handler that will be called during the execution of a PostUserRegistration flow. + * + * @param {Event} event - Details about the context and user that has registered. + * @param {PostUserRegistrationAPI} api - Methods and utilities to help change the behavior after a signup. + */ +exports.onExecutePostUserRegistration = async (event, api) => { + if (!event.secrets.METADATA_KEY) { + console.log('missing event.secrets.METADATA_KEY'); + return; + } + + const metadataKey = event.secrets.METADATA_KEY; + + if (!event.secrets.METADATA_DEFAULT_VALUE) { + console.log('missing event.secrets.METADATA_DEFAULT_VALUE'); + return; + } + + const metadataValue = event.user.user_metadata[metadataKey]; + + // quit early if metadata is already set + if (metadataValue) { + return; + } + + const metadataDefaultValue = event.secrets.METADATA_DEFAULT_VALUE; + + if (!event.secrets.CLIENT_ID) { + console.log('missing event.secrets.CLIENT_ID'); + return; + } + + const clientId = event.secrets.CLIENT_ID; + + if (!event.secrets.CLIENT_SECRET) { + console.log('missing event.secrets.CLIENT_SECRET'); + return; + } + + const clientSecret = event.secrets.CLIENT_SECRET; + + if (!event.secrets.TENANT_DOMAIN) { + console.log('missing event.secrets.TENANT_DOMAIN'); + return; + } + + const domain = event.secrets.TENANT_DOMAIN; + + const management = new ManagementClient({ + domain, + clientId, + clientSecret, + httpTimeout: HTTP_TIMEOUT, + }); + + await management.users.update( + { id: event.user.user_id }, + { + user_metadata: { + [metadataKey]: metadataValue || metadataDefaultValue, + }, + } + ); +}; diff --git a/templates/add-persistence-attribute-POST_USER_REGISTRATION/manifest.yaml b/templates/add-persistence-attribute-POST_USER_REGISTRATION/manifest.yaml new file mode 100644 index 0000000..c25a6a4 --- /dev/null +++ b/templates/add-persistence-attribute-POST_USER_REGISTRATION/manifest.yaml @@ -0,0 +1,22 @@ +id: 'da09b247-9dee-44b6-8fa4-c0adc4a8ad9b' +name: 'Add Persistent Attributes to the User' +description: 'Set any preference value to a user (using `user_metadata`).' +public: true +triggers: + - 'POST_USER_REGISTRATION' +runtime: 'node18' +modules: + - name: 'auth0' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-POST_USER_REGISTRATION' +notes: | + **Required Secrets** + + * `METADATA_KEY` - key to be used to store the user preference, for example: 'favorite_color' + * `METADATA_DEFAULT_VALUE` - value to be stored for the user preference in the event that it does not already exist, for example: 'blue' + * `CLIENT_ID` - client id for an application that is permitted to update your users + * `CLIENT_SECRET` - corresponding client secret for `CLIENT_ID`, you'll find this in the settings alongside `CLIENT_ID` + * `TENANT_DOMAIN` - corresponding tenant domain for `CLIENT_ID`, you'll find this in the settings alongside `CLIENT_ID` + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/add-persistence-attribute-SEND_PHONE_MESSAGE/code.js b/templates/add-persistence-attribute-SEND_PHONE_MESSAGE/code.js new file mode 100644 index 0000000..27eebdf --- /dev/null +++ b/templates/add-persistence-attribute-SEND_PHONE_MESSAGE/code.js @@ -0,0 +1,70 @@ +const { ManagementClient } = require('auth0'); + +const HTTP_TIMEOUT = 1000; // 1s + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-SEND_PHONE_MESSAGE --- +/** + * Handler that will be called during the execution of a SendPhoneMessage flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {SendPhoneMessageAPI} api - Methods and utilities to help change the behavior of sending a phone message. + */ +exports.onExecuteSendPhoneMessage = async (event, api) => { + if (!event.secrets.METADATA_KEY) { + console.log('missing event.secrets.METADATA_KEY'); + return; + } + + const metadataKey = event.secrets.METADATA_KEY; + + if (!event.secrets.METADATA_DEFAULT_VALUE) { + console.log('missing event.secrets.METADATA_DEFAULT_VALUE'); + return; + } + + const metadataValue = event.user.user_metadata[metadataKey]; + + // quit early if metadata is already set + if (metadataValue) { + return; + } + + const metadataDefaultValue = event.secrets.METADATA_DEFAULT_VALUE; + + if (!event.secrets.CLIENT_ID) { + console.log('missing event.secrets.CLIENT_ID'); + return; + } + + const clientId = event.secrets.CLIENT_ID; + + if (!event.secrets.CLIENT_SECRET) { + console.log('missing event.secrets.CLIENT_SECRET'); + return; + } + + const clientSecret = event.secrets.CLIENT_SECRET; + + if (!event.secrets.TENANT_DOMAIN) { + console.log('missing event.secrets.TENANT_DOMAIN'); + return; + } + + const domain = event.secrets.TENANT_DOMAIN; + + const management = new ManagementClient({ + domain, + clientId, + clientSecret, + httpTimeout: HTTP_TIMEOUT, + }); + + await management.users.update( + { id: event.user.user_id }, + { + user_metadata: { + [metadataKey]: metadataValue || metadataDefaultValue, + }, + } + ); +}; diff --git a/templates/add-persistence-attribute-SEND_PHONE_MESSAGE/manifest.yaml b/templates/add-persistence-attribute-SEND_PHONE_MESSAGE/manifest.yaml new file mode 100644 index 0000000..5733215 --- /dev/null +++ b/templates/add-persistence-attribute-SEND_PHONE_MESSAGE/manifest.yaml @@ -0,0 +1,21 @@ +id: '5396a2bb-6b32-41d4-b8e1-450f9b3c2f4c' +name: 'Add Persistent Attributes to the User' +description: 'Set any preference value to a user (using `user_metadata`).' +triggers: + - 'SEND_PHONE_MESSAGE' +runtime: 'node18' +modules: + - name: 'auth0' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/add-persistence-attribute-SEND_PHONE_MESSAGE' +notes: | + **Required Secrets** + + * `METADATA_KEY` - key to be used to store the user preference, for example: 'favorite_color' + * `METADATA_DEFAULT_VALUE` - value to be stored for the user preference in the event that it does not already exist, for example: 'blue' + * `CLIENT_ID` - client id for an application that is permitted to update your users + * `CLIENT_SECRET` - corresponding client secret for `CLIENT_ID`, you'll find this in the settings alongside `CLIENT_ID` + * `TENANT_DOMAIN` - corresponding tenant domain for `CLIENT_ID`, you'll find this in the settings alongside `CLIENT_ID` + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/check-last-password-reset-POST_LOGIN/code.js b/templates/check-last-password-reset-POST_LOGIN/code.js new file mode 100644 index 0000000..6bc99db --- /dev/null +++ b/templates/check-last-password-reset-POST_LOGIN/code.js @@ -0,0 +1,47 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/check-last-password-reset-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // ensure the secret is valid + if (!event.secrets.MAX_PASSWORD_DAYS) { + return api.access.deny('Invalid configuration'); + } + + // function to calculate the difference (in days) between two dates + const daydiff = (first, second) => (second - first) / (1000 * 60 * 60 * 24); + + // capture the teimstamp of the last password change or account creation + const lastPasswordChange = + event.user.last_password_reset || event.user.created_at; + + // ensure password rotation is configured correctly + let maxDays; + try { + maxDays = Number(event.secrets.MAX_PASSWORD_DAYS); + } catch { + return api.access.deny('Invalid configuration'); + } + if (!maxDays) { + return api.access.deny('Invalid configuration'); + } + + // if the password is beyond the configured threshold, reject access with a message to change it + if (daydiff(new Date(lastPasswordChange), new Date()) > maxDays) { + return api.access.deny('please change your password'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/check-last-password-reset-POST_LOGIN/manifest.yaml b/templates/check-last-password-reset-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..da9e9ac --- /dev/null +++ b/templates/check-last-password-reset-POST_LOGIN/manifest.yaml @@ -0,0 +1,13 @@ +id: '5b683a11-c2a2-48cf-b64a-f953a6e2b16e' +name: 'Check Last Password Reset' +description: 'Check how long it has been since the users password has been reset and deny access if it is beyond a configured threshold.' +public: true +triggers: ['POST_LOGIN'] +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/check-last-password-reset-POST_LOGIN' +notes: | + **Secrets** + + * `MAX_PASSWORD_DAYS` - maximum age of a user password in days. +useCases: ['ACCESS_CONTROL'] diff --git a/templates/creates-lead-salesforce-POST_LOGIN/code.js b/templates/creates-lead-salesforce-POST_LOGIN/code.js new file mode 100644 index 0000000..437828e --- /dev/null +++ b/templates/creates-lead-salesforce-POST_LOGIN/code.js @@ -0,0 +1,113 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/creates-lead-salesforce-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // if a lead has already been recorded then end the action successfully + if (event.user.app_metadata.recordedAsLead) { + return; + } + //Populate the variables below with appropriate values, failing if any secrets are missing + const sfDomain = event.secrets.SALESFORCE_DOMAIN; + if (!sfDomain) { + console.log(`Unable to create lead: Salesforce domain not configured`); + return; + } + const sfClientId = event.secrets.SALESFORCE_CLIENT_ID; + if (!sfClientId) { + console.log( + `Unable to create lead: Salesforce client id not configured` + ); + return; + } + const sfClientSecret = event.secrets.SALESFORCE_CLIENT_SECRET; + if (!sfClientSecret) { + console.log( + `Unable to create lead: Salesforce client secret not configured` + ); + return; + } + const sfCompany = event.secrets.SALESFORCE_COMPANY; + if (!sfCompany) { + console.log(`Unable to create lead: Salesforce company not configured`); + return; + } + + // fetch the token from the cache or regenreate it if it cannot be retrieved, see + // https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_endpoints.htm + const fetchAccessToken = async () => { + const cachedToken = api.cache.get('sf_access_token'); + if (cachedToken) { + return cachedToken.value; + } + const sfLogin = `https://${sfDomain}/services/oauth2/token`; + const body = new FormData(); + body.set('grant_type', 'client_credentials'); + body.set('client_id', sfClientId); + body.set('client_secret', sfClientSecret); + // force hourly refresh on a per-host basis + const expiry = Date.now() + 3600000; + const response = await fetch(sfLogin, { + method: 'POST', + body: body, + }); + if (!response.ok) { + throw new Error('Unable to fetch token'); + } + const data = await response.json(); + api.cache.set('sf_access_token', data.access_token, { + expires_at: expiry, + }); + return data.access_token; + }; + + //See http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_lead.htm + const createLead = async (access_token) => { + // see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_lead.htm + const body = { + LastName: event.user.name || event.user.email, + Company: sfCompany, + Email: event.user.email, + }; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token}`, + }; + const sfLead = `https://${sfDomain}/services/data/v59.0/sobjects/Lead`; + const response = await fetch(sfLead, { + method: 'POST', + body: JSON.stringify(body), + headers: headers, + }); + if (!response.ok) { + throw new Error('Unable to create lead'); + } + }; + + try { + const token = await fetchAccessToken(); + await createLead(token); + + // if no errors were raised, the lead was created + api.user.setAppMetadata('recordedAsLead', true); + } catch (error) { + // fail gracefully *withough* recording that lead generation has + // completed. Failing to handle these errors here would result + // in a failedl login flow, which we do not want. + return; + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/creates-lead-salesforce-POST_LOGIN/manifest.yaml b/templates/creates-lead-salesforce-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..51977c7 --- /dev/null +++ b/templates/creates-lead-salesforce-POST_LOGIN/manifest.yaml @@ -0,0 +1,26 @@ +id: 'b21e12b8-c733-442d-8180-c38565232619' +name: 'Call Salesforce API to record the contact as a new lead' +description: 'Call Salesforce API to record the contact as a new Lead. It is using Salesforce REST APIs and the `resource owner` flow to obtain an `access_token`.' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/creates-lead-salesforce-POST_LOGIN' +notes: | + **Secrets** + + * `SALESFORCE_DOMAIN` - your salesforce domain, eg: `EXAMPLE.my.salesforce.com` + * `SALESFORCE_COMPANY` - the name of your company + * `SALESFORCE_CLIENT_ID` - the Salesforce client id + * `SALESFORCE_CLIENT_SECRET` - the Salesforce client secret + + **Notes** + + This should *not* be used in combination with creates-lead-salesforce-POST_USER_REGISTRATION, + as it would result in multiple leads for the same user. Choose one trigger. + + + You will need to create a connected application in your salesforce instance + and enable client credentials. +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/creates-lead-salesforce-POST_USER_REGISTRATION/code.js b/templates/creates-lead-salesforce-POST_USER_REGISTRATION/code.js new file mode 100644 index 0000000..b6bebe9 --- /dev/null +++ b/templates/creates-lead-salesforce-POST_USER_REGISTRATION/code.js @@ -0,0 +1,97 @@ +/** + * Handler that will be called during the execution of a PostUserRegistration flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/creates-lead-salesforce-POST_USER_REGISTRATION --- + * + * @param {Event} event - Details about the context and user that has registered. + * @param {PostUserRegistrationAPI} api - Methods and utilities to help change the behavior after a signup. + */ +exports.onExecutePostUserRegistration = async (event, api) => { + //Populate the variables below with appropriate values, failing if any secrets are missing + const sfDomain = event.secrets.SALESFORCE_DOMAIN; + if (!sfDomain) { + console.log(`Unable to create lead: Salesforce domain not configured`); + return; + } + const sfClientId = event.secrets.SALESFORCE_CLIENT_ID; + if (!sfClientId) { + console.log( + `Unable to create lead: Salesforce client id not configured` + ); + return; + } + const sfClientSecret = event.secrets.SALESFORCE_CLIENT_SECRET; + if (!sfClientSecret) { + console.log( + `Unable to create lead: Salesforce client secret not configured` + ); + return; + } + const sfCompany = event.secrets.SALESFORCE_COMPANY; + if (!sfCompany) { + console.log(`Unable to create lead: Salesforce company not configured`); + return; + } + + // fetch the token from the cache or regenreate it if it cannot be retrieved, see + // https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_endpoints.htm + const fetchAccessToken = async () => { + const cachedToken = api.cache.get('sf_access_token'); + if (cachedToken) { + return cachedToken.value; + } + const sfLogin = `https://${sfDomain}/services/oauth2/token`; + const body = new FormData(); + body.set('grant_type', 'client_credentials'); + body.set('client_id', sfClientId); + body.set('client_secret', sfClientSecret); + // force hourly refresh on a per-host basis + const expiry = Date.now() + 3600000; + const response = await fetch(sfLogin, { + method: 'POST', + body: body, + }); + if (!response.ok) { + throw new Error('Unable to fetch token'); + } + const data = await response.json(); + api.cache.set('sf_access_token', data.access_token, { + expires_at: expiry, + }); + return data.access_token; + }; + + //See http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_lead.htm + const createLead = async (access_token) => { + // see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_lead.htm + const body = { + LastName: event.user.name || event.user.email, + Company: sfCompany, + Email: event.user.email, + }; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token}`, + }; + const sfLead = `https://${sfDomain}/services/data/v59.0/sobjects/Lead`; + const response = await fetch(sfLead, { + method: 'POST', + body: JSON.stringify(body), + headers: headers, + }); + if (!response.ok) { + throw new Error('Unable to create lead'); + } + }; + + try { + const token = await fetchAccessToken(); + await createLead(token); + } catch (error) { + // an error here indicates the lead generation failure. If you do not + // have an external system to record the faiulre to, use the POST_LOGIN + // version of this action template instead, which will attempt lead + // generation on every login until the user has a lead generated + return; + } +}; diff --git a/templates/creates-lead-salesforce-POST_USER_REGISTRATION/manifest.yaml b/templates/creates-lead-salesforce-POST_USER_REGISTRATION/manifest.yaml new file mode 100644 index 0000000..dc623f5 --- /dev/null +++ b/templates/creates-lead-salesforce-POST_USER_REGISTRATION/manifest.yaml @@ -0,0 +1,26 @@ +id: '362b841a-eec1-43be-9a01-78a2a7c31c5a' +name: 'Creates a new Lead in Salesforce after user registration' +description: 'Call Salesforce API to record the contact as a new Lead. It is using Salesforce REST APIs and the `client credentials` flow to obtain an `access_token`.' +public: true +triggers: + - 'POST_USER_REGISTRATION' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/creates-lead-salesforce-POST_USER_REGISTRATION' +notes: | + **Secrets** + + * `SALESFORCE_DOMAIN` - your salesforce domain, eg: `EXAMPLE.my.salesforce.com` + * `SALESFORCE_COMPANY` - the name of your company + * `SALESFORCE_CLIENT_ID` - the Salesforce client id + * `SALESFORCE_CLIENT_SECRET` - the Salesforce client secret + + **Notes** + + This should *not* be used in combination with creates-lead-salesforce-POST_LOGIN, + as it would result in multiple leads for the same user. Choose one trigger. + + You will need to create a connected application in your salesforce instance + and enable client credentials. + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/email-verified-PASSWORD_RESET_POST_CHALLENGE/code.js b/templates/email-verified-PASSWORD_RESET_POST_CHALLENGE/code.js new file mode 100644 index 0000000..254808f --- /dev/null +++ b/templates/email-verified-PASSWORD_RESET_POST_CHALLENGE/code.js @@ -0,0 +1,25 @@ +/** + * Handler that will be called during the execution of a Password Reset / Post Challenge Flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/email-verified-PASSWORD_RESET_POST_CHALLENGE --- + * + * @param {Event} event - Details about the post challenge request. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +exports.onExecutePostChallenge = async (event, api) => { + if (!event.user.email_verified) { + api.access.deny( + 'Please verify your email before changing your password.' + ); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostChallenge function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +// exports.onContinuePostChallenge = async (event, api) => { +// }; diff --git a/templates/email-verified-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml b/templates/email-verified-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml new file mode 100644 index 0000000..2b9a4a5 --- /dev/null +++ b/templates/email-verified-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml @@ -0,0 +1,9 @@ +id: '2e396d57-5c71-4d2d-a0e4-aa14ba1e8e7b' +name: 'Confirm Email is Verified' +description: 'Confirm the user email is verified before allowing the password reset to complete' +public: true +triggers: ['PASSWORD_RESET_POST_CHALLENGE'] +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/email-verified-PASSWORD_RESET_POST_CHALLENGE' +useCases: ['ACCESS_CONTROL'] diff --git a/templates/email-verified-POST_LOGIN/code.js b/templates/email-verified-POST_LOGIN/code.js new file mode 100644 index 0000000..68982a5 --- /dev/null +++ b/templates/email-verified-POST_LOGIN/code.js @@ -0,0 +1,23 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/email-verified-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + if (!event.user.email_verified) { + api.access.deny('Please verify your email before logging in.'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/email-verified-POST_LOGIN/manifest.yaml b/templates/email-verified-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..4469a5d --- /dev/null +++ b/templates/email-verified-POST_LOGIN/manifest.yaml @@ -0,0 +1,9 @@ +id: '33f740c8-9a8e-4886-be63-83afb6ef40cc' +name: 'Confirm Email is Verified' +description: 'Confirm user email is verified before completing login' +public: true +triggers: ['POST_LOGIN'] +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/email-verified-POST_LOGIN' +useCases: ['ACCESS_CONTROL'] diff --git a/templates/guardian-multifactor-authorization-extension-POST_LOGIN/code.js b/templates/guardian-multifactor-authorization-extension-POST_LOGIN/code.js new file mode 100644 index 0000000..f73f687 --- /dev/null +++ b/templates/guardian-multifactor-authorization-extension-POST_LOGIN/code.js @@ -0,0 +1,43 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/guardian-multifactor-authorization-extension-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // ensure the user has verified their email + if (!event.user.email_verified) { + return api.access.deny('Email not verified'); + } + + // ensure the MFA roles are configured + const mfaRoles = event.secrets.MFA_ROLES?.split(',') + .map((role) => role.trim().toLowerCase()) + .filter((role) => role); + if (!mfaRoles) { + return api.access.deny('Invalid configuration'); + } + + // get the list of user roles that require MFA + const userMFARoles = event.authorization?.roles.filter((role) => + mfaRoles.includes(role) + ); + + // if there are roles that require MFA then enable guardian MFA + if (userMFARoles && userMFARoles.length) { + // set allowRememberBrowser to force MFA every time + api.multifactor.enable('guardian', { allowRememberBrowser: true }); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/guardian-multifactor-authorization-extension-POST_LOGIN/manifest.yaml b/templates/guardian-multifactor-authorization-extension-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..1f161d7 --- /dev/null +++ b/templates/guardian-multifactor-authorization-extension-POST_LOGIN/manifest.yaml @@ -0,0 +1,23 @@ +id: '6da6fb1f-5215-4160-bbc6-73cb067a1de3' +name: 'Trigger multifactor authentication based on role membership' +description: 'Trigger multifactor authentication using Auth0 Guardian based on the users role membership' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/guardian-multifactor-authorization-extension-POST_LOGIN' +notes: | + **Secrets** + + `MFA_ROLES` - comma-delimited list of roles that require multi-factor authentication + + **Notes** + + * For more information on Auth0 Guardian, please refer to https://auth0.com/docs/secure/multi-factor-authentication/auth0-guardian + * To enable Auth0 Guardian push notification, refer to https://auth0.com/docs/secure/multi-factor-authentication/auth0-guardian/guardian-for-android-sdk#enable-guardian-push-notifications + * MFA prompt frequency is discussed further at https://auth0.com/docs/secure/multi-factor-authentication/customize-mfa#change-frequency-of-mfa-prompts + * For more information on Role Based Access Control, refer to https://auth0.com/docs/manage-users/access-control/rbac + +useCases: + - 'MULTIFACTOR' diff --git a/templates/guardian-multifactor-ip-range-POST_LOGIN/code.js b/templates/guardian-multifactor-ip-range-POST_LOGIN/code.js new file mode 100644 index 0000000..67461aa --- /dev/null +++ b/templates/guardian-multifactor-ip-range-POST_LOGIN/code.js @@ -0,0 +1,48 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/guardian-multifactor-ip-range-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + const ipaddr = require('ipaddr.js'); + + // get the trusted CIDR and ensure it is valid + const corp_network = event.secrets.TRUSTED_CIDR; + if (!corp_network) { + return api.access.deny('Invalid configuration'); + } + + // parse the request IP from and ensure it is valid + let current_ip; + try { + current_ip = ipaddr.parse(event.request.ip); + } catch (error) { + return api.access.deny('Invalid request'); + } + + // parse the CIDR and ensure validity + let cidr; + try { + cidr = ipaddr.parseCIDR(corp_network); + } catch (error) { + return api.access.deny('Invalid configuration'); + } + + // enforce guardian MFA if the IP is not in the trusted allocation + if (!current_ip.match(cidr)) { + api.multifactor.enable('guardian', { allowRememberBrowser: false }); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/guardian-multifactor-ip-range-POST_LOGIN/manifest.yaml b/templates/guardian-multifactor-ip-range-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..eee8c41 --- /dev/null +++ b/templates/guardian-multifactor-ip-range-POST_LOGIN/manifest.yaml @@ -0,0 +1,17 @@ +id: 'cd0916fa-e496-4b7c-ba1f-4b9cd181b76b' +name: 'Trigger MFA when the requesting IP is from outside a specific IP range' +description: 'Trigger MFA when the requesting IP is from outside a specific IP range' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +modules: + - name: 'ipaddr.js' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/guardian-multifactor-ip-range-POST_LOGIN' +useCases: + - 'MULTIFACTOR' +notes: | + **Secrets** + + * `TRUSTED_CIDR` - The trusted CIDR allocation, for example: `192.168.0.0/16`. diff --git a/templates/guardian-multifactor-stepup-authentication-POST_LOGIN/code.js b/templates/guardian-multifactor-stepup-authentication-POST_LOGIN/code.js new file mode 100644 index 0000000..460e89c --- /dev/null +++ b/templates/guardian-multifactor-stepup-authentication-POST_LOGIN/code.js @@ -0,0 +1,34 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/guardian-multifactor-stepup-authentication-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // confirm if MFA is enabled. For more information on client config, refer to + // https://auth0.com/docs/secure/multi-factor-authentication/step-up-authentication/configure-step-up-authentication-for-web-apps#configure-app + const isMfa = event.transaction?.acr_values.includes( + 'http://schemas.openid.net/pape/policies/2007/06/multi-factor' + ); + + let authMethods = []; + if (event.authentication && Array.isArray(event.authentication.methods)) { + authMethods = event.authentication.methods; + } + + if (isMfa && !authMethods.some((method) => method.name === 'mfa')) { + api.multifactor.enable('any', { allowRememberBrowser: false }); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/guardian-multifactor-stepup-authentication-POST_LOGIN/manifest.yaml b/templates/guardian-multifactor-stepup-authentication-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..d7508d9 --- /dev/null +++ b/templates/guardian-multifactor-stepup-authentication-POST_LOGIN/manifest.yaml @@ -0,0 +1,16 @@ +id: '91ed1e01-689c-4d4d-92a2-b1f789871a6c' +name: 'Challenge for a Second Authentication Factor on Request' +description: 'Challenge for a second authentication factor on request (step up) when acr_values is sent in the request and MFA has not already been completed' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/guardian-multifactor-stepup-authentication-POST_LOGIN' +notes: | + **Notes** + + * Requires acr_values to be set to http://schemas.openid.net/pape/policies/2007/06/multi-factor + * For detailed instructions, see https://auth0.com/docs/secure/multi-factor-authentication/step-up-authentication/configure-step-up-authentication-for-web-apps#configure-app +useCases: + - 'MULTIFACTOR' diff --git a/templates/ip-address-allowlist-CREDENTIALS_EXCHANGE/code.js b/templates/ip-address-allowlist-CREDENTIALS_EXCHANGE/code.js new file mode 100644 index 0000000..913b45a --- /dev/null +++ b/templates/ip-address-allowlist-CREDENTIALS_EXCHANGE/code.js @@ -0,0 +1,23 @@ +/** + * Handler that will be called during the execution of a Client Credentials exchange. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/ip-address-allowlist-CREDENTIALS_EXCHANGE --- + * + * @param {Event} event - Details about client credentials grant request. + * @param {CredentialsExchangeAPI} api - Interface whose methods can be used to change the behavior of client credentials grant. + */ +exports.onExecuteCredentialsExchange = async (event, api) => { + // obtain the list of allowed IPs + const ips = event.secrets.ALLOW_LIST?.split(','); + if (!ips) { + return api.access.deny('server_error', 'Invalid configuration'); + } + + // ensure the request IP is from an allowed IP address + if (!ips.includes(event.request.ip)) { + return api.access.deny( + 'invalid_request', + 'Access denied for this IP address' + ); + } +}; diff --git a/templates/ip-address-allowlist-CREDENTIALS_EXCHANGE/manifest.yaml b/templates/ip-address-allowlist-CREDENTIALS_EXCHANGE/manifest.yaml new file mode 100644 index 0000000..aa2aa15 --- /dev/null +++ b/templates/ip-address-allowlist-CREDENTIALS_EXCHANGE/manifest.yaml @@ -0,0 +1,15 @@ +id: '15907f76-1d60-439d-bdc1-3d0514650cd7' +name: 'Only allow access from a specific IP address' +description: 'Only allow access from a specific IP address' +public: true +triggers: + - 'CREDENTIALS_EXCHANGE' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/ip-address-allowlist-CREDENTIALS_EXCHANGE' +useCases: + - 'ACCESS_CONTROL' +notes: | + **Secrets** + + * `ALLOW_LIST` - comma-delimited list of allowed IP addresses. diff --git a/templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE/code.js b/templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE/code.js new file mode 100644 index 0000000..c61610c --- /dev/null +++ b/templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE/code.js @@ -0,0 +1,30 @@ +/** + * Handler that will be called during the execution of a Password Reset / Post Challenge Flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE --- + * + * @param {Event} event - Details about the post challenge request. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +exports.onExecutePostChallenge = async (event, api) => { + // obtain the list of allowed IPs + const ips = event.secrets.ALLOW_LIST?.split(','); + if (!ips) { + return api.access.deny('Invalid configuration'); + } + + // ensure the request IP is from an allowed IP address + if (!ips.includes(event.request.ip)) { + return api.access.deny('Access denied for this IP address'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostChallenge function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +// exports.onContinuePostChallenge = async (event, api) => { +// }; diff --git a/templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml b/templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml new file mode 100644 index 0000000..225a515 --- /dev/null +++ b/templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml @@ -0,0 +1,15 @@ +id: 'f637671e-bf17-4563-bbcc-3d06850e685b' +name: 'Only allow access from a specific IP address' +description: 'Only allow access from a specific IP address' +public: true +triggers: + - 'PASSWORD_RESET_POST_CHALLENGE' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/ip-address-allowlist-PASSWORD_RESET_POST_CHALLENGE' +useCases: + - 'ACCESS_CONTROL' +notes: | + **Secrets** + + * `ALLOW_LIST` - comma-delimited list of allowed IP addresses. diff --git a/templates/ip-address-allowlist-POST_LOGIN/code.js b/templates/ip-address-allowlist-POST_LOGIN/code.js new file mode 100644 index 0000000..bd7fa0e --- /dev/null +++ b/templates/ip-address-allowlist-POST_LOGIN/code.js @@ -0,0 +1,30 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/ip-address-allowlist-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // obtain the list of allowed IPs + const ips = event.secrets.ALLOW_LIST?.split(','); + if (!ips) { + return api.access.deny('Invalid configuration'); + } + + // ensure the request IP is from an allowed IP address + if (!ips.includes(event.request.ip)) { + return api.access.deny('Access denied for this IP address'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/ip-address-allowlist-POST_LOGIN/manifest.yaml b/templates/ip-address-allowlist-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..6d1cce9 --- /dev/null +++ b/templates/ip-address-allowlist-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: 'f1caa9b5-9122-4fb3-acd8-a2529f247f2f' +name: 'Only allow access from a specific IP address' +description: 'Only allow access from a specific IP address' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/ip-address-allowlist-POST_LOGIN' +useCases: + - 'ACCESS_CONTROL' +notes: | + **Secrets** + + * `ALLOW_LIST` - comma-delimited list of allowed IP addresses. diff --git a/templates/ip-address-allowlist-PRE_USER_REGISTRATION/code.js b/templates/ip-address-allowlist-PRE_USER_REGISTRATION/code.js new file mode 100644 index 0000000..7f10b9f --- /dev/null +++ b/templates/ip-address-allowlist-PRE_USER_REGISTRATION/code.js @@ -0,0 +1,25 @@ +/** + * Handler that will be called during the execution of a PreUserRegistration flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/ip-address-allowlist-PRE_USER_REGISTRATION --- + * + * @param {Event} event - Details about the context and user that is attempting to register. + * @param {PreUserRegistrationAPI} api - Interface whose methods can be used to change the behavior of the signup. + */ +exports.onExecutePreUserRegistration = async (event, api) => { + // obtain the list of allowed IPs + const ips = event.secrets.ALLOW_LIST?.split(','); + if (!ips) { + // special note for this flow: userMessage (the second parameter) is displayed to the user. + return api.access.deny('IP not allowed', 'Invalid configuration'); + } + + // ensure the request IP is from an allowed IP address + if (!ips.includes(event.request.ip)) { + // special note for this flow: userMessage (the second parameter) is displayed to the user. + return api.access.deny( + 'IP not allowed', + 'Access denied for this IP address' + ); + } +}; diff --git a/templates/ip-address-allowlist-PRE_USER_REGISTRATION/manifest.yaml b/templates/ip-address-allowlist-PRE_USER_REGISTRATION/manifest.yaml new file mode 100644 index 0000000..f4c0b33 --- /dev/null +++ b/templates/ip-address-allowlist-PRE_USER_REGISTRATION/manifest.yaml @@ -0,0 +1,14 @@ +id: 'dd3e7967-33b2-4b55-845b-de24ae7989aa' +name: 'Only allow access from a specific IP address' +description: 'Only allow access from a specific IP address' +public: true +triggers: + - 'PRE_USER_REGISTRATION' +runtime: 'node18' +secrets: + - label: 'ALLOW_LIST' + defaultValue: 'ALLOWED,IP,ADDRESSES' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/ip-address-allowlist-PRE_USER_REGISTRATION' +useCases: + - 'ACCESS_CONTROL' diff --git a/templates/mfa-require-enrollment-POST_LOGIN/code.js b/templates/mfa-require-enrollment-POST_LOGIN/code.js new file mode 100644 index 0000000..0f026ba --- /dev/null +++ b/templates/mfa-require-enrollment-POST_LOGIN/code.js @@ -0,0 +1,23 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/mfa-require-enrollment-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + if (!event.user.multifactor?.length) { + api.multifactor.enable('any', { allowRememberBrowser: false }); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/mfa-require-enrollment-POST_LOGIN/manifest.yaml b/templates/mfa-require-enrollment-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..4733d25 --- /dev/null +++ b/templates/mfa-require-enrollment-POST_LOGIN/manifest.yaml @@ -0,0 +1,22 @@ +id: '57f91526-e358-4d08-84e5-c18931948b84' +name: 'Require MFA Enrollment' +description: 'Requires that any user not already enrolled in MFA will be presented with an enrollment prompt on their next login.' +public: true +triggers: ['POST_LOGIN'] +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/mfa-require-enrollment-POST_LOGIN' +notes: | + # Notes + This action requires that any user not already enrolled in MFA will be presented with an + enrollment prompt on their next login. + + This action can be paired with the Adaptive MFA feature that prompts for an MFA challenge only + when the login confidence is low. It can also be combined with another custom action that prompts + for an MFA challenge under a custom condition. + + Use of the Adaptive MFA feature requires an add-on for the Enterprise plan. Please contact sales + with any questions. See our Adaptive MFA documentation at https://auth0.com/docs/mfa/adaptive-mfa + for more information. +useCases: + - 'MULTIFACTOR' diff --git a/templates/querystring-PASSWORD_RESET_POST_CHALLENGE/code.js b/templates/querystring-PASSWORD_RESET_POST_CHALLENGE/code.js new file mode 100644 index 0000000..8cd098c --- /dev/null +++ b/templates/querystring-PASSWORD_RESET_POST_CHALLENGE/code.js @@ -0,0 +1,33 @@ +/** + * Handler that will be called during the execution of a Password Reset / Post Challenge Flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/querystring-PASSWORD_RESET_POST_CHALLENGE --- + * + * @param {Event} event - Details about the post challenge request. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +exports.onExecutePostChallenge = async (event, api) => { + // ensure the stored query key name is valid + if (!event.secrets.QUERY_KEY) { + return api.access.deny('Invalid configuration'); + } + // ensure the stored query key value is valid + if (!event.secrets.QUERY_VALUE) { + return api.access.deny('Invalid configuration'); + } + // this is a generic example of accessing the query string + const queryValue = event.request.query[event.secrets.QUERY_KEY]; + if (queryValue === event.secrets.QUERY_VALUE) { + // this is a specific PASSWORD_RESET_POST_CHALLENGE example of using the query string to trigger setting custom claims + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostChallenge function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +// exports.onContinuePostChallenge = async (event, api) => { +// }; diff --git a/templates/querystring-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml b/templates/querystring-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml new file mode 100644 index 0000000..f51f93c --- /dev/null +++ b/templates/querystring-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml @@ -0,0 +1,15 @@ +id: '0ec391ed-9eb5-4eba-99b6-a054f1aefbfa' +name: 'Show how to check for variables in the querystring' +description: 'Checks if the password reset transaction includes a query variable from the QUERY_KEY secret matching a value set in the QUERY_VALUE secret' +public: true +triggers: ['PASSWORD_RESET_POST_CHALLENGE'] +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/querystring-PASSWORD_RESET_POST_CHALLENGE' +useCases: + - 'ENRICH_PROFILE' +notes: | + **Secrets** + + * `QUERY_KEY` - the query parameter name that will be evaluated to invoke custom code + * `QUERY_VALUE` - the query parameter value that must be matched to invoke custom code diff --git a/templates/querystring-POST_LOGIN/code.js b/templates/querystring-POST_LOGIN/code.js new file mode 100644 index 0000000..7a70026 --- /dev/null +++ b/templates/querystring-POST_LOGIN/code.js @@ -0,0 +1,47 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/querystring-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // ensure the query key is valid + if (!event.secrets.QUERY_KEY) { + return api.access.deny('Invalid configuration'); + } + //ensure the query value is valid + if (!event.secrets.QUERY_VALUE) { + return api.access.deny('Invalid configuration'); + } + + // this is a generic example of accessing the query string + const queryValue = event.request.query[event.secrets.QUERY_KEY]; + if (queryValue === event.secrets.QUERY_VALUE) { + // this is a specific POST_LOGIN example of using the query string to trigger setting custom claims + const { + CUSTOM_CLAIM_NAME: customClaimName, + CUSTOM_CLAIM_VALUE: customClaimValue, + } = event.secrets; + // ensure the custom claim name secret is valid + if (!customClaimName) { + return api.access.deny('Invalid configuration'); + } + // ensure the custom claim value is valid + if (!customClaimValue) { + return api.access.deny('Invalid configuration'); + } + api.idToken.setCustomClaim(customClaimName, customClaimValue); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/querystring-POST_LOGIN/manifest.yaml b/templates/querystring-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..720aa4e --- /dev/null +++ b/templates/querystring-POST_LOGIN/manifest.yaml @@ -0,0 +1,17 @@ +id: '45c70d47-761c-4b78-9c80-17ff347dc1ce' +name: 'Show how to check for variables in the querystring' +description: 'Checks if the login transaction includes a query variable from the QUERY_KEY secret matching a value set in the QUERY_VALUE secret and if it does, it will add an attribute named by the value of EXAMPLE_CUSTOM_CLAIM_NAME and a value of EXAMPLE_CUSTOM_CLAIM_VALUE to the user profile.' +public: true +triggers: ['POST_LOGIN'] +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/querystring-POST_LOGIN' +useCases: + - 'ENRICH_PROFILE' +notes: | + **Secrets** + + * `QUERY_KEY` - the query parameter name that will be evaluated to set the custom claim + * `QUERY_VALUE` - the query parameter value that must be matched to set the custom claim + * `CUSTOM_CLAIM_NAME` - the custom claim name + * `CUSTOM_CLAIM_VALUE` - the custom claim value diff --git a/templates/require-mfa-once-per-session-POST_LOGIN/code.js b/templates/require-mfa-once-per-session-POST_LOGIN/code.js new file mode 100644 index 0000000..b37a15a --- /dev/null +++ b/templates/require-mfa-once-per-session-POST_LOGIN/code.js @@ -0,0 +1,28 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/require-mfa-once-per-session-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // if the array of authentication methods is valid and contains a method named 'mfa', mfa has been done in this session already + if ( + !event.authentication || + !Array.isArray(event.authentication.methods) || + !event.authentication.methods.find((method) => method.name === 'mfa') + ) { + api.multifactor.enable('any'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/require-mfa-once-per-session-POST_LOGIN/manifest.yaml b/templates/require-mfa-once-per-session-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..adfe1af --- /dev/null +++ b/templates/require-mfa-once-per-session-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: '365115c1-c962-4769-a6c5-851c4494cc0d' +name: 'Require MFA once per session' +description: 'Avoid prompting a user for multifactor authentication if they have successfully completed MFA in their current session. This is particularly useful when performing silent authentication (`prompt=none`) to renew short-lived access tokens in a SPA (Single Page Application) during the duration of a user session without having to rely on setting `allowRememberBrowser` to `true`.' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/require-mfa-once-per-session-POST_LOGIN' +notes: | + # Notes + * This action code requires at least one factor to be enabled for your tenant. + * For more information please see [Enable Multi-Factor Authentication](https://auth0.com/docs/secure/multi-factor-authentication/enable-mfa). + +useCases: + - 'MULTIFACTOR' diff --git a/templates/role-creation-POST_LOGIN/code.js b/templates/role-creation-POST_LOGIN/code.js new file mode 100644 index 0000000..74823b4 --- /dev/null +++ b/templates/role-creation-POST_LOGIN/code.js @@ -0,0 +1,52 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/role-creation-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // Roles should only be set to verified users. + if (!event.user.email || !event.user.email_verified) { + return api.access.deny('access denied'); + } + + // get the special role name, value, and users to which should be granted. + const specialName = event.secrets.SPECIAL_ROLE_NAME; + if (!specialName) { + return api.access.deny('Invalid configuration'); + } + + // get the special role values and ensure they are valid + const specialValues = event.secrets.SPECIAL_ROLE_VALUE?.split(',').map( + (v) => v.trim() + ); + if (!specialValues) { + return api.access.deny('Invalid configuration'); + } + + // get the special role users, ensuring they are valid + const specialRoleUsers = event.secrets.SPECIAL_ROLE_USERS?.split(',').map( + (u) => u.trim() + ); + if (!specialRoleUsers) { + return api.access.deny('Invalid configuration'); + } + + // if this user is a special user set their custom claim + if (specialRoleUsers.includes(event.user.email)) { + api.idToken.setCustomClaim(specialName, specialValues); + api.accessToken.setCustomClaim(specialName, specialValues); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/role-creation-POST_LOGIN/manifest.yaml b/templates/role-creation-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..8ad11f1 --- /dev/null +++ b/templates/role-creation-POST_LOGIN/manifest.yaml @@ -0,0 +1,22 @@ +id: '5b524ee4-1d82-4671-811c-5bea1f3eede3' +name: 'Set roles to a user' +description: 'Add roles to a user based on an arbitrary pattern.' +public: true +triggers: + - 'POST_LOGIN' +useCases: + - 'ACCESS_CONTROL' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/role-creation-POST_LOGIN' +notes: | + **Secrets** + + * `SPECIAL_ROLE_NAME` - the name of the special role claim. + * `SPECIAL_ROLE_VALUE` - comma-delimited list of special roles + * `SPECIAL_ROLE_USERS` - comma-delimited list of users who should be assigned the special role claim + + **Notes** + + * Custom claims must be named appropriately. + * For more information on restrictions, please see [General restrictions](https://auth0.com/docs/secure/tokens/json-web-tokens/create-custom-claims#general-restrictions) diff --git a/templates/saml-attribute-mapping-POST_LOGIN/code.js b/templates/saml-attribute-mapping-POST_LOGIN/code.js new file mode 100644 index 0000000..df5f731 --- /dev/null +++ b/templates/saml-attribute-mapping-POST_LOGIN/code.js @@ -0,0 +1,43 @@ +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/saml-attribute-mapping-POST_LOGIN --- +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + api.samlResponse.setAttribute( + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier', + event.user.user_id + ); + + if (event.user.email) { + api.samlResponse.setAttribute( + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', + event.user.email + ); + } + + if (event.user.name) { + api.samlResponse.setAttribute( + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name', + event.user.name + ); + } + + // example of mapping a user metadata field + // api.samlResponse.setAttribute('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/food', event.user.user_metadata.favorite_food); + + // an example of mapping an app metadata field + // api.samlResponse.setAttribute('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/address', event.user.app_metadata.shipping_address); +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/saml-attribute-mapping-POST_LOGIN/manifest.yaml b/templates/saml-attribute-mapping-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..524dcd7 --- /dev/null +++ b/templates/saml-attribute-mapping-POST_LOGIN/manifest.yaml @@ -0,0 +1,10 @@ +id: '2af5b91a-be49-49ba-9dcc-934f4bd6dd8f' +name: 'SAML Attributes Mapping' +description: 'In a SAML application, customize the mapping between the Auth0 user and the SAML attributes' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/saml-attribute-mapping-POST_LOGIN' +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/saml-configuration-POST_LOGIN/code.js b/templates/saml-configuration-POST_LOGIN/code.js new file mode 100644 index 0000000..a6edc5e --- /dev/null +++ b/templates/saml-configuration-POST_LOGIN/code.js @@ -0,0 +1,48 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/saml-configuration-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + if (!event.secrets.CLIENT_ID) { + return api.access.deny('Invalid configuration'); + } + + if (!event.secrets.SAML_AUD) { + return api.access.deny('Invalid configuration'); + } + + if (!event.secrets.SAML_RECIPIENT) { + return api.access.deny('Invalid configuration'); + } + + if (!event.secrets.SAML_DESTINATION) { + return api.access.deny('Invalid configuration'); + } + + if (!event.secrets.SAML_LIFETIME_SEC) { + return api.access.deny('Invalid configuration'); + } + + if (event.client.client_id === event.secrets.CLIENT_ID) { + api.samlResponse.setAudience(event.secrets.SAML_AUD); + api.samlResponse.setRecipient(event.secrets.SAML_RECIPIENT); + api.samlResponse.setDestination(event.secrets.SAML_DESTINATION); + api.samlResponse.setLifetimeInSeconds( + Number(event.secrets.SAML_LIFETIME_SEC) + ); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/saml-configuration-POST_LOGIN/manifest.yaml b/templates/saml-configuration-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..fdd3666 --- /dev/null +++ b/templates/saml-configuration-POST_LOGIN/manifest.yaml @@ -0,0 +1,26 @@ +id: '85e0b92c-c4e6-4f26-bb98-bf5f41423b93' +name: 'SAML Configuration' +description: 'Programatically add fields to your SAML configuration.' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/saml-configuration-POST_LOGIN' +notes: | + **Secrets** + + * `CLIENT_ID` - the client ID of your IDP + * `SAML_AUD` - the audience, for example `urn:auth0:{yourTenant}:{yourConnectionName}` + * `SAML_RECIPIENT` - the recipient, for example `https://{yourTenant}.us.auth0.com/login/callback?connection={yourConnectionName} + * `SAML_DESTINATION` - the destination, for example `https://{yourTenant}.us.auth0.com/login/callback?connection={yourConnectionName} + * `SAML_LIFETIME_SEC` - the expiration in seconds + + **Notes** + + * Requires a tenant configured as an IdP with the SAML Addon. + * For more information see [Test SAML SSO with Auth0 as Service Provider and Identity Provider](https://auth0.com/docs/authenticate/protocols/saml/saml-configuration/configure-auth0-as-service-and-identity-provider) + * Requires sound and valid values for your audience, recipient, destination and lifetime. + * For more information see [this documentation page](https://auth0.com/docs/saml-configuration#configuration-options) + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/simple-domain-allowlist-POST_LOGIN/code.js b/templates/simple-domain-allowlist-POST_LOGIN/code.js new file mode 100644 index 0000000..c2bb9be --- /dev/null +++ b/templates/simple-domain-allowlist-POST_LOGIN/code.js @@ -0,0 +1,58 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/simple-domain-allowlist-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // ensure user email is present + if (!event.user.email) { + // note that userMessage (the second parameter) is displayed in red on the failed sign-up prompt + return api.access.deny('Email is invalid'); + } + + // ensure the user has verified their email address + if (!event.user.email_verified) { + api.access.deny('Email is unverified'); + } + + // ensure the allowed domains are configured + if (!event.secrets.ALLOWED_DOMAINS) { + return api.access.deny('Configuration error'); + } + + // parse the allow list of domain and ensure there is at least one + const domains = event.secrets.ALLOWED_DOMAINS.split(',').map((domain) => + domain.trim().toLowerCase() + ); + if (!domains) { + // note that userMessage (the second parameter) is displayed in red on the failed sign-up prompt + return api.access.deny('Configuration error'); + } + + // ensure a reasonable format for the email + const splitEMail = event.user.email.split('@'); + if (splitEMail.length !== 2) { + // note that userMessage (the second parameter) is displayed in red on the failed sign-up prompt + return api.access.deny('Email is invalid'); + } + + // if the email domain is not explicitly in our allow list, deny access + const domain = splitEMail[1].toLowerCase(); + if (!domains.includes(domain)) { + // note that userMessage (the second parameter) is displayed in red on the failed sign-up prompt + return api.access.deny('Email domain is prohibited'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/simple-domain-allowlist-POST_LOGIN/manifest.yaml b/templates/simple-domain-allowlist-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..3aee371 --- /dev/null +++ b/templates/simple-domain-allowlist-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: '557d5c47-54ff-46e4-b082-765f0c3f5cbc' +name: 'Simple Domain Allow List' +description: 'This actions template will only allow access for users with specific email domains.' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/simple-domain-allowlist-POST_LOGIN' +useCases: + - 'ACCESS_CONTROL' +notes: | + **Secrets** + + * `ALLOWED_DOMAINS` - comma delimited list of email domains users are allowed to use. diff --git a/templates/simple-domain-allowlist-PRE_USER_REGISTRATION/code.js b/templates/simple-domain-allowlist-PRE_USER_REGISTRATION/code.js new file mode 100644 index 0000000..2419a61 --- /dev/null +++ b/templates/simple-domain-allowlist-PRE_USER_REGISTRATION/code.js @@ -0,0 +1,42 @@ +/** + * Handler that will be called during the execution of a PreUserRegistration flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/simple-domain-allowlist-PRE_USER_REGISTRATION --- + * + * @param {Event} event - Details about the context and user that is attempting to register. + * @param {PreUserRegistrationAPI} api - Interface whose methods can be used to change the behavior of the signup. + */ +exports.onExecutePreUserRegistration = async (event, api) => { + // ensure user email is present + if (!event.user.email) { + // note that userMessage (the second parameter) is displayed in red on the failed sign-up prompt + return api.access.deny('invalid_email', 'Email is invalid'); + } + + if (!event.secrets.ALLOWED_DOMAINS) { + return api.access.deny('invalid_config', 'Invalid configuration'); + } + + // parse the allow list of domain and ensure there is at least one + const domains = event.secrets.ALLOWED_DOMAINS.split(',').map((domain) => + domain.trim().toLowerCase() + ); + if (!domains) { + // note that userMessage (the second parameter) is displayed in red on the failed sign-up prompt + return api.access.deny('invalid_config', 'Invalid configuration'); + } + + // ensure a reasonable format for the email + const splitEMail = event.user.email.split('@'); + if (splitEMail.length !== 2) { + // note that userMessage (the second parameter) is displayed in red on the failed sign-up prompt + return api.access.deny('invalid_email', 'Email is invalid'); + } + + // if the email domain is not explicitly in our allow list, deny access + const domain = splitEMail[1].toLowerCase(); + if (!domains.includes(domain)) { + // note that userMessage (the second parameter) is displayed in red on the failed sign-up prompt + return api.access.deny('invalid_domain', 'Email domain is prohibited'); + } +}; diff --git a/templates/simple-domain-allowlist-PRE_USER_REGISTRATION/manifest.yaml b/templates/simple-domain-allowlist-PRE_USER_REGISTRATION/manifest.yaml new file mode 100644 index 0000000..e2d9c31 --- /dev/null +++ b/templates/simple-domain-allowlist-PRE_USER_REGISTRATION/manifest.yaml @@ -0,0 +1,15 @@ +id: 'f8fd2198-e982-46b3-addc-8cf805efac91' +name: 'Simple Domain Allow List' +description: 'This actions template will only allow registration for users with specific email domains.' +public: true +triggers: + - 'PRE_USER_REGISTRATION' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/simple-domain-allowlist-PRE_USER_REGISTRATION' +useCases: + - 'ACCESS_CONTROL' +notes: | + **Secrets** + + * `ALLOWED_DOMAINS` - comma delimited list of email domains users are allowed to use. diff --git a/templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE/code.js b/templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE/code.js new file mode 100644 index 0000000..ba4d4b3 --- /dev/null +++ b/templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE/code.js @@ -0,0 +1,38 @@ +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE --- +/** + * Handler that will be called during the execution of a Password Reset / Post Challenge Flow. + * + * @param {Event} event - Details about the post challenge request. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +exports.onExecutePostChallenge = async (event, api) => { + if (!event.secrets.ALLOWED_USER_EMAILS) { + return api.access.deny('missing allowed user emails'); + } + + // Access should only be granted to verified users. + if (!event.user.email || !event.user.email_verified) { + return api.access.deny('access denied.'); + } + + const allowedUsers = event.secrets.ALLOWED_USER_EMAILS.split(',') + .map((email) => email.trim()) + .filter((email) => !!email); + const userHasAccess = allowedUsers.some( + (email) => email === event.user.email + ); + + if (!userHasAccess) { + return api.access.deny('access denied.'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostChallenge function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +// exports.onContinuePostChallenge = async (event, api) => { +// }; diff --git a/templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml b/templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml new file mode 100644 index 0000000..ed309af --- /dev/null +++ b/templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml @@ -0,0 +1,15 @@ +id: 'a3a3caf7-4d96-439a-b5dd-1069c4ad0eed' +name: 'Simple User Allowlist' +description: 'Only allow access to a specific list of users' +public: true +triggers: + - 'PASSWORD_RESET_POST_CHALLENGE' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-PASSWORD_RESET_POST_CHALLENGE' +notes: | + **Requird Secrets** + + * ALLOWED_USER_EMAILS - comma separated list of allowed user emails + +useCases: + - 'ACCESS_CONTROL' diff --git a/templates/simple-user-allowlist-POST_LOGIN/code.js b/templates/simple-user-allowlist-POST_LOGIN/code.js new file mode 100644 index 0000000..5356a10 --- /dev/null +++ b/templates/simple-user-allowlist-POST_LOGIN/code.js @@ -0,0 +1,38 @@ +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-POST_LOGIN --- +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + if (!event.secrets.ALLOWED_USER_EMAILS) { + return api.access.deny('missing allowed user emails'); + } + + // Access should only be granted to verified users. + if (!event.user.email || !event.user.email_verified) { + return api.access.deny('access denied.'); + } + + const allowedUsers = event.secrets.ALLOWED_USER_EMAILS.split(',') + .map((email) => email.trim()) + .filter((email) => !!email); + const userHasAccess = allowedUsers.some( + (email) => email === event.user.email + ); + + if (!userHasAccess) { + return api.access.deny('access denied.'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/simple-user-allowlist-POST_LOGIN/manifest.yaml b/templates/simple-user-allowlist-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..c581034 --- /dev/null +++ b/templates/simple-user-allowlist-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: 'a14b414a-ebfd-4df3-bfbb-7e10fff90cdc' +name: 'Simple User Allowlist' +description: 'Only allow access to a specific list of users' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-POST_LOGIN' +notes: | + **Requird Secrets** + + * ALLOWED_USER_EMAILS - comma separated list of allowed user emails + +useCases: + - 'ACCESS_CONTROL' diff --git a/templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE/code.js b/templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE/code.js new file mode 100644 index 0000000..1288cb3 --- /dev/null +++ b/templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE/code.js @@ -0,0 +1,49 @@ +/** + * Handler that will be called during the execution of a Password Reset / Post Challenge Flow. + * + * @param {Event} event - Details about the post challenge request. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE --- + */ +exports.onExecutePostChallenge = async (event, api) => { + if (!event.secrets.ALLOWED_CLIENT_ID) { + return api.access.deny('missing allowed client id'); + } + + if (!event.secrets.ALLOWED_USER_EMAILS) { + return api.access.deny('missing allowed user emails'); + } + + // Access should only be granted to verified users. + if (!event.user.email || !event.user.email_verified) { + return api.access.deny('access denied.'); + } + + // only enforce for event.secrets.ALLOWED_CLIENT_ID + // bypass this rule for all other apps + if (event.client.client_id !== event.secrets.ALLOWED_CLIENT_ID) { + return; + } + + const allowedUsers = event.secrets.ALLOWED_USER_EMAILS.split(',') + .map((email) => email.trim()) + .filter((email) => !!email); + const userHasAccess = allowedUsers.some( + (email) => email === event.user.email + ); + + if (!userHasAccess) { + return api.access.deny('access denied.'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostChallenge function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +// exports.onContinuePostChallenge = async (event, api) => { +// }; diff --git a/templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml b/templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml new file mode 100644 index 0000000..7953022 --- /dev/null +++ b/templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml @@ -0,0 +1,15 @@ +id: 'dca2e879-7380-41de-b683-063e9e9fd28e' +name: 'Allowlist for a Specific App' +description: 'Only allow specific users access to an app' +public: true +triggers: + - 'PASSWORD_RESET_POST_CHALLENGE' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-for-app-PASSWORD_RESET_POST_CHALLENGE' +notes: | + **Requird Secrets** + * `ALLOWED_CLIENT_ID` - client id to allow access for + * `ALLOWED_USER_EMAILS` - comma separated list of user emails to allow access for + +useCases: + - 'ACCESS_CONTROL' diff --git a/templates/simple-user-allowlist-for-app-POST_LOGIN/code.js b/templates/simple-user-allowlist-for-app-POST_LOGIN/code.js new file mode 100644 index 0000000..887584e --- /dev/null +++ b/templates/simple-user-allowlist-for-app-POST_LOGIN/code.js @@ -0,0 +1,49 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-for-app-POST_LOGIN --- + */ +exports.onExecutePostLogin = async (event, api) => { + if (!event.secrets.ALLOWED_CLIENT_ID) { + return api.access.deny('missing allowed client id'); + } + + if (!event.secrets.ALLOWED_USER_EMAILS) { + return api.access.deny('missing allowed user emails'); + } + + // Access should only be granted to verified users. + if (!event.user.email || !event.user.email_verified) { + return api.access.deny('access denied.'); + } + + // only enforce for event.secrets.ALLOWED_CLIENT_ID + // bypass this rule for all other apps + if (event.client.client_id !== event.secrets.ALLOWED_CLIENT_ID) { + return; + } + + const allowedUsers = event.secrets.ALLOWED_USER_EMAILS.split(',') + .map((email) => email.trim()) + .filter((email) => !!email); + const userHasAccess = allowedUsers.some( + (email) => email === event.user.email + ); + + if (!userHasAccess) { + return api.access.deny('access denied.'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/simple-user-allowlist-for-app-POST_LOGIN/manifest.yaml b/templates/simple-user-allowlist-for-app-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..490bcc3 --- /dev/null +++ b/templates/simple-user-allowlist-for-app-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: '26ed2851-dc1f-42f3-b131-03747770ed1c' +name: 'Allowlist for a Specific App' +description: 'Only allow specific users access to an app' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-for-app-POST_LOGIN' +notes: | + **Requird Secrets** + * `ALLOWED_CLIENT_ID` - client id to allow access for + * `ALLOWED_USER_EMAILS` - comma separated list of user emails to allow access for + +useCases: + - 'ACCESS_CONTROL' diff --git a/templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE/code.js b/templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE/code.js new file mode 100644 index 0000000..844e878 --- /dev/null +++ b/templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE/code.js @@ -0,0 +1,46 @@ +/** + * Handler that will be called during the execution of a Password Reset / Post Challenge Flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE --- + * + * @param {Event} event - Details about the post challenge request. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +exports.onExecutePostChallenge = async (event, api) => { + // ensure the connection secret is valid + if (!event.secrets.ALLOW_LIST_CONNECTION) { + return api.access.deny('Invalid configuration'); + } + + // ensure the list of allowed user email is valid + if (!event.secrets.ALLOW_USER_EMAILS) { + return api.access.deny('Invalid configuration'); + } + + // access should only be granted to verified users + if (!event.user.email || !event.user.email_verified) { + return api.access.deny('Access denied.'); + } + + // require allow list validation for the configured connection + if (event.secrets.ALLOW_LIST_CONNECTION === event.connection.name) { + // determine if this user is in the allowed user email list + const userHasAccess = !!event.secrets.ALLOW_USER_EMAILS.split(',').some( + (email) => email.trim() === event.user.email + ); + // if they are not in the allowed list then deny access + if (!userHasAccess) { + return api.access.deny('Access denied.'); + } + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostChallenge function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +// exports.onContinuePostChallenge = async (event, api) => { +// }; diff --git a/templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml b/templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml new file mode 100644 index 0000000..445c0b5 --- /dev/null +++ b/templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml @@ -0,0 +1,16 @@ +id: '52afbe4c-470e-495e-b400-1a60a33e419f' +name: 'Simple User Allow List on a Connection' +description: 'Allow password reset only to certain users coming from a specific connection' +public: true +triggers: + - 'PASSWORD_RESET_POST_CHALLENGE' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-on-a-connection-PASSWORD_RESET_POST_CHALLENGE' +notes: | + **Secrets** + + * `ALLOW_LIST_CONNECTION` - the name of the connection against which accounts will be validated, eg: `Username-Password-Authentication` + * `ALLOW_USER_EMAILS` - a comma-delimited list of allowed email addresses + +useCases: + - 'ACCESS_CONTROL' diff --git a/templates/simple-user-allowlist-on-a-connection-POST_LOGIN/code.js b/templates/simple-user-allowlist-on-a-connection-POST_LOGIN/code.js new file mode 100644 index 0000000..112e541 --- /dev/null +++ b/templates/simple-user-allowlist-on-a-connection-POST_LOGIN/code.js @@ -0,0 +1,45 @@ +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-on-a-connection-POST_LOGIN --- + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + // ensure allowed connection list secret is valid + if (!event.secrets.ALLOW_LIST_CONNECTION) { + return api.access.deny('Invalid configuration'); + } + + // ensure allowed user email list is valid + if (!event.secrets.ALLOW_USER_EMAILS) { + return api.access.deny('Invalid configuration'); + } + + // access should only be granted to verified users + if (!event.user.email || !event.user.email_verified) { + return api.access.deny('Access denied.'); + } + // require allow list validation for the configured connection + if (event.secrets.ALLOW_LIST_CONNECTION === event.connection.name) { + // determine if this user is in the allowed user email list + const userHasAccess = event.secrets.ALLOW_USER_EMAILS.split(',').some( + (email) => email.trim() === event.user.email + ); + // if they are not in the allowed list then deny access + if (!userHasAccess) { + return api.access.deny('Access denied.'); + } + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/simple-user-allowlist-on-a-connection-POST_LOGIN/manifest.yaml b/templates/simple-user-allowlist-on-a-connection-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..b158d97 --- /dev/null +++ b/templates/simple-user-allowlist-on-a-connection-POST_LOGIN/manifest.yaml @@ -0,0 +1,17 @@ +id: '7950292f-81ee-41f4-9e4d-5dfabd49f882' +name: 'Simple User Allow List on a Connection' +description: 'Allow access to certain users coming from a specific connection' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +modules: [] +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/simple-user-allowlist-on-a-connection-POST_LOGIN' +notes: | + **Secrets** + + * `ALLOW_LIST_CONNECTION` - the name of the connection against which accounts will be validated, eg: `Username-Password-Authentication` + * `ALLOW_USER_EMAILS` - a comma-delimited list of allowed email addresses + +useCases: + - 'ACCESS_CONTROL' diff --git a/templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE/code.js b/templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE/code.js new file mode 100644 index 0000000..34678d4 --- /dev/null +++ b/templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE/code.js @@ -0,0 +1,71 @@ +const xmldom = require('@xmldom/xmldom'); +const xpath = require('xpath'); + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE --- +/** + * Handler that will be called during the execution of a Password Reset / Post Challenge Flow. + * + * @param {Event} event - Details about the post challenge request. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ + +const FETCH_TIMEOUT = 1000; // 1s + +async function fetchRoles(domain, action) { + // make sure the request times out after `FETCH_TIMEOUT`ms + const controller = new AbortController(); + setTimeout(() => controller.abort(), FETCH_TIMEOUT); + + const response = await fetch(`https://${domain}/RoleService.svc`, { + method: 'POST', + signal: controller.signal, + headers: { + 'Content-Type': 'text/xml; charset=utf-8', + SOAPAction: action, + }, + }); + + const body = await response.text(); + const parser = new xmldom.DOMParser(); + const doc = parser.parseFromString(body); + const roles = xpath + .select("//*[local-name(.)='string']", doc) + .map(function (node) { + return node.textContent; + }); + return roles; +} + +exports.onExecutePostChallenge = async (event, api) => { + const domain = event.secrets.DOMAIN; + if (!domain) { + return api.access.deny('invalid server domain'); + } + + const action = event.secrets.SOAP_ACTION; + if (!action) { + return api.access.deny('invalid soap action'); + } + + try { + const roles = await fetchRoles(domain, action); + + // at this point, you can use the roles, for example you can set a + // custom claim on the id token + } catch (error) { + // during debugging, you can uncomment the line below to see what the error is: + // console.log('failed to fetch roles, error:', error); + + return api.access.deny('action failed'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostChallenge function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow. + */ +// exports.onContinuePostChallenge = async (event, api) => { +// }; diff --git a/templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml b/templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml new file mode 100644 index 0000000..1eca907 --- /dev/null +++ b/templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE/manifest.yaml @@ -0,0 +1,21 @@ +id: 'c17dd4ce-28e0-4401-8cef-133eec1debc8' +name: 'Roles from a SOAP Service' +description: 'Shows how to query a basic profile http binding SOAP web service for roles.' +public: true +triggers: + - 'PASSWORD_RESET_POST_CHALLENGE' +runtime: 'node18' +modules: + - name: '@xmldom/xmldom' + version: 'latest' + - name: 'xpath' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-PASSWORD_RESET_POST_CHALLENGE' +notes: | + **Required Secrets** + + * `DOMAIN` - domain name of your SOAP server, eg. my-soap-server.com + * `SOAP_ACTION` - `SOAPAction` header for the soap request. + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/soap-webservice-POST_CHANGE_PASSWORD/code.js b/templates/soap-webservice-POST_CHANGE_PASSWORD/code.js new file mode 100644 index 0000000..7e75b3e --- /dev/null +++ b/templates/soap-webservice-POST_CHANGE_PASSWORD/code.js @@ -0,0 +1,61 @@ +const xmldom = require('@xmldom/xmldom'); +const xpath = require('xpath'); + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-POST_CHANGE_PASSWORD --- +/** + * Handler that will be called during the execution of a PostChangePassword flow. + * + * @param {Event} event - Details about the user and the context in which the change password is happening. + * @param {PostChangePasswordAPI} api - Methods and utilities to help change the behavior after a user changes their password. + */ + +const FETCH_TIMEOUT = 1000; // 1s + +async function fetchRoles(domain, action) { + // make sure the request times out after `FETCH_TIMEOUT`ms + const controller = new AbortController(); + setTimeout(() => controller.abort(), FETCH_TIMEOUT); + + const response = await fetch(`https://${domain}/RoleService.svc`, { + method: 'POST', + signal: controller.signal, + headers: { + 'Content-Type': 'text/xml; charset=utf-8', + SOAPAction: action, + }, + }); + + const body = await response.text(); + const parser = new xmldom.DOMParser(); + const doc = parser.parseFromString(body); + const roles = xpath + .select("//*[local-name(.)='string']", doc) + .map(function (node) { + return node.textContent; + }); + return roles; +} + +exports.onExecutePostChangePassword = async (event, api) => { + const domain = event.secrets.DOMAIN; + if (!domain) { + return; + } + + const action = event.secrets.SOAP_ACTION; + if (!action) { + return; + } + + try { + const roles = await fetchRoles(domain, action); + + // at this point, you can use the roles, for example you can set a + // custom claim on the id token + } catch (error) { + // during debugging, you can uncomment the line below to see what the error is: + // console.log('failed to fetch roles, error:', error); + + return; + } +}; diff --git a/templates/soap-webservice-POST_CHANGE_PASSWORD/manifest.yaml b/templates/soap-webservice-POST_CHANGE_PASSWORD/manifest.yaml new file mode 100644 index 0000000..26208f6 --- /dev/null +++ b/templates/soap-webservice-POST_CHANGE_PASSWORD/manifest.yaml @@ -0,0 +1,21 @@ +id: 'dab805d5-aa6c-4e44-abd8-d59d5ddb3c78' +name: 'Roles from a SOAP Service' +description: 'Shows how to query a basic profile http binding SOAP web service for roles.' +public: true +triggers: + - 'POST_CHANGE_PASSWORD' +runtime: 'node18' +modules: + - name: '@xmldom/xmldom' + version: 'latest' + - name: 'xpath' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-POST_CHANGE_PASSWORD' +notes: | + **Required Secrets** + + * `DOMAIN` - domain name of your SOAP server, eg. my-soap-server.com + * `SOAP_ACTION` - `SOAPAction` header for the soap request. + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/soap-webservice-POST_LOGIN/code.js b/templates/soap-webservice-POST_LOGIN/code.js new file mode 100644 index 0000000..faffd59 --- /dev/null +++ b/templates/soap-webservice-POST_LOGIN/code.js @@ -0,0 +1,77 @@ +const xmldom = require('@xmldom/xmldom'); +const xpath = require('xpath'); + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-POST_LOGIN --- +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ + +const FETCH_TIMEOUT = 1000; // 1s + +async function fetchRoles(domain, action) { + // make sure the request times out after `FETCH_TIMEOUT`ms + const controller = new AbortController(); + setTimeout(() => controller.abort(), FETCH_TIMEOUT); + + const response = await fetch(`https://${domain}/RoleService.svc`, { + method: 'POST', + signal: controller.signal, + headers: { + 'Content-Type': 'text/xml; charset=utf-8', + SOAPAction: action, + }, + }); + + const body = await response.text(); + const parser = new xmldom.DOMParser(); + const doc = parser.parseFromString(body); + const roles = xpath + .select("//*[local-name(.)='string']", doc) + .map(function (node) { + return node.textContent; + }); + return roles; +} + +exports.onExecutePostLogin = async (event, api) => { + const domain = event.secrets.DOMAIN; + if (!domain) { + return api.access.deny('invalid server domain'); + } + + let namespace = event.secrets.ID_TOKEN_NAMESPACE || ''; + if (namespace && !namespace.endsWith('/')) { + namespace += '/'; + } + + const action = event.secrets.SOAP_ACTION; + if (!action) { + return api.access.deny('invalid soap action'); + } + + try { + const roles = await fetchRoles(domain, action); + + // at this point, you can use the roles, for example you can set a + // custom claim on the id token + api.idToken.setCustomClaim(`${namespace}roles`, roles); + } catch (error) { + // during debugging, you can uncomment the line below to see what the error is: + // console.log('failed to fetch roles, error:', error); + + return api.access.deny('action failed'); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/soap-webservice-POST_LOGIN/manifest.yaml b/templates/soap-webservice-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..effee23 --- /dev/null +++ b/templates/soap-webservice-POST_LOGIN/manifest.yaml @@ -0,0 +1,25 @@ +id: '36e0c077-bf8c-4350-9f6a-85fd90c5ace1' +name: 'Roles from a SOAP Service' +description: 'Shows how to query a basic profile http binding SOAP web service for roles.' +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +modules: + - name: '@xmldom/xmldom' + version: 'latest' + - name: 'xpath' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-POST_LOGIN' +notes: | + **Requied Secrets** + + * `DOMAIN` - domain name of your SOAP server, eg. my-soap-server.com + * `SOAP_ACTION` - `SOAPAction` header for the soap request. + + **Optional Secrets** + + * `ID_TOKEN_NAMESPACE` - optional namespace for the id token field where the roles will be saved, eg. https://my-domain.com + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/soap-webservice-POST_USER_REGISTRATION/code.js b/templates/soap-webservice-POST_USER_REGISTRATION/code.js new file mode 100644 index 0000000..b9c34a7 --- /dev/null +++ b/templates/soap-webservice-POST_USER_REGISTRATION/code.js @@ -0,0 +1,61 @@ +const xmldom = require('@xmldom/xmldom'); +const xpath = require('xpath'); + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-POST_USER_REGISTRATION --- +/** + * Handler that will be called during the execution of a PostUserRegistration flow. + * + * @param {Event} event - Details about the context and user that has registered. + * @param {PostUserRegistrationAPI} api - Methods and utilities to help change the behavior after a signup. + */ + +const FETCH_TIMEOUT = 1000; // 1s + +async function fetchRoles(domain, action) { + // make sure the request times out after `FETCH_TIMEOUT`ms + const controller = new AbortController(); + setTimeout(() => controller.abort(), FETCH_TIMEOUT); + + const response = await fetch(`https://${domain}/RoleService.svc`, { + method: 'POST', + signal: controller.signal, + headers: { + 'Content-Type': 'text/xml; charset=utf-8', + SOAPAction: action, + }, + }); + + const body = await response.text(); + const parser = new xmldom.DOMParser(); + const doc = parser.parseFromString(body); + const roles = xpath + .select("//*[local-name(.)='string']", doc) + .map(function (node) { + return node.textContent; + }); + return roles; +} + +exports.onExecutePostUserRegistration = async (event, api) => { + const domain = event.secrets.DOMAIN; + if (!domain) { + return; + } + + const action = event.secrets.SOAP_ACTION; + if (!action) { + return; + } + + try { + const roles = await fetchRoles(domain, action); + + // at this point, you can use the roles, for example you can set a + // custom claim on the id token + } catch (error) { + // during debugging, you can uncomment the line below to see what the error is: + // console.log('failed to fetch roles, error:', error); + + return; + } +}; diff --git a/templates/soap-webservice-POST_USER_REGISTRATION/manifest.yaml b/templates/soap-webservice-POST_USER_REGISTRATION/manifest.yaml new file mode 100644 index 0000000..d03e6f5 --- /dev/null +++ b/templates/soap-webservice-POST_USER_REGISTRATION/manifest.yaml @@ -0,0 +1,21 @@ +id: 'e51d4044-bed0-4069-9574-aa1da44fcbc7' +name: 'Roles from a SOAP Service' +description: 'Shows how to query a basic profile http binding SOAP web service for roles.' +public: true +triggers: + - 'POST_USER_REGISTRATION' +runtime: 'node18' +modules: + - name: '@xmldom/xmldom' + version: 'latest' + - name: 'xpath' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-POST_USER_REGISTRATION' +notes: | + **Required Secrets** + + * `DOMAIN` - domain name of your SOAP server, eg. my-soap-server.com + * `SOAP_ACTION` - `SOAPAction` header for the soap request. + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/soap-webservice-SEND_PHONE_MESSAGE/code.js b/templates/soap-webservice-SEND_PHONE_MESSAGE/code.js new file mode 100644 index 0000000..212052e --- /dev/null +++ b/templates/soap-webservice-SEND_PHONE_MESSAGE/code.js @@ -0,0 +1,61 @@ +const xmldom = require('@xmldom/xmldom'); +const xpath = require('xpath'); + +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-SEND_PHONE_MESSAGE --- +/** + * Handler that will be called during the execution of a SendPhoneMessage flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {SendPhoneMessageAPI} api - Methods and utilities to help change the behavior of sending a phone message. + */ + +const FETCH_TIMEOUT = 1000; // 1s + +async function fetchRoles(domain, action) { + // make sure the request times out after `FETCH_TIMEOUT`ms + const controller = new AbortController(); + setTimeout(() => controller.abort(), FETCH_TIMEOUT); + + const response = await fetch(`https://${domain}/RoleService.svc`, { + method: 'POST', + signal: controller.signal, + headers: { + 'Content-Type': 'text/xml; charset=utf-8', + SOAPAction: action, + }, + }); + + const body = await response.text(); + const parser = new xmldom.DOMParser(); + const doc = parser.parseFromString(body); + const roles = xpath + .select("//*[local-name(.)='string']", doc) + .map(function (node) { + return node.textContent; + }); + return roles; +} + +exports.onExecuteSendPhoneMessage = async (event, api) => { + const domain = event.secrets.DOMAIN; + if (!domain) { + return; + } + + const action = event.secrets.SOAP_ACTION; + if (!action) { + return; + } + + try { + const roles = await fetchRoles(domain, action); + + // at this point, you can use the roles, for example you can set a + // custom claim on the id token + } catch (error) { + // during debugging, you can uncomment the line below to see what the error is: + // console.log('failed to fetch roles, error:', error); + + return; + } +}; diff --git a/templates/soap-webservice-SEND_PHONE_MESSAGE/manifest.yaml b/templates/soap-webservice-SEND_PHONE_MESSAGE/manifest.yaml new file mode 100644 index 0000000..3001d16 --- /dev/null +++ b/templates/soap-webservice-SEND_PHONE_MESSAGE/manifest.yaml @@ -0,0 +1,21 @@ +id: '88e3b384-0aaa-44c8-818f-ee7b195fecc4' +name: 'Roles from a SOAP Service' +description: 'Shows how to query a basic profile http binding SOAP web service for roles.' +public: true +triggers: + - 'SEND_PHONE_MESSAGE' +runtime: 'node18' +modules: + - name: '@xmldom/xmldom' + version: 'latest' + - name: 'xpath' + version: 'latest' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/soap-webservice-SEND_PHONE_MESSAGE' +notes: | + **Required Secrets** + + * `DOMAIN` - domain name of your SOAP server, eg. my-soap-server.com + * `SOAP_ACTION` - `SOAPAction` header for the soap request. + +useCases: + - 'ENRICH_PROFILE' diff --git a/templates/track-consent-POST_LOGIN/code.js b/templates/track-consent-POST_LOGIN/code.js new file mode 100644 index 0000000..a901d12 --- /dev/null +++ b/templates/track-consent-POST_LOGIN/code.js @@ -0,0 +1,32 @@ +// --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/track-consent-POST_LOGIN --- +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * See https://auth0.com/docs/compliance/gdpr/features-aiding-compliance/user-consent/track-consent-with-lock for more information + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + const { consentGiven } = event.user.user_metadata || {}; + + // short-circuit if the user signed up already + if (consentGiven) { + return; + } + + // first time login/signup + api.user.setUserMetadata('consentGiven', true); + api.user.setUserMetadata('consentTimestamp', Date.now()); + return; +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/templates/track-consent-POST_LOGIN/manifest.yaml b/templates/track-consent-POST_LOGIN/manifest.yaml new file mode 100644 index 0000000..8a034a9 --- /dev/null +++ b/templates/track-consent-POST_LOGIN/manifest.yaml @@ -0,0 +1,15 @@ +id: '008eec1c-de87-462b-9ab6-ce8712e3394d' +name: 'Track consent from Auth0 Lock' +description: "Adds metadata on when a user has accepted the terms and conditions from within Auth0's Lock" +public: true +triggers: + - 'POST_LOGIN' +runtime: 'node18' +sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/track-consent-POST_LOGIN' +notes: | + **Note** + + * This action is useful for cases where you want to track a user's consent from within Auth0 Lock, however it does not explicitly record whether consent was checked. See https://auth0.com/docs/compliance/gdpr/features-aiding-compliance/user-consent/track-consent-with-lock for more information. + +useCases: + - 'ACCESS_CONTROL'