function(user, context, callback) { const propertiesToComplete = ["given_name", "family_name", "name"]; // go over each property and try to get missing // information from secondary identities for(var property of propertiesToComplete) { if (!user[property]) { for(var identity of user.identities) { if (identity.profileData && identity.profileData[property]) { user[property] = identity.profileData[property]; break; } } } } callback(null, user, context);}
// @ts-checkconst { ManagementClient } = require("auth0");// High-level Workflow for Performing Account Linking in Actions// --------------------------------------------------------------------------------------------//// The goal of this workflow is to systematically process all users for potential account// linking. We want to detect situations where an end-user// may have other identities in Auth0. These other identities would be discovered through// matching verified email addresses. The Auth0 Action will ensure that all users are processed// for account linking.//// A redirect app will be hosted by the customer to which we will redirect the user's browser// when account linking might be available. The customer's account linking app is responsible// authenticating that the current user owns the candidate linking identities. It will actually// perform the account linking via Management API before redirecting back to the login flow's// `/continue?state` endpoint.//// The Action will pick up here and update the primary user for the login if necessary and mark// the user as having been successfully processed.//// Here are the details of the workflow://// 1. Check if the user has been processed for account linking. The state for this is encoded in// the user's `app_metadata`. If the user has already been processed, exit this flow.// 2. Discover other user identities that are candidates for account linking. If candidates are// found:// 1. Encode the current user and any candidate identities into a signed session token to be// passed to the account linking app.// 2. Redirect the user's browser to the account linking app with the signed session token.// 3. The account linking app should challenge the user to authenticate using the candidate// identities.// 4. If the user choses to proceed with account linking and successfully authenticates with// candidate identities, determine the primary user under which to consolidate identities.// 5. Use the management API to perform account linking and to map any user or app metadata// from the other user accounts into the new primary user account.// 6. Redirect back to `/continue?state` with a new signed token encoding the primary user// that is the outcome of the account linking app.// 7. Validate the returned session token in the `onContinuePostLogin` entrypoint of the// Action. If a change of primary user is required, change the primary user via// `api.authentication.setPrimaryUser(newPrimaryUserId)`.// 3. Mark the user as having been processed for account linking by storing a flag in the// user's `app_metadata`.const LINKING_STATE_KEY = 'account_linking_state';/** * 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.user.email_verified) { // We won't process users for account linking until they have verified their email address. // We might consider rejecting logins here or redirecting users to an external tool to // remind the user to confirm their email address before proceeding. // // In this example, we simply won't process users unless their email is verified. return; } const accountLinkingState = event.user.app_metadata[LINKING_STATE_KEY]; if (accountLinkingState) { // Account linking has already been processed and completed for this user. No further work // to be done in this Action. return; } const token = await getManagementApiToken(event, api); const domain = event.secrets.TENANT_DOMAIN; const management = new ManagementClient({ domain, token }); // Search for other candidate users const { data: candidateUsers } = await management.usersByEmail.getByEmail({ email: event.user.email, }); if (!Array.isArray(candidateUsers)) { return; } const candidateIdentities = candidateUsers.flatMap((user) => user.identities); if (!candidateIdentities.length) { // No candidate users for linking so mark the user as processed. api.user.setAppMetadata(LINKING_STATE_KEY, Date.now()); } // Encode the current user and an array of their const sessionToken = api.redirect.encodeToken({ payload: { current_user: event.user, candidate_identities: candidateIdentities, }, secret: event.secrets.REDIRECT_SECRET, expiresInSeconds: 20, }); api.redirect.sendUserTo('https://url.for/account/linking/service', { query: { session_token: sessionToken }, });};/** * 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) => { // Validate the session token passed to `/continue?state` and extract the `user_id` claim. const { user_id } = api.redirect.validateToken({ secret: event.secrets.REDIRECT_SECRET, tokenParameterName: 'session_token', }); if (user_id !== event.user.user_id) { // The account linking service indicated that the primary user changed. api.authentication.setPrimaryUser(user_id); } // Mark the user as having been processed for account linking api.user.setAppMetadata(LINKING_STATE_KEY, Date.now());};