UNPKG

supertokens-node

Version:
230 lines (229 loc) 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = getAPIInterface; const authUtils_1 = require("../../../authUtils"); const logger_1 = require("../../../logger"); const utils_1 = require("../../../utils"); function getAPIInterface(stInstance) { return { authorisationUrlGET: async function ({ tenantId, provider, redirectURIOnProviderDashboard, userContext }) { const authUrl = await provider.getAuthorisationRedirectURL({ tenantId, redirectURIOnProviderDashboard, userContext, }); return Object.assign({ status: "OK" }, authUrl); }, signInUpPOST: async function (input) { const errorCodeMap = { SIGN_UP_NOT_ALLOWED: "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_006)", SIGN_IN_NOT_ALLOWED: "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_004)", LINKING_TO_SESSION_USER_FAILED: { EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_020)", RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_021)", ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_022)", SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_023)", }, }; const { provider, tenantId, options, userContext } = input; let oAuthTokensToUse = {}; if ("redirectURIInfo" in input && input.redirectURIInfo !== undefined) { if (provider.type === "oauth2") { oAuthTokensToUse = await provider.exchangeAuthCodeForOAuthTokens({ tenantId, redirectURIInfo: input.redirectURIInfo, userContext, }); } else if (provider.type === "saml") { oAuthTokensToUse.access_token = input.redirectURIInfo.redirectURIQueryParams.code; } } else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { oAuthTokensToUse = input.oAuthTokens; } else { throw Error("should never come here"); } const userInfo = provider.type === "oauth2" ? await provider.getUserInfo({ tenantId, oAuthTokens: oAuthTokensToUse, userContext }) : await provider.getUserInfo({ tenantId, accessToken: oAuthTokensToUse.access_token, userContext }); if (userInfo.email === undefined && provider.config.requireEmail === false) { userInfo.email = { id: await provider.config.generateFakeEmail({ thirdPartyUserId: userInfo.thirdPartyUserId, tenantId, userContext, }), isVerified: true, }; } let emailInfo = userInfo.email; if (emailInfo === undefined) { return { status: "NO_EMAIL_GIVEN_BY_PROVIDER", }; } const recipeId = "thirdparty"; let checkCredentialsOnTenant = async () => { // We essentially did this above when calling exchangeAuthCodeForOAuthTokens return true; }; const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( { stInstance, accountInfo: { thirdParty: { userId: userInfo.thirdPartyUserId, id: provider.id, }, }, recipeId, userContext: input.userContext, session: input.session, tenantId, checkCredentialsOnTenant, } ); const isSignUp = authenticatingUser === undefined; if (authenticatingUser !== undefined) { // This is a sign in. So before we proceed, we need to check if an email change // is allowed since the email could have changed from the social provider's side. // We do this check here and not in the recipe function cause we want to keep the // recipe function checks to a minimum so that the dev has complete control of // what they can do. // The isEmailChangeAllowed and preAuthChecks functions take an isVerified boolean. // Now, even though we already have that from the input, that's just what the provider says. // If the provider says that the email is NOT verified, it could have been that the email // is verified on the user's account via supertokens on a previous sign in / up. // So we just check that as well before calling isEmailChangeAllowed const recipeUserId = authenticatingUser.loginMethod.recipeUserId; const emailVerificationRecipe = stInstance.getRecipeInstance("emailverification"); if (!emailInfo.isVerified && emailVerificationRecipe !== undefined) { emailInfo.isVerified = await emailVerificationRecipe.recipeInterfaceImpl.isEmailVerified({ recipeUserId: recipeUserId, email: emailInfo.id, userContext, }); } } const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ stInstance, authenticatingAccountInfo: { recipeId, email: emailInfo.id, thirdParty: { userId: userInfo.thirdPartyUserId, id: provider.id, }, }, authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, factorIds: ["thirdparty"], isSignUp, isVerified: emailInfo.isVerified, // this can be true if: // - the third party provider marked the email as verified // - the email address is changing and the new address has been verified previously // in both cases, the user will end up with a verified login method after sign in completes signInVerifiesLoginMethod: emailInfo.isVerified, skipSessionUserUpdateInCore: false, tenantId: input.tenantId, userContext: input.userContext, session: input.session, shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { (0, logger_1.logDebugMessage)( "signInUpPOST: erroring out because preAuthChecks returned " + preAuthChecks.status ); // On the frontend, this should show a UI of asking the user // to login using a different method. return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( preAuthChecks, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED" ); } let response = await options.recipeImplementation.signInUp({ thirdPartyId: provider.id, thirdPartyUserId: userInfo.thirdPartyUserId, email: emailInfo.id, isVerified: emailInfo.isVerified, oAuthTokens: oAuthTokensToUse, rawUserInfoFromProvider: userInfo.rawUserInfoFromProvider, session: input.session, shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId, userContext, }); if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { // In this case we do not need to do mapping, since the recipe function already has the right response shape. return response; } if (response.status !== "OK") { (0, logger_1.logDebugMessage)( "signInUpPOST: erroring out because signInUp returned " + response.status ); return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( response, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED" ); } // Here we do these checks after sign in is done cause: // - We first want to check if the credentials are correct first or not // - The above recipe function marks the email as verified // - Even though the above call to signInUp is state changing (it changes the email // of the user), it's OK to do this check here cause the preAuthChecks checks // conditions related to account linking const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ stInstance, factorId: "thirdparty", isSignUp, authenticatedUser: response.user, recipeUserId: response.recipeUserId, req: input.options.req, res: input.options.res, tenantId: input.tenantId, userContext: input.userContext, session: input.session, }); if (postAuthChecks.status !== "OK") { (0, logger_1.logDebugMessage)( "signInUpPOST: erroring out because postAuthChecks returned " + postAuthChecks.status ); return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( postAuthChecks, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED" ); } return { status: "OK", createdNewRecipeUser: response.createdNewRecipeUser, user: postAuthChecks.user, session: postAuthChecks.session, oAuthTokens: oAuthTokensToUse, rawUserInfoFromProvider: userInfo.rawUserInfoFromProvider, }; }, appleRedirectHandlerPOST: async function ({ formPostInfoFromProvider, options }) { const stateInBase64 = formPostInfoFromProvider.state; const state = (0, utils_1.decodeBase64)(stateInBase64); const stateObj = JSON.parse(state); const redirectURI = stateObj.frontendRedirectURI; const urlObj = new URL(redirectURI); for (const [key, value] of Object.entries(formPostInfoFromProvider)) { urlObj.searchParams.set(key, `${value}`); } options.res.setHeader("Location", urlObj.toString(), false); options.res.setStatusCode(303); options.res.sendHTMLResponse(""); }, }; }