I found an edge case in our Auth0 to SuperTokens m...
# support-questions-legacy
n
I found an edge case in our Auth0 to SuperTokens migration. Users that do not yet exist within SuperTokens might have forgotten their passwords. So we need a way for creating their supertokens account upon doing a password reset request 🤔 What I have in mind is the following: 1. Overwrite
getUsersByEmail
2. Do a RPC call to our services/auth0 for checking whether an email/password user with a given email exists 3. if he does not exist use
originalImplementation.emailPasswordSignUp
for creating the user with a random password 4. return that newly created user from the
getUsersByEmail
function I think one problem here is that
getUsersByEmail
is not exclusively used by the reset password functionality - I can observe that function being called for a basic login attempt as well. 🤔 As I workaround I figured out that using the
userContext
might work - though it seems a bit like an hacky attempt.
Copy code
...
async getUsersByEmail(input) {
  // In case this does not happen as part of the reset password token flow we just use the default implementation.
  if (
    input.userContext?.['_default']?.['request']?.['original']?.['url'] !==
    '/api/auth/user/password/reset/token'
  ) {
    return originalImplementation.getUsersByEmail(input);
  }
 ...
Does that seem reasonable? Are there any better alternatives? Did anyone do something similar before?
k
interesting, why did you decide to move out of auth0 to supertokens?
n
This is a bit off-topic for the problem solution here 😄 Anyways, we are building https://graphql-hive.com/, which is both a SaaS and an open-source self-hostable platform. Requiring people that self-host to use non open source solutions (Auth0) feels wrong 😉
k
lol 😄
r
Good catch @n1ru4l . So the action of resetting a password can be done by anyone - wether their account exists or not. If their account doesn't exist, we don't create a password reset token. What you should do is to override the reset password create code API, and in that (you have the email), check if the user exists in supertokens. If it does, continue with the original implementation. If it doesn't check if it exists in Auth0. If it doesn't, continue with the original implementation. If it exists in Auth0, call the sign up function on supertokens with the email and a random password. Then call the original implementation - this should work.
n
@rp_st Any pointers on how to overwrite the password reset functionality?
r
Sure. Give me sometime.
which backend SDK are you using?
n
supertokens-node
This is what I had in mind:
Copy code
async getUsersByEmail(input) {
  // In case this does not happen as part of the reset password token flow we just use the default implementation.
  if (
    // This is the original http request :)
    input.userContext?.['_default']?.['request']?.['original']?.['url'] !==
    '/api/auth/user/password/reset/token'
  ) {
    return originalImplementation.getUsersByEmail(input);
  }

  // We first use the existing implementation for looking for users within supertokens.
  const users = await originalImplementation.getUsersByEmail(input);

  // If there is no email/password SuperTokens user yet, we need to check if there is an Auth0 user for this email.
  if (users.some(user => user.thirdParty == null) === false) {
    // RPC call to check if email/password user exists in Auth0
    const dbUser = await checkWhetherAuth0EmailUserExists(config, { email: input.email });

    if (dbUser) {
      // If we have this user within our database we create our new supertokens user
      const newUserResult = await originalImplementation.emailPasswordSignUp({
        email: dbUser.email,
        password: await generateRandomPassword(),
        userContext: input.userContext,
      });

      if (newUserResult.status !== 'OK') {
        return users;
      }

      // link the db record to the new supertokens user
      await setUserIdMapping(config, {
        auth0UserId: dbUser.auth0UserId,
        supertokensUserId: newUserResult.user.id,
      });

      // return the new user so he is used for the password reset.
      return [newUserResult.user];
    }
  }

  return users;
},
r
Copy code
ts
EmailPassword.init({
    override: {
        apis: (oI) => {
            return {
                ...oI,
                generatePasswordResetTokenPOST: async function (input) {
                    let email = input.formFields.find(i => i.id === "email");
                    // TODO: logic here to check if email exists in supertokens
                    // or in auth0 and create a user based on this email if needed
                    return oI.generatePasswordResetTokenPOST!(input);
                }
            }
        }
    }
})
n
In there I don't have access to the
functions
originalImplementation, as it is a different scope 🤔 Any pointers on how to use them regardless of that? Should I just use the static methods on the singleton? e.g.
ThirdPartyEmailPasswordNode.getUsersByEmail(email)
?
r
Yea. You can just do that
also, my code snippet is for emailpasword recipe, since you are using ThirdPartyEmailPassword, be careful when copy / pasting
n
got it working!
r
awesome!
n
As some general feedback, I expected this to be soleveable by using the functions override with something like
passwordReset
. Tinkering within apis
generatePasswordResetTokenPOST
feels a bit dangerous, but in the end it works 🙂
r
hmmm. The function override is not called if the email doesn't exist (since the input to that is the userId).
n
yes that is exactly what I figured out 😄
maybe something like
resolvePasswordResetUser
could fit in there, but I can also understand that adding to much overrides might become to convoluted at some point
r
yeaaaa.. there is always a balance. hehe
n
3 Views