UNPKG

supertokens-node

Version:
376 lines (375 loc) 16.2 kB
// @ts-nocheck import { SessionContainerInterface } from "./recipe/session/types"; import { UserContext } from "./types"; import { LoginMethod, User } from "./user"; import RecipeUserId from "./recipeUserId"; import { AccountInfoWithRecipeId } from "./recipe/accountlinking/types"; import type { BaseRequest, BaseResponse } from "./framework"; import type SuperTokens from "./supertokens"; export declare const AuthUtils: { /** * This helper function can be used to map error statuses (w/ an optional reason) to error responses with human readable reasons. * This maps to a response in the format of `{ status: "3rd param", reason: "human readable string from second param" }` * * The errorCodeMap is expected to be something like: * ``` * { * EMAIL_VERIFICATION_REQUIRED: "This is returned as reason if the resp(1st param) has the status code EMAIL_VERIFICATION_REQUIRED and an undefined reason", * STATUS: { * REASON: "This is returned as reason if the resp(1st param) has STATUS in the status prop and REASON in the reason prop" * } * } * ``` */ getErrorStatusResponseWithReason<T = "SIGN_IN_UP_NOT_ALLOWED">( resp: { status: string; reason?: string; }, errorCodeMap: Record<string, Record<string, string | undefined> | string | undefined>, errorStatus: T ): { status: T; reason: string; }; /** * Runs all checks we need to do before trying to authenticate a user: * - if this is a first factor auth or not * - if the session user is required to be primary (and tries to make it primary if necessary) * - if any of the factorids are valid (as first or secondary factors), taking into account mfa factor setup rules * - if sign up is allowed (if isSignUp === true) * * It returns the following statuses: * - OK: the auth flow can proceed * - SIGN_UP_NOT_ALLOWED: if isSignUpAllowed returned false. This is mostly because of conflicting users with the same account info * - LINKING_TO_SESSION_USER_FAILED (SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if the session user should become primary but we couldn't make it primary because of a conflicting primary user. */ preAuthChecks: ({ stInstance, authenticatingAccountInfo, tenantId, isSignUp, isVerified, signInVerifiesLoginMethod, authenticatingUser, factorIds, skipSessionUserUpdateInCore, session, shouldTryLinkingWithSessionUser, userContext, }: { stInstance: SuperTokens; authenticatingAccountInfo: AccountInfoWithRecipeId; authenticatingUser: User | undefined; tenantId: string; factorIds: string[]; isSignUp: boolean; isVerified: boolean; signInVerifiesLoginMethod: boolean; skipSessionUserUpdateInCore: boolean; session?: SessionContainerInterface; shouldTryLinkingWithSessionUser: boolean | undefined; userContext: UserContext; }) => Promise< | { status: "OK"; validFactorIds: string[]; isFirstFactor: boolean; } | { status: "SIGN_UP_NOT_ALLOWED"; } | { status: "SIGN_IN_NOT_ALLOWED"; } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; } >; /** * Runs the linking process and all check we need to before creating a session + creates the new session if necessary: * - runs the linking process which will: try to link to the session user, or link by account info or try to make the authenticated user primary * - checks if sign in is allowed (if isSignUp === false) * - creates a session if necessary * - marks the factor as completed if necessary * * It returns the following statuses: * - OK: the auth flow went as expected * - LINKING_TO_SESSION_USER_FAILED(EMAIL_VERIFICATION_REQUIRED): if we couldn't link to the session user because linking requires email verification * - LINKING_TO_SESSION_USER_FAILED(RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if we couldn't link to the session user because the authenticated user has been linked to another primary user concurrently * - LINKING_TO_SESSION_USER_FAILED(ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if we couldn't link to the session user because of a conflicting primary user that has the same account info as authenticatedUser * - LINKING_TO_SESSION_USER_FAILED (SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if the session user should be primary but we couldn't make it primary because of a conflicting primary user. */ postAuthChecks: ({ stInstance, authenticatedUser, recipeUserId, isSignUp, factorId, session, req, res, tenantId, userContext, }: { stInstance: SuperTokens; authenticatedUser: User; recipeUserId: RecipeUserId; tenantId: string; factorId: string; isSignUp: boolean; session?: SessionContainerInterface; userContext: UserContext; req: BaseRequest; res: BaseResponse; }) => Promise< | { status: "OK"; session: SessionContainerInterface; user: User; } | { status: "SIGN_IN_NOT_ALLOWED"; } >; /** * This function tries to find the authenticating user (we use this information to see if the current auth is sign in or up) * if a session was passed and the authenticating user was not found on the current tenant, it checks if the session user * has a matching login method on other tenants. If it does and the credentials check out on the other tenant, it associates * the recipe user for the login method (matching account info, recipeId and credentials) with the current tenant. * * While this initially complicates the auth logic, we want to avoid creating a new recipe user if a tenant association will do, * because it'll make managing MFA factors (i.e.: secondary passwords) a lot easier for the app, and, * most importantly, this way all secondary factors are app-wide instead of mixing app-wide (totp) and tenant-wide (password) factors. */ getAuthenticatingUserAndAddToCurrentTenantIfRequired: ({ stInstance, recipeId, accountInfo, checkCredentialsOnTenant, tenantId, session, userContext, }: { stInstance: SuperTokens; recipeId: string; accountInfo: | { email: string; thirdParty?: undefined; phoneNumber?: undefined; webauthn?: undefined; } | { email?: undefined; thirdParty?: undefined; phoneNumber: string; webauthn?: undefined; } | { email?: undefined; thirdParty: { id: string; userId: string; }; phoneNumber?: undefined; webauthn?: undefined; } | { email?: undefined; thirdParty?: undefined; phoneNumber?: undefined; webauthn: { credentialId: string; }; }; tenantId: string; session: SessionContainerInterface | undefined; checkCredentialsOnTenant: (tenantId: string) => Promise<boolean>; userContext: UserContext; }) => Promise< | { user: User; loginMethod: LoginMethod; } | undefined >; /** * This function checks if the current authentication attempt should be considered a first factor or not. * To do this it'll also need to (if a session was passed): * - load the session user (and possibly make it primary) * - check the linking status of the input and session user * - call and check the results of shouldDoAutomaticAccountLinking * So in the non-first factor case it also returns the results of those checks/operations. * * It returns the following statuses: * - OK: if everything went well * - LINKING_TO_SESSION_USER_FAILED (SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if the session user should be primary but we couldn't make it primary because of a conflicting primary user. */ checkAuthTypeAndLinkingStatus: ( stInstance: SuperTokens, session: SessionContainerInterface | undefined, shouldTryLinkingWithSessionUser: boolean | undefined, accountInfo: AccountInfoWithRecipeId, inputUser: User | undefined, skipSessionUserUpdateInCore: boolean, userContext: UserContext ) => Promise< | { status: "OK"; isFirstFactor: true; } | { status: "OK"; isFirstFactor: false; inputUserAlreadyLinkedToSessionUser: true; sessionUser: User; } | { status: "OK"; isFirstFactor: false; inputUserAlreadyLinkedToSessionUser: false; sessionUser: User; linkingToSessionUserRequiresVerification: boolean; } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; } >; /** * This function checks the auth type (first factor or not), links by account info for first factor auths otherwise * it tries to link the input user to the session user * * It returns the following statuses: * - OK: the linking went as expected * - LINKING_TO_SESSION_USER_FAILED(EMAIL_VERIFICATION_REQUIRED): if we couldn't link to the session user because linking requires email verification * - LINKING_TO_SESSION_USER_FAILED(RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if we couldn't link to the session user because the authenticated user has been linked to another primary user concurrently * - LINKING_TO_SESSION_USER_FAILED(ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if we couldn't link to the session user because of a conflicting primary user that has the same account info as authenticatedUser * - LINKING_TO_SESSION_USER_FAILED (SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if the session user should be primary but we couldn't make it primary because of a conflicting primary user. */ linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo: ({ stInstance, tenantId, inputUser, recipeUserId, session, shouldTryLinkingWithSessionUser, userContext, }: { stInstance: SuperTokens; tenantId: string; inputUser: User; recipeUserId: RecipeUserId; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; userContext: UserContext; }) => Promise< | { status: "OK"; user: User; } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: | "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; } >; /** * This function loads the session user and tries to make it primary. * It returns: * - OK: if the session user was a primary user or we made it into one or it can/should become one but `skipSessionUserUpdateInCore` is set to true * - SHOULD_AUTOMATICALLY_LINK_FALSE: if shouldDoAutomaticAccountLinking returned `{ shouldAutomaticallyLink: false }` * - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: * If we tried to make it into a primary user but it didn't succeed because of a conflicting primary user * * It throws INVALID_CLAIM_ERROR if shouldDoAutomaticAccountLinking returned `{ shouldAutomaticallyLink: false }` but the email verification status was wrong */ tryAndMakeSessionUserIntoAPrimaryUser: ( stInstance: SuperTokens, session: SessionContainerInterface, skipSessionUserUpdateInCore: boolean, userContext: UserContext ) => Promise< | { status: "OK"; sessionUser: User; } | { status: "SHOULD_AUTOMATICALLY_LINK_FALSE"; } | { status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; } >; /** * This function tries linking by session, and doesn't attempt to make the authenticated user a primary or link it by account info * * It returns the following statuses: * - OK: the linking went as expected * - LINKING_TO_SESSION_USER_FAILED(EMAIL_VERIFICATION_REQUIRED): if we couldn't link to the session user because linking requires email verification * - LINKING_TO_SESSION_USER_FAILED(RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if we couldn't link to the session user because the authenticated user has been linked to another primary user concurrently * - LINKING_TO_SESSION_USER_FAILED(ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if we couldn't link to the session user because of a conflicting primary user that has the same account info as authenticatedUser * - LINKING_TO_SESSION_USER_FAILED (INPUT_USER_IS_NOT_A_PRIMARY_USER): * if the session user is not primary. This can be resolved by making it primary and retrying the call. */ tryLinkingBySession: ({ stInstance, linkingToSessionUserRequiresVerification, authLoginMethod, authenticatedUser, sessionUser, userContext, }: { stInstance: SuperTokens; authenticatedUser: User; linkingToSessionUserRequiresVerification: boolean; sessionUser: User; authLoginMethod: LoginMethod; userContext: UserContext; }) => Promise< | { status: "OK"; user: User; } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: | "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; } >; filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid: ( stInstance: SuperTokens, factorIds: string[], tenantId: string, hasSession: boolean, userContext: UserContext ) => Promise<string[]>; loadSessionInAuthAPIIfNeeded: ( stInstance: SuperTokens, req: BaseRequest, res: BaseResponse, shouldTryLinkingWithSessionUser: boolean | undefined, userContext: UserContext ) => Promise<SessionContainerInterface | undefined>; };