Isolation guarantees in Actions

Hi. I’ve written a post-login action that prevents users logging in with multiple identities for a given email-i query the management API’s usersByEmail function and deny if the identity being used to log in doesn’t match one of the earliest created identities.

I’m worried that if a user logs in with two different methods simultaneously, that neither check with the management API will reveal the alternate login method and thus the user could be allowed to login with two methods with the same email at the same time.

Is there any way to guarantee some kind of isolation or other mechanism to avoid this situation?

Hi @daniel26,

Welcome to the Auth0 Community!

To better understand your scenario, could you please share your Action script?

Thanks,
Rueben

Sure. Below is a condensed version of the action.

Specifically, I just need to know how to guarantee, if possible, that a user has no other logins under the same email. The worry is that two simultaneous calls to management.usersByEmail.getByEmail from different accounts sharing an email could fail to reveal each other.

function findObjectsWithMinCreatedAt(objects) {
    // Find the minimum value for the "created_at" property
    const minCreatedAt = Math.min(...objects.map(obj => (new Date(obj.created_at)).getTime()))
    const objectsWithMinCreatedAt = objects.filter(obj => (new Date(obj.created_at)).getTime() === minCreatedAt);
    return objectsWithMinCreatedAt;
}

function identitiesOverlap(identities1, identities2) {
  const ids1 = new Set(identities1.map((identity) => JSON.stringify([identity.provider, identity.user_id])))
  return identities2.filter((identity) => ids1.has(JSON.stringify([identity.provider, identity.user_id]))).length !== 0;
}
exports.onExecutePostLogin = async (event, api) => {
  const ManagementClient = require('auth0').ManagementClient;
  const management = new ManagementClient({
      domain: event.secrets.domain,
      clientId: event.secrets.client_id,
      clientSecret: event.secrets.client_secret,
  });

  const res = await (async () => {try {
    return await management.usersByEmail.getByEmail({email: event.user.email, fields: 'created_at,identities', include_fields: true});
    } catch (e) {
      console.error(e)
      api.access.deny('internal_error', 'no idea what happen')
  }})()
  if (!res) {return;}
  
  const identities = findObjectsWithMinCreatedAt(res.data).flatMap((user) => user.identities);
  
  if (!identitiesOverlap(identities, event.user.identities)) {
    const prettyProviders = identities.map(identity => PROVIDER_NAMES[identity.provider] || identity);
    api.access.deny('no_multiple_accounts_for_one_email', "An account by this email exists under a different login method. Please login with any of " + identity.map(identity => identity.provider).join(',') + '.')
  }
};

Hi @daniel26,

Thanks for sharing your Action script.

This should not be a worry since you are using Post Login aciton script. In this situation, there is an assumption that the user was already created (signed up) on your applicaiton, which means that their identities should be linked before they login.

And because these calls are synchrounous, you can be certain that simulateneous calls will associate the user’s identities correctly.

I hope this was clear!

Let me know if you have any questions.

Thanks,
Rueben