UNPKG

supertokens-node

Version:
233 lines (232 loc) 11.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = getRecipeInterface; const constants_1 = require("./constants"); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const constants_2 = require("../multitenancy/constants"); const user_1 = require("../../user"); const authUtils_1 = require("../../authUtils"); function getRecipeInterface(stInstance, querier, getEmailPasswordConfig) { return { signUp: async function ({ email, password, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) { const response = await this.createNewRecipeUser({ email, password, tenantId, userContext, }); if (response.status !== "OK") { return response; } let updatedUser = response.user; const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ stInstance, tenantId, inputUser: updatedUser, recipeUserId: response.recipeUserId, session, shouldTryLinkingWithSessionUser, userContext, }); if (linkResult.status != "OK") { return linkResult; } updatedUser = linkResult.user; return { status: "OK", user: updatedUser, recipeUserId: response.recipeUserId, }; }, createNewRecipeUser: async function (input) { const resp = await querier.sendPostRequest({ path: "/<tenantId>/recipe/signup", params: { tenantId: input.tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : input.tenantId, }, }, { email: input.email, password: input.password, }, input.userContext); if (resp.status === "OK") { return { status: "OK", user: new user_1.User(resp.user), recipeUserId: new recipeUserId_1.default(resp.recipeUserId), }; } return resp; // we do not do email verification here cause it's a new user and email password // users are always initially unverified. }, signIn: async function ({ email, password, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) { const response = await this.verifyCredentials({ email, password, tenantId, userContext }); if (response.status === "OK") { const loginMethod = response.user.loginMethods.find((lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString()); if (!loginMethod.verified) { await stInstance .getRecipeInstanceOrThrow("accountlinking") .verifyEmailForRecipeUserIfLinkedAccountsAreVerified({ user: response.user, recipeUserId: response.recipeUserId, userContext, }); // Unlike in the sign up recipe function, we do not do account linking here // cause we do not want sign in to change the potentially user ID of a user // due to linking when this function is called by the dev in their API - // for example in their update password API. If we did account linking // then we would have to ask the dev to also change the session // in such API calls. // In the case of sign up, since we are creating a new user, it's fine // to link there since there is no user id change really from the dev's // point of view who is calling the sign up recipe function. // We do this so that we get the updated user (in case the above // function updated the verification status) and can return that response.user = (await stInstance .getRecipeInstanceOrThrow("accountlinking") .recipeInterfaceImpl.getUser({ userId: response.recipeUserId.getAsString(), userContext })); } const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ stInstance, tenantId, inputUser: response.user, recipeUserId: response.recipeUserId, session, shouldTryLinkingWithSessionUser, userContext, }); if (linkResult.status === "LINKING_TO_SESSION_USER_FAILED") { return linkResult; } response.user = linkResult.user; } return response; }, verifyCredentials: async function ({ email, password, tenantId, userContext }) { const response = await querier.sendPostRequest({ path: "/<tenantId>/recipe/signin", params: { tenantId: tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId, }, }, { email, password, }, userContext); if (response.status === "OK") { return { status: "OK", user: new user_1.User(response.user), recipeUserId: new recipeUserId_1.default(response.recipeUserId), }; } return { status: "WRONG_CREDENTIALS_ERROR", }; }, createResetPasswordToken: async function ({ userId, email, tenantId, userContext, }) { // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest({ path: "/<tenantId>/recipe/user/password/reset/token", params: { tenantId: tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId, }, }, { userId, email, }, userContext); }, consumePasswordResetToken: async function ({ token, tenantId, userContext, }) { return await querier.sendPostRequest({ path: "/<tenantId>/recipe/user/password/reset/token/consume", params: { tenantId: tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId, }, }, { token, }, userContext); }, updateEmailOrPassword: async function (input) { const accountLinking = stInstance.getRecipeInstanceOrThrow("accountlinking"); if (input.email) { const user = await stInstance.getRecipeInstanceOrThrow("accountlinking").recipeInterfaceImpl.getUser({ userId: input.recipeUserId.getAsString(), userContext: input.userContext, }); if (user === undefined) { return { status: "UNKNOWN_USER_ID_ERROR" }; } const evInstance = stInstance.getRecipeInstance("emailverification"); let isEmailVerified = false; if (evInstance) { isEmailVerified = await evInstance.recipeInterfaceImpl.isEmailVerified({ recipeUserId: input.recipeUserId, email: input.email, userContext: input.userContext, }); } const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ user, isVerified: isEmailVerified, newEmail: input.email, session: undefined, userContext: input.userContext, }); if (!isEmailChangeAllowed.allowed) { return { status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", reason: isEmailChangeAllowed.reason === "ACCOUNT_TAKEOVER_RISK" ? "New email cannot be applied to existing account because of account takeover risks." : "New email cannot be applied to existing account because of there is another primary user with the same email address.", }; } } if (input.applyPasswordPolicy || input.applyPasswordPolicy === undefined) { let formFields = getEmailPasswordConfig().signUpFeature.formFields; if (input.password !== undefined) { const passwordField = formFields.filter((el) => el.id === constants_1.FORM_FIELD_PASSWORD_ID)[0]; const error = await passwordField.validate(input.password, input.tenantIdForPasswordPolicy, input.userContext); if (error !== undefined) { return { status: "PASSWORD_POLICY_VIOLATED_ERROR", failureReason: error, }; } } } // We do not check for AccountLinking.isEmailChangeAllowed here cause // that may return false if the user's email is not verified, and this // function should not fail due to lack of email verification - since it's // really up to the developer to decide what should be the pre condition for // a change in email. The check for email verification should actually go in // an update email API (post login update). let response = await querier.sendPutRequest("/recipe/user", { recipeUserId: input.recipeUserId.getAsString(), email: input.email, password: input.password, }, {}, input.userContext); if (response.status === "OK") { const user = await stInstance.getRecipeInstanceOrThrow("accountlinking").recipeInterfaceImpl.getUser({ userId: input.recipeUserId.getAsString(), userContext: input.userContext, }); if (user === undefined) { // This means that the user was deleted between the put and get requests return { status: "UNKNOWN_USER_ID_ERROR", }; } await stInstance .getRecipeInstanceOrThrow("accountlinking") .verifyEmailForRecipeUserIfLinkedAccountsAreVerified({ user, recipeUserId: input.recipeUserId, userContext: input.userContext, }); } return response; }, }; }