From c57b645e8bc0abef1bd61ae71a5726fc4ddad0ea Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Thu, 10 Oct 2024 15:47:16 +0300 Subject: [PATCH 01/36] add initial passkey types --- lib/ts/recipe/passkey/types.ts | 473 +++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 lib/ts/recipe/passkey/types.ts diff --git a/lib/ts/recipe/passkey/types.ts b/lib/ts/recipe/passkey/types.ts new file mode 100644 index 000000000..c04f27aae --- /dev/null +++ b/lib/ts/recipe/passkey/types.ts @@ -0,0 +1,473 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import type { BaseRequest, BaseResponse } from "../../framework"; +import OverrideableBuilder from "supertokens-js-override"; +import { SessionContainerInterface } from "../session/types"; +import { + TypeInput as EmailDeliveryTypeInput, + TypeInputWithService as EmailDeliveryTypeInputWithService, +} from "../../ingredients/emaildelivery/types"; +import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; +import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; +import RecipeUserId from "../../recipeUserId"; + +// default implementation for the TypeInput +// todo update this ??? +export type TypeNormalisedInput = { + validateEmail: (value: any, tenantId: string, userContext: UserContext) => Promise; + relyingPartyId: (input: { request: BaseRequest | undefined; userContext: UserContext }) => string; // should return the domain of the origin + relyingPartyName: (input: { request: BaseRequest | undefined; userContext: UserContext }) => string; // should return the app name + getEmailDeliveryConfig: ( + isInServerlessEnv: boolean + ) => EmailDeliveryTypeInputWithService; + override: { + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; + +export type TypeInput = { + emailDelivery?: EmailDeliveryTypeInput; + validateEmail?: (value: any, tenantId: string, userContext: UserContext) => Promise; + relyingPartyId?: string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); + relyingPartyName?: string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); + override?: { + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; + +export type RecipeInterface = { + registerPasskeyOptions(input: { + email: string; + password: string; + session: SessionContainerInterface | undefined; + tenantId: string; + userContext: UserContext; + }): Promise<{ + status: "OK"; + passkeyGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: string; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + }>; + + signInPasskeyOptions(input: { + session: SessionContainerInterface | undefined; + tenantId: string; + userContext: UserContext; + }): Promise<{ + status: "OK"; + passkeyGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + }>; + + signUp(input: { + email: string | undefined; + passkeyGeneratedOptionsId: string; + passkey: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { + 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"; + } + >; + + signIn(input: { + passkeyGeneratedOptionsId: string; + passkey: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + }): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { + 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"; + } + >; + + /** + * We pass in the email as well to this function cause the input userId + * may not be associated with an passkey account. In this case, we + * need to know which email to use to create an passkey account later on. + */ + generateRecoverAccountToken(input: { + userId: string; // the id can be either recipeUserId or primaryUserId + email: string; + tenantId: string; + userContext: UserContext; + }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>; + + consumeRecoverAccountToken(input: { + token: string; + passkey: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { status: "RECOVER_ACCOUNT_INVALID_TOKEN_ERROR" } + >; + + // this function is meant only for creating the recipe in the core and nothing else. + // we added this even though signUp exists cause devs may override signup expecting it + // to be called just during sign up. But we also need a version of signing up which can be + // called during operations like creating a user during password reset flow. + createNewRecipeUser(input: { + email: string; + passkeyGeneratedOptionsId: string; + passkey: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + >; +}; + +export type APIOptions = { + recipeImplementation: RecipeInterface; + appInfo: NormalisedAppinfo; + config: TypeNormalisedInput; + recipeId: string; + isInServerlessEnv: boolean; + req: BaseRequest; + res: BaseResponse; + emailDelivery: EmailDeliveryIngredient; +}; + +export type APIInterface = { + registerPasskeyOptionsPOST: + | undefined + | ((input: { + email: string | undefined; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + passkeyGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: string; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + } + | GeneralErrorResponse + >); + + signInPasskeyOptionsPOST: + | undefined + | ((input: { + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + passkeyGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + } + | GeneralErrorResponse + >); + + signUpPOST: + | undefined + | ((input: { + email: string; + passkeyGeneratedOptionsId: string; + passkey: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + } + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | GeneralErrorResponse + >); + + signInPOST: + | undefined + | ((input: { + passkeyGeneratedOptionsId: string; + passkey: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | GeneralErrorResponse + >); + + generateRecoverAccountTokenPOST: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | { + status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + >); + + recoverAccountPOST: + | undefined + | ((input: { + passkey: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + token: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + email: string; + user: User; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_TOKEN_ERROR"; + } + | GeneralErrorResponse + >); + + // used for checking if the email already exists before generating the passkey + emailExistsGET: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + exists: boolean; + } + | GeneralErrorResponse + >); +}; +// todo update this ??? +export type TypeEmailPasswordPasswordResetEmailDeliveryInput = { + type: "PASSWORD_RESET"; + user: { + id: string; + recipeUserId: RecipeUserId | undefined; + email: string; + }; + passwordResetLink: string; + tenantId: string; +}; + +export type TypeEmailPasswordEmailDeliveryInput = TypeEmailPasswordPasswordResetEmailDeliveryInput; From 56422f35d7907d3ab907268440a36970d79ed58b Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 11 Oct 2024 09:46:18 +0300 Subject: [PATCH 02/36] passkey types cleanup --- lib/ts/recipe/passkey/types.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/ts/recipe/passkey/types.ts b/lib/ts/recipe/passkey/types.ts index c04f27aae..cae369644 100644 --- a/lib/ts/recipe/passkey/types.ts +++ b/lib/ts/recipe/passkey/types.ts @@ -24,15 +24,13 @@ import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; -// default implementation for the TypeInput -// todo update this ??? export type TypeNormalisedInput = { validateEmail: (value: any, tenantId: string, userContext: UserContext) => Promise; relyingPartyId: (input: { request: BaseRequest | undefined; userContext: UserContext }) => string; // should return the domain of the origin relyingPartyName: (input: { request: BaseRequest | undefined; userContext: UserContext }) => string; // should return the app name getEmailDeliveryConfig: ( isInServerlessEnv: boolean - ) => EmailDeliveryTypeInputWithService; + ) => EmailDeliveryTypeInputWithService; override: { functions: ( originalImplementation: RecipeInterface, @@ -43,7 +41,7 @@ export type TypeNormalisedInput = { }; export type TypeInput = { - emailDelivery?: EmailDeliveryTypeInput; + emailDelivery?: EmailDeliveryTypeInput; validateEmail?: (value: any, tenantId: string, userContext: UserContext) => Promise; relyingPartyId?: string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); relyingPartyName?: string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); @@ -59,8 +57,6 @@ export type TypeInput = { export type RecipeInterface = { registerPasskeyOptions(input: { email: string; - password: string; - session: SessionContainerInterface | undefined; tenantId: string; userContext: UserContext; }): Promise<{ @@ -253,7 +249,7 @@ export type APIOptions = { isInServerlessEnv: boolean; req: BaseRequest; res: BaseResponse; - emailDelivery: EmailDeliveryIngredient; + emailDelivery: EmailDeliveryIngredient; }; export type APIInterface = { @@ -458,16 +454,16 @@ export type APIInterface = { | GeneralErrorResponse >); }; -// todo update this ??? -export type TypeEmailPasswordPasswordResetEmailDeliveryInput = { - type: "PASSWORD_RESET"; + +export type TypePasskeyRecoverAccountEmailDeliveryInput = { + type: "RECOVER_ACCOUNT"; user: { id: string; recipeUserId: RecipeUserId | undefined; email: string; }; - passwordResetLink: string; + recoverAccountLink: string; tenantId: string; }; -export type TypeEmailPasswordEmailDeliveryInput = TypeEmailPasswordPasswordResetEmailDeliveryInput; +export type TypePasskeyEmailDeliveryInput = TypePasskeyRecoverAccountEmailDeliveryInput; From f102128d1de238d47131b75eb6672f951aa28664 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Wed, 16 Oct 2024 18:07:39 +0300 Subject: [PATCH 03/36] added untested support for options, sign in and sign up methods --- lib/ts/recipe/accountlinking/types.ts | 2 +- lib/ts/recipe/multifactorauth/types.ts | 1 + lib/ts/recipe/webauthn/api/implementation.ts | 1059 +++++++++++++++++ lib/ts/recipe/webauthn/api/registerOptions.ts | 50 + lib/ts/recipe/webauthn/api/signInOptions.ts | 38 + lib/ts/recipe/webauthn/api/signin.ts | 75 ++ lib/ts/recipe/webauthn/api/signup.ts | 92 ++ lib/ts/recipe/webauthn/api/utils.ts | 50 + lib/ts/recipe/webauthn/constants.ts | 34 + lib/ts/recipe/webauthn/core-mock.ts | 84 ++ lib/ts/recipe/webauthn/error.ts | 41 + lib/ts/recipe/webauthn/index.ts | 386 ++++++ lib/ts/recipe/webauthn/recipe.ts | 357 ++++++ .../recipe/webauthn/recipeImplementation.ts | 376 ++++++ lib/ts/recipe/{passkey => webauthn}/types.ts | 329 ++--- lib/ts/recipe/webauthn/utils.ts | 171 +++ 16 files changed, 3005 insertions(+), 140 deletions(-) create mode 100644 lib/ts/recipe/webauthn/api/implementation.ts create mode 100644 lib/ts/recipe/webauthn/api/registerOptions.ts create mode 100644 lib/ts/recipe/webauthn/api/signInOptions.ts create mode 100644 lib/ts/recipe/webauthn/api/signin.ts create mode 100644 lib/ts/recipe/webauthn/api/signup.ts create mode 100644 lib/ts/recipe/webauthn/api/utils.ts create mode 100644 lib/ts/recipe/webauthn/constants.ts create mode 100644 lib/ts/recipe/webauthn/core-mock.ts create mode 100644 lib/ts/recipe/webauthn/error.ts create mode 100644 lib/ts/recipe/webauthn/index.ts create mode 100644 lib/ts/recipe/webauthn/recipe.ts create mode 100644 lib/ts/recipe/webauthn/recipeImplementation.ts rename lib/ts/recipe/{passkey => webauthn}/types.ts (66%) create mode 100644 lib/ts/recipe/webauthn/utils.ts diff --git a/lib/ts/recipe/accountlinking/types.ts b/lib/ts/recipe/accountlinking/types.ts index 83aec5230..9426edbc9 100644 --- a/lib/ts/recipe/accountlinking/types.ts +++ b/lib/ts/recipe/accountlinking/types.ts @@ -195,7 +195,7 @@ export type AccountInfo = { }; export type AccountInfoWithRecipeId = { - recipeId: "emailpassword" | "thirdparty" | "passwordless"; + recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; } & AccountInfo; export type RecipeLevelUser = { diff --git a/lib/ts/recipe/multifactorauth/types.ts b/lib/ts/recipe/multifactorauth/types.ts index a7e340662..693e8e646 100644 --- a/lib/ts/recipe/multifactorauth/types.ts +++ b/lib/ts/recipe/multifactorauth/types.ts @@ -154,6 +154,7 @@ export type GetPhoneNumbersForFactorsFromOtherRecipesFunc = ( export const FactorIds = { EMAILPASSWORD: "emailpassword", + WEBAUTHN: "webauthn", OTP_EMAIL: "otp-email", OTP_PHONE: "otp-phone", LINK_EMAIL: "link-email", diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts new file mode 100644 index 000000000..2408114ff --- /dev/null +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -0,0 +1,1059 @@ +import { APIInterface, APIOptions } from ".."; +import { GeneralErrorResponse, User, UserContext } from "../../../types"; +import AccountLinking from "../../accountlinking/recipe"; +import { AuthUtils } from "../../../authUtils"; +import { isFakeEmail } from "../../thirdparty/utils"; +import { SessionContainerInterface } from "../../session/types"; +import { + DEFAULT_REGISTER_ATTESTATION, + DEFAULT_REGISTER_OPTIONS_TIMEOUT, + DEFAULT_SIGNIN_OPTIONS_TIMEOUT, +} from "../constants"; + +export default function getAPIImplementation(): APIInterface { + return { + signInOptionsPOST: async function ({ + tenantId, + options, + userContext, + }: { + tenantId: string; + options: APIOptions; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + } + | GeneralErrorResponse + > { + // todo move to recipe implementation + const timeout = DEFAULT_SIGNIN_OPTIONS_TIMEOUT; + + const relyingPartyId = options.config.relyingPartyId({ request: options.req, userContext: userContext }); + + // use this to get the full url instead of only the domain url + const origin = options.appInfo + .getOrigin({ request: options.req, userContext: userContext }) + .getAsStringDangerous(); + + let response = await options.recipeImplementation.signInOptions({ + origin, + relyingPartyId, + timeout, + tenantId, + userContext, + }); + + return { + status: "OK", + webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, + challenge: response.challenge, + timeout: response.timeout, + userVerification: response.userVerification, + }; + }, + registerOptionsPOST: async function ({ + email, + tenantId, + options, + userContext, + }: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: string; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + } + | GeneralErrorResponse + > { + // todo move to recipe implementation + const timeout = DEFAULT_REGISTER_OPTIONS_TIMEOUT; + // todo move to recipe implementation + const attestation = DEFAULT_REGISTER_ATTESTATION; + + const relyingPartyId = options.config.relyingPartyId({ request: options.req, userContext: userContext }); + const relyingPartyName = options.config.relyingPartyName({ + request: options.req, + userContext: userContext, + }); + + const origin = options.appInfo + .getOrigin({ request: options.req, userContext: userContext }) + .getAsStringDangerous(); + + let response = await options.recipeImplementation.registerOptions({ + email, + attestation, + origin, + relyingPartyId, + relyingPartyName, + timeout, + tenantId, + userContext, + }); + + return { + status: "OK", + webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, + challenge: response.challenge, + timeout: response.timeout, + attestation: response.attestation, + pubKeyCredParams: response.pubKeyCredParams, + excludeCredentials: response.excludeCredentials, + rp: response.rp, + user: response.user, + authenticatorSelection: response.authenticatorSelection, + }; + }, + signUpPOST: async function ({ + email, + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }: { + email: string; + webauthnGeneratedOptionsId: string; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + session?: SessionContainerInterface; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + session: SessionContainerInterface; + user: User; + } + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | GeneralErrorResponse + > { + const errorCodeMap = { + SIGN_UP_NOT_ALLOWED: + "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + LINKING_TO_SESSION_USER_FAILED: { + EMAIL_VERIFICATION_REQUIRED: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", + 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_016)", + }, + }; + + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (typeof email !== "string") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + + const preAuthCheckRes = await AuthUtils.preAuthChecks({ + authenticatingAccountInfo: { + recipeId: "webauthn", + email, + }, + factorIds: ["webauthn"], + isSignUp: true, + isVerified: isFakeEmail(email), + signInVerifiesLoginMethod: false, + skipSessionUserUpdateInCore: false, + authenticatingUser: undefined, // since this a sign up, this is undefined + tenantId, + userContext, + session, + shouldTryLinkingWithSessionUser, + }); + + if (preAuthCheckRes.status === "SIGN_UP_NOT_ALLOWED") { + const conflictingUsers = await AccountLinking.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + if ( + conflictingUsers.some((u) => + u.loginMethods.some((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) + ) + ) { + return { + status: "EMAIL_ALREADY_EXISTS_ERROR", + }; + } + } + if (preAuthCheckRes.status !== "OK") { + return AuthUtils.getErrorStatusResponseWithReason(preAuthCheckRes, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + } + + if (isFakeEmail(email) && preAuthCheckRes.isFirstFactor) { + // Fake emails cannot be used as a first factor + return { + status: "EMAIL_ALREADY_EXISTS_ERROR", + }; + } + + // we are using the email from the register options + const signUpResponse = await options.recipeImplementation.signUp({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }); + + if (signUpResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { + return signUpResponse; + } + if (signUpResponse.status !== "OK") { + return AuthUtils.getErrorStatusResponseWithReason(signUpResponse, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + } + + const postAuthChecks = await AuthUtils.postAuthChecks({ + authenticatedUser: signUpResponse.user, + recipeUserId: signUpResponse.recipeUserId, + isSignUp: true, + factorId: "emailpassword", + session, + req: options.req, + res: options.res, + tenantId, + userContext, + }); + + if (postAuthChecks.status !== "OK") { + // It should never actually come here, but we do it cause of consistency. + // If it does come here (in case there is a bug), it would make this func throw + // anyway, cause there is no SIGN_IN_NOT_ALLOWED in the errorCodeMap. + AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + throw new Error("This should never happen"); + } + + return { + status: "OK", + session: postAuthChecks.session, + user: postAuthChecks.user, + }; + }, + + signInPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }: { + webauthnGeneratedOptionsId: string; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + session?: SessionContainerInterface; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + session: SessionContainerInterface; + user: User; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + > { + const errorCodeMap = { + SIGN_IN_NOT_ALLOWED: + "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", + LINKING_TO_SESSION_USER_FAILED: { + EMAIL_VERIFICATION_REQUIRED: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", + 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_012)", + }, + }; + + const recipeId = "webauthn"; + + // do the verification before in order to retrieve the user email + const verifyCredentialsResponse = await options.recipeImplementation.verifyCredentials({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + const checkCredentialsOnTenant = async () => { + return verifyCredentialsResponse.status === "OK"; + }; + + // todo check if this is the correct way to retrieve the email + let email: string; + if (verifyCredentialsResponse.status == "OK") { + email = verifyCredentialsResponse.user.emails[0]; + } else { + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } + + const authenticatingUser = await AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ + accountInfo: { email }, + userContext, + recipeId, + session, + tenantId, + checkCredentialsOnTenant, + }); + + const isVerified = authenticatingUser !== undefined && authenticatingUser.loginMethod!.verified; + // We check this before preAuthChecks, because that function assumes that if isSignUp is false, + // then authenticatingUser is defined. While it wouldn't technically cause any problems with + // the implementation of that function, this way we can guarantee that either isSignInAllowed or + // isSignUpAllowed will be called as expected. + if (authenticatingUser === undefined) { + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } + const preAuthChecks = await AuthUtils.preAuthChecks({ + authenticatingAccountInfo: { + recipeId, + email, + }, + factorIds: ["webauthn"], + isSignUp: false, + authenticatingUser: authenticatingUser?.user, + isVerified, + signInVerifiesLoginMethod: false, + skipSessionUserUpdateInCore: false, + tenantId, + userContext, + session, + shouldTryLinkingWithSessionUser, + }); + if (preAuthChecks.status === "SIGN_UP_NOT_ALLOWED") { + throw new Error("This should never happen: pre-auth checks should not fail for sign in"); + } + if (preAuthChecks.status !== "OK") { + return AuthUtils.getErrorStatusResponseWithReason(preAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + } + + if (isFakeEmail(email) && preAuthChecks.isFirstFactor) { + // Fake emails cannot be used as a first factor + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } + + const signInResponse = await options.recipeImplementation.signIn({ + webauthnGeneratedOptionsId, + credential, + session, + shouldTryLinkingWithSessionUser, + tenantId, + userContext, + }); + + if (signInResponse.status === "WRONG_CREDENTIALS_ERROR") { + return signInResponse; + } + if (signInResponse.status !== "OK") { + return AuthUtils.getErrorStatusResponseWithReason(signInResponse, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + } + + const postAuthChecks = await AuthUtils.postAuthChecks({ + authenticatedUser: signInResponse.user, + recipeUserId: signInResponse.recipeUserId, + isSignUp: false, + factorId: "webauthn", + session, + req: options.req, + res: options.res, + tenantId, + userContext, + }); + + if (postAuthChecks.status !== "OK") { + return AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + } + + return { + status: "OK", + session: postAuthChecks.session, + user: postAuthChecks.user, + }; + }, + + // emailExistsGET: async function ({ + // email, + // tenantId, + // userContext, + // }: { + // email: string; + // tenantId: string; + // options: APIOptions; + // userContext: UserContext; + // }): Promise< + // | { + // status: "OK"; + // exists: boolean; + // } + // | GeneralErrorResponse + // > { + // // even if the above returns true, we still need to check if there + // // exists an email password user with the same email cause the function + // // above does not check for that. + // let users = await AccountLinking.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + // tenantId, + // accountInfo: { + // email, + // }, + // doUnionOfAccountInfo: false, + // userContext, + // }); + // let emailPasswordUserExists = + // users.find((u) => { + // return ( + // u.loginMethods.find((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)) !== + // undefined + // ); + // }) !== undefined; + + // return { + // status: "OK", + // exists: emailPasswordUserExists, + // }; + // }, + // generatePasswordResetTokenPOST: async function ({ + // formFields, + // tenantId, + // options, + // userContext, + // }): Promise< + // | { + // status: "OK"; + // } + // | { status: "PASSWORD_RESET_NOT_ALLOWED"; reason: string } + // | GeneralErrorResponse + // > { + // // NOTE: Check for email being a non-string value. This check will likely + // // never evaluate to `true` as there is an upper-level check for the type + // // in validation but kept here to be safe. + // const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; + // if (typeof emailAsUnknown !== "string") + // throw new Error( + // "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + // ); + // const email: string = emailAsUnknown; + + // // this function will be reused in different parts of the flow below.. + // async function generateAndSendPasswordResetToken( + // primaryUserId: string, + // recipeUserId: RecipeUserId | undefined + // ): Promise< + // | { + // status: "OK"; + // } + // | { status: "PASSWORD_RESET_NOT_ALLOWED"; reason: string } + // | GeneralErrorResponse + // > { + // // the user ID here can be primary or recipe level. + // let response = await options.recipeImplementation.createResetPasswordToken({ + // tenantId, + // userId: recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString(), + // email, + // userContext, + // }); + // if (response.status === "UNKNOWN_USER_ID_ERROR") { + // logDebugMessage( + // `Password reset email not sent, unknown user id: ${ + // recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() + // }` + // ); + // return { + // status: "OK", + // }; + // } + + // let passwordResetLink = getPasswordResetLink({ + // appInfo: options.appInfo, + // token: response.token, + // tenantId, + // request: options.req, + // userContext, + // }); + + // logDebugMessage(`Sending password reset email to ${email}`); + // await options.emailDelivery.ingredientInterfaceImpl.sendEmail({ + // tenantId, + // type: "PASSWORD_RESET", + // user: { + // id: primaryUserId, + // recipeUserId, + // email, + // }, + // passwordResetLink, + // userContext, + // }); + + // return { + // status: "OK", + // }; + // } + + // /** + // * check if primaryUserId is linked with this email + // */ + // let users = await AccountLinking.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + // tenantId, + // accountInfo: { + // email, + // }, + // doUnionOfAccountInfo: false, + // userContext, + // }); + + // // we find the recipe user ID of the email password account from the user's list + // // for later use. + // let emailPasswordAccount: RecipeLevelUser | undefined = undefined; + // for (let i = 0; i < users.length; i++) { + // let emailPasswordAccountTmp = users[i].loginMethods.find( + // (l) => l.recipeId === "emailpassword" && l.hasSameEmailAs(email) + // ); + // if (emailPasswordAccountTmp !== undefined) { + // emailPasswordAccount = emailPasswordAccountTmp; + // break; + // } + // } + + // // we find the primary user ID from the user's list for later use. + // let primaryUserAssociatedWithEmail = users.find((u) => u.isPrimaryUser); + + // // first we check if there even exists a primary user that has the input email + // // if not, then we do the regular flow for password reset. + // if (primaryUserAssociatedWithEmail === undefined) { + // if (emailPasswordAccount === undefined) { + // logDebugMessage(`Password reset email not sent, unknown user email: ${email}`); + // return { + // status: "OK", + // }; + // } + // return await generateAndSendPasswordResetToken( + // emailPasswordAccount.recipeUserId.getAsString(), + // emailPasswordAccount.recipeUserId + // ); + // } + + // // Next we check if there is any login method in which the input email is verified. + // // If that is the case, then it's proven that the user owns the email and we can + // // trust linking of the email password account. + // let emailVerified = + // primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // return lm.hasSameEmailAs(email) && lm.verified; + // }) !== undefined; + + // // finally, we check if the primary user has any other email / phone number + // // associated with this account - and if it does, then it means that + // // there is a risk of account takeover, so we do not allow the token to be generated + // let hasOtherEmailOrPhone = + // primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // // we do the extra undefined check below cause + // // hasSameEmailAs returns false if the lm.email is undefined, and + // // we want to check that the email is different as opposed to email + // // not existing in lm. + // return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + // }) !== undefined; + + // if (!emailVerified && hasOtherEmailOrPhone) { + // return { + // status: "PASSWORD_RESET_NOT_ALLOWED", + // reason: + // "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + // }; + // } + + // let shouldDoAccountLinkingResponse = await AccountLinking.getInstance().config.shouldDoAutomaticAccountLinking( + // emailPasswordAccount !== undefined + // ? emailPasswordAccount + // : { + // recipeId: "emailpassword", + // email, + // }, + // primaryUserAssociatedWithEmail, + // undefined, + // tenantId, + // userContext + // ); + + // // Now we need to check that if there exists any email password user at all + // // for the input email. If not, then it implies that when the token is consumed, + // // then we will create a new user - so we should only generate the token if + // // the criteria for the new user is met. + // if (emailPasswordAccount === undefined) { + // // this means that there is no email password user that exists for the input email. + // // So we check for the sign up condition and only go ahead if that condition is + // // met. + + // // But first we must check if account linking is enabled at all - cause if it's + // // not, then the new email password user that will be created in password reset + // // code consume cannot be linked to the primary user - therefore, we should + // // not generate a password reset token + // if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { + // logDebugMessage( + // `Password reset email not sent, since email password user didn't exist, and account linking not enabled` + // ); + // return { + // status: "OK", + // }; + // } + + // let isSignUpAllowed = await AccountLinking.getInstance().isSignUpAllowed({ + // newUser: { + // recipeId: "emailpassword", + // email, + // }, + // isVerified: true, // cause when the token is consumed, we will mark the email as verified + // session: undefined, + // tenantId, + // userContext, + // }); + // if (isSignUpAllowed) { + // // notice that we pass in the primary user ID here. This means that + // // we will be creating a new email password account when the token + // // is consumed and linking it to this primary user. + // return await generateAndSendPasswordResetToken(primaryUserAssociatedWithEmail.id, undefined); + // } else { + // logDebugMessage( + // `Password reset email not sent, isSignUpAllowed returned false for email: ${email}` + // ); + // return { + // status: "OK", + // }; + // } + // } + + // // At this point, we know that some email password user exists with this email + // // and also some primary user ID exist. We now need to find out if they are linked + // // together or not. If they are linked together, then we can just generate the token + // // else we check for more security conditions (since we will be linking them post token generation) + // let areTheTwoAccountsLinked = + // primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // return lm.recipeUserId.getAsString() === emailPasswordAccount!.recipeUserId.getAsString(); + // }) !== undefined; + + // if (areTheTwoAccountsLinked) { + // return await generateAndSendPasswordResetToken( + // primaryUserAssociatedWithEmail.id, + // emailPasswordAccount.recipeUserId + // ); + // } + + // // Here we know that the two accounts are NOT linked. We now need to check for an + // // extra security measure here to make sure that the input email in the primary user + // // is verified, and if not, we need to make sure that there is no other email / phone number + // // associated with the primary user account. If there is, then we do not proceed. + + // /* + // This security measure helps prevent the following attack: + // An attacker has email A and they create an account using TP and it doesn't matter if A is verified or not. Now they create another account using EP with email A and verifies it. Both these accounts are linked. Now the attacker changes the email for EP recipe to B which makes the EP account unverified, but it's still linked. + + // If the real owner of B tries to signup using EP, it will say that the account already exists so they may try to reset password which should be denied because then they will end up getting access to attacker's account and verify the EP account. + + // The problem with this situation is if the EP account is verified, it will allow further sign-ups with email B which will also be linked to this primary account (that the attacker had created with email A). + + // It is important to realize that the attacker had created another account with A because if they hadn't done that, then they wouldn't have access to this account after the real user resets the password which is why it is important to check there is another non-EP account linked to the primary such that the email is not the same as B. + + // Exception to the above is that, if there is a third recipe account linked to the above two accounts and has B as verified, then we should allow reset password token generation because user has already proven that the owns the email B + // */ + + // // But first, this only matters it the user cares about checking for email verification status.. + + // if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { + // // here we will go ahead with the token generation cause + // // even when the token is consumed, we will not be linking the accounts + // // so no need to check for anything + // return await generateAndSendPasswordResetToken( + // emailPasswordAccount.recipeUserId.getAsString(), + // emailPasswordAccount.recipeUserId + // ); + // } + + // if (!shouldDoAccountLinkingResponse.shouldRequireVerification) { + // // the checks below are related to email verification, and if the user + // // does not care about that, then we should just continue with token generation + // return await generateAndSendPasswordResetToken( + // primaryUserAssociatedWithEmail.id, + // emailPasswordAccount.recipeUserId + // ); + // } + + // return await generateAndSendPasswordResetToken( + // primaryUserAssociatedWithEmail.id, + // emailPasswordAccount.recipeUserId + // ); + // }, + // passwordResetPOST: async function ({ + // formFields, + // token, + // tenantId, + // options, + // userContext, + // }: { + // formFields: { + // id: string; + // value: unknown; + // }[]; + // token: string; + // tenantId: string; + // options: APIOptions; + // userContext: UserContext; + // }): Promise< + // | { + // status: "OK"; + // user: User; + // email: string; + // } + // | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } + // | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } + // | GeneralErrorResponse + // > { + // async function markEmailAsVerified(recipeUserId: RecipeUserId, email: string) { + // const emailVerificationInstance = EmailVerification.getInstance(); + // if (emailVerificationInstance) { + // const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( + // { + // tenantId, + // recipeUserId, + // email, + // userContext, + // } + // ); + + // if (tokenResponse.status === "OK") { + // await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ + // tenantId, + // token: tokenResponse.token, + // attemptAccountLinking: false, // we pass false here cause + // // we anyway do account linking in this API after this function is + // // called. + // userContext, + // }); + // } + // } + // } + + // async function doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( + // recipeUserId: RecipeUserId + // ): Promise< + // | { + // status: "OK"; + // user: User; + // email: string; + // } + // | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } + // | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } + // | GeneralErrorResponse + // > { + // let updateResponse = await options.recipeImplementation.updateEmailOrPassword({ + // tenantIdForPasswordPolicy: tenantId, + // // we can treat userIdForWhomTokenWasGenerated as a recipe user id cause + // // whenever this function is called, + // recipeUserId, + // password: newPassword, + // userContext, + // }); + // if ( + // updateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR" || + // updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" + // ) { + // throw new Error("This should never come here because we are not updating the email"); + // } else if (updateResponse.status === "UNKNOWN_USER_ID_ERROR") { + // // This should happen only cause of a race condition where the user + // // might be deleted before token creation and consumption. + // return { + // status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", + // }; + // } else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { + // return { + // status: "PASSWORD_POLICY_VIOLATED_ERROR", + // failureReason: updateResponse.failureReason, + // }; + // } else { + // // status: "OK" + + // // If the update was successful, we try to mark the email as verified. + // // We do this because we assume that the password reset token was delivered by email (and to the appropriate email address) + // // so consuming it means that the user actually has access to the emails we send. + + // // We only do this if the password update was successful, otherwise the following scenario is possible: + // // 1. User M: signs up using the email of user V with their own password. They can't validate the email, because it is not their own. + // // 2. User A: tries signing up but sees the email already exists message + // // 3. User A: resets their password, but somehow this fails (e.g.: password policy issue) + // // If we verified (and linked) the existing user with the original password, User M would get access to the current user and any linked users. + // await markEmailAsVerified(recipeUserId, emailForWhomTokenWasGenerated); + // // We refresh the user information here, because the verification status may be updated, which is used during linking. + // const updatedUserAfterEmailVerification = await getUser(recipeUserId.getAsString(), userContext); + // if (updatedUserAfterEmailVerification === undefined) { + // throw new Error("Should never happen - user deleted after during password reset"); + // } + + // if (updatedUserAfterEmailVerification.isPrimaryUser) { + // // If the user is already primary, we do not need to do any linking + // return { + // status: "OK", + // email: emailForWhomTokenWasGenerated, + // user: updatedUserAfterEmailVerification, + // }; + // } + + // // If the user was not primary: + + // // Now we try and link the accounts. + // // The function below will try and also create a primary user of the new account, this can happen if: + // // 1. the user was unverified and linking requires verification + // // We do not take try linking by session here, since this is supposed to be called without a session + // // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking + // const linkRes = await AccountLinking.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ + // tenantId, + // inputUser: updatedUserAfterEmailVerification, + // session: undefined, + // userContext, + // }); + // const userAfterWeTriedLinking = + // linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; + + // return { + // status: "OK", + // email: emailForWhomTokenWasGenerated, + // user: userAfterWeTriedLinking, + // }; + // } + // } + + // // NOTE: Check for password being a non-string value. This check will likely + // // never evaluate to `true` as there is an upper-level check for the type + // // in validation but kept here to be safe. + // const newPasswordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; + // if (typeof newPasswordAsUnknown !== "string") + // throw new Error( + // "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + // ); + // let newPassword: string = newPasswordAsUnknown; + + // let tokenConsumptionResponse = await options.recipeImplementation.consumePasswordResetToken({ + // token, + // tenantId, + // userContext, + // }); + + // if (tokenConsumptionResponse.status === "RESET_PASSWORD_INVALID_TOKEN_ERROR") { + // return tokenConsumptionResponse; + // } + + // let userIdForWhomTokenWasGenerated = tokenConsumptionResponse.userId; + // let emailForWhomTokenWasGenerated = tokenConsumptionResponse.email; + + // let existingUser = await getUser(tokenConsumptionResponse.userId, userContext); + + // if (existingUser === undefined) { + // // This should happen only cause of a race condition where the user + // // might be deleted before token creation and consumption. + // // Also note that this being undefined doesn't mean that the email password + // // user does not exist, but it means that there is no recipe or primary user + // // for whom the token was generated. + // return { + // status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", + // }; + // } + + // // We start by checking if the existingUser is a primary user or not. If it is, + // // then we will try and create a new email password user and link it to the primary user (if required) + + // if (existingUser.isPrimaryUser) { + // // If this user contains an email password account for whom the token was generated, + // // then we update that user's password. + // let emailPasswordUserIsLinkedToExistingUser = + // existingUser.loginMethods.find((lm) => { + // // we check based on user ID and not email because the only time + // // the primary user ID is used for token generation is if the email password + // // user did not exist - in which case the value of emailPasswordUserExists will + // // resolve to false anyway, and that's what we want. + + // // there is an edge case where if the email password recipe user was created + // // after the password reset token generation, and it was linked to the + // // primary user id (userIdForWhomTokenWasGenerated), in this case, + // // we still don't allow password update, cause the user should try again + // // and the token should be regenerated for the right recipe user. + // return ( + // lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && + // lm.recipeId === "emailpassword" + // ); + // }) !== undefined; + + // if (emailPasswordUserIsLinkedToExistingUser) { + // return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( + // new RecipeUserId(userIdForWhomTokenWasGenerated) + // ); + // } else { + // // this means that the existingUser does not have an emailpassword user associated + // // with it. It could now mean that no emailpassword user exists, or it could mean that + // // the the ep user exists, but it's not linked to the current account. + // // If no ep user doesn't exists, we will create one, and link it to the existing account. + // // If ep user exists, then it means there is some race condition cause + // // then the token should have been generated for that user instead of the primary user, + // // and it shouldn't have come into this branch. So we can simply send a password reset + // // invalid error and the user can try again. + + // // NOTE: We do not ask the dev if we should do account linking or not here + // // cause we already have asked them this when generating an password reset token. + // // In the edge case that the dev changes account linking allowance from true to false + // // when it comes here, only a new recipe user id will be created and not linked + // // cause createPrimaryUserIdOrLinkAccounts will disallow linking. This doesn't + // // really cause any security issue. + + // let createUserResponse = await options.recipeImplementation.createNewRecipeUser({ + // tenantId, + // email: tokenConsumptionResponse.email, + // password: newPassword, + // userContext, + // }); + // if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { + // // this means that the user already existed and we can just return an invalid + // // token (see the above comment) + // return { + // status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", + // }; + // } else { + // // we mark the email as verified because password reset also requires + // // access to the email to work.. This has a good side effect that + // // any other login method with the same email in existingAccount will also get marked + // // as verified. + // await markEmailAsVerified( + // createUserResponse.user.loginMethods[0].recipeUserId, + // tokenConsumptionResponse.email + // ); + // const updatedUser = await getUser(createUserResponse.user.id, userContext); + // if (updatedUser === undefined) { + // throw new Error("Should never happen - user deleted after during password reset"); + // } + // createUserResponse.user = updatedUser; + // // Now we try and link the accounts. The function below will try and also + // // create a primary user of the new account, and if it does that, it's OK.. + // // But in most cases, it will end up linking to existing account since the + // // email is shared. + // // We do not take try linking by session here, since this is supposed to be called without a session + // // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking + // const linkRes = await AccountLinking.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ + // tenantId, + // inputUser: createUserResponse.user, + // session: undefined, + // userContext, + // }); + // const userAfterLinking = linkRes.status === "OK" ? linkRes.user : createUserResponse.user; + // if (linkRes.status === "OK" && linkRes.user.id !== existingUser.id) { + // // this means that the account we just linked to + // // was not the one we had expected to link it to. This can happen + // // due to some race condition or the other.. Either way, this + // // is not an issue and we can just return OK + // } + // return { + // status: "OK", + // email: tokenConsumptionResponse.email, + // user: userAfterLinking, + // }; + // } + // } + // } else { + // // This means that the existing user is not a primary account, which implies that + // // it must be a non linked email password account. In this case, we simply update the password. + // // Linking to an existing account will be done after the user goes through the email + // // verification flow once they log in (if applicable). + // return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( + // new RecipeUserId(userIdForWhomTokenWasGenerated) + // ); + // } + // }, + }; +} diff --git a/lib/ts/recipe/webauthn/api/registerOptions.ts b/lib/ts/recipe/webauthn/api/registerOptions.ts new file mode 100644 index 000000000..74d309a48 --- /dev/null +++ b/lib/ts/recipe/webauthn/api/registerOptions.ts @@ -0,0 +1,50 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { send200Response } from "../../../utils"; +import STError from "../error"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; + +export default async function registerOptions( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.registerOptionsPOST === undefined) { + return false; + } + + const requestBody = await options.req.getJSONBody(); + + let email = requestBody.email; + if (email === undefined || typeof email !== "string") { + throw new STError({ + type: STError.BAD_INPUT_ERROR, + message: "Please provide the email", + }); + } + + let result = await apiImplementation.registerOptionsPOST({ + email, + tenantId, + options, + userContext, + }); + + send200Response(options.res, result); + return true; +} diff --git a/lib/ts/recipe/webauthn/api/signInOptions.ts b/lib/ts/recipe/webauthn/api/signInOptions.ts new file mode 100644 index 000000000..2e2736aa5 --- /dev/null +++ b/lib/ts/recipe/webauthn/api/signInOptions.ts @@ -0,0 +1,38 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; + +export default async function signInOptions( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.signInOptionsPOST === undefined) { + return false; + } + + let result = await apiImplementation.signInOptionsPOST({ + tenantId, + options, + userContext, + }); + + send200Response(options.res, result); + return true; +} diff --git a/lib/ts/recipe/webauthn/api/signin.ts b/lib/ts/recipe/webauthn/api/signin.ts new file mode 100644 index 000000000..b834ecb84 --- /dev/null +++ b/lib/ts/recipe/webauthn/api/signin.ts @@ -0,0 +1,75 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { + getBackwardsCompatibleUserInfo, + getNormalisedShouldTryLinkingWithSessionUserFlag, + send200Response, +} from "../../../utils"; +import { validatewebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +import { AuthUtils } from "../../../authUtils"; + +export default async function signInAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + // Logic as per https://github.com/supertokens/supertokens-node/issues/20#issuecomment-710346362 + if (apiImplementation.signInPOST === undefined) { + return false; + } + + const requestBody = await options.req.getJSONBody(); + const webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); + const credential = await validateCredentialOrThrowError(requestBody.credential); + + const shouldTryLinkingWithSessionUser = getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); + + if (session !== undefined) { + tenantId = session.getTenantId(); + } + + let result = await apiImplementation.signInPOST({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }); + + if (result.status === "OK") { + send200Response(options.res, { + status: "OK", + ...getBackwardsCompatibleUserInfo(options.req, result, userContext), + }); + } else { + send200Response(options.res, result); + } + return true; +} diff --git a/lib/ts/recipe/webauthn/api/signup.ts b/lib/ts/recipe/webauthn/api/signup.ts new file mode 100644 index 000000000..3695cbe3e --- /dev/null +++ b/lib/ts/recipe/webauthn/api/signup.ts @@ -0,0 +1,92 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { + getBackwardsCompatibleUserInfo, + getNormalisedShouldTryLinkingWithSessionUserFlag, + send200Response, +} from "../../../utils"; +import { + validateEmailOrThrowError, + validatewebauthnGeneratedOptionsIdOrThrowError, + validateCredentialOrThrowError, +} from "./utils"; +import { APIInterface, APIOptions } from ".."; +import STError from "../error"; +import { UserContext } from "../../../types"; +import { AuthUtils } from "../../../authUtils"; + +export default async function signUpAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.signUpPOST === undefined) { + return false; + } + + const requestBody = await options.req.getJSONBody(); + const email = await validateEmailOrThrowError(requestBody.email); + const webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); + const credential = await validateCredentialOrThrowError(requestBody.credential); + + const shouldTryLinkingWithSessionUser = getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); + if (session !== undefined) { + tenantId = session.getTenantId(); + } + + let result = await apiImplementation.signUpPOST({ + email, + credential, + webauthnGeneratedOptionsId, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext: userContext, + }); + if (result.status === "OK") { + send200Response(options.res, { + status: "OK", + ...getBackwardsCompatibleUserInfo(options.req, result, userContext), + }); + } else if (result.status === "GENERAL_ERROR") { + send200Response(options.res, result); + } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { + throw new STError({ + type: STError.FIELD_ERROR, + payload: [ + { + id: "email", + error: "This email already exists. Please sign in instead.", + }, + ], + message: "Error in input formFields", + }); + } else { + send200Response(options.res, result); + } + return true; +} diff --git a/lib/ts/recipe/webauthn/api/utils.ts b/lib/ts/recipe/webauthn/api/utils.ts new file mode 100644 index 000000000..4b1ecaaf8 --- /dev/null +++ b/lib/ts/recipe/webauthn/api/utils.ts @@ -0,0 +1,50 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import STError from "../error"; +import { defaultEmailValidator } from "../utils"; + +export async function validateEmailOrThrowError(email: string): Promise { + const error = await defaultEmailValidator(email); + if (error) { + throw newBadRequestError(error); + } + + return email; +} + +export async function validatewebauthnGeneratedOptionsIdOrThrowError( + webauthnGeneratedOptionsId: string +): Promise { + if (webauthnGeneratedOptionsId === undefined) { + throw newBadRequestError("webauthnGeneratedOptionsId is required"); + } + + return webauthnGeneratedOptionsId; +} + +export async function validateCredentialOrThrowError(credential: T): Promise { + if (credential === undefined) { + throw newBadRequestError("credential is required"); + } + + return credential; +} + +function newBadRequestError(message: string) { + return new STError({ + type: STError.BAD_INPUT_ERROR, + message, + }); +} diff --git a/lib/ts/recipe/webauthn/constants.ts b/lib/ts/recipe/webauthn/constants.ts new file mode 100644 index 000000000..d23bf8dd7 --- /dev/null +++ b/lib/ts/recipe/webauthn/constants.ts @@ -0,0 +1,34 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +export const DEFAULT_REGISTER_ATTESTATION = "none"; + +export const DEFAULT_REGISTER_OPTIONS_TIMEOUT = 5000; + +export const DEFAULT_SIGNIN_OPTIONS_TIMEOUT = 5000; + +export const REGISTER_OPTIONS_API = "/webauthn/options/register"; + +export const SIGNIN_OPTIONS_API = "/webauthn/options/signin"; + +export const SIGN_UP_API = "/webauthn/signup"; + +export const SIGN_IN_API = "/webauthn/signin"; + +export const GENERATE_RECOVER_ACCOUNT_TOKEN_API = "/user/webauthn/reset/token"; + +export const RECOVER_ACCOUNT_API = "/user/webauthn/reset"; + +export const SIGNUP_EMAIL_EXISTS_API = "/webauthn/email/exists"; diff --git a/lib/ts/recipe/webauthn/core-mock.ts b/lib/ts/recipe/webauthn/core-mock.ts new file mode 100644 index 000000000..f6e725433 --- /dev/null +++ b/lib/ts/recipe/webauthn/core-mock.ts @@ -0,0 +1,84 @@ +import NormalisedURLPath from "../../normalisedURLPath"; +import { Querier } from "../../querier"; +import { UserContext } from "../../types"; + +export const getMockQuerier = (recipeId: string) => { + const querier = Querier.getNewInstanceOrThrowError(recipeId); + + const sendPostRequest = async ( + path: NormalisedURLPath, + body: any, + userContext: UserContext + ): Promise => { + if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { + // @ts-ignore + return { + status: "OK", + webauthnGeneratedOptionsId: "7ab03f6a-61b8-4f65-992f-b8b8469bc18f", + rp: { id: "example.com", name: "Example App" }, + user: { id: "dummy-user-id", name: "user@example.com", displayName: "User" }, + challenge: "dummy-challenge", + timeout: 60000, + excludeCredentials: [], + attestation: "none", + pubKeyCredParams: [{ alg: -7, type: "public-key" }], + authenticatorSelection: { + requireResidentKey: false, + residentKey: "preferred", + userVerification: "preferred", + }, + }; + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + // @ts-ignore + return { + status: "OK", + webauthnGeneratedOptionsId: "18302759-87c6-4d88-990d-c7cab43653cc", + challenge: "dummy-signin-challenge", + timeout: 60000, + userVerification: "preferred", + }; + // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token")) { + // // @ts-ignore + // return { + // status: "OK", + // token: "dummy-recovery-token", + // }; + // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token/consume")) { + // // @ts-ignore + // return { + // status: "OK", + // userId: "dummy-user-id", + // email: "user@example.com", + // }; + // } + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { + // @ts-ignore + return { + status: "OK", + user: { + id: "dummy-user-id", + email: "user@example.com", + timeJoined: Date.now(), + }, + recipeUserId: "dummy-recipe-user-id", + }; + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + // @ts-ignore + return { + status: "OK", + user: { + id: "dummy-user-id", + email: "user@example.com", + timeJoined: Date.now(), + }, + recipeUserId: "dummy-recipe-user-id", + }; + } + + throw new Error(`Unmocked endpoint: ${path}`); + }; + + querier.sendPostRequest = sendPostRequest; + + return querier; +}; diff --git a/lib/ts/recipe/webauthn/error.ts b/lib/ts/recipe/webauthn/error.ts new file mode 100644 index 000000000..7b984cbaf --- /dev/null +++ b/lib/ts/recipe/webauthn/error.ts @@ -0,0 +1,41 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import STError from "../../error"; + +export default class SessionError extends STError { + static FIELD_ERROR: "FIELD_ERROR" = "FIELD_ERROR"; + + constructor( + options: + | { + type: "FIELD_ERROR"; + payload: { + id: string; + error: string; + }[]; + message: string; + } + | { + type: "BAD_INPUT_ERROR"; + message: string; + } + ) { + super({ + ...options, + }); + this.fromRecipe = "webauthn"; + } +} diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts new file mode 100644 index 000000000..78ad6385b --- /dev/null +++ b/lib/ts/recipe/webauthn/index.ts @@ -0,0 +1,386 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import Recipe from "./recipe"; +import SuperTokensError from "./error"; +import { RecipeInterface, APIOptions, APIInterface, TypeWebauthnEmailDeliveryInput } from "./types"; +import RecipeUserId from "../../recipeUserId"; +import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; +import { getPasswordResetLink } from "./utils"; +import { getRequestFromUserContext, getUser } from "../.."; +import { getUserContext } from "../../utils"; +import { SessionContainerInterface } from "../session/types"; +import { User, UserContext } from "../../types"; + +export default class Wrapper { + static init = Recipe.init; + + static Error = SuperTokensError; + + static registerOptions( + email: string, + relyingPartyId: string, + relyingPartyName: string, + origin: string, + timeout: number, + attestation: "none" | "indirect" | "direct" | "enterprise" = "none", + tenantId: string, + userContext: Record + ): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: string; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + }> { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions({ + email, + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation, + tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + userContext: getUserContext(userContext), + }); + } + + static signInOptions( + relyingPartyId: string, + origin: string, + timeout: number, + tenantId: string, + userContext: Record + ): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + }> { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ + relyingPartyId, + origin, + timeout, + tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + userContext: getUserContext(userContext), + }); + } + + // static signIn( + // tenantId: string, + // email: string, + // password: string, + // session?: undefined, + // userContext?: Record + // ): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" }>; + // static signIn( + // tenantId: string, + // email: string, + // password: string, + // session: SessionContainerInterface, + // userContext?: Record + // ): Promise< + // | { status: "OK"; user: User; recipeUserId: RecipeUserId } + // | { status: "WRONG_CREDENTIALS_ERROR" } + // | { + // 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"; + // } + // >; + // static signIn( + // tenantId: string, + // email: string, + // password: string, + // session?: SessionContainerInterface, + // userContext?: Record + // ): Promise< + // | { status: "OK"; user: User; recipeUserId: RecipeUserId } + // | { status: "WRONG_CREDENTIALS_ERROR" } + // | { + // 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"; + // } + // > { + // return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signIn({ + // email, + // password, + // session, + // shouldTryLinkingWithSessionUser: !!session, + // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + // userContext: getUserContext(userContext), + // }); + // } + + // static async verifyCredentials( + // tenantId: string, + // email: string, + // password: string, + // userContext?: Record + // ): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }> { + // const resp = await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ + // email, + // password, + // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + // userContext: getUserContext(userContext), + // }); + + // // Here we intentionally skip the user and recipeUserId props, because we do not want apps to accidentally use this to sign in + // return { + // status: resp.status, + // }; + // } + + // /** + // * We do not make email optional here cause we want to + // * allow passing in primaryUserId. If we make email optional, + // * and if the user provides a primaryUserId, then it may result in two problems: + // * - there is no recipeUserId = input primaryUserId, in this case, + // * this function will throw an error + // * - There is a recipe userId = input primaryUserId, but that recipe has no email, + // * or has wrong email compared to what the user wanted to generate a reset token for. + // * + // * And we want to allow primaryUserId being passed in. + // */ + // static createResetPasswordToken( + // tenantId: string, + // userId: string, + // email: string, + // userContext?: Record + // ): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + // return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createResetPasswordToken({ + // userId, + // email, + // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + // userContext: getUserContext(userContext), + // }); + // } + + // static async resetPasswordUsingToken( + // tenantId: string, + // token: string, + // newPassword: string, + // userContext?: Record + // ): Promise< + // | { + // status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + // } + // | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } + // > { + // const consumeResp = await Wrapper.consumePasswordResetToken(tenantId, token, userContext); + + // if (consumeResp.status !== "OK") { + // return consumeResp; + // } + + // let result = await Wrapper.updateEmailOrPassword({ + // recipeUserId: new RecipeUserId(consumeResp.userId), + // email: consumeResp.email, + // password: newPassword, + // tenantIdForPasswordPolicy: tenantId, + // userContext, + // }); + + // if (result.status === "EMAIL_ALREADY_EXISTS_ERROR" || result.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { + // throw new global.Error("Should never come here cause we are not updating email"); + // } + // if (result.status === "PASSWORD_POLICY_VIOLATED_ERROR") { + // return { + // status: "PASSWORD_POLICY_VIOLATED_ERROR", + // failureReason: result.failureReason, + // }; + // } + // return { + // status: result.status, + // }; + // } + + // static consumePasswordResetToken( + // tenantId: string, + // token: string, + // userContext?: Record + // ): Promise< + // | { + // status: "OK"; + // email: string; + // userId: string; + // } + // | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } + // > { + // return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.consumePasswordResetToken({ + // token, + // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + // userContext: getUserContext(userContext), + // }); + // } + + // static updateEmailOrPassword(input: { + // recipeUserId: RecipeUserId; + // email?: string; + // password?: string; + // userContext?: Record; + // applyPasswordPolicy?: boolean; + // tenantIdForPasswordPolicy?: string; + // }): Promise< + // | { + // status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; + // } + // | { + // status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + // reason: string; + // } + // | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } + // > { + // return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.updateEmailOrPassword({ + // ...input, + // userContext: getUserContext(input.userContext), + // tenantIdForPasswordPolicy: + // input.tenantIdForPasswordPolicy === undefined ? DEFAULT_TENANT_ID : input.tenantIdForPasswordPolicy, + // }); + // } + + // static async createResetPasswordLink( + // tenantId: string, + // userId: string, + // email: string, + // userContext?: Record + // ): Promise<{ status: "OK"; link: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + // const ctx = getUserContext(userContext); + // let token = await createResetPasswordToken(tenantId, userId, email, ctx); + // if (token.status === "UNKNOWN_USER_ID_ERROR") { + // return token; + // } + + // const recipeInstance = Recipe.getInstanceOrThrowError(); + // return { + // status: "OK", + // link: getPasswordResetLink({ + // appInfo: recipeInstance.getAppInfo(), + // token: token.token, + // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + // request: getRequestFromUserContext(ctx), + // userContext: ctx, + // }), + // }; + // } + + // static async sendResetPasswordEmail( + // tenantId: string, + // userId: string, + // email: string, + // userContext?: Record + // ): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }> { + // const user = await getUser(userId, userContext); + // if (!user) { + // return { status: "UNKNOWN_USER_ID_ERROR" }; + // } + + // const loginMethod = user.loginMethods.find((m) => m.recipeId === "emailpassword" && m.hasSameEmailAs(email)); + // if (!loginMethod) { + // return { status: "UNKNOWN_USER_ID_ERROR" }; + // } + + // let link = await createResetPasswordLink(tenantId, userId, email, userContext); + // if (link.status === "UNKNOWN_USER_ID_ERROR") { + // return link; + // } + + // await sendEmail({ + // passwordResetLink: link.link, + // type: "PASSWORD_RESET", + // user: { + // id: user.id, + // recipeUserId: loginMethod.recipeUserId, + // email: loginMethod.email!, + // }, + // tenantId, + // userContext, + // }); + + // return { + // status: "OK", + // }; + // } + + // static async sendEmail( + // input: TypeWebauthnEmailDeliveryInput & { userContext?: Record } + // ): Promise { + // let recipeInstance = Recipe.getInstanceOrThrowError(); + // return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail({ + // ...input, + // tenantId: input.tenantId === undefined ? DEFAULT_TENANT_ID : input.tenantId, + // userContext: getUserContext(input.userContext), + // }); + // } +} + +export let init = Wrapper.init; + +export let Error = Wrapper.Error; + +export let registerOptions = Wrapper.registerOptions; + +export let signInOptions = Wrapper.signInOptions; + +// export let signIn = Wrapper.signIn; + +// export let verifyCredentials = Wrapper.verifyCredentials; + +// export let createResetPasswordToken = Wrapper.createResetPasswordToken; + +// export let resetPasswordUsingToken = Wrapper.resetPasswordUsingToken; + +// export let consumePasswordResetToken = Wrapper.consumePasswordResetToken; + +// export let updateEmailOrPassword = Wrapper.updateEmailOrPassword; + +export type { RecipeInterface, APIOptions, APIInterface }; + +// export let createResetPasswordLink = Wrapper.createResetPasswordLink; + +// export let sendResetPasswordEmail = Wrapper.sendResetPasswordEmail; + +// export let sendEmail = Wrapper.sendEmail; diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts new file mode 100644 index 000000000..779855fc6 --- /dev/null +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -0,0 +1,357 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import RecipeModule from "../../recipeModule"; +import { TypeInput, TypeNormalisedInput, RecipeInterface, APIInterface } from "./types"; +import { NormalisedAppinfo, APIHandled, HTTPMethod, RecipeListFunction, UserContext } from "../../types"; +import STError from "./error"; +import { validateAndNormaliseUserInput } from "./utils"; +import NormalisedURLPath from "../../normalisedURLPath"; +import { SIGN_UP_API, SIGN_IN_API, REGISTER_OPTIONS_API, SIGNIN_OPTIONS_API } from "./constants"; +import signUpAPI from "./api/signup"; +import signInAPI from "./api/signin"; +import registerOptionsAPI from "./api/registerOptions"; +import signInOptionsAPI from "./api/signInOptions"; +import { isTestEnv, send200Response } from "../../utils"; +import RecipeImplementation from "./recipeImplementation"; +import APIImplementation from "./api/implementation"; +import type { BaseRequest, BaseResponse } from "../../framework"; +import OverrideableBuilder from "supertokens-js-override"; +import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; +import { TypeWebauthnEmailDeliveryInput } from "./types"; +import { PostSuperTokensInitCallbacks } from "../../postSuperTokensInitCallbacks"; +import MultiFactorAuthRecipe from "../multifactorauth/recipe"; +import MultitenancyRecipe from "../multitenancy/recipe"; +import { User } from "../../user"; +import { isFakeEmail } from "../thirdparty/utils"; +import { FactorIds } from "../multifactorauth"; +import { getMockQuerier } from "./core-mock"; + +export default class Recipe extends RecipeModule { + private static instance: Recipe | undefined = undefined; + static RECIPE_ID = "webauthn"; + + config: TypeNormalisedInput; + + recipeInterfaceImpl: RecipeInterface; + + apiImpl: APIInterface; + + isInServerlessEnv: boolean; + + emailDelivery: EmailDeliveryIngredient; + + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput | undefined, + ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + } + ) { + super(recipeId, appInfo); + this.isInServerlessEnv = isInServerlessEnv; + this.config = validateAndNormaliseUserInput(this, appInfo, config); + { + const getWebauthnConfig = () => this.config; + // const querier = Querier.getNewInstanceOrThrowError(recipeId); + const querier = getMockQuerier(recipeId); + let builder = new OverrideableBuilder(RecipeImplementation(querier, getWebauthnConfig)); + this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); + } + { + let builder = new OverrideableBuilder(APIImplementation()); + this.apiImpl = builder.override(this.config.override.apis).build(); + } + + /** + * emailDelivery will always needs to be declared after isInServerlessEnv + * and recipeInterfaceImpl values are set + */ + this.emailDelivery = + ingredients.emailDelivery === undefined + ? new EmailDeliveryIngredient(this.config.getEmailDeliveryConfig(this.isInServerlessEnv)) + : ingredients.emailDelivery; + + PostSuperTokensInitCallbacks.addPostInitCallback(() => { + const mfaInstance = MultiFactorAuthRecipe.getInstance(); + if (mfaInstance !== undefined) { + mfaInstance.addFuncToGetAllAvailableSecondaryFactorIdsFromOtherRecipes(() => { + return ["webauthn"]; + }); + mfaInstance.addFuncToGetFactorsSetupForUserFromOtherRecipes(async (user: User) => { + for (const loginMethod of user.loginMethods) { + // We don't check for tenantId here because if we find the user + // with emailpassword loginMethod from different tenant, then + // we assume the factor is setup for this user. And as part of factor + // completion, we associate that loginMethod with the session's tenantId + if (loginMethod.recipeId === Recipe.RECIPE_ID) { + return ["webauthn"]; + } + } + return []; + }); + + mfaInstance.addFuncToGetEmailsForFactorFromOtherRecipes((user: User, sessionRecipeUserId) => { + // This function is called in the MFA info endpoint API. + // Based on https://github.com/supertokens/supertokens-node/pull/741#discussion_r1432749346 + + // preparing some reusable variables for the logic below... + let sessionLoginMethod = user.loginMethods.find((lM) => { + return lM.recipeUserId.getAsString() === sessionRecipeUserId.getAsString(); + }); + if (sessionLoginMethod === undefined) { + // this can happen maybe cause this login method + // was unlinked from the user or deleted entirely... + return { + status: "UNKNOWN_SESSION_RECIPE_USER_ID", + }; + } + + // We order the login methods based on timeJoined (oldest first) + const orderedLoginMethodsByTimeJoinedOldestFirst = user.loginMethods.sort((a, b) => { + return a.timeJoined - b.timeJoined; + }); + // Then we take the ones that belong to this recipe + const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter( + (lm) => lm.recipeId === Recipe.RECIPE_ID + ); + + let result: string[]; + if (recipeLoginMethodsOrderedByTimeJoinedOldestFirst.length !== 0) { + // If there are login methods belonging to this recipe, the factor is set up + // In this case we only list email addresses that have a password associated with them + result = [ + // First we take the verified real emails associated with emailpassword login methods ordered by timeJoined (oldest first) + ...recipeLoginMethodsOrderedByTimeJoinedOldestFirst + .filter((lm) => !isFakeEmail(lm.email!) && lm.verified === true) + .map((lm) => lm.email!), + // Then we take the non-verified real emails associated with emailpassword login methods ordered by timeJoined (oldest first) + ...recipeLoginMethodsOrderedByTimeJoinedOldestFirst + .filter((lm) => !isFakeEmail(lm.email!) && lm.verified === false) + .map((lm) => lm.email!), + // Lastly, fake emails associated with emailpassword login methods ordered by timeJoined (oldest first) + // We also add these into the list because they already have a password added to them so they can be a valid choice when signing in + // We do not want to remove the previously added "MFA password", because a new email password user was linked + // E.g.: + // 1. A discord user adds a password for MFA (which will use the fake email associated with the discord user) + // 2. Later they also sign up and (manually) link a full emailpassword user that they intend to use as a first factor + // 3. The next time they sign in using Discord, they could be asked for a secondary password. + // In this case, they'd be checked against the first user that they originally created for MFA, not the one later linked to the account + ...recipeLoginMethodsOrderedByTimeJoinedOldestFirst + .filter((lm) => isFakeEmail(lm.email!)) + .map((lm) => lm.email!), + ]; + // We handle moving the session email to the top of the list later + } else { + // This factor hasn't been set up, we list all emails belonging to the user + if ( + orderedLoginMethodsByTimeJoinedOldestFirst.some( + (lm) => lm.email !== undefined && !isFakeEmail(lm.email) + ) + ) { + // If there is at least one real email address linked to the user, we only suggest real addresses + result = orderedLoginMethodsByTimeJoinedOldestFirst + .filter((lm) => lm.email !== undefined && !isFakeEmail(lm.email)) + .map((lm) => lm.email!); + } else { + // Else we use the fake ones + result = orderedLoginMethodsByTimeJoinedOldestFirst + .filter((lm) => lm.email !== undefined && isFakeEmail(lm.email)) + .map((lm) => lm.email!); + } + // We handle moving the session email to the top of the list later + + // Since in this case emails are not guaranteed to be unique, we de-duplicate the results, keeping the oldest one in the list. + // The Set constructor keeps the original insertion order (OrderedByTimeJoinedOldestFirst), but de-duplicates the items, + // keeping the first one added (so keeping the older one if there are two entries with the same email) + // e.g.: [4,2,3,2,1] -> [4,2,3,1] + result = Array.from(new Set(result)); + } + + // If the loginmethod associated with the session has an email address, we move it to the top of the list (if it's already in the list) + if (sessionLoginMethod.email !== undefined && result.includes(sessionLoginMethod.email)) { + result = [ + sessionLoginMethod.email, + ...result.filter((email) => email !== sessionLoginMethod!.email), + ]; + } + + // todo how to implement this? + + // If the list is empty we generate an email address to make the flow where the user is never asked for + // an email address easier to implement. In many cases when the user adds an email-password factor, they + // actually only want to add a password and do not care about the associated email address. + // Custom implementations can choose to ignore this, and ask the user for the email anyway. + if (result.length === 0) { + result.push(`${sessionRecipeUserId.getAsString()}@stfakeemail.supertokens.com`); + } + + return { + status: "OK", + factorIdToEmailsMap: { + emailpassword: result, + }, + }; + }); + } + + const mtRecipe = MultitenancyRecipe.getInstance(); + if (mtRecipe !== undefined) { + mtRecipe.allAvailableFirstFactors.push(FactorIds.WEBAUTHN); + } + }); + } + + static getInstanceOrThrowError(): Recipe { + if (Recipe.instance !== undefined) { + return Recipe.instance; + } + throw new Error("Initialisation not done. Did you forget to call the Webauthn.init function?"); + } + + static init(config?: TypeInput): RecipeListFunction { + return (appInfo, isInServerlessEnv) => { + if (Recipe.instance === undefined) { + Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config, { + emailDelivery: undefined, + }); + + return Recipe.instance; + } else { + throw new Error("Webauthn recipe has already been initialised. Please check your code for bugs."); + } + }; + } + + static reset() { + if (!isTestEnv()) { + throw new Error("calling testing function in non testing env"); + } + Recipe.instance = undefined; + } + + // abstract instance functions below............... + + getAPIsHandled = (): APIHandled[] => { + return [ + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(REGISTER_OPTIONS_API), + id: REGISTER_OPTIONS_API, + disabled: this.apiImpl.registerOptionsPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(SIGNIN_OPTIONS_API), + id: SIGNIN_OPTIONS_API, + disabled: this.apiImpl.signInOptionsPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(SIGN_UP_API), + id: SIGN_UP_API, + disabled: this.apiImpl.signUpPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(SIGN_IN_API), + id: SIGN_IN_API, + disabled: this.apiImpl.signInPOST === undefined, + }, + + // { + // method: "post", + // pathWithoutApiBasePath: new NormalisedURLPath(GENERATE_RECOVER_ACCOUNT_TOKEN_API), + // id: GENERATE_RECOVER_ACCOUNT_TOKEN_API, + // disabled: this.apiImpl.generateRecoverAccountTokenPOST === undefined, + // }, + // { + // method: "post", + // pathWithoutApiBasePath: new NormalisedURLPath(RECOVER_ACCOUNT_API), + // id: RECOVER_ACCOUNT_API, + // disabled: this.apiImpl.recoverAccountPOST === undefined, + // }, + // { + // method: "get", + // pathWithoutApiBasePath: new NormalisedURLPath(SIGNUP_EMAIL_EXISTS_API), + // id: SIGNUP_EMAIL_EXISTS_API, + // disabled: this.apiImpl.emailExistsGET === undefined, + // }, + ]; + }; + + handleAPIRequest = async ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ): Promise => { + let options = { + config: this.config, + recipeId: this.getRecipeId(), + isInServerlessEnv: this.isInServerlessEnv, + recipeImplementation: this.recipeInterfaceImpl, + req, + res, + emailDelivery: this.emailDelivery, + appInfo: this.getAppInfo(), + }; + if (id === REGISTER_OPTIONS_API) { + return await registerOptionsAPI(this.apiImpl, tenantId, options, userContext); + } else if (id === SIGNIN_OPTIONS_API) { + return await signInOptionsAPI(this.apiImpl, tenantId, options, userContext); + } else if (id === SIGN_UP_API) { + return await signUpAPI(this.apiImpl, tenantId, options, userContext); + } else if (id === SIGN_IN_API) { + return await signInAPI(this.apiImpl, tenantId, options, userContext); + } + //else if (id === GENERATE_RECOVER_ACCOUNT_TOKEN_API) { + // return await generateRecoverAccountTokenAPI(this.apiImpl, tenantId, options, userContext); + // } else if (id === RECOVER_ACCOUNT_API) { + // return await recoverAccountAPI(this.apiImpl, tenantId, options, userContext); + // } else if (id === SIGNUP_EMAIL_EXISTS_API) { + // return await emailExistsAPI(this.apiImpl, tenantId, options, userContext); + // } + else return false; + }; + + handleError = async (err: STError, _request: BaseRequest, response: BaseResponse): Promise => { + if (err.fromRecipe === Recipe.RECIPE_ID) { + if (err.type === STError.FIELD_ERROR) { + return send200Response(response, { + status: "FIELD_ERROR", + formFields: err.payload, + }); + } else { + throw err; + } + } else { + throw err; + } + }; + + getAllCORSHeaders = (): string[] => { + return []; + }; + + isErrorFromThisRecipe = (err: any): err is STError => { + return STError.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; + }; +} diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts new file mode 100644 index 000000000..026e6b63a --- /dev/null +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -0,0 +1,376 @@ +import { RecipeInterface, TypeNormalisedInput } from "./types"; +import AccountLinking from "../accountlinking/recipe"; +import { Querier } from "../../querier"; +import NormalisedURLPath from "../../normalisedURLPath"; +import { getUser } from "../.."; +import RecipeUserId from "../../recipeUserId"; +import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; +import { UserContext, User as UserType } from "../../types"; +import { LoginMethod, User } from "../../user"; +import { AuthUtils } from "../../authUtils"; + +export default function getRecipeInterface( + querier: Querier, + getWebauthnConfig: () => TypeNormalisedInput +): RecipeInterface { + return { + registerOptions: async function ({ + email, + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation = "none", + tenantId, + userContext, + }: { + email: string; + timeout: number; + attestation: "none" | "indirect" | "direct" | "enterprise"; + relyingPartyId: string; + relyingPartyName: string; + origin: string; + tenantId: string; + userContext: UserContext; + }): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: string; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + }> { + // the input user ID can be a recipe or a primary user ID. + return await querier.sendPostRequest( + new NormalisedURLPath( + `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/register` + ), + { + email, + relyingPartyName, + relyingPartyId, + origin, + timeout, + attestation, + }, + userContext + ); + }, + + signInOptions: async function ({ + relyingPartyId, + origin, + timeout, + tenantId, + userContext, + }: { + relyingPartyId: string; + origin: string; + timeout: number; + tenantId: string; + userContext: UserContext; + }): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + }> { + // todo crrectly retrieve relying party id and origin + // the input user ID can be a recipe or a primary user ID. + return await querier.sendPostRequest( + new NormalisedURLPath( + `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/signin` + ), + { + relyingPartyId, + origin, + timeout, + }, + userContext + ); + }, + + signUp: async function ( + this: RecipeInterface, + { webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, userContext } + ): Promise< + | { + status: "OK"; + user: UserType; + recipeUserId: RecipeUserId; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { + 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"; + } + > { + const response = await this.createNewRecipeUser({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (response.status !== "OK") { + return response; + } + + let updatedUser = response.user; + + const linkResult = await AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + tenantId, + inputUser: response.user, + 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: { + tenantId: string; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + webauthnGeneratedOptionsId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + > { + const resp = await querier.sendPostRequest( + new NormalisedURLPath( + `/${input.tenantId === undefined ? DEFAULT_TENANT_ID : input.tenantId}/recipe/webauthn/signup` + ), + { + webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId, + credential: input.credential, + }, + input.userContext + ); + + if (resp.status === "OK") { + return { + status: "OK", + user: new User(resp.user), + recipeUserId: new RecipeUserId(resp.recipeUserId), + }; + } + + return resp; + }, + + verifyCredentials: async function ({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { status: "WRONG_CREDENTIALS_ERROR" } + > { + const response = await querier.sendPostRequest( + new NormalisedURLPath( + `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/signin` + ), + { + credential, + webauthnGeneratedOptionsId, + }, + userContext + ); + + if (response.status === "OK") { + return { + status: "OK", + user: new User(response.user), + recipeUserId: new RecipeUserId(response.recipeUserId), + }; + } + + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + }, + + signIn: async function ( + this: RecipeInterface, + { credential, webauthnGeneratedOptionsId, tenantId, session, shouldTryLinkingWithSessionUser, userContext } + ) { + const response = await this.verifyCredentials({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + + if (response.status === "OK") { + const loginMethod: LoginMethod = response.user.loginMethods.find( + (lm: LoginMethod) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString() + )!; + + if (!loginMethod.verified) { + await AccountLinking.getInstance().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 getUser(response.recipeUserId!.getAsString(), userContext))!; + } + + const linkResult = await AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + 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; + }, + + // generateRecoverAccountToken: async function ({ + // userId, + // email, + // tenantId, + // userContext, + // }: { + // userId: string; + // email: string; + // tenantId: string; + // userContext: UserContext; + // }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + // // the input user ID can be a recipe or a primary user ID. + // return await querier.sendPostRequest( + // new NormalisedURLPath( + // `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token` + // ), + // { + // userId, + // email, + // }, + // userContext + // ); + // }, + + // consumeRecoverAccountToken: async function ({ + // token, + // webauthnGeneratedOptionsId, + // credential, + // tenantId, + // userContext, + // }: { + // token: string; + // webauthnGeneratedOptionsId: string; + // credential: { + // id: string; + // rawId: string; + // response: { + // clientDataJSON: string; + // attestationObject: string; + // transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + // userHandle: string; + // }; + // authenticatorAttachment: "platform" | "cross-platform"; + // clientExtensionResults: Record; + // type: "public-key"; + // }; + // tenantId: string; + // userContext: UserContext; + // }): Promise< + // | { + // status: "OK"; + // userId: string; + // email: string; + // } + // | { status: "RECOVER_ACCOUNT_INVALID_TOKEN_ERROR" } + // > { + // return await querier.sendPostRequest( + // new NormalisedURLPath( + // `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/paskey/user/recover/token/consume` + // ), + // { + // webauthnGeneratedOptionsId, + // credential, + // token, + // }, + // userContext + // ); + // }, + }; +} diff --git a/lib/ts/recipe/passkey/types.ts b/lib/ts/recipe/webauthn/types.ts similarity index 66% rename from lib/ts/recipe/passkey/types.ts rename to lib/ts/recipe/webauthn/types.ts index cae369644..b068e2895 100644 --- a/lib/ts/recipe/passkey/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -25,12 +25,12 @@ import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../. import RecipeUserId from "../../recipeUserId"; export type TypeNormalisedInput = { - validateEmail: (value: any, tenantId: string, userContext: UserContext) => Promise; - relyingPartyId: (input: { request: BaseRequest | undefined; userContext: UserContext }) => string; // should return the domain of the origin - relyingPartyName: (input: { request: BaseRequest | undefined; userContext: UserContext }) => string; // should return the app name + relyingPartyId: TypeNormalisedInputRelyingPartyId; + relyingPartyName: TypeNormalisedInputRelyingPartyName; + getOrigin: TypeNormalisedInputGetOrigin; getEmailDeliveryConfig: ( isInServerlessEnv: boolean - ) => EmailDeliveryTypeInputWithService; + ) => EmailDeliveryTypeInputWithService; override: { functions: ( originalImplementation: RecipeInterface, @@ -40,11 +40,23 @@ export type TypeNormalisedInput = { }; }; +export type TypeNormalisedInputRelyingPartyId = (input: { + request: BaseRequest | undefined; + userContext: UserContext; +}) => string; // should return the domain of the origin + +export type TypeNormalisedInputRelyingPartyName = (input: { + request: BaseRequest | undefined; + userContext: UserContext; +}) => string; // should return the app name + +export type TypeNormalisedInputGetOrigin = (input: { request: BaseRequest; userContext: UserContext }) => string; // should return the app name + export type TypeInput = { - emailDelivery?: EmailDeliveryTypeInput; - validateEmail?: (value: any, tenantId: string, userContext: UserContext) => Promise; - relyingPartyId?: string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); - relyingPartyName?: string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); + emailDelivery?: EmailDeliveryTypeInput; + relyingPartyId?: TypeInputRelyingPartyId; + relyingPartyName?: TypeInputRelyingPartyName; + getOrigin?: TypeInputGetOrigin; override?: { functions?: ( originalImplementation: RecipeInterface, @@ -54,14 +66,29 @@ export type TypeInput = { }; }; +export type TypeInputRelyingPartyId = + | string + | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); + +export type TypeInputRelyingPartyName = + | string + | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); + +export type TypeInputGetOrigin = (input: { request: BaseRequest; userContext: UserContext }) => string; + export type RecipeInterface = { - registerPasskeyOptions(input: { + registerOptions(input: { email: string; + relyingPartyId: string; + relyingPartyName: string; + origin: string; + timeout: number; + attestation: "none" | "indirect" | "direct" | "enterprise"; tenantId: string; userContext: UserContext; }): Promise<{ status: "OK"; - passkeyGeneratedOptionsId: string; + webauthnGeneratedOptionsId: string; rp: { id: string; name: string; @@ -90,22 +117,23 @@ export type RecipeInterface = { }; }>; - signInPasskeyOptions(input: { - session: SessionContainerInterface | undefined; + signInOptions(input: { + relyingPartyId: string; + origin: string; + timeout: number; tenantId: string; userContext: UserContext; }): Promise<{ status: "OK"; - passkeyGeneratedOptionsId: string; + webauthnGeneratedOptionsId: string; challenge: string; timeout: number; userVerification: "required" | "preferred" | "discouraged"; }>; signUp(input: { - email: string | undefined; - passkeyGeneratedOptionsId: string; - passkey: { + webauthnGeneratedOptionsId: string; + credential: { id: string; rawId: string; response: { @@ -139,9 +167,13 @@ export type RecipeInterface = { } >; - signIn(input: { - passkeyGeneratedOptionsId: string; - passkey: { + // this function is meant only for creating the recipe in the core and nothing else. + // we added this even though signUp exists cause devs may override signup expecting it + // to be called just during sign up. But we also need a version of signing up which can be + // called during operations like creating a user during password reset flow. + createNewRecipeUser(input: { + webauthnGeneratedOptionsId: string; + credential: { id: string; rawId: string; response: { @@ -154,38 +186,20 @@ export type RecipeInterface = { clientExtensionResults: Record; type: "public-key"; }; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< - | { status: "OK"; user: User; recipeUserId: RecipeUserId } - | { status: "WRONG_CREDENTIALS_ERROR" } | { - 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"; + status: "OK"; + user: User; + recipeUserId: RecipeUserId; } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } >; - /** - * We pass in the email as well to this function cause the input userId - * may not be associated with an passkey account. In this case, we - * need to know which email to use to create an passkey account later on. - */ - generateRecoverAccountToken(input: { - userId: string; // the id can be either recipeUserId or primaryUserId - email: string; - tenantId: string; - userContext: UserContext; - }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>; - - consumeRecoverAccountToken(input: { - token: string; - passkey: { + verifyCredentials(input: { + webauthnGeneratedOptionsId: string; + credential: { id: string; rawId: string; response: { @@ -200,23 +214,11 @@ export type RecipeInterface = { }; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - email: string; - userId: string; - } - | { status: "RECOVER_ACCOUNT_INVALID_TOKEN_ERROR" } - >; + }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" }>; - // this function is meant only for creating the recipe in the core and nothing else. - // we added this even though signUp exists cause devs may override signup expecting it - // to be called just during sign up. But we also need a version of signing up which can be - // called during operations like creating a user during password reset flow. - createNewRecipeUser(input: { - email: string; - passkeyGeneratedOptionsId: string; - passkey: { + signIn(input: { + webauthnGeneratedOptionsId: string; + credential: { id: string; rawId: string; response: { @@ -229,16 +231,64 @@ export type RecipeInterface = { clientExtensionResults: Record; type: "public-key"; }; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + | { status: "WRONG_CREDENTIALS_ERROR" } | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; + 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"; } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } >; + + // todo uncomment one by one once starting implementation + + /** + * We pass in the email as well to this function cause the input userId + * may not be associated with an passkey account. In this case, we + * need to know which email to use to create an passkey account later on. + */ + // generateRecoverAccountToken(input: { + // userId: string; // the id can be either recipeUserId or primaryUserId + // email: string; + // tenantId: string; + // userContext: UserContext; + // }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>; + + // // make sure the email maps to options email + // consumeRecoverAccountToken(input: { + // token: string; + // webauthnGeneratedOptionsId: string; + // credential: { + // id: string; + // rawId: string; + // response: { + // clientDataJSON: string; + // attestationObject: string; + // transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + // userHandle: string; + // }; + // authenticatorAttachment: "platform" | "cross-platform"; + // clientExtensionResults: Record; + // type: "public-key"; + // }; + // tenantId: string; + // userContext: UserContext; + // }): Promise< + // | { + // status: "OK"; + // email: string; + // userId: string; + // } + // | { status: "RECOVER_ACCOUNT_INVALID_TOKEN_ERROR" } + // >; }; export type APIOptions = { @@ -249,21 +299,21 @@ export type APIOptions = { isInServerlessEnv: boolean; req: BaseRequest; res: BaseResponse; - emailDelivery: EmailDeliveryIngredient; + emailDelivery: EmailDeliveryIngredient; }; export type APIInterface = { - registerPasskeyOptionsPOST: + registerOptionsPOST: | undefined | ((input: { - email: string | undefined; + email: string; tenantId: string; options: APIOptions; userContext: UserContext; }) => Promise< | { status: "OK"; - passkeyGeneratedOptionsId: string; + webauthnGeneratedOptionsId: string; rp: { id: string; name: string; @@ -294,7 +344,7 @@ export type APIInterface = { | GeneralErrorResponse >); - signInPasskeyOptionsPOST: + signInOptionsPOST: | undefined | ((input: { tenantId: string; @@ -303,7 +353,7 @@ export type APIInterface = { }) => Promise< | { status: "OK"; - passkeyGeneratedOptionsId: string; + webauthnGeneratedOptionsId: string; challenge: string; timeout: number; userVerification: "required" | "preferred" | "discouraged"; @@ -315,8 +365,8 @@ export type APIInterface = { | undefined | ((input: { email: string; - passkeyGeneratedOptionsId: string; - passkey: { + webauthnGeneratedOptionsId: string; + credential: { id: string; rawId: string; response: { @@ -353,8 +403,8 @@ export type APIInterface = { signInPOST: | undefined | ((input: { - passkeyGeneratedOptionsId: string; - passkey: { + webauthnGeneratedOptionsId: string; + credential: { id: string; rawId: string; response: { @@ -388,74 +438,75 @@ export type APIInterface = { | GeneralErrorResponse >); - generateRecoverAccountTokenPOST: - | undefined - | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - } - | { - status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; - reason: string; - } - | GeneralErrorResponse - >); + // todo uncomment one by one once starting implementation + // generateRecoverAccountTokenPOST: + // | undefined + // | ((input: { + // email: string; + // tenantId: string; + // options: APIOptions; + // userContext: UserContext; + // }) => Promise< + // | { + // status: "OK"; + // } + // | { + // status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; + // reason: string; + // } + // | GeneralErrorResponse + // >); - recoverAccountPOST: - | undefined - | ((input: { - passkey: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; - token: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - email: string; - user: User; - } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_TOKEN_ERROR"; - } - | GeneralErrorResponse - >); + // recoverAccountPOST: + // | undefined + // | ((input: { + // credential: { + // id: string; + // rawId: string; + // response: { + // clientDataJSON: string; + // attestationObject: string; + // transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + // userHandle: string; + // }; + // authenticatorAttachment: "platform" | "cross-platform"; + // clientExtensionResults: Record; + // type: "public-key"; + // }; + // token: string; + // tenantId: string; + // options: APIOptions; + // userContext: UserContext; + // }) => Promise< + // | { + // status: "OK"; + // email: string; + // user: User; + // } + // | { + // status: "RECOVER_ACCOUNT_TOKEN_INVALID_TOKEN_ERROR"; + // } + // | GeneralErrorResponse + // >); - // used for checking if the email already exists before generating the passkey - emailExistsGET: - | undefined - | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - exists: boolean; - } - | GeneralErrorResponse - >); + // // used for checking if the email already exists before generating the passkey + // emailExistsGET: + // | undefined + // | ((input: { + // email: string; + // tenantId: string; + // options: APIOptions; + // userContext: UserContext; + // }) => Promise< + // | { + // status: "OK"; + // exists: boolean; + // } + // | GeneralErrorResponse + // >); }; -export type TypePasskeyRecoverAccountEmailDeliveryInput = { +export type TypeWebauthnRecoverAccountEmailDeliveryInput = { type: "RECOVER_ACCOUNT"; user: { id: string; @@ -466,4 +517,4 @@ export type TypePasskeyRecoverAccountEmailDeliveryInput = { tenantId: string; }; -export type TypePasskeyEmailDeliveryInput = TypePasskeyRecoverAccountEmailDeliveryInput; +export type TypeWebauthnEmailDeliveryInput = TypeWebauthnRecoverAccountEmailDeliveryInput; diff --git a/lib/ts/recipe/webauthn/utils.ts b/lib/ts/recipe/webauthn/utils.ts new file mode 100644 index 000000000..278da3a9b --- /dev/null +++ b/lib/ts/recipe/webauthn/utils.ts @@ -0,0 +1,171 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import Recipe from "./recipe"; +import { + TypeInput, + TypeInputGetOrigin, + TypeInputRelyingPartyId, + TypeInputRelyingPartyName, + TypeNormalisedInput, + TypeNormalisedInputGetOrigin, + TypeNormalisedInputRelyingPartyId, + TypeNormalisedInputRelyingPartyName, +} from "./types"; +import { NormalisedAppinfo, UserContext } from "../../types"; +import { RecipeInterface, APIInterface } from "./types"; +import { BaseRequest } from "../../framework"; + +export function validateAndNormaliseUserInput( + recipeInstance: Recipe, + appInfo: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput { + let relyingPartyId = validateAndNormaliseRelyingPartyIdConfig(recipeInstance, appInfo, config?.relyingPartyId); + let relyingPartyName = validateAndNormaliseRelyingPartyNameConfig( + recipeInstance, + appInfo, + config?.relyingPartyName + ); + let getOrigin = validateAndNormaliseGetOriginConfig(recipeInstance, appInfo, config?.getOrigin); + + let override = { + functions: (originalImplementation: RecipeInterface) => originalImplementation, + apis: (originalImplementation: APIInterface) => originalImplementation, + ...config?.override, + }; + + function getEmailDeliveryConfig(isInServerlessEnv: boolean) { + let emailService = config?.emailDelivery?.service; + /** + * If the user has not passed even that config, we use the default + * createAndSendCustomEmail implementation which calls our supertokens API + */ + // if (emailService === undefined) { + // emailService = new BackwardCompatibilityService(appInfo, isInServerlessEnv); + // } + return { + ...config?.emailDelivery, + /** + * if we do + * let emailDelivery = { + * service: emailService, + * ...config.emailDelivery, + * }; + * + * and if the user has passed service as undefined, + * it it again get set to undefined, so we + * set service at the end + */ + // todo implemenet this + service: null as any, + }; + } + return { + override, + getOrigin, + relyingPartyId, + relyingPartyName, + getEmailDeliveryConfig, + }; +} + +function validateAndNormaliseRelyingPartyIdConfig( + _: Recipe, + __: NormalisedAppinfo, + relyingPartyIdConfig: TypeInputRelyingPartyId | undefined +): TypeNormalisedInputRelyingPartyId { + return (props) => { + if (typeof relyingPartyIdConfig === "string") { + return relyingPartyIdConfig; + } else if (typeof relyingPartyIdConfig === "function") { + return relyingPartyIdConfig(props); + } else { + return __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous(); + } + }; +} + +function validateAndNormaliseRelyingPartyNameConfig( + _: Recipe, + __: NormalisedAppinfo, + relyingPartyNameConfig: TypeInputRelyingPartyName | undefined +): TypeNormalisedInputRelyingPartyName { + return (props) => { + if (typeof relyingPartyNameConfig === "string") { + return relyingPartyNameConfig; + } else if (typeof relyingPartyNameConfig === "function") { + return relyingPartyNameConfig(props); + } else { + return __.appName; + } + }; +} + +function validateAndNormaliseGetOriginConfig( + _: Recipe, + __: NormalisedAppinfo, + getOriginConfig: TypeInputGetOrigin | undefined +): TypeNormalisedInputGetOrigin { + return (props) => { + if (typeof getOriginConfig === "function") { + return getOriginConfig(props); + } else { + return __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous(); + } + }; +} + +export async function defaultEmailValidator(value: any) { + // We check if the email syntax is correct + // As per https://github.com/supertokens/supertokens-auth-react/issues/5#issuecomment-709512438 + // Regex from https://stackoverflow.com/a/46181/3867175 + + if (typeof value !== "string") { + return "Development bug: Please make sure the email field yields a string"; + } + + if ( + value.match( + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ) === null + ) { + return "Email is invalid"; + } + + return undefined; +} + +export function getPasswordResetLink(input: { + appInfo: NormalisedAppinfo; + token: string; + tenantId: string; + request: BaseRequest | undefined; + userContext: UserContext; +}): string { + return ( + input.appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + + input.appInfo.websiteBasePath.getAsStringDangerous() + + "/reset-password?token=" + + input.token + + "&tenantId=" + + input.tenantId + ); +} From 40bd6e9795f8fd1be1392a201ad1465042d36064 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Thu, 17 Oct 2024 18:03:04 +0300 Subject: [PATCH 04/36] updated types based on pr changes --- lib/ts/recipe/webauthn/types.ts | 477 +++++++++++++++++++------------- 1 file changed, 291 insertions(+), 186 deletions(-) diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index b068e2895..34108e0c7 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -45,10 +45,7 @@ export type TypeNormalisedInputRelyingPartyId = (input: { userContext: UserContext; }) => string; // should return the domain of the origin -export type TypeNormalisedInputRelyingPartyName = (input: { - request: BaseRequest | undefined; - userContext: UserContext; -}) => string; // should return the app name +export type TypeNormalisedInputRelyingPartyName = () => string; // should return the app name export type TypeNormalisedInputGetOrigin = (input: { request: BaseRequest; userContext: UserContext }) => string; // should return the app name @@ -70,66 +67,103 @@ export type TypeInputRelyingPartyId = | string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); -export type TypeInputRelyingPartyName = - | string - | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); +export type TypeInputRelyingPartyName = string | (() => string); export type TypeInputGetOrigin = (input: { request: BaseRequest; userContext: UserContext }) => string; export type RecipeInterface = { - registerOptions(input: { - email: string; - relyingPartyId: string; - relyingPartyName: string; - origin: string; - timeout: number; - attestation: "none" | "indirect" | "direct" | "enterprise"; - tenantId: string; - userContext: UserContext; - }): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: string; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: string; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; - }; - }>; + // should have a way to access the user email: passed as a param, through session, or using recoverAccountToken + // it should have at least one of those 3 options + registerOptions( + input: { + relyingPartyId: string; + relyingPartyName: string; + origin: string; + requireResidentKey: boolean | undefined; // should default to false in order to allow multiple authenticators to be used; see https://auth0.com/blog/a-look-at-webauthn-resident-credentials/ + // default to 'required' in order store the private key locally on the device and not on the server + residentKey: "required" | "preferred" | "discouraged" | undefined; + // default to 'preferred' in order to verify the user (biometrics, pin, etc) based on the device preferences + userVerification: "required" | "preferred" | "discouraged" | undefined; + // default to 'none' in order to allow any authenticator and not verify attestation + attestation: "none" | "indirect" | "direct" | "enterprise" | undefined; + // default to 5 seconds + timeout: number | undefined; + tenantId: string; + userContext: UserContext; + } & ( + | { + recoverAccountToken: string; + } + | { + email: string; + } + | { + session: SessionContainerInterface; + } + ) + ): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; // user email + displayName: string; //user email + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + // we will default to [-8, -7, -257] as supported algorithms. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + } + | { status: "EMAIL_MISSING_ERROR" } // email is required if not using session or recoverAccountToken + | { status: "SESSION_MISSING_ERROR" } // session is required if not using email or recoverAccountToken + | { status: "RECOVER_ACCOUNT_TOKEN_MISSING_ERROR" } // recoverAccountToken is required if not using email or session + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "RELYING_PARTY_ID_MISSING_ERROR" } + | { status: "RELYING_PARTY_ID_INVALID_ERROR" } // wrong format; must be domain + | { status: "RELYING_PARTY_NAME_MISSING_ERROR" } // relyingPartyName is required + | { status: "ORIGIN_MISSING_ERROR" } + | { status: "ORIGIN_INVALID_ERROR" } // wrong format; must be domain url + >; signInOptions(input: { relyingPartyId: string; origin: string; - timeout: number; + userVerification: "required" | "preferred" | "discouraged" | undefined; // see register options + timeout: number | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: "required" | "preferred" | "discouraged"; - }>; + }): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + } + | { status: "RELYING_PARTY_ID_MISSING_ERROR" } + | { status: "RELYING_PARTY_ID_INVALID_ERROR" } // wrong format; must be domain + | { status: "ORIGIN_MISSING_ERROR" } + | { status: "ORIGIN_INVALID_ERROR" } // wrong format; must be domain url + >; signUp(input: { webauthnGeneratedOptionsId: string; @@ -157,6 +191,9 @@ export type RecipeInterface = { recipeUserId: RecipeUserId; } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "CREDENTIAL_MISSING_ERROR" } + | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -167,11 +204,91 @@ export type RecipeInterface = { } >; - // this function is meant only for creating the recipe in the core and nothing else. - // we added this even though signUp exists cause devs may override signup expecting it - // to be called just during sign up. But we also need a version of signing up which can be - // called during operations like creating a user during password reset flow. - createNewRecipeUser(input: { + signIn(input: { + webauthnGeneratedOptionsId: string; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + }): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "CREDENTIAL_MISSING_ERROR" } + | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } + | { + 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"; + } + >; + + /** + * We pass in the email as well to this function cause the input userId + * may not be associated with an webauthn account. In this case, we + * need to know which email to use to create an webauthn account later on. + */ + generateRecoverAccountToken(input: { + userId: string; // the id can be either recipeUserId or primaryUserId + email: string; + tenantId: string; + userContext: UserContext; + }): Promise< + { status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" } | { status: "UNKNOWN_EMAIL_ERROR" } + >; + + // make sure the email maps to options email + consumeRecoverAccountToken(input: { + token: string; + webauthnGeneratedOptionsId: string; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "CREDENTIAL_MISSING_ERROR" } + | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } + | { status: "RECOVER_ACCOUNT_TOKEN_MISSING_ERROR" } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + >; + + // used internally for creating a passkey during password reset flow or when adding a credential to an existing user + // email will be taken from the options + // no need for recoverAccountToken, as that will be used upstream + // (in consumeRecoverAccountToken invalidating the token and in registerOptions for storing the email in the generated options) + registerPasskey(input: { webauthnGeneratedOptionsId: string; credential: { id: string; @@ -194,10 +311,16 @@ export type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "CREDENTIAL_MISSING_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } >; - verifyCredentials(input: { + // this function is meant only for creating the recipe in the core and nothing else. + // we added this even though signUp exists cause devs may override signup expecting it + // to be called just during sign up. But we also need a version of signing up which can be + // called during operations like creating a user during password reset flow. + createNewRecipeUser(input: { webauthnGeneratedOptionsId: string; credential: { id: string; @@ -214,9 +337,19 @@ export type RecipeInterface = { }; tenantId: string; userContext: UserContext; - }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "CREDENTIAL_MISSING_ERROR" } + | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } + >; - signIn(input: { + verifyCredentials(input: { webauthnGeneratedOptionsId: string; credential: { id: string; @@ -231,64 +364,26 @@ export type RecipeInterface = { clientExtensionResults: Record; type: "public-key"; }; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" } - | { - 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"; - } + | { status: "CREDENTIAL_MISSING_ERROR" } + | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } >; - // todo uncomment one by one once starting implementation - - /** - * We pass in the email as well to this function cause the input userId - * may not be associated with an passkey account. In this case, we - * need to know which email to use to create an passkey account later on. - */ - // generateRecoverAccountToken(input: { - // userId: string; // the id can be either recipeUserId or primaryUserId - // email: string; - // tenantId: string; - // userContext: UserContext; - // }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>; - - // // make sure the email maps to options email - // consumeRecoverAccountToken(input: { - // token: string; - // webauthnGeneratedOptionsId: string; - // credential: { - // id: string; - // rawId: string; - // response: { - // clientDataJSON: string; - // attestationObject: string; - // transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - // userHandle: string; - // }; - // authenticatorAttachment: "platform" | "cross-platform"; - // clientExtensionResults: Record; - // type: "public-key"; - // }; - // tenantId: string; - // userContext: UserContext; - // }): Promise< - // | { - // status: "OK"; - // email: string; - // userId: string; - // } - // | { status: "RECOVER_ACCOUNT_INVALID_TOKEN_ERROR" } - // >; + // used for retrieving the user details (email) from the recover account token + // should be used in the registerOptions function when the user recovers the account and generates the credentials + getUserFromRecoverAccountToken(input: { + token: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + | { status: "RECOVER_ACCOUNT_TOKEN_MISSING_ERROR" } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + >; }; export type APIOptions = { @@ -305,12 +400,13 @@ export type APIOptions = { export type APIInterface = { registerOptionsPOST: | undefined - | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< + | (( + input: { + tenantId: string; + options: APIOptions; + userContext: UserContext; + } & ({ email: string } | { recoverAccountToken: string } | { session: SessionContainerInterface }) + ) => Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; @@ -327,7 +423,7 @@ export type APIInterface = { timeout: number; excludeCredentials: { id: string; - type: string; + type: "public-key"; transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; }[]; attestation: "none" | "indirect" | "direct" | "enterprise"; @@ -342,6 +438,15 @@ export type APIInterface = { }; } | GeneralErrorResponse + | { status: "EMAIL_MISSING_ERROR" } // email is required if not using session or recoverAccountToken + | { status: "SESSION_MISSING_ERROR" } // session is required if not using email or recoverAccountToken + | { status: "RECOVER_ACCOUNT_TOKEN_MISSING_ERROR" } // recoverAccountToken is required if not using email or session + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "RELYING_PARTY_ID_MISSING_ERROR" } + | { status: "RELYING_PARTY_ID_INVALID_ERROR" } // wrong format; must be domain + | { status: "RELYING_PARTY_NAME_MISSING_ERROR" } // relyingPartyName is required + | { status: "ORIGIN_MISSING_ERROR" } + | { status: "ORIGIN_INVALID_ERROR" } // wrong format; must be domain url >); signInOptionsPOST: @@ -359,12 +464,12 @@ export type APIInterface = { userVerification: "required" | "preferred" | "discouraged"; } | GeneralErrorResponse + | { status: "SIGN_IN_OPTIONS_NOT_ALLOWED"; reason: string } >); signUpPOST: | undefined | ((input: { - email: string; webauthnGeneratedOptionsId: string; credential: { id: string; @@ -438,72 +543,72 @@ export type APIInterface = { | GeneralErrorResponse >); - // todo uncomment one by one once starting implementation - // generateRecoverAccountTokenPOST: - // | undefined - // | ((input: { - // email: string; - // tenantId: string; - // options: APIOptions; - // userContext: UserContext; - // }) => Promise< - // | { - // status: "OK"; - // } - // | { - // status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; - // reason: string; - // } - // | GeneralErrorResponse - // >); + generateRecoverAccountTokenPOST: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | { + status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + >); - // recoverAccountPOST: - // | undefined - // | ((input: { - // credential: { - // id: string; - // rawId: string; - // response: { - // clientDataJSON: string; - // attestationObject: string; - // transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - // userHandle: string; - // }; - // authenticatorAttachment: "platform" | "cross-platform"; - // clientExtensionResults: Record; - // type: "public-key"; - // }; - // token: string; - // tenantId: string; - // options: APIOptions; - // userContext: UserContext; - // }) => Promise< - // | { - // status: "OK"; - // email: string; - // user: User; - // } - // | { - // status: "RECOVER_ACCOUNT_TOKEN_INVALID_TOKEN_ERROR"; - // } - // | GeneralErrorResponse - // >); + recoverAccountPOST: + | undefined + | ((input: { + webauthnGeneratedOptionsId: string; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + token: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + email: string; + user: User; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_TOKEN_ERROR"; + } + | GeneralErrorResponse + >); - // // used for checking if the email already exists before generating the passkey - // emailExistsGET: - // | undefined - // | ((input: { - // email: string; - // tenantId: string; - // options: APIOptions; - // userContext: UserContext; - // }) => Promise< - // | { - // status: "OK"; - // exists: boolean; - // } - // | GeneralErrorResponse - // >); + // used for checking if the email already exists before generating the credential + emailExistsGET: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + exists: boolean; + } + | GeneralErrorResponse + >); }; export type TypeWebauthnRecoverAccountEmailDeliveryInput = { From e150fc2e242631af22fcaa1b064423c953436fc2 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 18 Oct 2024 15:57:54 +0300 Subject: [PATCH 05/36] pr changes. removed incorrect errors and added missing ones --- lib/ts/recipe/webauthn/types.ts | 85 +++++++++++++-------------------- 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 34108e0c7..65ad9e66c 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -45,9 +45,16 @@ export type TypeNormalisedInputRelyingPartyId = (input: { userContext: UserContext; }) => string; // should return the domain of the origin -export type TypeNormalisedInputRelyingPartyName = () => string; // should return the app name +export type TypeNormalisedInputRelyingPartyName = (input: { + tenantId: string; + userContext: UserContext; +}) => Promise; // should return the app name -export type TypeNormalisedInputGetOrigin = (input: { request: BaseRequest; userContext: UserContext }) => string; // should return the app name +export type TypeNormalisedInputGetOrigin = (input: { + tenantId: string; + request: BaseRequest; + userContext: UserContext; +}) => Promise; // should return the app name export type TypeInput = { emailDelivery?: EmailDeliveryTypeInput; @@ -65,11 +72,17 @@ export type TypeInput = { export type TypeInputRelyingPartyId = | string - | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); + | ((input: { tenantId: string; request: BaseRequest | undefined; userContext: UserContext }) => Promise); -export type TypeInputRelyingPartyName = string | (() => string); +export type TypeInputRelyingPartyName = + | string + | ((input: { tenantId: string; userContext: UserContext }) => Promise); -export type TypeInputGetOrigin = (input: { request: BaseRequest; userContext: UserContext }) => string; +export type TypeInputGetOrigin = (input: { + tenantId: string; + request: BaseRequest; + userContext: UserContext; +}) => Promise; export type RecipeInterface = { // should have a way to access the user email: passed as a param, through session, or using recoverAccountToken @@ -133,15 +146,7 @@ export type RecipeInterface = { userVerification: "required" | "preferred" | "discouraged"; }; } - | { status: "EMAIL_MISSING_ERROR" } // email is required if not using session or recoverAccountToken - | { status: "SESSION_MISSING_ERROR" } // session is required if not using email or recoverAccountToken - | { status: "RECOVER_ACCOUNT_TOKEN_MISSING_ERROR" } // recoverAccountToken is required if not using email or session | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "RELYING_PARTY_ID_MISSING_ERROR" } - | { status: "RELYING_PARTY_ID_INVALID_ERROR" } // wrong format; must be domain - | { status: "RELYING_PARTY_NAME_MISSING_ERROR" } // relyingPartyName is required - | { status: "ORIGIN_MISSING_ERROR" } - | { status: "ORIGIN_INVALID_ERROR" } // wrong format; must be domain url >; signInOptions(input: { @@ -151,19 +156,13 @@ export type RecipeInterface = { timeout: number | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: "required" | "preferred" | "discouraged"; - } - | { status: "RELYING_PARTY_ID_MISSING_ERROR" } - | { status: "RELYING_PARTY_ID_INVALID_ERROR" } // wrong format; must be domain - | { status: "ORIGIN_MISSING_ERROR" } - | { status: "ORIGIN_INVALID_ERROR" } // wrong format; must be domain url - >; + }): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + }>; signUp(input: { webauthnGeneratedOptionsId: string; @@ -191,9 +190,9 @@ export type RecipeInterface = { recipeUserId: RecipeUserId; } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct + | { status: "INVALID_AUTHENTICATOR_ERROR" } | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "CREDENTIAL_MISSING_ERROR" } - | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -226,8 +225,8 @@ export type RecipeInterface = { }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "CREDENTIAL_MISSING_ERROR" } - | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } + // when the attestation is checked and is not valid or other cases in which the authenticator is not correct + | { status: "INVALID_AUTHENTICATOR_ERROR" } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -278,13 +277,10 @@ export type RecipeInterface = { userId: string; } | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "CREDENTIAL_MISSING_ERROR" } - | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } - | { status: "RECOVER_ACCOUNT_TOKEN_MISSING_ERROR" } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } >; - // used internally for creating a passkey during password reset flow or when adding a credential to an existing user + // used internally for creating a credential during account recovery flow or when adding a credential to an existing user // email will be taken from the options // no need for recoverAccountToken, as that will be used upstream // (in consumeRecoverAccountToken invalidating the token and in registerOptions for storing the email in the generated options) @@ -311,9 +307,8 @@ export type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - | { status: "CREDENTIAL_MISSING_ERROR" } | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR" } >; // this function is meant only for creating the recipe in the core and nothing else. @@ -345,8 +340,6 @@ export type RecipeInterface = { } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "CREDENTIAL_MISSING_ERROR" } - | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } >; verifyCredentials(input: { @@ -369,8 +362,7 @@ export type RecipeInterface = { }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "CREDENTIAL_MISSING_ERROR" } - | { status: "GENERATED_OPTIONS_ID_MISSING_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR" } >; // used for retrieving the user details (email) from the recover account token @@ -380,9 +372,7 @@ export type RecipeInterface = { tenantId: string; userContext: UserContext; }): Promise< - | { status: "OK"; user: User; recipeUserId: RecipeUserId } - | { status: "RECOVER_ACCOUNT_TOKEN_MISSING_ERROR" } - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + { status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } >; }; @@ -439,14 +429,7 @@ export type APIInterface = { } | GeneralErrorResponse | { status: "EMAIL_MISSING_ERROR" } // email is required if not using session or recoverAccountToken - | { status: "SESSION_MISSING_ERROR" } // session is required if not using email or recoverAccountToken - | { status: "RECOVER_ACCOUNT_TOKEN_MISSING_ERROR" } // recoverAccountToken is required if not using email or session | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "RELYING_PARTY_ID_MISSING_ERROR" } - | { status: "RELYING_PARTY_ID_INVALID_ERROR" } // wrong format; must be domain - | { status: "RELYING_PARTY_NAME_MISSING_ERROR" } // relyingPartyName is required - | { status: "ORIGIN_MISSING_ERROR" } - | { status: "ORIGIN_INVALID_ERROR" } // wrong format; must be domain url >); signInOptionsPOST: @@ -589,7 +572,7 @@ export type APIInterface = { user: User; } | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_TOKEN_ERROR"; + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; } | GeneralErrorResponse >); From 8c8d71121037a2625419711e6718443a9105bacc Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 18 Oct 2024 16:21:45 +0300 Subject: [PATCH 06/36] added missing user type --- lib/ts/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ts/user.ts b/lib/ts/user.ts index 365e0f846..f7daf67aa 100644 --- a/lib/ts/user.ts +++ b/lib/ts/user.ts @@ -136,7 +136,7 @@ export type UserWithoutHelperFunctions = { userId: string; }[]; loginMethods: { - recipeId: "emailpassword" | "thirdparty" | "passwordless"; + recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; recipeUserId: string; tenantIds: string[]; From ced57b11260144d1e1b89039db3c1bdbbc258318 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 18 Oct 2024 20:38:36 +0300 Subject: [PATCH 07/36] added webauthn details to user object --- lib/ts/recipe/accountlinking/types.ts | 3 +++ lib/ts/types.ts | 3 +++ lib/ts/user.ts | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/ts/recipe/accountlinking/types.ts b/lib/ts/recipe/accountlinking/types.ts index 9426edbc9..5559796c9 100644 --- a/lib/ts/recipe/accountlinking/types.ts +++ b/lib/ts/recipe/accountlinking/types.ts @@ -192,6 +192,9 @@ export type AccountInfo = { id: string; userId: string; }; + webauthn?: { + credentialIds: string[]; + }; }; export type AccountInfoWithRecipeId = { diff --git a/lib/ts/types.ts b/lib/ts/types.ts index e87e6b2a7..482d41b9d 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -115,6 +115,9 @@ export type User = { id: string; userId: string; }[]; + webauthn: { + credentialIds: string[]; + }[]; loginMethods: (RecipeLevelUser & { verified: boolean; hasSameEmailAs: (email: string | undefined) => boolean; diff --git a/lib/ts/user.ts b/lib/ts/user.ts index f7daf67aa..87213468e 100644 --- a/lib/ts/user.ts +++ b/lib/ts/user.ts @@ -11,6 +11,7 @@ export class LoginMethod implements RecipeLevelUser { public readonly email?: string; public readonly phoneNumber?: string; public readonly thirdParty?: RecipeLevelUser["thirdParty"]; + public readonly webauthn?: RecipeLevelUser["webauthn"]; public readonly verified: boolean; public readonly timeJoined: number; @@ -23,7 +24,7 @@ export class LoginMethod implements RecipeLevelUser { this.email = loginMethod.email; this.phoneNumber = loginMethod.phoneNumber; this.thirdParty = loginMethod.thirdParty; - + this.webauthn = loginMethod.webauthn; this.timeJoined = loginMethod.timeJoined; this.verified = loginMethod.verified; } @@ -73,6 +74,7 @@ export class LoginMethod implements RecipeLevelUser { email: this.email, phoneNumber: this.phoneNumber, thirdParty: this.thirdParty, + webauthn: this.webauthn, timeJoined: this.timeJoined, verified: this.verified, }; @@ -90,6 +92,9 @@ export class User implements UserType { id: string; userId: string; }[]; + public readonly webauthn: { + credentialIds: string[]; + }[]; public readonly loginMethods: LoginMethod[]; public readonly timeJoined: number; // minimum timeJoined value from linkedRecipes @@ -102,7 +107,7 @@ export class User implements UserType { this.emails = user.emails; this.phoneNumbers = user.phoneNumbers; this.thirdParty = user.thirdParty; - + this.webauthn = user.webauthn; this.timeJoined = user.timeJoined; this.loginMethods = user.loginMethods.map((m) => new LoginMethod(m)); @@ -117,6 +122,7 @@ export class User implements UserType { emails: this.emails, phoneNumbers: this.phoneNumbers, thirdParty: this.thirdParty, + webauthn: this.webauthn, loginMethods: this.loginMethods.map((m) => m.toJson()), timeJoined: this.timeJoined, @@ -135,6 +141,9 @@ export type UserWithoutHelperFunctions = { id: string; userId: string; }[]; + webauthn: { + credentialIds: string[]; + }[]; loginMethods: { recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; recipeUserId: string; @@ -146,6 +155,9 @@ export type UserWithoutHelperFunctions = { id: string; userId: string; }; + webauthn?: { + credentialIds: string[]; + }; verified: boolean; timeJoined: number; From fbbed56109bcccbd51d19fc0aa0c307a8b7bab08 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 21 Oct 2024 12:00:10 +0300 Subject: [PATCH 08/36] pr fixes. centralized error types and added crud for credentials --- lib/ts/recipe/multitenancy/types.ts | 3 + lib/ts/recipe/webauthn/types.ts | 223 ++++++++++++++++++++++------ 2 files changed, 178 insertions(+), 48 deletions(-) diff --git a/lib/ts/recipe/multitenancy/types.ts b/lib/ts/recipe/multitenancy/types.ts index 3dfb26a7a..e0d56dff3 100644 --- a/lib/ts/recipe/multitenancy/types.ts +++ b/lib/ts/recipe/multitenancy/types.ts @@ -173,6 +173,9 @@ export type APIInterface = { passwordless: { enabled: boolean; }; + webauthn: { + credentialIds: string[]; + }; firstFactors: string[]; } | GeneralErrorResponse diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 65ad9e66c..c3f93f7bc 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -84,6 +84,37 @@ export type TypeInputGetOrigin = (input: { userContext: UserContext; }) => Promise; +// centralize error types in order to prevent missing cascading errors +type RegisterCredentialErrorResponse = + | { status: "WRONG_CREDENTIALS_ERROR" } + // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct + | { status: "INVALID_AUTHENTICATOR_ERROR" }; + +type VerifyCredentialsErrorResponse = + | { status: "WRONG_CREDENTIALS_ERROR" } + // when the attestation is checked and is not valid or other cases in which the authenticator is not correct + | { status: "INVALID_AUTHENTICATOR_ERROR" }; + +type CreateNewRecipeUserErrorResponse = RegisterCredentialErrorResponse | { status: "EMAIL_ALREADY_EXISTS_ERROR" }; + +type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; + +type RegisterOptionsErrorResponse = GetUserFromRecoverAccountTokenErrorResponse | { status: "EMAIL_MISSING_ERROR" }; + +type SignUpErrorResponse = CreateNewRecipeUserErrorResponse; + +type SignInErrorResponse = VerifyCredentialsErrorResponse; + +type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" } | { status: "UNKNOWN_EMAIL_ERROR" }; + +type ConsumeRecoverAccountTokenErrorResponse = + | RegisterCredentialErrorResponse + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; + +type AddCredentialErrorResponse = RegisterCredentialErrorResponse; + +type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; + export type RecipeInterface = { // should have a way to access the user email: passed as a param, through session, or using recoverAccountToken // it should have at least one of those 3 options @@ -146,7 +177,7 @@ export type RecipeInterface = { userVerification: "required" | "preferred" | "discouraged"; }; } - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | RegisterOptionsErrorResponse >; signInOptions(input: { @@ -189,10 +220,7 @@ export type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct - | { status: "INVALID_AUTHENTICATOR_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } + | SignUpErrorResponse | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -224,9 +252,7 @@ export type RecipeInterface = { userContext: UserContext; }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } - | { status: "WRONG_CREDENTIALS_ERROR" } - // when the attestation is checked and is not valid or other cases in which the authenticator is not correct - | { status: "INVALID_AUTHENTICATOR_ERROR" } + | SignInErrorResponse | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -247,9 +273,7 @@ export type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise< - { status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" } | { status: "UNKNOWN_EMAIL_ERROR" } - >; + }): Promise<{ status: "OK"; token: string } | GenerateRecoverAccountTokenErrorResponse>; // make sure the email maps to options email consumeRecoverAccountToken(input: { @@ -276,15 +300,14 @@ export type RecipeInterface = { email: string; userId: string; } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | ConsumeRecoverAccountTokenErrorResponse >; // used internally for creating a credential during account recovery flow or when adding a credential to an existing user // email will be taken from the options // no need for recoverAccountToken, as that will be used upstream // (in consumeRecoverAccountToken invalidating the token and in registerOptions for storing the email in the generated options) - registerPasskey(input: { + registerCredential(input: { webauthnGeneratedOptionsId: string; credential: { id: string; @@ -307,8 +330,7 @@ export type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR" } + | RegisterCredentialErrorResponse >; // this function is meant only for creating the recipe in the core and nothing else. @@ -338,8 +360,7 @@ export type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } + | CreateNewRecipeUserErrorResponse >; verifyCredentials(input: { @@ -359,11 +380,7 @@ export type RecipeInterface = { }; tenantId: string; userContext: UserContext; - }): Promise< - | { status: "OK"; user: User; recipeUserId: RecipeUserId } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR" } - >; + }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | VerifyCredentialsErrorResponse>; // used for retrieving the user details (email) from the recover account token // should be used in the registerOptions function when the user recovers the account and generates the credentials @@ -371,8 +388,45 @@ export type RecipeInterface = { token: string; tenantId: string; userContext: UserContext; + }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | GetUserFromRecoverAccountTokenErrorResponse>; + + // credentials CRUD + + // this will call registerCredential internally + addCredential(input: { + webauthnGeneratedOptionsId: string; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + } + | AddCredentialErrorResponse + >; + + // credentials CRUD + removeCredential(input: { + webauthnCredentialId: string; + tenantId: string; + userContext: UserContext; }): Promise< - { status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { + status: "OK"; + } + | RemoveCredentialErrorResponse >; }; @@ -387,6 +441,51 @@ export type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; +type RegisterOptionsPOSTErrorResponse = + | RegisterOptionsErrorResponse + | { status: "REGISTER_OPTIONS_NOT_ALLOWED"; reason: string }; + +type SignInOptionsPOSTErrorResponse = { status: "SIGN_IN_OPTIONS_NOT_ALLOWED"; reason: string }; + +type SignUpPOSTErrorResponse = + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | SignUpErrorResponse; + +type SignInPOSTErrorResponse = + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } + | SignInErrorResponse; + +type GenerateRecoverAccountTokenPOSTErrorResponse = { + status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; + reason: string; +}; + +type RecoverAccountPOSTErrorResponse = + | { + status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; + reason: string; + } + | ConsumeRecoverAccountTokenErrorResponse; + +type AddCredentialPOSTErrorResponse = + | { + status: "ADD_CREDENTIAL_NOT_ALLOWED"; + reason: string; + } + | AddCredentialErrorResponse; + +type RemoveCredentialPOSTErrorResponse = + | { + status: "REMOVE_CREDENTIAL_NOT_ALLOWED"; + reason: string; + } + | RemoveCredentialErrorResponse; export type APIInterface = { registerOptionsPOST: | undefined @@ -428,8 +527,7 @@ export type APIInterface = { }; } | GeneralErrorResponse - | { status: "EMAIL_MISSING_ERROR" } // email is required if not using session or recoverAccountToken - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | RegisterOptionsPOSTErrorResponse >); signInOptionsPOST: @@ -447,7 +545,7 @@ export type APIInterface = { userVerification: "required" | "preferred" | "discouraged"; } | GeneralErrorResponse - | { status: "SIGN_IN_OPTIONS_NOT_ALLOWED"; reason: string } + | SignInOptionsPOSTErrorResponse >); signUpPOST: @@ -478,14 +576,8 @@ export type APIInterface = { user: User; session: SessionContainerInterface; } - | { - status: "SIGN_UP_NOT_ALLOWED"; - reason: string; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } | GeneralErrorResponse + | SignUpPOSTErrorResponse >); signInPOST: @@ -516,14 +608,8 @@ export type APIInterface = { user: User; session: SessionContainerInterface; } - | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } - | { - status: "WRONG_CREDENTIALS_ERROR"; - } | GeneralErrorResponse + | SignInPOSTErrorResponse >); generateRecoverAccountTokenPOST: @@ -537,10 +623,7 @@ export type APIInterface = { | { status: "OK"; } - | { - status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; - reason: string; - } + | GenerateRecoverAccountTokenPOSTErrorResponse | GeneralErrorResponse >); @@ -571,9 +654,7 @@ export type APIInterface = { email: string; user: User; } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } + | RecoverAccountPOSTErrorResponse | GeneralErrorResponse >); @@ -592,6 +673,52 @@ export type APIInterface = { } | GeneralErrorResponse >); + + //credentials CRUD + addCredentialPOST: + | undefined + | ((input: { + webauthnGeneratedOptionsId: string; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + tenantId: string; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | AddCredentialPOSTErrorResponse + | GeneralErrorResponse + >); + + removeCredentialPOST: + | undefined + | ((input: { + webauthnCredentialId: string; + tenantId: string; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | RemoveCredentialPOSTErrorResponse + | GeneralErrorResponse + >); }; export type TypeWebauthnRecoverAccountEmailDeliveryInput = { From 64a4a6bb90af4dc78d2c66218aa071a2f8eb867b Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 21 Oct 2024 16:26:04 +0300 Subject: [PATCH 09/36] pr fixes --- lib/ts/recipe/multitenancy/types.ts | 3 -- lib/ts/recipe/webauthn/types.ts | 69 ++++++++++++++++------------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/lib/ts/recipe/multitenancy/types.ts b/lib/ts/recipe/multitenancy/types.ts index e0d56dff3..3dfb26a7a 100644 --- a/lib/ts/recipe/multitenancy/types.ts +++ b/lib/ts/recipe/multitenancy/types.ts @@ -173,9 +173,6 @@ export type APIInterface = { passwordless: { enabled: boolean; }; - webauthn: { - credentialIds: string[]; - }; firstFactors: string[]; } | GeneralErrorResponse diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index c3f93f7bc..205fa6067 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -111,8 +111,6 @@ type ConsumeRecoverAccountTokenErrorResponse = | RegisterCredentialErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; -type AddCredentialErrorResponse = RegisterCredentialErrorResponse; - type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; export type RecipeInterface = { @@ -390,37 +388,9 @@ export type RecipeInterface = { userContext: UserContext; }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | GetUserFromRecoverAccountTokenErrorResponse>; - // credentials CRUD - - // this will call registerCredential internally - addCredential(input: { - webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; - tenantId: string; - userContext: UserContext; - }): Promise< - | { - status: "OK"; - } - | AddCredentialErrorResponse - >; - // credentials CRUD removeCredential(input: { webauthnCredentialId: string; - tenantId: string; userContext: UserContext; }): Promise< | { @@ -428,6 +398,17 @@ export type RecipeInterface = { } | RemoveCredentialErrorResponse >; + + listCredentials(input: { + userContext: UserContext; + }): Promise<{ + status: "OK"; + credentials: { + id: string; + rp_id: string; + created_at: number; + }[]; + }>; }; export type APIOptions = { @@ -478,7 +459,7 @@ type AddCredentialPOSTErrorResponse = status: "ADD_CREDENTIAL_NOT_ALLOWED"; reason: string; } - | AddCredentialErrorResponse; + | RegisterCredentialErrorResponse; type RemoveCredentialPOSTErrorResponse = | { @@ -486,6 +467,12 @@ type RemoveCredentialPOSTErrorResponse = reason: string; } | RemoveCredentialErrorResponse; + +type ListCredentialsPOSTErrorResponse = { + status: "LIST_CREDENTIALS_NOT_ALLOWED"; + reason: string; +}; + export type APIInterface = { registerOptionsPOST: | undefined @@ -719,6 +706,26 @@ export type APIInterface = { | RemoveCredentialPOSTErrorResponse | GeneralErrorResponse >); + + listCredentialsPOST: + | undefined + | ((input: { + tenantId: string; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + credentials: { + id: string; + rp_id: string; + created_at: number; + }[]; + } + | ListCredentialsPOSTErrorResponse + | GeneralErrorResponse + >); }; export type TypeWebauthnRecoverAccountEmailDeliveryInput = { From 3186aa5aa282fa79a3ae2ea2dee76d3db3c1138e Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 21 Oct 2024 19:27:50 +0300 Subject: [PATCH 10/36] pr fixes and added decode method --- lib/ts/recipe/webauthn/types.ts | 130 ++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 7 deletions(-) diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 205fa6067..3503aea4c 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -88,7 +88,7 @@ export type TypeInputGetOrigin = (input: { type RegisterCredentialErrorResponse = | { status: "WRONG_CREDENTIALS_ERROR" } // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct - | { status: "INVALID_AUTHENTICATOR_ERROR" }; + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; type VerifyCredentialsErrorResponse = | { status: "WRONG_CREDENTIALS_ERROR" } @@ -113,6 +113,10 @@ type ConsumeRecoverAccountTokenErrorResponse = type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; +type DecodeCredentialErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; + +type Base64URLString = string; + export type RecipeInterface = { // should have a way to access the user email: passed as a param, through session, or using recoverAccountToken // it should have at least one of those 3 options @@ -139,9 +143,6 @@ export type RecipeInterface = { | { email: string; } - | { - session: SessionContainerInterface; - } ) ): Promise< | { @@ -301,6 +302,78 @@ export type RecipeInterface = { | ConsumeRecoverAccountTokenErrorResponse >; + decodeCredential(input: { + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; + }; + }): Promise< + | { + status: "OK"; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: { + type: string; + challenge: string; + origin: string; + crossOrigin?: boolean; + tokenBinding?: { + id?: string; + status: "present" | "supported" | "not-supported"; + }; + }; + attestationObject: { + fmt: "packed" | "tpm" | "android-key" | "android-safetynet" | "fido-u2f" | "none"; + authData: { + rpIdHash: string; + flags: { + up: boolean; + uv: boolean; + be: boolean; + bs: boolean; + at: boolean; + ed: boolean; + flagsInt: number; + }; + counter: number; + aaguid?: string; + credentialID?: string; + credentialPublicKey?: string; + extensionsData?: unknown; + }; + attStmt: { + sig?: Base64URLString; + x5c?: Base64URLString[]; + response?: Base64URLString; + alg?: number; + ver?: string; + certInfo?: Base64URLString; + pubArea?: Base64URLString; + size: number; + }; + }; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: string; + }; + } + | DecodeCredentialErrorResponse + >; + // used internally for creating a credential during account recovery flow or when adding a credential to an existing user // email will be taken from the options // no need for recoverAccountToken, as that will be used upstream @@ -322,11 +395,10 @@ export type RecipeInterface = { }; tenantId: string; userContext: UserContext; + recipeUserId: RecipeUserId; }): Promise< | { status: "OK"; - user: User; - recipeUserId: RecipeUserId; } | RegisterCredentialErrorResponse >; @@ -391,6 +463,7 @@ export type RecipeInterface = { // credentials CRUD removeCredential(input: { webauthnCredentialId: string; + recipeUserId: RecipeUserId; userContext: UserContext; }): Promise< | { @@ -399,7 +472,24 @@ export type RecipeInterface = { | RemoveCredentialErrorResponse >; + getCredential(input: { + webauthnCredentialId: string; + recipeUserId: RecipeUserId; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + credential: { + id: string; + rp_id: string; + created_at: number; + }; + } + | RemoveCredentialErrorResponse + >; + listCredentials(input: { + recipeUserId: RecipeUserId; userContext: UserContext; }): Promise<{ status: "OK"; @@ -473,6 +563,11 @@ type ListCredentialsPOSTErrorResponse = { reason: string; }; +type GetCredentialGETErrorResponse = { + status: "GET_CREDENTIAL_NOT_ALLOWED"; + reason: string; +}; + export type APIInterface = { registerOptionsPOST: | undefined @@ -707,7 +802,7 @@ export type APIInterface = { | GeneralErrorResponse >); - listCredentialsPOST: + listCredentialsGET: | undefined | ((input: { tenantId: string; @@ -726,6 +821,27 @@ export type APIInterface = { | ListCredentialsPOSTErrorResponse | GeneralErrorResponse >); + + getCredentialGET: + | undefined + | ((input: { + webauthnCredentialId: string; + tenantId: string; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + credential: { + id: string; + rp_id: string; + created_at: number; + }; + } + | GetCredentialGETErrorResponse + | GeneralErrorResponse + >); }; export type TypeWebauthnRecoverAccountEmailDeliveryInput = { From 3fa31cee027ee06dc052bdcfbdacb6f4cf517343 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 28 Oct 2024 08:26:21 +0200 Subject: [PATCH 11/36] added types implementation and minor fixes --- lib/ts/recipe/webauthn/api/emailExists.ts | 51 + .../api/generateRecoverAccountToken.ts | 50 + lib/ts/recipe/webauthn/api/implementation.ts | 1399 +++++++++-------- lib/ts/recipe/webauthn/api/recoverAccount.ts | 72 + lib/ts/recipe/webauthn/api/registerOptions.ts | 10 +- lib/ts/recipe/webauthn/api/signInOptions.ts | 1 + lib/ts/recipe/webauthn/api/signin.ts | 1 - lib/ts/recipe/webauthn/constants.ts | 18 +- lib/ts/recipe/webauthn/index.ts | 600 +++---- lib/ts/recipe/webauthn/recipe.ts | 70 +- .../recipe/webauthn/recipeImplementation.ts | 260 +-- lib/ts/recipe/webauthn/types.ts | 209 +-- lib/ts/recipe/webauthn/utils.ts | 18 +- 13 files changed, 1464 insertions(+), 1295 deletions(-) create mode 100644 lib/ts/recipe/webauthn/api/emailExists.ts create mode 100644 lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts create mode 100644 lib/ts/recipe/webauthn/api/recoverAccount.ts diff --git a/lib/ts/recipe/webauthn/api/emailExists.ts b/lib/ts/recipe/webauthn/api/emailExists.ts new file mode 100644 index 000000000..3ed5ecab6 --- /dev/null +++ b/lib/ts/recipe/webauthn/api/emailExists.ts @@ -0,0 +1,51 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { send200Response } from "../../../utils"; +import STError from "../error"; +import { APIInterface, APIOptions } from "../"; +import { UserContext } from "../../../types"; + +export default async function emailExists( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + // Logic as per https://github.com/supertokens/supertokens-node/issues/47#issue-751571692 + + if (apiImplementation.emailExistsGET === undefined) { + return false; + } + + let email = options.req.getKeyValueFromQuery("email"); + + if (email === undefined || typeof email !== "string") { + throw new STError({ + type: STError.BAD_INPUT_ERROR, + message: "Please provide the email as a GET param", + }); + } + + let result = await apiImplementation.emailExistsGET({ + email, + tenantId, + options, + userContext, + }); + + send200Response(options.res, result); + return true; +} diff --git a/lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts b/lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts new file mode 100644 index 000000000..4fe1ef211 --- /dev/null +++ b/lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts @@ -0,0 +1,50 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from "../"; +import { UserContext } from "../../../types"; +import STError from "../error"; + +export default async function generateRecoverAccountToken( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.generateRecoverAccountTokenPOST === undefined) { + return false; + } + + const requestBody = await options.req.getJSONBody(); + const email = requestBody.email; + + if (email === undefined || typeof email !== "string") { + throw new STError({ + type: STError.BAD_INPUT_ERROR, + message: "Please provide the email", + }); + } + + let result = await apiImplementation.generateRecoverAccountTokenPOST({ + email, + tenantId, + options, + userContext, + }); + + send200Response(options.res, result); + return true; +} diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 2408114ff..dc87e0e0b 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -1,68 +1,130 @@ import { APIInterface, APIOptions } from ".."; import { GeneralErrorResponse, User, UserContext } from "../../../types"; import AccountLinking from "../../accountlinking/recipe"; +import EmailVerification from "../../emailverification/recipe"; import { AuthUtils } from "../../../authUtils"; import { isFakeEmail } from "../../thirdparty/utils"; import { SessionContainerInterface } from "../../session/types"; import { - DEFAULT_REGISTER_ATTESTATION, + DEFAULT_REGISTER_OPTIONS_ATTESTATION, DEFAULT_REGISTER_OPTIONS_TIMEOUT, + DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, + DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, + DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, DEFAULT_SIGNIN_OPTIONS_TIMEOUT, + DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, } from "../constants"; +import RecipeUserId from "../../../recipeUserId"; +import { getRecoverAccountLink } from "../utils"; +import { logDebugMessage } from "../../../logger"; +import { RecipeLevelUser } from "../../accountlinking/types"; +import { getUser } from "../../.."; +import { CredentialPayload } from "../types"; export default function getAPIImplementation(): APIInterface { return { - signInOptionsPOST: async function ({ + registerOptionsPOST: async function ({ tenantId, options, userContext, + ...props }: { tenantId: string; options: APIOptions; userContext: UserContext; - }): Promise< + } & ({ email: string } | { recoverAccountToken: string })): Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; challenge: string; timeout: number; - userVerification: "required" | "preferred" | "discouraged"; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; } - | GeneralErrorResponse + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "EMAIL_MISSING_ERROR" } > { - // todo move to recipe implementation - const timeout = DEFAULT_SIGNIN_OPTIONS_TIMEOUT; + const relyingPartyId = await options.config.relyingPartyId({ + tenantId, + request: options.req, + userContext, + }); + const relyingPartyName = await options.config.relyingPartyName({ + tenantId, + userContext, + }); - const relyingPartyId = options.config.relyingPartyId({ request: options.req, userContext: userContext }); + const origin = await options.config.getOrigin({ + tenantId, + request: options.req, + userContext, + }); - // use this to get the full url instead of only the domain url - const origin = options.appInfo - .getOrigin({ request: options.req, userContext: userContext }) - .getAsStringDangerous(); + const timeout = DEFAULT_REGISTER_OPTIONS_TIMEOUT; + const attestation = DEFAULT_REGISTER_OPTIONS_ATTESTATION; + const requireResidentKey = DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY; + const residentKey = DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY; + const userVerification = DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION; - let response = await options.recipeImplementation.signInOptions({ + let response = await options.recipeImplementation.registerOptions({ + ...props, + attestation, + requireResidentKey, + residentKey, + userVerification, origin, relyingPartyId, + relyingPartyName, timeout, tenantId, userContext, }); + if (response.status !== "OK") { + return response; + } + return { status: "OK", webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, challenge: response.challenge, timeout: response.timeout, - userVerification: response.userVerification, + attestation: response.attestation, + pubKeyCredParams: response.pubKeyCredParams, + excludeCredentials: response.excludeCredentials, + rp: response.rp, + user: response.user, + authenticatorSelection: response.authenticatorSelection, }; }, - registerOptionsPOST: async function ({ - email, + + signInOptionsPOST: async function ({ tenantId, options, userContext, }: { - email: string; tenantId: string; options: APIOptions; userContext: UserContext; @@ -70,74 +132,50 @@ export default function getAPIImplementation(): APIInterface { | { status: "OK"; webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; challenge: string; timeout: number; - excludeCredentials: { - id: string; - type: string; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: string; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; - }; + userVerification: "required" | "preferred" | "discouraged"; } | GeneralErrorResponse > { - // todo move to recipe implementation - const timeout = DEFAULT_REGISTER_OPTIONS_TIMEOUT; - // todo move to recipe implementation - const attestation = DEFAULT_REGISTER_ATTESTATION; + const relyingPartyId = await options.config.relyingPartyId({ + tenantId, + request: options.req, + userContext, + }); - const relyingPartyId = options.config.relyingPartyId({ request: options.req, userContext: userContext }); - const relyingPartyName = options.config.relyingPartyName({ + // use this to get the full url instead of only the domain url + const origin = await options.config.getOrigin({ + tenantId, request: options.req, - userContext: userContext, + userContext, }); - const origin = options.appInfo - .getOrigin({ request: options.req, userContext: userContext }) - .getAsStringDangerous(); + const timeout = DEFAULT_SIGNIN_OPTIONS_TIMEOUT; + const userVerification = DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION; - let response = await options.recipeImplementation.registerOptions({ - email, - attestation, + let response = await options.recipeImplementation.signInOptions({ + userVerification, origin, relyingPartyId, - relyingPartyName, timeout, tenantId, userContext, }); + if (response.status !== "OK") { + return response; + } + return { status: "OK", webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, challenge: response.challenge, timeout: response.timeout, - attestation: response.attestation, - pubKeyCredParams: response.pubKeyCredParams, - excludeCredentials: response.excludeCredentials, - rp: response.rp, - user: response.user, - authenticatorSelection: response.authenticatorSelection, + userVerification: response.userVerification, }; }, + signUpPOST: async function ({ email, webauthnGeneratedOptionsId, @@ -150,19 +188,7 @@ export default function getAPIImplementation(): APIInterface { }: { email: string; webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; tenantId: string; session?: SessionContainerInterface; shouldTryLinkingWithSessionUser: boolean | undefined; @@ -178,14 +204,18 @@ export default function getAPIImplementation(): APIInterface { status: "SIGN_UP_NOT_ALLOWED"; reason: string; } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse > { const errorCodeMap = { SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + INVALID_AUTHENTICATOR_ERROR: { + // TODO: add more cases + }, + WRONG_CREDENTIALS_ERROR: "The sign up credentials are incorrect. Please use a different authenticator.", LINKING_TO_SESSION_USER_FAILED: { EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", @@ -274,7 +304,7 @@ export default function getAPIImplementation(): APIInterface { authenticatedUser: signUpResponse.user, recipeUserId: signUpResponse.recipeUserId, isSignUp: true, - factorId: "emailpassword", + factorId: "webauthn", session, req: options.req, res: options.res, @@ -307,19 +337,7 @@ export default function getAPIImplementation(): APIInterface { userContext, }: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; tenantId: string; session?: SessionContainerInterface; shouldTryLinkingWithSessionUser: boolean | undefined; @@ -342,7 +360,7 @@ export default function getAPIImplementation(): APIInterface { > { const errorCodeMap = { SIGN_IN_NOT_ALLOWED: - "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", + "Cannot sign in due to security reasons. Please try recovering your account, use a different login method or contact support. (ERR_CODE_008)", LINKING_TO_SESSION_USER_FAILED: { EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", @@ -368,10 +386,19 @@ export default function getAPIImplementation(): APIInterface { return verifyCredentialsResponse.status === "OK"; }; - // todo check if this is the correct way to retrieve the email + // doing it like this because the email is only available after verifyCredentials is called let email: string; if (verifyCredentialsResponse.status == "OK") { - email = verifyCredentialsResponse.user.emails[0]; + const loginMethod = verifyCredentialsResponse.user.loginMethods.find((lm) => lm.recipeId === recipeId); + // there should be a webauthn login method and an email when trying to sign in using webauthn + if (!loginMethod || !loginMethod.email) { + return AuthUtils.getErrorStatusResponseWithReason( + verifyCredentialsResponse, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); + } + email = loginMethod?.email; } else { return { status: "WRONG_CREDENTIALS_ERROR", @@ -402,7 +429,7 @@ export default function getAPIImplementation(): APIInterface { recipeId, email, }, - factorIds: ["webauthn"], + factorIds: [recipeId], isSignUp: false, authenticatingUser: authenticatingUser?.user, isVerified, @@ -447,7 +474,7 @@ export default function getAPIImplementation(): APIInterface { authenticatedUser: signInResponse.user, recipeUserId: signInResponse.recipeUserId, isSignUp: false, - factorId: "webauthn", + factorId: recipeId, session, req: options.req, res: options.res, @@ -466,594 +493,596 @@ export default function getAPIImplementation(): APIInterface { }; }, - // emailExistsGET: async function ({ - // email, - // tenantId, - // userContext, - // }: { - // email: string; - // tenantId: string; - // options: APIOptions; - // userContext: UserContext; - // }): Promise< - // | { - // status: "OK"; - // exists: boolean; - // } - // | GeneralErrorResponse - // > { - // // even if the above returns true, we still need to check if there - // // exists an email password user with the same email cause the function - // // above does not check for that. - // let users = await AccountLinking.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ - // tenantId, - // accountInfo: { - // email, - // }, - // doUnionOfAccountInfo: false, - // userContext, - // }); - // let emailPasswordUserExists = - // users.find((u) => { - // return ( - // u.loginMethods.find((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)) !== - // undefined - // ); - // }) !== undefined; - - // return { - // status: "OK", - // exists: emailPasswordUserExists, - // }; - // }, - // generatePasswordResetTokenPOST: async function ({ - // formFields, - // tenantId, - // options, - // userContext, - // }): Promise< - // | { - // status: "OK"; - // } - // | { status: "PASSWORD_RESET_NOT_ALLOWED"; reason: string } - // | GeneralErrorResponse - // > { - // // NOTE: Check for email being a non-string value. This check will likely - // // never evaluate to `true` as there is an upper-level check for the type - // // in validation but kept here to be safe. - // const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; - // if (typeof emailAsUnknown !== "string") - // throw new Error( - // "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" - // ); - // const email: string = emailAsUnknown; - - // // this function will be reused in different parts of the flow below.. - // async function generateAndSendPasswordResetToken( - // primaryUserId: string, - // recipeUserId: RecipeUserId | undefined - // ): Promise< - // | { - // status: "OK"; - // } - // | { status: "PASSWORD_RESET_NOT_ALLOWED"; reason: string } - // | GeneralErrorResponse - // > { - // // the user ID here can be primary or recipe level. - // let response = await options.recipeImplementation.createResetPasswordToken({ - // tenantId, - // userId: recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString(), - // email, - // userContext, - // }); - // if (response.status === "UNKNOWN_USER_ID_ERROR") { - // logDebugMessage( - // `Password reset email not sent, unknown user id: ${ - // recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() - // }` - // ); - // return { - // status: "OK", - // }; - // } - - // let passwordResetLink = getPasswordResetLink({ - // appInfo: options.appInfo, - // token: response.token, - // tenantId, - // request: options.req, - // userContext, - // }); - - // logDebugMessage(`Sending password reset email to ${email}`); - // await options.emailDelivery.ingredientInterfaceImpl.sendEmail({ - // tenantId, - // type: "PASSWORD_RESET", - // user: { - // id: primaryUserId, - // recipeUserId, - // email, - // }, - // passwordResetLink, - // userContext, - // }); - - // return { - // status: "OK", - // }; - // } - - // /** - // * check if primaryUserId is linked with this email - // */ - // let users = await AccountLinking.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ - // tenantId, - // accountInfo: { - // email, - // }, - // doUnionOfAccountInfo: false, - // userContext, - // }); - - // // we find the recipe user ID of the email password account from the user's list - // // for later use. - // let emailPasswordAccount: RecipeLevelUser | undefined = undefined; - // for (let i = 0; i < users.length; i++) { - // let emailPasswordAccountTmp = users[i].loginMethods.find( - // (l) => l.recipeId === "emailpassword" && l.hasSameEmailAs(email) - // ); - // if (emailPasswordAccountTmp !== undefined) { - // emailPasswordAccount = emailPasswordAccountTmp; - // break; - // } - // } - - // // we find the primary user ID from the user's list for later use. - // let primaryUserAssociatedWithEmail = users.find((u) => u.isPrimaryUser); - - // // first we check if there even exists a primary user that has the input email - // // if not, then we do the regular flow for password reset. - // if (primaryUserAssociatedWithEmail === undefined) { - // if (emailPasswordAccount === undefined) { - // logDebugMessage(`Password reset email not sent, unknown user email: ${email}`); - // return { - // status: "OK", - // }; - // } - // return await generateAndSendPasswordResetToken( - // emailPasswordAccount.recipeUserId.getAsString(), - // emailPasswordAccount.recipeUserId - // ); - // } - - // // Next we check if there is any login method in which the input email is verified. - // // If that is the case, then it's proven that the user owns the email and we can - // // trust linking of the email password account. - // let emailVerified = - // primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - // return lm.hasSameEmailAs(email) && lm.verified; - // }) !== undefined; - - // // finally, we check if the primary user has any other email / phone number - // // associated with this account - and if it does, then it means that - // // there is a risk of account takeover, so we do not allow the token to be generated - // let hasOtherEmailOrPhone = - // primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - // // we do the extra undefined check below cause - // // hasSameEmailAs returns false if the lm.email is undefined, and - // // we want to check that the email is different as opposed to email - // // not existing in lm. - // return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; - // }) !== undefined; - - // if (!emailVerified && hasOtherEmailOrPhone) { - // return { - // status: "PASSWORD_RESET_NOT_ALLOWED", - // reason: - // "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", - // }; - // } - - // let shouldDoAccountLinkingResponse = await AccountLinking.getInstance().config.shouldDoAutomaticAccountLinking( - // emailPasswordAccount !== undefined - // ? emailPasswordAccount - // : { - // recipeId: "emailpassword", - // email, - // }, - // primaryUserAssociatedWithEmail, - // undefined, - // tenantId, - // userContext - // ); - - // // Now we need to check that if there exists any email password user at all - // // for the input email. If not, then it implies that when the token is consumed, - // // then we will create a new user - so we should only generate the token if - // // the criteria for the new user is met. - // if (emailPasswordAccount === undefined) { - // // this means that there is no email password user that exists for the input email. - // // So we check for the sign up condition and only go ahead if that condition is - // // met. - - // // But first we must check if account linking is enabled at all - cause if it's - // // not, then the new email password user that will be created in password reset - // // code consume cannot be linked to the primary user - therefore, we should - // // not generate a password reset token - // if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { - // logDebugMessage( - // `Password reset email not sent, since email password user didn't exist, and account linking not enabled` - // ); - // return { - // status: "OK", - // }; - // } - - // let isSignUpAllowed = await AccountLinking.getInstance().isSignUpAllowed({ - // newUser: { - // recipeId: "emailpassword", - // email, - // }, - // isVerified: true, // cause when the token is consumed, we will mark the email as verified - // session: undefined, - // tenantId, - // userContext, - // }); - // if (isSignUpAllowed) { - // // notice that we pass in the primary user ID here. This means that - // // we will be creating a new email password account when the token - // // is consumed and linking it to this primary user. - // return await generateAndSendPasswordResetToken(primaryUserAssociatedWithEmail.id, undefined); - // } else { - // logDebugMessage( - // `Password reset email not sent, isSignUpAllowed returned false for email: ${email}` - // ); - // return { - // status: "OK", - // }; - // } - // } - - // // At this point, we know that some email password user exists with this email - // // and also some primary user ID exist. We now need to find out if they are linked - // // together or not. If they are linked together, then we can just generate the token - // // else we check for more security conditions (since we will be linking them post token generation) - // let areTheTwoAccountsLinked = - // primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - // return lm.recipeUserId.getAsString() === emailPasswordAccount!.recipeUserId.getAsString(); - // }) !== undefined; - - // if (areTheTwoAccountsLinked) { - // return await generateAndSendPasswordResetToken( - // primaryUserAssociatedWithEmail.id, - // emailPasswordAccount.recipeUserId - // ); - // } - - // // Here we know that the two accounts are NOT linked. We now need to check for an - // // extra security measure here to make sure that the input email in the primary user - // // is verified, and if not, we need to make sure that there is no other email / phone number - // // associated with the primary user account. If there is, then we do not proceed. - - // /* - // This security measure helps prevent the following attack: - // An attacker has email A and they create an account using TP and it doesn't matter if A is verified or not. Now they create another account using EP with email A and verifies it. Both these accounts are linked. Now the attacker changes the email for EP recipe to B which makes the EP account unverified, but it's still linked. - - // If the real owner of B tries to signup using EP, it will say that the account already exists so they may try to reset password which should be denied because then they will end up getting access to attacker's account and verify the EP account. - - // The problem with this situation is if the EP account is verified, it will allow further sign-ups with email B which will also be linked to this primary account (that the attacker had created with email A). - - // It is important to realize that the attacker had created another account with A because if they hadn't done that, then they wouldn't have access to this account after the real user resets the password which is why it is important to check there is another non-EP account linked to the primary such that the email is not the same as B. - - // Exception to the above is that, if there is a third recipe account linked to the above two accounts and has B as verified, then we should allow reset password token generation because user has already proven that the owns the email B - // */ - - // // But first, this only matters it the user cares about checking for email verification status.. - - // if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { - // // here we will go ahead with the token generation cause - // // even when the token is consumed, we will not be linking the accounts - // // so no need to check for anything - // return await generateAndSendPasswordResetToken( - // emailPasswordAccount.recipeUserId.getAsString(), - // emailPasswordAccount.recipeUserId - // ); - // } - - // if (!shouldDoAccountLinkingResponse.shouldRequireVerification) { - // // the checks below are related to email verification, and if the user - // // does not care about that, then we should just continue with token generation - // return await generateAndSendPasswordResetToken( - // primaryUserAssociatedWithEmail.id, - // emailPasswordAccount.recipeUserId - // ); - // } - - // return await generateAndSendPasswordResetToken( - // primaryUserAssociatedWithEmail.id, - // emailPasswordAccount.recipeUserId - // ); - // }, - // passwordResetPOST: async function ({ - // formFields, - // token, - // tenantId, - // options, - // userContext, - // }: { - // formFields: { - // id: string; - // value: unknown; - // }[]; - // token: string; - // tenantId: string; - // options: APIOptions; - // userContext: UserContext; - // }): Promise< - // | { - // status: "OK"; - // user: User; - // email: string; - // } - // | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } - // | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } - // | GeneralErrorResponse - // > { - // async function markEmailAsVerified(recipeUserId: RecipeUserId, email: string) { - // const emailVerificationInstance = EmailVerification.getInstance(); - // if (emailVerificationInstance) { - // const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( - // { - // tenantId, - // recipeUserId, - // email, - // userContext, - // } - // ); - - // if (tokenResponse.status === "OK") { - // await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ - // tenantId, - // token: tokenResponse.token, - // attemptAccountLinking: false, // we pass false here cause - // // we anyway do account linking in this API after this function is - // // called. - // userContext, - // }); - // } - // } - // } - - // async function doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( - // recipeUserId: RecipeUserId - // ): Promise< - // | { - // status: "OK"; - // user: User; - // email: string; - // } - // | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } - // | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } - // | GeneralErrorResponse - // > { - // let updateResponse = await options.recipeImplementation.updateEmailOrPassword({ - // tenantIdForPasswordPolicy: tenantId, - // // we can treat userIdForWhomTokenWasGenerated as a recipe user id cause - // // whenever this function is called, - // recipeUserId, - // password: newPassword, - // userContext, - // }); - // if ( - // updateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR" || - // updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" - // ) { - // throw new Error("This should never come here because we are not updating the email"); - // } else if (updateResponse.status === "UNKNOWN_USER_ID_ERROR") { - // // This should happen only cause of a race condition where the user - // // might be deleted before token creation and consumption. - // return { - // status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", - // }; - // } else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { - // return { - // status: "PASSWORD_POLICY_VIOLATED_ERROR", - // failureReason: updateResponse.failureReason, - // }; - // } else { - // // status: "OK" - - // // If the update was successful, we try to mark the email as verified. - // // We do this because we assume that the password reset token was delivered by email (and to the appropriate email address) - // // so consuming it means that the user actually has access to the emails we send. - - // // We only do this if the password update was successful, otherwise the following scenario is possible: - // // 1. User M: signs up using the email of user V with their own password. They can't validate the email, because it is not their own. - // // 2. User A: tries signing up but sees the email already exists message - // // 3. User A: resets their password, but somehow this fails (e.g.: password policy issue) - // // If we verified (and linked) the existing user with the original password, User M would get access to the current user and any linked users. - // await markEmailAsVerified(recipeUserId, emailForWhomTokenWasGenerated); - // // We refresh the user information here, because the verification status may be updated, which is used during linking. - // const updatedUserAfterEmailVerification = await getUser(recipeUserId.getAsString(), userContext); - // if (updatedUserAfterEmailVerification === undefined) { - // throw new Error("Should never happen - user deleted after during password reset"); - // } - - // if (updatedUserAfterEmailVerification.isPrimaryUser) { - // // If the user is already primary, we do not need to do any linking - // return { - // status: "OK", - // email: emailForWhomTokenWasGenerated, - // user: updatedUserAfterEmailVerification, - // }; - // } - - // // If the user was not primary: - - // // Now we try and link the accounts. - // // The function below will try and also create a primary user of the new account, this can happen if: - // // 1. the user was unverified and linking requires verification - // // We do not take try linking by session here, since this is supposed to be called without a session - // // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking - // const linkRes = await AccountLinking.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ - // tenantId, - // inputUser: updatedUserAfterEmailVerification, - // session: undefined, - // userContext, - // }); - // const userAfterWeTriedLinking = - // linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; - - // return { - // status: "OK", - // email: emailForWhomTokenWasGenerated, - // user: userAfterWeTriedLinking, - // }; - // } - // } - - // // NOTE: Check for password being a non-string value. This check will likely - // // never evaluate to `true` as there is an upper-level check for the type - // // in validation but kept here to be safe. - // const newPasswordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; - // if (typeof newPasswordAsUnknown !== "string") - // throw new Error( - // "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" - // ); - // let newPassword: string = newPasswordAsUnknown; - - // let tokenConsumptionResponse = await options.recipeImplementation.consumePasswordResetToken({ - // token, - // tenantId, - // userContext, - // }); - - // if (tokenConsumptionResponse.status === "RESET_PASSWORD_INVALID_TOKEN_ERROR") { - // return tokenConsumptionResponse; - // } - - // let userIdForWhomTokenWasGenerated = tokenConsumptionResponse.userId; - // let emailForWhomTokenWasGenerated = tokenConsumptionResponse.email; - - // let existingUser = await getUser(tokenConsumptionResponse.userId, userContext); - - // if (existingUser === undefined) { - // // This should happen only cause of a race condition where the user - // // might be deleted before token creation and consumption. - // // Also note that this being undefined doesn't mean that the email password - // // user does not exist, but it means that there is no recipe or primary user - // // for whom the token was generated. - // return { - // status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", - // }; - // } - - // // We start by checking if the existingUser is a primary user or not. If it is, - // // then we will try and create a new email password user and link it to the primary user (if required) - - // if (existingUser.isPrimaryUser) { - // // If this user contains an email password account for whom the token was generated, - // // then we update that user's password. - // let emailPasswordUserIsLinkedToExistingUser = - // existingUser.loginMethods.find((lm) => { - // // we check based on user ID and not email because the only time - // // the primary user ID is used for token generation is if the email password - // // user did not exist - in which case the value of emailPasswordUserExists will - // // resolve to false anyway, and that's what we want. - - // // there is an edge case where if the email password recipe user was created - // // after the password reset token generation, and it was linked to the - // // primary user id (userIdForWhomTokenWasGenerated), in this case, - // // we still don't allow password update, cause the user should try again - // // and the token should be regenerated for the right recipe user. - // return ( - // lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && - // lm.recipeId === "emailpassword" - // ); - // }) !== undefined; - - // if (emailPasswordUserIsLinkedToExistingUser) { - // return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( - // new RecipeUserId(userIdForWhomTokenWasGenerated) - // ); - // } else { - // // this means that the existingUser does not have an emailpassword user associated - // // with it. It could now mean that no emailpassword user exists, or it could mean that - // // the the ep user exists, but it's not linked to the current account. - // // If no ep user doesn't exists, we will create one, and link it to the existing account. - // // If ep user exists, then it means there is some race condition cause - // // then the token should have been generated for that user instead of the primary user, - // // and it shouldn't have come into this branch. So we can simply send a password reset - // // invalid error and the user can try again. - - // // NOTE: We do not ask the dev if we should do account linking or not here - // // cause we already have asked them this when generating an password reset token. - // // In the edge case that the dev changes account linking allowance from true to false - // // when it comes here, only a new recipe user id will be created and not linked - // // cause createPrimaryUserIdOrLinkAccounts will disallow linking. This doesn't - // // really cause any security issue. - - // let createUserResponse = await options.recipeImplementation.createNewRecipeUser({ - // tenantId, - // email: tokenConsumptionResponse.email, - // password: newPassword, - // userContext, - // }); - // if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { - // // this means that the user already existed and we can just return an invalid - // // token (see the above comment) - // return { - // status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", - // }; - // } else { - // // we mark the email as verified because password reset also requires - // // access to the email to work.. This has a good side effect that - // // any other login method with the same email in existingAccount will also get marked - // // as verified. - // await markEmailAsVerified( - // createUserResponse.user.loginMethods[0].recipeUserId, - // tokenConsumptionResponse.email - // ); - // const updatedUser = await getUser(createUserResponse.user.id, userContext); - // if (updatedUser === undefined) { - // throw new Error("Should never happen - user deleted after during password reset"); - // } - // createUserResponse.user = updatedUser; - // // Now we try and link the accounts. The function below will try and also - // // create a primary user of the new account, and if it does that, it's OK.. - // // But in most cases, it will end up linking to existing account since the - // // email is shared. - // // We do not take try linking by session here, since this is supposed to be called without a session - // // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking - // const linkRes = await AccountLinking.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ - // tenantId, - // inputUser: createUserResponse.user, - // session: undefined, - // userContext, - // }); - // const userAfterLinking = linkRes.status === "OK" ? linkRes.user : createUserResponse.user; - // if (linkRes.status === "OK" && linkRes.user.id !== existingUser.id) { - // // this means that the account we just linked to - // // was not the one we had expected to link it to. This can happen - // // due to some race condition or the other.. Either way, this - // // is not an issue and we can just return OK - // } - // return { - // status: "OK", - // email: tokenConsumptionResponse.email, - // user: userAfterLinking, - // }; - // } - // } - // } else { - // // This means that the existing user is not a primary account, which implies that - // // it must be a non linked email password account. In this case, we simply update the password. - // // Linking to an existing account will be done after the user goes through the email - // // verification flow once they log in (if applicable). - // return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( - // new RecipeUserId(userIdForWhomTokenWasGenerated) - // ); - // } - // }, + emailExistsGET: async function ({ + email, + tenantId, + userContext, + }: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + exists: boolean; + } + | GeneralErrorResponse + > { + // even if the above returns true, we still need to check if there + // exists an webauthn user with the same email cause the function + // above does not check for that. + let users = await AccountLinking.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + let webauthnUserExists = + users.find((u) => { + return ( + u.loginMethods.find((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) !== + undefined + ); + }) !== undefined; + + return { + status: "OK", + exists: webauthnUserExists, + }; + }, + + generateRecoverAccountTokenPOST: async function ({ + email, + tenantId, + options, + userContext, + }): Promise< + | { + status: "OK"; + } + | { status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; reason: string } + | GeneralErrorResponse + > { + // NOTE: Check for email being a non-string value. This check will likely + // never evaluate to `true` as there is an upper-level check for the type + // in validation but kept here to be safe. + if (typeof email !== "string") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + + // this function will be reused in different parts of the flow below.. + async function generateAndSendRecoverAccountToken( + primaryUserId: string, + recipeUserId: RecipeUserId | undefined + ): Promise< + | { + status: "OK"; + } + | { status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; reason: string } + | GeneralErrorResponse + > { + // the user ID here can be primary or recipe level. + let response = await options.recipeImplementation.generateRecoverAccountToken({ + tenantId, + userId: recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString(), + email, + userContext, + }); + + if (response.status === "UNKNOWN_USER_ID_ERROR") { + logDebugMessage( + `Account recovery email not sent, unknown user id: ${ + recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() + }` + ); + return { + status: "OK", + }; + } + + let recoverAccountLink = getRecoverAccountLink({ + appInfo: options.appInfo, + token: response.token, + tenantId, + request: options.req, + userContext, + }); + + logDebugMessage(`Sending account recovery email to ${email}`); + await options.emailDelivery.ingredientInterfaceImpl.sendEmail({ + tenantId, + type: "RECOVER_ACCOUNT", + user: { + id: primaryUserId, + recipeUserId, + email, + }, + recoverAccountLink, + userContext, + }); + + return { + status: "OK", + }; + } + + /** + * check if primaryUserId is linked with this email + */ + let users = await AccountLinking.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + + // we find the recipe user ID of the webauthn account from the user's list + // for later use. + let webauthnAccount: RecipeLevelUser | undefined = undefined; + for (let i = 0; i < users.length; i++) { + let webauthnAccountTmp = users[i].loginMethods.find( + (l) => l.recipeId === "webauthn" && l.hasSameEmailAs(email) + ); + if (webauthnAccountTmp !== undefined) { + webauthnAccount = webauthnAccountTmp; + break; + } + } + + // we find the primary user ID from the user's list for later use. + let primaryUserAssociatedWithEmail = users.find((u) => u.isPrimaryUser); + + // first we check if there even exists a primary user that has the input email + // if not, then we do the regular flow for account recovery + if (primaryUserAssociatedWithEmail === undefined) { + if (webauthnAccount === undefined) { + logDebugMessage(`Account recovery email not sent, unknown user email: ${email}`); + return { + status: "OK", + }; + } + return await generateAndSendRecoverAccountToken( + webauthnAccount.recipeUserId.getAsString(), + webauthnAccount.recipeUserId + ); + } + + // Next we check if there is any login method in which the input email is verified. + // If that is the case, then it's proven that the user owns the email and we can + // trust linking of the webauthn account. + let emailVerified = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.hasSameEmailAs(email) && lm.verified; + }) !== undefined; + + // finally, we check if the primary user has any other email / phone number + // associated with this account - and if it does, then it means that + // there is a risk of account takeover, so we do not allow the token to be generated + let hasOtherEmailOrPhone = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + }) !== undefined; + + if (!emailVerified && hasOtherEmailOrPhone) { + return { + status: "ACCOUNT_RECOVERY_NOT_ALLOWED", + reason: + "Account recovery link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + }; + } + + let shouldDoAccountLinkingResponse = await AccountLinking.getInstance().config.shouldDoAutomaticAccountLinking( + webauthnAccount !== undefined + ? webauthnAccount + : { + recipeId: "webauthn", + email, + }, + primaryUserAssociatedWithEmail, + undefined, + tenantId, + userContext + ); + + // Now we need to check that if there exists any webauthn user at all + // for the input email. If not, then it implies that when the token is consumed, + // then we will create a new user - so we should only generate the token if + // the criteria for the new user is met. + if (webauthnAccount === undefined) { + // this means that there is no webauthn user that exists for the input email. + // So we check for the sign up condition and only go ahead if that condition is + // met. + + // But first we must check if account linking is enabled at all - cause if it's + // not, then the new webauthn user that will be created in account recovery + // code consume cannot be linked to the primary user - therefore, we should + // not generate a account recovery reset token + if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { + logDebugMessage( + `Account recovery email not sent, since webauthn user didn't exist, and account linking not enabled` + ); + return { + status: "OK", + }; + } + + let isSignUpAllowed = await AccountLinking.getInstance().isSignUpAllowed({ + newUser: { + recipeId: "webauthn", + email, + }, + isVerified: true, // cause when the token is consumed, we will mark the email as verified + session: undefined, + tenantId, + userContext, + }); + if (isSignUpAllowed) { + // notice that we pass in the primary user ID here. This means that + // we will be creating a new webauthn account when the token + // is consumed and linking it to this primary user. + return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, undefined); + } else { + logDebugMessage( + `Account recovery email not sent, isSignUpAllowed returned false for email: ${email}` + ); + return { + status: "OK", + }; + } + } + + // At this point, we know that some webauthn user exists with this email + // and also some primary user ID exist. We now need to find out if they are linked + // together or not. If they are linked together, then we can just generate the token + // else we check for more security conditions (since we will be linking them post token generation) + let areTheTwoAccountsLinked = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.recipeUserId.getAsString() === webauthnAccount!.recipeUserId.getAsString(); + }) !== undefined; + + if (areTheTwoAccountsLinked) { + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); + } + + // Here we know that the two accounts are NOT linked. We now need to check for an + // extra security measure here to make sure that the input email in the primary user + // is verified, and if not, we need to make sure that there is no other email / phone number + // associated with the primary user account. If there is, then we do not proceed. + + /* + This security measure helps prevent the following attack: + An attacker has email A and they create an account using TP and it doesn't matter if A is verified or not. Now they create another account using the webauthn with email A and verifies it. Both these accounts are linked. Now the attacker changes the email for webauthn recipe to B which makes the webauthn account unverified, but it's still linked. + + If the real owner of B tries to signup using webauthn, it will say that the account already exists so they may try to recover the account which should be denied because then they will end up getting access to attacker's account and verify the webauthn account. + + The problem with this situation is if the webauthn account is verified, it will allow further sign-ups with email B which will also be linked to this primary account (that the attacker had created with email A). + + It is important to realize that the attacker had created another account with A because if they hadn't done that, then they wouldn't have access to this account after the real user recovers the account which is why it is important to check there is another non-webauthn account linked to the primary such that the email is not the same as B. + + Exception to the above is that, if there is a third recipe account linked to the above two accounts and has B as verified, then we should allow account recovery token generation because user has already proven that the owns the email B + */ + + // But first, this only matters it the user cares about checking for email verification status.. + + if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { + // here we will go ahead with the token generation cause + // even when the token is consumed, we will not be linking the accounts + // so no need to check for anything + return await generateAndSendRecoverAccountToken( + webauthnAccount.recipeUserId.getAsString(), + webauthnAccount.recipeUserId + ); + } + + if (!shouldDoAccountLinkingResponse.shouldRequireVerification) { + // the checks below are related to email verification, and if the user + // does not care about that, then we should just continue with token generation + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); + } + + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); + }, + recoverAccountTokenPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + token, + tenantId, + options, + userContext, + }: { + token: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + email: string; + } + | GeneralErrorResponse + | { + status: "CONSUME_RECOVER_ACCOUNT_TOKEN_NOT_ALLOWED"; + reason: string; + } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + > { + async function markEmailAsVerified(recipeUserId: RecipeUserId, email: string) { + const emailVerificationInstance = EmailVerification.getInstance(); + if (emailVerificationInstance) { + const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( + { + tenantId, + recipeUserId, + email, + userContext, + } + ); + + if (tokenResponse.status === "OK") { + await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ + tenantId, + token: tokenResponse.token, + attemptAccountLinking: false, // we pass false here cause + // we anyway do account linking in this API after this function is + // called. + userContext, + }); + } + } + } + + async function doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( + recipeUserId: RecipeUserId + ): Promise< + | { + status: "OK"; + user: User; + email: string; + } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | GeneralErrorResponse + > { + let updateResponse = await options.recipeImplementation.registerCredential({ + recipeUserId, + webauthnGeneratedOptionsId, + tenantId, + credential, + userContext, + }); + + // todo decide how to handle these + if (updateResponse.status === "INVALID_AUTHENTICATOR_ERROR") { + // This should happen only cause of a race condition where the user + // might be deleted before token creation and consumption. + return { + status: "INVALID_AUTHENTICATOR_ERROR", + reason: updateResponse.reason, + }; + } else if (updateResponse.status === "WRONG_CREDENTIALS_ERROR") { + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } else { + // status: "OK" + + // If the update was successful, we try to mark the email as verified. + // We do this because we assume that the account recovery token was delivered by email (and to the appropriate email address) + // so consuming it means that the user actually has access to the emails we send. + + // We only do this if the account recovery was successful, otherwise the following scenario is possible: + // 1. User M: signs up using the email of user V with their own credential. They can't validate the email, because it is not their own. + // 2. User A: tries signing up but sees the email already exists message + // 3. User A: recovers the account, but somehow this fails + // If we verified (and linked) the existing user with the original credential, User M would get access to the current user and any linked users. + await markEmailAsVerified(recipeUserId, emailForWhomTokenWasGenerated); + // We refresh the user information here, because the verification status may be updated, which is used during linking. + const updatedUserAfterEmailVerification = await getUser(recipeUserId.getAsString(), userContext); + if (updatedUserAfterEmailVerification === undefined) { + throw new Error("Should never happen - user deleted after during account recovery"); + } + + if (updatedUserAfterEmailVerification.isPrimaryUser) { + // If the user is already primary, we do not need to do any linking + return { + status: "OK", + email: emailForWhomTokenWasGenerated, + user: updatedUserAfterEmailVerification, + }; + } + + // If the user was not primary: + + // Now we try and link the accounts. + // The function below will try and also create a primary user of the new account, this can happen if: + // 1. the user was unverified and linking requires verification + // We do not take try linking by session here, since this is supposed to be called without a session + // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking + const linkRes = await AccountLinking.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ + tenantId, + inputUser: updatedUserAfterEmailVerification, + session: undefined, + userContext, + }); + const userAfterWeTriedLinking = + linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; + + return { + status: "OK", + email: emailForWhomTokenWasGenerated, + user: userAfterWeTriedLinking, + }; + } + } + + let tokenConsumptionResponse = await options.recipeImplementation.consumeRecoverAccountToken({ + token, + tenantId, + userContext, + }); + + // todo decide how to handle these + if (tokenConsumptionResponse.status === "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR") { + return tokenConsumptionResponse; + } else if (tokenConsumptionResponse.status === "WRONG_CREDENTIALS_ERROR") { + return tokenConsumptionResponse; + } else if (tokenConsumptionResponse.status === "INVALID_AUTHENTICATOR_ERROR") { + return tokenConsumptionResponse; + } + + let userIdForWhomTokenWasGenerated = tokenConsumptionResponse.userId; + let emailForWhomTokenWasGenerated = tokenConsumptionResponse.email; + + let existingUser = await getUser(tokenConsumptionResponse.userId, userContext); + + if (existingUser === undefined) { + // This should happen only cause of a race condition where the user + // might be deleted before token creation and consumption. + // Also note that this being undefined doesn't mean that the webauthn + // user does not exist, but it means that there is no recipe or primary user + // for whom the token was generated. + return { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", + }; + } + + // We start by checking if the existingUser is a primary user or not. If it is, + // then we will try and create a new webauthn user and link it to the primary user (if required) + + if (existingUser.isPrimaryUser) { + // If this user contains an webauthn account for whom the token was generated, + // then we update that user's credential. + let webauthnUserIsLinkedToExistingUser = + existingUser.loginMethods.find((lm) => { + // we check based on user ID and not email because the only time + // the primary user ID is used for token generation is if the webauthn + // user did not exist - in which case the value of emailPasswordUserExists will + // resolve to false anyway, and that's what we want. + + // there is an edge case where if the webauthn recipe user was created + // after the account recovery token generation, and it was linked to the + // primary user id (userIdForWhomTokenWasGenerated), in this case, + // we still don't allow credntials update, cause the user should try again + // and the token should be regenerated for the right recipe user. + return ( + lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && + lm.recipeId === "webauthn" + ); + }) !== undefined; + + if (webauthnUserIsLinkedToExistingUser) { + return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( + new RecipeUserId(userIdForWhomTokenWasGenerated) + ); + } else { + // this means that the existingUser does not have an webauthn user associated + // with it. It could now mean that no webauthn user exists, or it could mean that + // the the webauthn user exists, but it's not linked to the current account. + // If no webauthn user doesn't exists, we will create one, and link it to the existing account. + // If webauthn user exists, then it means there is some race condition cause + // then the token should have been generated for that user instead of the primary user, + // and it shouldn't have come into this branch. So we can simply send a recover account + // invalid error and the user can try again. + + // NOTE: We do not ask the dev if we should do account linking or not here + // cause we already have asked them this when generating an account recovery reset token. + // In the edge case that the dev changes account linking allowance from true to false + // when it comes here, only a new recipe user id will be created and not linked + // cause createPrimaryUserIdOrLinkAccounts will disallow linking. This doesn't + // really cause any security issue. + + let createUserResponse = await options.recipeImplementation.createNewRecipeUser({ + tenantId, + webauthnGeneratedOptionsId, + credential, + userContext, + }); + + // todo decide how to handle these + if (createUserResponse.status === "WRONG_CREDENTIALS_ERROR") { + return createUserResponse; + } else if (createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR") { + return createUserResponse; + } else if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { + // this means that the user already existed and we can just return an invalid + // token (see the above comment) + return { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", + }; + } else { + // we mark the email as verified because account recovery also requires + // access to the email to work.. This has a good side effect that + // any other login method with the same email in existingAccount will also get marked + // as verified. + await markEmailAsVerified( + createUserResponse.user.loginMethods[0].recipeUserId, + tokenConsumptionResponse.email + ); + const updatedUser = await getUser(createUserResponse.user.id, userContext); + if (updatedUser === undefined) { + throw new Error("Should never happen - user deleted after during account recovery"); + } + createUserResponse.user = updatedUser; + // Now we try and link the accounts. The function below will try and also + // create a primary user of the new account, and if it does that, it's OK.. + // But in most cases, it will end up linking to existing account since the + // email is shared. + // We do not take try linking by session here, since this is supposed to be called without a session + // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking + const linkRes = await AccountLinking.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ + tenantId, + inputUser: createUserResponse.user, + session: undefined, + userContext, + }); + const userAfterLinking = linkRes.status === "OK" ? linkRes.user : createUserResponse.user; + if (linkRes.status === "OK" && linkRes.user.id !== existingUser.id) { + // this means that the account we just linked to + // was not the one we had expected to link it to. This can happen + // due to some race condition or the other.. Either way, this + // is not an issue and we can just return OK + } + + return { + status: "OK", + email: tokenConsumptionResponse.email, + user: userAfterLinking, + }; + } + } + } else { + // This means that the existing user is not a primary account, which implies that + // it must be a non linked webauthn account. In this case, we simply update the credential. + // Linking to an existing account will be done after the user goes through the email + // verification flow once they log in (if applicable). + return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( + new RecipeUserId(userIdForWhomTokenWasGenerated) + ); + } + }, }; } diff --git a/lib/ts/recipe/webauthn/api/recoverAccount.ts b/lib/ts/recipe/webauthn/api/recoverAccount.ts new file mode 100644 index 000000000..9475beb36 --- /dev/null +++ b/lib/ts/recipe/webauthn/api/recoverAccount.ts @@ -0,0 +1,72 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { send200Response } from "../../../utils"; +import { validateCredentialOrThrowError, validatewebauthnGeneratedOptionsIdOrThrowError } from "./utils"; +import STError from "../error"; +import { APIInterface, APIOptions } from "../"; +import { UserContext } from "../../../types"; + +export default async function recoverAccount( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + // Logic as per https://github.com/supertokens/supertokens-node/issues/22#issuecomment-710512442 + + if (apiImplementation.recoverAccountTokenPOST === undefined) { + return false; + } + + const requestBody = await options.req.getJSONBody(); + let webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); + let credential = await validateCredentialOrThrowError(requestBody.credential); + let token = requestBody.token; + + if (token === undefined) { + throw new STError({ + type: STError.BAD_INPUT_ERROR, + message: "Please provide the account recovery token", + }); + } + if (typeof token !== "string") { + throw new STError({ + type: STError.BAD_INPUT_ERROR, + message: "The account recovery token must be a string", + }); + } + + let result = await apiImplementation.recoverAccountTokenPOST({ + webauthnGeneratedOptionsId, + credential, + token, + tenantId, + options, + userContext, + }); + + send200Response( + options.res, + result.status === "OK" + ? { + status: "OK", + } + : result + ); + return true; +} diff --git a/lib/ts/recipe/webauthn/api/registerOptions.ts b/lib/ts/recipe/webauthn/api/registerOptions.ts index 74d309a48..1eb7e4da1 100644 --- a/lib/ts/recipe/webauthn/api/registerOptions.ts +++ b/lib/ts/recipe/webauthn/api/registerOptions.ts @@ -31,15 +31,21 @@ export default async function registerOptions( const requestBody = await options.req.getJSONBody(); let email = requestBody.email; - if (email === undefined || typeof email !== "string") { + let recoverAccountToken = requestBody.recoverAccountToken; + + if ( + (email === undefined || typeof email !== "string") && + (recoverAccountToken === undefined || typeof recoverAccountToken !== "string") + ) { throw new STError({ type: STError.BAD_INPUT_ERROR, - message: "Please provide the email", + message: "Please provide the email or the recover account token", }); } let result = await apiImplementation.registerOptionsPOST({ email, + recoverAccountToken, tenantId, options, userContext, diff --git a/lib/ts/recipe/webauthn/api/signInOptions.ts b/lib/ts/recipe/webauthn/api/signInOptions.ts index 2e2736aa5..9cf292f9f 100644 --- a/lib/ts/recipe/webauthn/api/signInOptions.ts +++ b/lib/ts/recipe/webauthn/api/signInOptions.ts @@ -34,5 +34,6 @@ export default async function signInOptions( }); send200Response(options.res, result); + return true; } diff --git a/lib/ts/recipe/webauthn/api/signin.ts b/lib/ts/recipe/webauthn/api/signin.ts index b834ecb84..1d4eca98c 100644 --- a/lib/ts/recipe/webauthn/api/signin.ts +++ b/lib/ts/recipe/webauthn/api/signin.ts @@ -29,7 +29,6 @@ export default async function signInAPI( options: APIOptions, userContext: UserContext ): Promise { - // Logic as per https://github.com/supertokens/supertokens-node/issues/20#issuecomment-710346362 if (apiImplementation.signInPOST === undefined) { return false; } diff --git a/lib/ts/recipe/webauthn/constants.ts b/lib/ts/recipe/webauthn/constants.ts index d23bf8dd7..a94993d0f 100644 --- a/lib/ts/recipe/webauthn/constants.ts +++ b/lib/ts/recipe/webauthn/constants.ts @@ -13,12 +13,6 @@ * under the License. */ -export const DEFAULT_REGISTER_ATTESTATION = "none"; - -export const DEFAULT_REGISTER_OPTIONS_TIMEOUT = 5000; - -export const DEFAULT_SIGNIN_OPTIONS_TIMEOUT = 5000; - export const REGISTER_OPTIONS_API = "/webauthn/options/register"; export const SIGNIN_OPTIONS_API = "/webauthn/options/signin"; @@ -32,3 +26,15 @@ export const GENERATE_RECOVER_ACCOUNT_TOKEN_API = "/user/webauthn/reset/token"; export const RECOVER_ACCOUNT_API = "/user/webauthn/reset"; export const SIGNUP_EMAIL_EXISTS_API = "/webauthn/email/exists"; + +// defaults that can be overridden by the developer +export const DEFAULT_REGISTER_OPTIONS_ATTESTATION = "none"; +export const DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = false; +export const DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = "required"; +export const DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = "preferred"; + +export const DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION = "preferred"; + +export const DEFAULT_REGISTER_OPTIONS_TIMEOUT = 5000; + +export const DEFAULT_SIGNIN_OPTIONS_TIMEOUT = 5000; diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 78ad6385b..932ef4997 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -15,14 +15,21 @@ import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { RecipeInterface, APIOptions, APIInterface, TypeWebauthnEmailDeliveryInput } from "./types"; +import { RecipeInterface, APIOptions, APIInterface, TypeWebauthnEmailDeliveryInput, CredentialPayload } from "./types"; import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; -import { getPasswordResetLink } from "./utils"; +import { getRecoverAccountLink } from "./utils"; import { getRequestFromUserContext, getUser } from "../.."; import { getUserContext } from "../../utils"; import { SessionContainerInterface } from "../session/types"; import { User, UserContext } from "../../types"; +import { + DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, + DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, + DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, + DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, +} from "./constants"; +import { updateEmailOrPassword } from "../emailpassword/index"; export default class Wrapper { static init = Recipe.init; @@ -38,37 +45,44 @@ export default class Wrapper { attestation: "none" | "indirect" | "direct" | "enterprise" = "none", tenantId: string, userContext: Record - ): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: string; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: string; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; - }; - }> { + ): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "EMAIL_MISSING_ERROR" } + > { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions({ + requireResidentKey: DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, + residentKey: DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, + userVerification: DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, email, relyingPartyId, relyingPartyName, @@ -94,6 +108,7 @@ export default class Wrapper { userVerification: "required" | "preferred" | "discouraged"; }> { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ + userVerification: DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, relyingPartyId, origin, timeout, @@ -102,259 +117,256 @@ export default class Wrapper { }); } - // static signIn( - // tenantId: string, - // email: string, - // password: string, - // session?: undefined, - // userContext?: Record - // ): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" }>; - // static signIn( - // tenantId: string, - // email: string, - // password: string, - // session: SessionContainerInterface, - // userContext?: Record - // ): Promise< - // | { status: "OK"; user: User; recipeUserId: RecipeUserId } - // | { status: "WRONG_CREDENTIALS_ERROR" } - // | { - // 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"; - // } - // >; - // static signIn( - // tenantId: string, - // email: string, - // password: string, - // session?: SessionContainerInterface, - // userContext?: Record - // ): Promise< - // | { status: "OK"; user: User; recipeUserId: RecipeUserId } - // | { status: "WRONG_CREDENTIALS_ERROR" } - // | { - // 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"; - // } - // > { - // return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signIn({ - // email, - // password, - // session, - // shouldTryLinkingWithSessionUser: !!session, - // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, - // userContext: getUserContext(userContext), - // }); - // } - - // static async verifyCredentials( - // tenantId: string, - // email: string, - // password: string, - // userContext?: Record - // ): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }> { - // const resp = await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ - // email, - // password, - // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, - // userContext: getUserContext(userContext), - // }); - - // // Here we intentionally skip the user and recipeUserId props, because we do not want apps to accidentally use this to sign in - // return { - // status: resp.status, - // }; - // } - - // /** - // * We do not make email optional here cause we want to - // * allow passing in primaryUserId. If we make email optional, - // * and if the user provides a primaryUserId, then it may result in two problems: - // * - there is no recipeUserId = input primaryUserId, in this case, - // * this function will throw an error - // * - There is a recipe userId = input primaryUserId, but that recipe has no email, - // * or has wrong email compared to what the user wanted to generate a reset token for. - // * - // * And we want to allow primaryUserId being passed in. - // */ - // static createResetPasswordToken( - // tenantId: string, - // userId: string, - // email: string, - // userContext?: Record - // ): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { - // return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createResetPasswordToken({ - // userId, - // email, - // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, - // userContext: getUserContext(userContext), - // }); - // } - - // static async resetPasswordUsingToken( - // tenantId: string, - // token: string, - // newPassword: string, - // userContext?: Record - // ): Promise< - // | { - // status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - // } - // | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } - // > { - // const consumeResp = await Wrapper.consumePasswordResetToken(tenantId, token, userContext); - - // if (consumeResp.status !== "OK") { - // return consumeResp; - // } - - // let result = await Wrapper.updateEmailOrPassword({ - // recipeUserId: new RecipeUserId(consumeResp.userId), - // email: consumeResp.email, - // password: newPassword, - // tenantIdForPasswordPolicy: tenantId, - // userContext, - // }); - - // if (result.status === "EMAIL_ALREADY_EXISTS_ERROR" || result.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { - // throw new global.Error("Should never come here cause we are not updating email"); - // } - // if (result.status === "PASSWORD_POLICY_VIOLATED_ERROR") { - // return { - // status: "PASSWORD_POLICY_VIOLATED_ERROR", - // failureReason: result.failureReason, - // }; - // } - // return { - // status: result.status, - // }; - // } - - // static consumePasswordResetToken( - // tenantId: string, - // token: string, - // userContext?: Record - // ): Promise< - // | { - // status: "OK"; - // email: string; - // userId: string; - // } - // | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } - // > { - // return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.consumePasswordResetToken({ - // token, - // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, - // userContext: getUserContext(userContext), - // }); - // } - - // static updateEmailOrPassword(input: { - // recipeUserId: RecipeUserId; - // email?: string; - // password?: string; - // userContext?: Record; - // applyPasswordPolicy?: boolean; - // tenantIdForPasswordPolicy?: string; - // }): Promise< - // | { - // status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; - // } - // | { - // status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - // reason: string; - // } - // | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } - // > { - // return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.updateEmailOrPassword({ - // ...input, - // userContext: getUserContext(input.userContext), - // tenantIdForPasswordPolicy: - // input.tenantIdForPasswordPolicy === undefined ? DEFAULT_TENANT_ID : input.tenantIdForPasswordPolicy, - // }); - // } - - // static async createResetPasswordLink( - // tenantId: string, - // userId: string, - // email: string, - // userContext?: Record - // ): Promise<{ status: "OK"; link: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { - // const ctx = getUserContext(userContext); - // let token = await createResetPasswordToken(tenantId, userId, email, ctx); - // if (token.status === "UNKNOWN_USER_ID_ERROR") { - // return token; - // } - - // const recipeInstance = Recipe.getInstanceOrThrowError(); - // return { - // status: "OK", - // link: getPasswordResetLink({ - // appInfo: recipeInstance.getAppInfo(), - // token: token.token, - // tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, - // request: getRequestFromUserContext(ctx), - // userContext: ctx, - // }), - // }; - // } - - // static async sendResetPasswordEmail( - // tenantId: string, - // userId: string, - // email: string, - // userContext?: Record - // ): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }> { - // const user = await getUser(userId, userContext); - // if (!user) { - // return { status: "UNKNOWN_USER_ID_ERROR" }; - // } - - // const loginMethod = user.loginMethods.find((m) => m.recipeId === "emailpassword" && m.hasSameEmailAs(email)); - // if (!loginMethod) { - // return { status: "UNKNOWN_USER_ID_ERROR" }; - // } - - // let link = await createResetPasswordLink(tenantId, userId, email, userContext); - // if (link.status === "UNKNOWN_USER_ID_ERROR") { - // return link; - // } - - // await sendEmail({ - // passwordResetLink: link.link, - // type: "PASSWORD_RESET", - // user: { - // id: user.id, - // recipeUserId: loginMethod.recipeUserId, - // email: loginMethod.email!, - // }, - // tenantId, - // userContext, - // }); - - // return { - // status: "OK", - // }; - // } - - // static async sendEmail( - // input: TypeWebauthnEmailDeliveryInput & { userContext?: Record } - // ): Promise { - // let recipeInstance = Recipe.getInstanceOrThrowError(); - // return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail({ - // ...input, - // tenantId: input.tenantId === undefined ? DEFAULT_TENANT_ID : input.tenantId, - // userContext: getUserContext(input.userContext), - // }); - // } + static signIn( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + session?: undefined, + userContext?: Record + ): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR" } + >; + static signIn( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + session: SessionContainerInterface, + userContext?: Record + ): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR" } + | { + 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"; + } + >; + static signIn( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + session?: SessionContainerInterface, + userContext?: Record + ): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR" } + | { + 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"; + } + > { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signIn({ + webauthnGeneratedOptionsId, + credential, + session, + shouldTryLinkingWithSessionUser: !!session, + tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + userContext: getUserContext(userContext), + }); + } + + static async verifyCredentials( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + userContext?: Record + ): Promise<{ status: "OK" } | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR" }> { + const resp = await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ + webauthnGeneratedOptionsId, + credential, + tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + userContext: getUserContext(userContext), + }); + + // Here we intentionally skip the user and recipeUserId props, because we do not want apps to accidentally use this to sign in + return { + status: resp.status, + }; + } + + /** + * We do not make email optional here cause we want to + * allow passing in primaryUserId. If we make email optional, + * and if the user provides a primaryUserId, then it may result in two problems: + * - there is no recipeUserId = input primaryUserId, in this case, + * this function will throw an error + * - There is a recipe userId = input primaryUserId, but that recipe has no email, + * or has wrong email compared to what the user wanted to generate a reset token for. + * + * And we want to allow primaryUserId being passed in. + */ + static generateRecoverAccountToken( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.generateRecoverAccountToken({ + userId, + email, + tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + userContext: getUserContext(userContext), + }); + } + + static async recoverAccountUsingToken( + tenantId: string, + webauthnGeneratedOptionsId: string, + token: string, + credential: CredentialPayload, + userContext?: Record + ): Promise< + | { + status: "OK" | "WRONG_CREDENTIALS_ERROR" | "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { status: "INVALID_AUTHENTICATOR_ERROR"; failureReason: string } + > { + const consumeResp = await Wrapper.consumeRecoverAccountToken(tenantId, token, userContext); + + if (consumeResp.status !== "OK") { + return consumeResp; + } + + let result = await Wrapper.registerCredential({ + recipeUserId: new RecipeUserId(consumeResp.userId), + webauthnGeneratedOptionsId, + credential, + tenantId, + userContext, + }); + + if (result.status === "INVALID_AUTHENTICATOR_ERROR") { + return { + status: "INVALID_AUTHENTICATOR_ERROR", + failureReason: result.reason, + }; + } + return { + status: result.status, + }; + } + + static consumeRecoverAccountToken( + tenantId: string, + token: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + > { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.consumeRecoverAccountToken({ + token, + tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + userContext: getUserContext(userContext), + }); + } + + static registerCredential(input: { + recipeUserId: RecipeUserId; + tenantId: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + userContext?: Record; + }): Promise< + | { + status: "OK" | "WRONG_CREDENTIALS_ERROR"; + } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + > { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerCredential({ + ...input, + userContext: getUserContext(input.userContext), + }); + } + + static async createRecoverAccountLink( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise<{ status: "OK"; link: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + const ctx = getUserContext(userContext); + let token = await this.generateRecoverAccountToken(tenantId, userId, email, ctx); + if (token.status === "UNKNOWN_USER_ID_ERROR") { + return token; + } + + const recipeInstance = Recipe.getInstanceOrThrowError(); + return { + status: "OK", + link: getRecoverAccountLink({ + appInfo: recipeInstance.getAppInfo(), + token: token.token, + tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + request: getRequestFromUserContext(ctx), + userContext: ctx, + }), + }; + } + + static async sendRecoverAccountEmail( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }> { + const user = await getUser(userId, userContext); + if (!user) { + return { status: "UNKNOWN_USER_ID_ERROR" }; + } + + const loginMethod = user.loginMethods.find((m) => m.recipeId === "webauthn" && m.hasSameEmailAs(email)); + if (!loginMethod) { + return { status: "UNKNOWN_USER_ID_ERROR" }; + } + + let link = await this.createRecoverAccountLink(tenantId, userId, email, userContext); + if (link.status === "UNKNOWN_USER_ID_ERROR") { + return link; + } + + await sendEmail({ + recoverAccountLink: link.link, + type: "RECOVER_ACCOUNT", + user: { + id: user.id, + recipeUserId: loginMethod.recipeUserId, + email: loginMethod.email!, + }, + tenantId, + userContext, + }); + + return { + status: "OK", + }; + } + + static async sendEmail( + input: TypeWebauthnEmailDeliveryInput & { userContext?: Record } + ): Promise { + let recipeInstance = Recipe.getInstanceOrThrowError(); + return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail({ + ...input, + tenantId: input.tenantId === undefined ? DEFAULT_TENANT_ID : input.tenantId, + userContext: getUserContext(input.userContext), + }); + } } export let init = Wrapper.init; @@ -365,22 +377,22 @@ export let registerOptions = Wrapper.registerOptions; export let signInOptions = Wrapper.signInOptions; -// export let signIn = Wrapper.signIn; +export let signIn = Wrapper.signIn; -// export let verifyCredentials = Wrapper.verifyCredentials; +export let verifyCredentials = Wrapper.verifyCredentials; -// export let createResetPasswordToken = Wrapper.createResetPasswordToken; +export let generateRecoverAccountToken = Wrapper.generateRecoverAccountToken; -// export let resetPasswordUsingToken = Wrapper.resetPasswordUsingToken; +export let recoverAccountUsingToken = Wrapper.recoverAccountUsingToken; -// export let consumePasswordResetToken = Wrapper.consumePasswordResetToken; +export let consumeRecoverAccountToken = Wrapper.consumeRecoverAccountToken; -// export let updateEmailOrPassword = Wrapper.updateEmailOrPassword; +export let registerCredential = Wrapper.registerCredential; export type { RecipeInterface, APIOptions, APIInterface }; -// export let createResetPasswordLink = Wrapper.createResetPasswordLink; +export let createRecoverAccountLink = Wrapper.createRecoverAccountLink; -// export let sendResetPasswordEmail = Wrapper.sendResetPasswordEmail; +export let sendRecoverAccountEmail = Wrapper.sendRecoverAccountEmail; -// export let sendEmail = Wrapper.sendEmail; +export let sendEmail = Wrapper.sendEmail; diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts index 779855fc6..6e0b94302 100644 --- a/lib/ts/recipe/webauthn/recipe.ts +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -19,11 +19,22 @@ import { NormalisedAppinfo, APIHandled, HTTPMethod, RecipeListFunction, UserCont import STError from "./error"; import { validateAndNormaliseUserInput } from "./utils"; import NormalisedURLPath from "../../normalisedURLPath"; -import { SIGN_UP_API, SIGN_IN_API, REGISTER_OPTIONS_API, SIGNIN_OPTIONS_API } from "./constants"; +import { + SIGN_UP_API, + SIGN_IN_API, + REGISTER_OPTIONS_API, + SIGNIN_OPTIONS_API, + GENERATE_RECOVER_ACCOUNT_TOKEN_API, + RECOVER_ACCOUNT_API, + SIGNUP_EMAIL_EXISTS_API, +} from "./constants"; import signUpAPI from "./api/signup"; import signInAPI from "./api/signin"; import registerOptionsAPI from "./api/registerOptions"; import signInOptionsAPI from "./api/signInOptions"; +import generateRecoverAccountTokenAPI from "./api/generateRecoverAccountToken"; +import recoverAccountAPI from "./api/recoverAccount"; +import emailExistsAPI from "./api/emailExists"; import { isTestEnv, send200Response } from "../../utils"; import RecipeImplementation from "./recipeImplementation"; import APIImplementation from "./api/implementation"; @@ -86,6 +97,7 @@ export default class Recipe extends RecipeModule { ? new EmailDeliveryIngredient(this.config.getEmailDeliveryConfig(this.isInServerlessEnv)) : ingredients.emailDelivery; + // todo check correctness PostSuperTokensInitCallbacks.addPostInitCallback(() => { const mfaInstance = MultiFactorAuthRecipe.getInstance(); if (mfaInstance !== undefined) { @@ -190,8 +202,6 @@ export default class Recipe extends RecipeModule { ]; } - // todo how to implement this? - // If the list is empty we generate an email address to make the flow where the user is never asked for // an email address easier to implement. In many cases when the user adds an email-password factor, they // actually only want to add a password and do not care about the associated email address. @@ -203,7 +213,7 @@ export default class Recipe extends RecipeModule { return { status: "OK", factorIdToEmailsMap: { - emailpassword: result, + webauthn: result, }, }; }); @@ -273,24 +283,24 @@ export default class Recipe extends RecipeModule { disabled: this.apiImpl.signInPOST === undefined, }, - // { - // method: "post", - // pathWithoutApiBasePath: new NormalisedURLPath(GENERATE_RECOVER_ACCOUNT_TOKEN_API), - // id: GENERATE_RECOVER_ACCOUNT_TOKEN_API, - // disabled: this.apiImpl.generateRecoverAccountTokenPOST === undefined, - // }, - // { - // method: "post", - // pathWithoutApiBasePath: new NormalisedURLPath(RECOVER_ACCOUNT_API), - // id: RECOVER_ACCOUNT_API, - // disabled: this.apiImpl.recoverAccountPOST === undefined, - // }, - // { - // method: "get", - // pathWithoutApiBasePath: new NormalisedURLPath(SIGNUP_EMAIL_EXISTS_API), - // id: SIGNUP_EMAIL_EXISTS_API, - // disabled: this.apiImpl.emailExistsGET === undefined, - // }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(GENERATE_RECOVER_ACCOUNT_TOKEN_API), + id: GENERATE_RECOVER_ACCOUNT_TOKEN_API, + disabled: this.apiImpl.generateRecoverAccountTokenPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(RECOVER_ACCOUNT_API), + id: RECOVER_ACCOUNT_API, + disabled: this.apiImpl.recoverAccountPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new NormalisedURLPath(SIGNUP_EMAIL_EXISTS_API), + id: SIGNUP_EMAIL_EXISTS_API, + disabled: this.apiImpl.emailExistsGET === undefined, + }, ]; }; @@ -321,15 +331,13 @@ export default class Recipe extends RecipeModule { return await signUpAPI(this.apiImpl, tenantId, options, userContext); } else if (id === SIGN_IN_API) { return await signInAPI(this.apiImpl, tenantId, options, userContext); - } - //else if (id === GENERATE_RECOVER_ACCOUNT_TOKEN_API) { - // return await generateRecoverAccountTokenAPI(this.apiImpl, tenantId, options, userContext); - // } else if (id === RECOVER_ACCOUNT_API) { - // return await recoverAccountAPI(this.apiImpl, tenantId, options, userContext); - // } else if (id === SIGNUP_EMAIL_EXISTS_API) { - // return await emailExistsAPI(this.apiImpl, tenantId, options, userContext); - // } - else return false; + } else if (id === GENERATE_RECOVER_ACCOUNT_TOKEN_API) { + return await generateRecoverAccountTokenAPI(this.apiImpl, tenantId, options, userContext); + } else if (id === RECOVER_ACCOUNT_API) { + return await recoverAccountAPI(this.apiImpl, tenantId, options, userContext); + } else if (id === SIGNUP_EMAIL_EXISTS_API) { + return await emailExistsAPI(this.apiImpl, tenantId, options, userContext); + } else return false; }; handleError = async (err: STError, _request: BaseRequest, response: BaseResponse): Promise => { diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 026e6b63a..f5e23fb40 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -1,4 +1,4 @@ -import { RecipeInterface, TypeNormalisedInput } from "./types"; +import { CredentialPayload, RecipeInterface, TypeNormalisedInput } from "./types"; import AccountLinking from "../accountlinking/recipe"; import { Querier } from "../../querier"; import NormalisedURLPath from "../../normalisedURLPath"; @@ -8,6 +8,7 @@ import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; import { UserContext, User as UserType } from "../../types"; import { LoginMethod, User } from "../../user"; import { AuthUtils } from "../../authUtils"; +import * as jose from "jose"; export default function getRecipeInterface( querier: Querier, @@ -15,7 +16,6 @@ export default function getRecipeInterface( ): RecipeInterface { return { registerOptions: async function ({ - email, relyingPartyId, relyingPartyName, origin, @@ -23,46 +23,94 @@ export default function getRecipeInterface( attestation = "none", tenantId, userContext, + ...rest }: { - email: string; - timeout: number; - attestation: "none" | "indirect" | "direct" | "enterprise"; relyingPartyId: string; relyingPartyName: string; origin: string; + requireResidentKey: boolean | undefined; // should default to false in order to allow multiple authenticators to be used; see https://auth0.com/blog/a-look-at-webauthn-resident-credentials/ + // default to 'required' in order store the private key locally on the device and not on the server + residentKey: "required" | "preferred" | "discouraged" | undefined; + // default to 'preferred' in order to verify the user (biometrics, pin, etc) based on the device preferences + userVerification: "required" | "preferred" | "discouraged" | undefined; + // default to 'none' in order to allow any authenticator and not verify attestation + attestation: "none" | "indirect" | "direct" | "enterprise" | undefined; + // default to 5 seconds + timeout: number | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: string; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: string; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; - }; - }> { - // the input user ID can be a recipe or a primary user ID. + } & ( + | { + recoverAccountToken: string; + } + | { + email: string; + } + )): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "EMAIL_MISSING_ERROR" } + > { + let email = "email" in rest ? rest.email : undefined; + const recoverAccountToken = "recoverAccountToken" in rest ? rest.recoverAccountToken : undefined; + if (email === undefined && recoverAccountToken === undefined) { + return { + status: "EMAIL_MISSING_ERROR", + }; + } + + // todo check if should decode using Core or using sdk; atm decided on usinng the sdk so to not make another roundtrip to the server + // the actual verification will be done during consumeRecoverAccountToken + if (recoverAccountToken !== undefined) { + let decoded: jose.JWTPayload | undefined; + try { + decoded = await jose.decodeJwt(recoverAccountToken); + } catch (e) { + console.error(e); + + return { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", + }; + } + + email = decoded?.email as string | undefined; + } + + if (!email) { + return { + status: "EMAIL_MISSING_ERROR", + }; + } + return await querier.sendPostRequest( new NormalisedURLPath( `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/register` @@ -88,7 +136,8 @@ export default function getRecipeInterface( }: { relyingPartyId: string; origin: string; - timeout: number; + userVerification: "required" | "preferred" | "discouraged" | undefined; // see register options + timeout: number | undefined; tenantId: string; userContext: UserContext; }): Promise<{ @@ -98,7 +147,6 @@ export default function getRecipeInterface( timeout: number; userVerification: "required" | "preferred" | "discouraged"; }> { - // todo crrectly retrieve relying party id and origin // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest( new NormalisedURLPath( @@ -123,6 +171,9 @@ export default function getRecipeInterface( recipeUserId: RecipeUserId; } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -167,19 +218,7 @@ export default function getRecipeInterface( createNewRecipeUser: async function (input: { tenantId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; webauthnGeneratedOptionsId: string; userContext: UserContext; }): Promise< @@ -188,6 +227,9 @@ export default function getRecipeInterface( user: User; recipeUserId: RecipeUserId; } + | { status: "WRONG_CREDENTIALS_ERROR" } + // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } > { const resp = await querier.sendPostRequest( @@ -304,73 +346,55 @@ export default function getRecipeInterface( return response; }, - // generateRecoverAccountToken: async function ({ - // userId, - // email, - // tenantId, - // userContext, - // }: { - // userId: string; - // email: string; - // tenantId: string; - // userContext: UserContext; - // }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { - // // the input user ID can be a recipe or a primary user ID. - // return await querier.sendPostRequest( - // new NormalisedURLPath( - // `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token` - // ), - // { - // userId, - // email, - // }, - // userContext - // ); - // }, + generateRecoverAccountToken: async function ({ + userId, + email, + tenantId, + userContext, + }: { + userId: string; + email: string; + tenantId: string; + userContext: UserContext; + }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + // the input user ID can be a recipe or a primary user ID. + return await querier.sendPostRequest( + new NormalisedURLPath( + `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token` + ), + { + userId, + email, + }, + userContext + ); + }, - // consumeRecoverAccountToken: async function ({ - // token, - // webauthnGeneratedOptionsId, - // credential, - // tenantId, - // userContext, - // }: { - // token: string; - // webauthnGeneratedOptionsId: string; - // credential: { - // id: string; - // rawId: string; - // response: { - // clientDataJSON: string; - // attestationObject: string; - // transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - // userHandle: string; - // }; - // authenticatorAttachment: "platform" | "cross-platform"; - // clientExtensionResults: Record; - // type: "public-key"; - // }; - // tenantId: string; - // userContext: UserContext; - // }): Promise< - // | { - // status: "OK"; - // userId: string; - // email: string; - // } - // | { status: "RECOVER_ACCOUNT_INVALID_TOKEN_ERROR" } - // > { - // return await querier.sendPostRequest( - // new NormalisedURLPath( - // `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/paskey/user/recover/token/consume` - // ), - // { - // webauthnGeneratedOptionsId, - // credential, - // token, - // }, - // userContext - // ); - // }, + consumeRecoverAccountToken: async function ({ + token, + tenantId, + userContext, + }: { + token: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + userId: string; + email: string; + } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + > { + return await querier.sendPostRequest( + new NormalisedURLPath( + `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/paskey/user/recover/token/consume` + ), + { + token, + }, + userContext + ); + }, }; } diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 3503aea4c..5ea126719 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -41,14 +41,15 @@ export type TypeNormalisedInput = { }; export type TypeNormalisedInputRelyingPartyId = (input: { + tenantId: string; request: BaseRequest | undefined; userContext: UserContext; -}) => string; // should return the domain of the origin +}) => Promise; // should return the domain of the origin export type TypeNormalisedInputRelyingPartyName = (input: { tenantId: string; userContext: UserContext; -}) => Promise; // should return the app name +}) => Promise; export type TypeNormalisedInputGetOrigin = (input: { tenantId: string; @@ -105,11 +106,9 @@ type SignUpErrorResponse = CreateNewRecipeUserErrorResponse; type SignInErrorResponse = VerifyCredentialsErrorResponse; -type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" } | { status: "UNKNOWN_EMAIL_ERROR" }; +type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; -type ConsumeRecoverAccountTokenErrorResponse = - | RegisterCredentialErrorResponse - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; @@ -196,19 +195,7 @@ export type RecipeInterface = { signUp(input: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; @@ -232,19 +219,7 @@ export type RecipeInterface = { signIn(input: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; @@ -277,20 +252,6 @@ export type RecipeInterface = { // make sure the email maps to options email consumeRecoverAccountToken(input: { token: string; - webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; tenantId: string; userContext: UserContext; }): Promise< @@ -303,19 +264,7 @@ export type RecipeInterface = { >; decodeCredential(input: { - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; }): Promise< | { status: "OK"; @@ -380,19 +329,7 @@ export type RecipeInterface = { // (in consumeRecoverAccountToken invalidating the token and in registerOptions for storing the email in the generated options) registerCredential(input: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; tenantId: string; userContext: UserContext; recipeUserId: RecipeUserId; @@ -409,19 +346,7 @@ export type RecipeInterface = { // called during operations like creating a user during password reset flow. createNewRecipeUser(input: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; tenantId: string; userContext: UserContext; }): Promise< @@ -435,19 +360,7 @@ export type RecipeInterface = { verifyCredentials(input: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; tenantId: string; userContext: UserContext; }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | VerifyCredentialsErrorResponse>; @@ -568,6 +481,13 @@ type GetCredentialGETErrorResponse = { reason: string; }; +type RecoverAccountTokenPOSTErrorResponse = + | { + status: "CONSUME_RECOVER_ACCOUNT_TOKEN_NOT_ALLOWED"; + reason: string; + } + | ConsumeRecoverAccountTokenErrorResponse; + export type APIInterface = { registerOptionsPOST: | undefined @@ -576,7 +496,7 @@ export type APIInterface = { tenantId: string; options: APIOptions; userContext: UserContext; - } & ({ email: string } | { recoverAccountToken: string } | { session: SessionContainerInterface }) + } & ({ email: string } | { recoverAccountToken: string }) ) => Promise< | { status: "OK"; @@ -633,25 +553,15 @@ export type APIInterface = { signUpPOST: | undefined | ((input: { + email: string; webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; tenantId: string; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; + // should also have the email or recoverAccountToken in order to do the preauth checks }) => Promise< | { status: "OK"; @@ -666,19 +576,7 @@ export type APIInterface = { | undefined | ((input: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; tenantId: string; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; @@ -713,19 +611,7 @@ export type APIInterface = { | undefined | ((input: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; token: string; tenantId: string; options: APIOptions; @@ -740,6 +626,25 @@ export type APIInterface = { | GeneralErrorResponse >); + recoverAccountTokenPOST: + | undefined + | ((input: { + token: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + email: string; + } + | GeneralErrorResponse + | RecoverAccountTokenPOSTErrorResponse + >); + // used for checking if the email already exists before generating the credential emailExistsGET: | undefined @@ -761,19 +666,7 @@ export type APIInterface = { | undefined | ((input: { webauthnGeneratedOptionsId: string; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: string; - attestationObject: string; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; - }; + credential: CredentialPayload; tenantId: string; session: SessionContainerInterface; options: APIOptions; @@ -856,3 +749,17 @@ export type TypeWebauthnRecoverAccountEmailDeliveryInput = { }; export type TypeWebauthnEmailDeliveryInput = TypeWebauthnRecoverAccountEmailDeliveryInput; + +export type CredentialPayload = { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; +}; diff --git a/lib/ts/recipe/webauthn/utils.ts b/lib/ts/recipe/webauthn/utils.ts index 278da3a9b..f58fc2e4d 100644 --- a/lib/ts/recipe/webauthn/utils.ts +++ b/lib/ts/recipe/webauthn/utils.ts @@ -89,11 +89,13 @@ function validateAndNormaliseRelyingPartyIdConfig( ): TypeNormalisedInputRelyingPartyId { return (props) => { if (typeof relyingPartyIdConfig === "string") { - return relyingPartyIdConfig; + return Promise.resolve(relyingPartyIdConfig); } else if (typeof relyingPartyIdConfig === "function") { return relyingPartyIdConfig(props); } else { - return __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous(); + return Promise.resolve( + __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous() + ); } }; } @@ -105,11 +107,11 @@ function validateAndNormaliseRelyingPartyNameConfig( ): TypeNormalisedInputRelyingPartyName { return (props) => { if (typeof relyingPartyNameConfig === "string") { - return relyingPartyNameConfig; + return Promise.resolve(relyingPartyNameConfig); } else if (typeof relyingPartyNameConfig === "function") { return relyingPartyNameConfig(props); } else { - return __.appName; + return Promise.resolve(__.appName); } }; } @@ -123,7 +125,9 @@ function validateAndNormaliseGetOriginConfig( if (typeof getOriginConfig === "function") { return getOriginConfig(props); } else { - return __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous(); + return Promise.resolve( + __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous() + ); } }; } @@ -148,7 +152,7 @@ export async function defaultEmailValidator(value: any) { return undefined; } -export function getPasswordResetLink(input: { +export function getRecoverAccountLink(input: { appInfo: NormalisedAppinfo; token: string; tenantId: string; @@ -163,7 +167,7 @@ export function getPasswordResetLink(input: { }) .getAsStringDangerous() + input.appInfo.websiteBasePath.getAsStringDangerous() + - "/reset-password?token=" + + "/recover-account?token=" + input.token + "&tenantId=" + input.tenantId From 177b58083eee4c182fe892626760f1cef54865b5 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 28 Oct 2024 08:34:59 +0200 Subject: [PATCH 12/36] pr fixes --- lib/ts/recipe/webauthn/types.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 5ea126719..b4ec1651e 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -112,6 +112,8 @@ type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_ type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; +type GetCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; + type DecodeCredentialErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; type Base64URLString = string; @@ -394,11 +396,12 @@ export type RecipeInterface = { status: "OK"; credential: { id: string; - rp_id: string; - created_at: number; + rpId: string; + recipeUserId: RecipeUserId; + createdAt: number; }; } - | RemoveCredentialErrorResponse + | GetCredentialErrorResponse >; listCredentials(input: { @@ -408,8 +411,8 @@ export type RecipeInterface = { status: "OK"; credentials: { id: string; - rp_id: string; - created_at: number; + rpId: string; + createdAt: number; }[]; }>; }; From 5d1363ef6c4788ce06532ab767ad60b2b4468559 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 28 Oct 2024 14:08:51 +0200 Subject: [PATCH 13/36] pr fixes and cleanup --- lib/ts/recipe/webauthn/api/implementation.ts | 47 +- lib/ts/recipe/webauthn/api/recoverAccount.ts | 8 +- lib/ts/recipe/webauthn/core-mock.ts | 2 +- lib/ts/recipe/webauthn/index.ts | 2 +- lib/ts/recipe/webauthn/types.ts | 426 +++++++++---------- lib/ts/recipe/webauthn/utils.ts | 22 + 6 files changed, 254 insertions(+), 253 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index dc87e0e0b..844d9385f 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -65,6 +65,7 @@ export default function getAPIImplementation(): APIInterface { } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "EMAIL_MISSING_ERROR" } + | { status: "REGISTER_OPTIONS_NOT_ALLOWED"; reason: string } > { const relyingPartyId = await options.config.relyingPartyId({ tenantId, @@ -543,7 +544,7 @@ export default function getAPIImplementation(): APIInterface { | { status: "OK"; } - | { status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; reason: string } + | { status: "RECOVER_ACCOUNT_NOT_ALLOWED"; reason: string } | GeneralErrorResponse > { // NOTE: Check for email being a non-string value. This check will likely @@ -562,7 +563,7 @@ export default function getAPIImplementation(): APIInterface { | { status: "OK"; } - | { status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; reason: string } + | { status: "RECOVER_ACCOUNT_NOT_ALLOWED"; reason: string } | GeneralErrorResponse > { // the user ID here can be primary or recipe level. @@ -575,7 +576,7 @@ export default function getAPIImplementation(): APIInterface { if (response.status === "UNKNOWN_USER_ID_ERROR") { logDebugMessage( - `Account recovery email not sent, unknown user id: ${ + `Recover account email not sent, unknown user id: ${ recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() }` ); @@ -592,7 +593,7 @@ export default function getAPIImplementation(): APIInterface { userContext, }); - logDebugMessage(`Sending account recovery email to ${email}`); + logDebugMessage(`Sending recover account email to ${email}`); await options.emailDelivery.ingredientInterfaceImpl.sendEmail({ tenantId, type: "RECOVER_ACCOUNT", @@ -639,10 +640,10 @@ export default function getAPIImplementation(): APIInterface { let primaryUserAssociatedWithEmail = users.find((u) => u.isPrimaryUser); // first we check if there even exists a primary user that has the input email - // if not, then we do the regular flow for account recovery + // if not, then we do the regular flow for recover account if (primaryUserAssociatedWithEmail === undefined) { if (webauthnAccount === undefined) { - logDebugMessage(`Account recovery email not sent, unknown user email: ${email}`); + logDebugMessage(`Recover account email not sent, unknown user email: ${email}`); return { status: "OK", }; @@ -675,9 +676,9 @@ export default function getAPIImplementation(): APIInterface { if (!emailVerified && hasOtherEmailOrPhone) { return { - status: "ACCOUNT_RECOVERY_NOT_ALLOWED", + status: "RECOVER_ACCOUNT_NOT_ALLOWED", reason: - "Account recovery link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + "Recover account link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", }; } @@ -704,12 +705,12 @@ export default function getAPIImplementation(): APIInterface { // met. // But first we must check if account linking is enabled at all - cause if it's - // not, then the new webauthn user that will be created in account recovery + // not, then the new webauthn user that will be created in recover account // code consume cannot be linked to the primary user - therefore, we should - // not generate a account recovery reset token + // not generate a recover account reset token if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { logDebugMessage( - `Account recovery email not sent, since webauthn user didn't exist, and account linking not enabled` + `Recover account email not sent, since webauthn user didn't exist, and account linking not enabled` ); return { status: "OK", @@ -733,7 +734,7 @@ export default function getAPIImplementation(): APIInterface { return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, undefined); } else { logDebugMessage( - `Account recovery email not sent, isSignUpAllowed returned false for email: ${email}` + `Recover account email not sent, isSignUpAllowed returned false for email: ${email}` ); return { status: "OK", @@ -772,7 +773,7 @@ export default function getAPIImplementation(): APIInterface { It is important to realize that the attacker had created another account with A because if they hadn't done that, then they wouldn't have access to this account after the real user recovers the account which is why it is important to check there is another non-webauthn account linked to the primary such that the email is not the same as B. - Exception to the above is that, if there is a third recipe account linked to the above two accounts and has B as verified, then we should allow account recovery token generation because user has already proven that the owns the email B + Exception to the above is that, if there is a third recipe account linked to the above two accounts and has B as verified, then we should allow recover account token generation because user has already proven that the owns the email B */ // But first, this only matters it the user cares about checking for email verification status.. @@ -801,7 +802,7 @@ export default function getAPIImplementation(): APIInterface { webauthnAccount.recipeUserId ); }, - recoverAccountTokenPOST: async function ({ + recoverAccountPOST: async function ({ webauthnGeneratedOptionsId, credential, token, @@ -891,10 +892,10 @@ export default function getAPIImplementation(): APIInterface { // status: "OK" // If the update was successful, we try to mark the email as verified. - // We do this because we assume that the account recovery token was delivered by email (and to the appropriate email address) + // We do this because we assume that the recover account token was delivered by email (and to the appropriate email address) // so consuming it means that the user actually has access to the emails we send. - // We only do this if the account recovery was successful, otherwise the following scenario is possible: + // We only do this if the recover account was successful, otherwise the following scenario is possible: // 1. User M: signs up using the email of user V with their own credential. They can't validate the email, because it is not their own. // 2. User A: tries signing up but sees the email already exists message // 3. User A: recovers the account, but somehow this fails @@ -903,7 +904,7 @@ export default function getAPIImplementation(): APIInterface { // We refresh the user information here, because the verification status may be updated, which is used during linking. const updatedUserAfterEmailVerification = await getUser(recipeUserId.getAsString(), userContext); if (updatedUserAfterEmailVerification === undefined) { - throw new Error("Should never happen - user deleted after during account recovery"); + throw new Error("Should never happen - user deleted after during recover account"); } if (updatedUserAfterEmailVerification.isPrimaryUser) { @@ -948,10 +949,6 @@ export default function getAPIImplementation(): APIInterface { // todo decide how to handle these if (tokenConsumptionResponse.status === "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR") { return tokenConsumptionResponse; - } else if (tokenConsumptionResponse.status === "WRONG_CREDENTIALS_ERROR") { - return tokenConsumptionResponse; - } else if (tokenConsumptionResponse.status === "INVALID_AUTHENTICATOR_ERROR") { - return tokenConsumptionResponse; } let userIdForWhomTokenWasGenerated = tokenConsumptionResponse.userId; @@ -984,7 +981,7 @@ export default function getAPIImplementation(): APIInterface { // resolve to false anyway, and that's what we want. // there is an edge case where if the webauthn recipe user was created - // after the account recovery token generation, and it was linked to the + // after the recover account token generation, and it was linked to the // primary user id (userIdForWhomTokenWasGenerated), in this case, // we still don't allow credntials update, cause the user should try again // and the token should be regenerated for the right recipe user. @@ -1009,7 +1006,7 @@ export default function getAPIImplementation(): APIInterface { // invalid error and the user can try again. // NOTE: We do not ask the dev if we should do account linking or not here - // cause we already have asked them this when generating an account recovery reset token. + // cause we already have asked them this when generating an recover account reset token. // In the edge case that the dev changes account linking allowance from true to false // when it comes here, only a new recipe user id will be created and not linked // cause createPrimaryUserIdOrLinkAccounts will disallow linking. This doesn't @@ -1034,7 +1031,7 @@ export default function getAPIImplementation(): APIInterface { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", }; } else { - // we mark the email as verified because account recovery also requires + // we mark the email as verified because recover account also requires // access to the email to work.. This has a good side effect that // any other login method with the same email in existingAccount will also get marked // as verified. @@ -1044,7 +1041,7 @@ export default function getAPIImplementation(): APIInterface { ); const updatedUser = await getUser(createUserResponse.user.id, userContext); if (updatedUser === undefined) { - throw new Error("Should never happen - user deleted after during account recovery"); + throw new Error("Should never happen - user deleted after during recover account"); } createUserResponse.user = updatedUser; // Now we try and link the accounts. The function below will try and also diff --git a/lib/ts/recipe/webauthn/api/recoverAccount.ts b/lib/ts/recipe/webauthn/api/recoverAccount.ts index 9475beb36..dfbc2a476 100644 --- a/lib/ts/recipe/webauthn/api/recoverAccount.ts +++ b/lib/ts/recipe/webauthn/api/recoverAccount.ts @@ -27,7 +27,7 @@ export default async function recoverAccount( ): Promise { // Logic as per https://github.com/supertokens/supertokens-node/issues/22#issuecomment-710512442 - if (apiImplementation.recoverAccountTokenPOST === undefined) { + if (apiImplementation.recoverAccountPOST === undefined) { return false; } @@ -41,17 +41,17 @@ export default async function recoverAccount( if (token === undefined) { throw new STError({ type: STError.BAD_INPUT_ERROR, - message: "Please provide the account recovery token", + message: "Please provide the recover account token", }); } if (typeof token !== "string") { throw new STError({ type: STError.BAD_INPUT_ERROR, - message: "The account recovery token must be a string", + message: "The recover account token must be a string", }); } - let result = await apiImplementation.recoverAccountTokenPOST({ + let result = await apiImplementation.recoverAccountPOST({ webauthnGeneratedOptionsId, credential, token, diff --git a/lib/ts/recipe/webauthn/core-mock.ts b/lib/ts/recipe/webauthn/core-mock.ts index f6e725433..7ee65cb53 100644 --- a/lib/ts/recipe/webauthn/core-mock.ts +++ b/lib/ts/recipe/webauthn/core-mock.ts @@ -41,7 +41,7 @@ export const getMockQuerier = (recipeId: string) => { // // @ts-ignore // return { // status: "OK", - // token: "dummy-recovery-token", + // token: "dummy-recover-token", // }; // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token/consume")) { // // @ts-ignore diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 932ef4997..9e53ff0d9 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -29,7 +29,6 @@ import { DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, } from "./constants"; -import { updateEmailOrPassword } from "../emailpassword/index"; export default class Wrapper { static init = Recipe.init; @@ -78,6 +77,7 @@ export default class Wrapper { } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "EMAIL_MISSING_ERROR" } + | { status: "INVALID_EMAIL_ERROR" } > { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions({ requireResidentKey: DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index b4ec1651e..d30fbbb33 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -31,6 +31,7 @@ export type TypeNormalisedInput = { getEmailDeliveryConfig: ( isInServerlessEnv: boolean ) => EmailDeliveryTypeInputWithService; + validateEmailAddress: TypeNormalisedInputValidateEmailAddress; override: { functions: ( originalImplementation: RecipeInterface, @@ -57,10 +58,16 @@ export type TypeNormalisedInputGetOrigin = (input: { userContext: UserContext; }) => Promise; // should return the app name +export type TypeNormalisedInputValidateEmailAddress = ( + email: string, + tenantId: string +) => Promise | string | undefined; + export type TypeInput = { emailDelivery?: EmailDeliveryTypeInput; relyingPartyId?: TypeInputRelyingPartyId; relyingPartyName?: TypeInputRelyingPartyName; + validateEmailAddress?: TypeInputValidateEmailAddress; getOrigin?: TypeInputGetOrigin; override?: { functions?: ( @@ -85,36 +92,52 @@ export type TypeInputGetOrigin = (input: { userContext: UserContext; }) => Promise; +export type TypeInputValidateEmailAddress = ( + email: string, + tenantId: string +) => Promise | string | undefined; + // centralize error types in order to prevent missing cascading errors -type RegisterCredentialErrorResponse = +type RegisterOptionsErrorResponse = + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "INVALID_EMAIL_ERROR" }; + +type SignInOptionsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_EMAIL_ERROR" }; + +type SignUpErrorResponse = + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { status: "WRONG_CREDENTIALS_ERROR" } - // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; -type VerifyCredentialsErrorResponse = - | { status: "WRONG_CREDENTIALS_ERROR" } - // when the attestation is checked and is not valid or other cases in which the authenticator is not correct - | { status: "INVALID_AUTHENTICATOR_ERROR" }; +type SignInErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type CreateNewRecipeUserErrorResponse = RegisterCredentialErrorResponse | { status: "EMAIL_ALREADY_EXISTS_ERROR" }; +type VerifyCredentialsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" } | { status: "INVALID_EMAIL_ERROR" }; -type RegisterOptionsErrorResponse = GetUserFromRecoverAccountTokenErrorResponse | { status: "EMAIL_MISSING_ERROR" }; +type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; -type SignUpErrorResponse = CreateNewRecipeUserErrorResponse; +type RegisterCredentialErrorResponse = + | { status: "WRONG_CREDENTIALS_ERROR" } + // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; -type SignInErrorResponse = VerifyCredentialsErrorResponse; +type CreateNewRecipeUserErrorResponse = + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" }; -type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; +type DecodeCredentialErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; type GetCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; -type DecodeCredentialErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +type RemoveGeneratedOptionsErrorResponse = { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + +type GetGeneratedOptionsErrorResponse = { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; type Base64URLString = string; @@ -177,23 +200,30 @@ export type RecipeInterface = { userVerification: "required" | "preferred" | "discouraged"; }; } - | RegisterOptionsErrorResponse + // | RegisterOptionsErrorResponse + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "INVALID_EMAIL_ERROR" } >; signInOptions(input: { + email?: string; relyingPartyId: string; origin: string; userVerification: "required" | "preferred" | "discouraged" | undefined; // see register options timeout: number | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: "required" | "preferred" | "discouraged"; - }>; + }): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + } + // | SignInOptionsErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } + >; signUp(input: { webauthnGeneratedOptionsId: string; @@ -208,7 +238,10 @@ export type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - | SignUpErrorResponse + // | SignUpErrorResponse + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -228,7 +261,8 @@ export type RecipeInterface = { userContext: UserContext; }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } - | SignInErrorResponse + // | SignInErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -239,6 +273,17 @@ export type RecipeInterface = { } >; + verifyCredentials(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + userContext: UserContext; + }): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + // | VerifyCredentialsErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } + >; + /** * We pass in the email as well to this function cause the input userId * may not be associated with an webauthn account. In this case, we @@ -249,7 +294,12 @@ export type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise<{ status: "OK"; token: string } | GenerateRecoverAccountTokenErrorResponse>; + }): Promise< + | { status: "OK"; token: string } + // | GenerateRecoverAccountTokenErrorResponse + | { status: "UNKNOWN_USER_ID_ERROR" } + | { status: "INVALID_EMAIL_ERROR" } + >; // make sure the email maps to options email consumeRecoverAccountToken(input: { @@ -262,7 +312,48 @@ export type RecipeInterface = { email: string; userId: string; } - | ConsumeRecoverAccountTokenErrorResponse + // | ConsumeRecoverAccountTokenErrorResponse + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + >; + + // used internally for creating a credential during recover account flow or when adding a credential to an existing user + // email will be taken from the options + // no need for recoverAccountToken, as that will be used upstream + // (in consumeRecoverAccountToken invalidating the token and in registerOptions for storing the email in the generated options) + registerCredential(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + userContext: UserContext; + recipeUserId: RecipeUserId; + }): Promise< + | { + status: "OK"; + } + // | RegisterCredentialErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + >; + + // this function is meant only for creating the recipe in the core and nothing else. + // we added this even though signUp exists cause devs may override signup expecting it + // to be called just during sign up. But we also need a version of signing up which can be + // called during operations like creating a user during password reset flow. + createNewRecipeUser(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + // | CreateNewRecipeUserErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } >; decodeCredential(input: { @@ -322,58 +413,21 @@ export type RecipeInterface = { type: string; }; } - | DecodeCredentialErrorResponse - >; - - // used internally for creating a credential during account recovery flow or when adding a credential to an existing user - // email will be taken from the options - // no need for recoverAccountToken, as that will be used upstream - // (in consumeRecoverAccountToken invalidating the token and in registerOptions for storing the email in the generated options) - registerCredential(input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - userContext: UserContext; - recipeUserId: RecipeUserId; - }): Promise< - | { - status: "OK"; - } - | RegisterCredentialErrorResponse + // | DecodeCredentialErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } >; - // this function is meant only for creating the recipe in the core and nothing else. - // we added this even though signUp exists cause devs may override signup expecting it - // to be called just during sign up. But we also need a version of signing up which can be - // called during operations like creating a user during password reset flow. - createNewRecipeUser(input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | CreateNewRecipeUserErrorResponse - >; - - verifyCredentials(input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - userContext: UserContext; - }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | VerifyCredentialsErrorResponse>; - // used for retrieving the user details (email) from the recover account token // should be used in the registerOptions function when the user recovers the account and generates the credentials getUserFromRecoverAccountToken(input: { token: string; tenantId: string; userContext: UserContext; - }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | GetUserFromRecoverAccountTokenErrorResponse>; + }): Promise< + | { status: "OK"; user: User; recipeUserId: RecipeUserId } + // | GetUserFromRecoverAccountTokenErrorResponse + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + >; // credentials CRUD removeCredential(input: { @@ -384,7 +438,8 @@ export type RecipeInterface = { | { status: "OK"; } - | RemoveCredentialErrorResponse + // | RemoveCredentialErrorResponse + | { status: "CREDENTIAL_NOT_FOUND_ERROR" } >; getCredential(input: { @@ -396,12 +451,13 @@ export type RecipeInterface = { status: "OK"; credential: { id: string; - rpId: string; + relyingPartyId: string; recipeUserId: RecipeUserId; createdAt: number; }; } - | GetCredentialErrorResponse + // | GetCredentialErrorResponse + | { status: "CREDENTIAL_NOT_FOUND_ERROR" } >; listCredentials(input: { @@ -411,10 +467,42 @@ export type RecipeInterface = { status: "OK"; credentials: { id: string; - rpId: string; + relyingPartyId: string; createdAt: number; }[]; }>; + + // Generated options CRUD + removeGeneratedOptions(input: { + webauthnGeneratedOptionsId: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { status: "OK" } + // | RemoveGeneratedOptionsErrorResponse + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + >; + + getGeneratedOptions(input: { + webauthnGeneratedOptionsId: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + id: string; + relyingPartyId: string; + relyingPartyName: string; + origin: string; + userName: string; + userDisplayName: string; + timeout: string; + attestation: string; + challenge: string; + } + // | GetGeneratedOptionsErrorResponse + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + >; }; export type APIOptions = { @@ -429,67 +517,34 @@ export type APIOptions = { }; type RegisterOptionsPOSTErrorResponse = - | RegisterOptionsErrorResponse - | { status: "REGISTER_OPTIONS_NOT_ALLOWED"; reason: string }; + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "INVALID_EMAIL_ERROR" }; -type SignInOptionsPOSTErrorResponse = { status: "SIGN_IN_OPTIONS_NOT_ALLOWED"; reason: string }; +type SignInOptionsPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_EMAIL_ERROR" }; type SignUpPOSTErrorResponse = | { status: "SIGN_UP_NOT_ALLOWED"; reason: string; } - | SignUpErrorResponse; - -type SignInPOSTErrorResponse = - | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } - | SignInErrorResponse; - -type GenerateRecoverAccountTokenPOSTErrorResponse = { - status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; - reason: string; -}; - -type RecoverAccountPOSTErrorResponse = - | { - status: "ACCOUNT_RECOVERY_NOT_ALLOWED"; - reason: string; - } - | ConsumeRecoverAccountTokenErrorResponse; + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; -type AddCredentialPOSTErrorResponse = - | { - status: "ADD_CREDENTIAL_NOT_ALLOWED"; - reason: string; - } - | RegisterCredentialErrorResponse; +type SignInPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type RemoveCredentialPOSTErrorResponse = +type GenerateRecoverAccountTokenPOSTErrorResponse = | { - status: "REMOVE_CREDENTIAL_NOT_ALLOWED"; + status: "RECOVER_ACCOUNT_NOT_ALLOWED"; reason: string; } - | RemoveCredentialErrorResponse; - -type ListCredentialsPOSTErrorResponse = { - status: "LIST_CREDENTIALS_NOT_ALLOWED"; - reason: string; -}; - -type GetCredentialGETErrorResponse = { - status: "GET_CREDENTIAL_NOT_ALLOWED"; - reason: string; -}; + | { status: "UNKNOWN_USER_ID_ERROR" } + | { status: "INVALID_EMAIL_ERROR" }; -type RecoverAccountTokenPOSTErrorResponse = - | { - status: "CONSUME_RECOVER_ACCOUNT_TOKEN_NOT_ALLOWED"; - reason: string; - } - | ConsumeRecoverAccountTokenErrorResponse; +type RecoverAccountPOSTErrorResponse = + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; export type APIInterface = { registerOptionsPOST: @@ -532,12 +587,15 @@ export type APIInterface = { }; } | GeneralErrorResponse - | RegisterOptionsPOSTErrorResponse + // | RegisterOptionsPOSTErrorResponse + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "INVALID_EMAIL_ERROR" } >); signInOptionsPOST: | undefined | ((input: { + email?: string; tenantId: string; options: APIOptions; userContext: UserContext; @@ -550,13 +608,14 @@ export type APIInterface = { userVerification: "required" | "preferred" | "discouraged"; } | GeneralErrorResponse - | SignInOptionsPOSTErrorResponse + // | SignInOptionsPOSTErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_EMAIL_ERROR" } >); signUpPOST: | undefined | ((input: { - email: string; webauthnGeneratedOptionsId: string; credential: CredentialPayload; tenantId: string; @@ -572,7 +631,14 @@ export type APIInterface = { session: SessionContainerInterface; } | GeneralErrorResponse - | SignUpPOSTErrorResponse + // | SignUpPOSTErrorResponse + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } >); signInPOST: @@ -592,7 +658,8 @@ export type APIInterface = { session: SessionContainerInterface; } | GeneralErrorResponse - | SignInPOSTErrorResponse + // | SignInPOSTErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } >); generateRecoverAccountTokenPOST: @@ -606,30 +673,17 @@ export type APIInterface = { | { status: "OK"; } - | GenerateRecoverAccountTokenPOSTErrorResponse | GeneralErrorResponse - >); - - recoverAccountPOST: - | undefined - | ((input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - token: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< + // | GenerateRecoverAccountTokenPOSTErrorResponse | { - status: "OK"; - email: string; - user: User; + status: "RECOVER_ACCOUNT_NOT_ALLOWED"; + reason: string; } - | RecoverAccountPOSTErrorResponse - | GeneralErrorResponse + | { status: "UNKNOWN_USER_ID_ERROR" } + | { status: "INVALID_EMAIL_ERROR" } >); - recoverAccountTokenPOST: + recoverAccountPOST: | undefined | ((input: { token: string; @@ -645,7 +699,10 @@ export type APIInterface = { email: string; } | GeneralErrorResponse - | RecoverAccountTokenPOSTErrorResponse + // | RecoverAccountPOSTErrorResponse + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } >); // used for checking if the email already exists before generating the credential @@ -663,81 +720,6 @@ export type APIInterface = { } | GeneralErrorResponse >); - - //credentials CRUD - addCredentialPOST: - | undefined - | ((input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - session: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - } - | AddCredentialPOSTErrorResponse - | GeneralErrorResponse - >); - - removeCredentialPOST: - | undefined - | ((input: { - webauthnCredentialId: string; - tenantId: string; - session: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - } - | RemoveCredentialPOSTErrorResponse - | GeneralErrorResponse - >); - - listCredentialsGET: - | undefined - | ((input: { - tenantId: string; - session: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - credentials: { - id: string; - rp_id: string; - created_at: number; - }[]; - } - | ListCredentialsPOSTErrorResponse - | GeneralErrorResponse - >); - - getCredentialGET: - | undefined - | ((input: { - webauthnCredentialId: string; - tenantId: string; - session: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - credential: { - id: string; - rp_id: string; - created_at: number; - }; - } - | GetCredentialGETErrorResponse - | GeneralErrorResponse - >); }; export type TypeWebauthnRecoverAccountEmailDeliveryInput = { diff --git a/lib/ts/recipe/webauthn/utils.ts b/lib/ts/recipe/webauthn/utils.ts index f58fc2e4d..9f94448c0 100644 --- a/lib/ts/recipe/webauthn/utils.ts +++ b/lib/ts/recipe/webauthn/utils.ts @@ -19,10 +19,12 @@ import { TypeInputGetOrigin, TypeInputRelyingPartyId, TypeInputRelyingPartyName, + TypeInputValidateEmailAddress, TypeNormalisedInput, TypeNormalisedInputGetOrigin, TypeNormalisedInputRelyingPartyId, TypeNormalisedInputRelyingPartyName, + TypeNormalisedInputValidateEmailAddress, } from "./types"; import { NormalisedAppinfo, UserContext } from "../../types"; import { RecipeInterface, APIInterface } from "./types"; @@ -40,6 +42,11 @@ export function validateAndNormaliseUserInput( config?.relyingPartyName ); let getOrigin = validateAndNormaliseGetOriginConfig(recipeInstance, appInfo, config?.getOrigin); + let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig( + recipeInstance, + appInfo, + config?.validateEmailAddress + ); let override = { functions: (originalImplementation: RecipeInterface) => originalImplementation, @@ -78,6 +85,7 @@ export function validateAndNormaliseUserInput( getOrigin, relyingPartyId, relyingPartyName, + validateEmailAddress, getEmailDeliveryConfig, }; } @@ -132,6 +140,20 @@ function validateAndNormaliseGetOriginConfig( }; } +function validateAndNormaliseValidateEmailAddressConfig( + _: Recipe, + __: NormalisedAppinfo, + validateEmailAddressConfig: TypeInputValidateEmailAddress | undefined +): TypeNormalisedInputValidateEmailAddress { + return (email, tenantId) => { + if (typeof validateEmailAddressConfig === "function") { + return validateEmailAddressConfig(email, tenantId); + } else { + return defaultEmailValidator(email); + } + }; +} + export async function defaultEmailValidator(value: any) { // We check if the email syntax is correct // As per https://github.com/supertokens/supertokens-auth-react/issues/5#issuecomment-709512438 From 57e210d71b8455180e4def45f2ebc4577d686127 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 28 Oct 2024 16:56:06 +0200 Subject: [PATCH 14/36] pr fixes --- lib/ts/recipe/webauthn/api/registerOptions.ts | 14 +++++++- lib/ts/recipe/webauthn/types.ts | 35 ++++++------------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/registerOptions.ts b/lib/ts/recipe/webauthn/api/registerOptions.ts index 1eb7e4da1..d2f1b1e58 100644 --- a/lib/ts/recipe/webauthn/api/registerOptions.ts +++ b/lib/ts/recipe/webauthn/api/registerOptions.ts @@ -30,7 +30,7 @@ export default async function registerOptions( const requestBody = await options.req.getJSONBody(); - let email = requestBody.email; + let email = requestBody.email?.trim(); let recoverAccountToken = requestBody.recoverAccountToken; if ( @@ -43,6 +43,18 @@ export default async function registerOptions( }); } + // same as for passwordless lib/ts/recipe/passwordless/api/createCode.ts + if (email !== undefined) { + const validateError = await options.config.validateEmailAddress(email, tenantId); + if (validateError !== undefined) { + send200Response(options.res, { + status: "INVALID_EMAIL_ERROR", + err: validateError, + }); + return true; + } + } + let result = await apiImplementation.registerOptionsPOST({ email, recoverAccountToken, diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index d30fbbb33..f2b24efbd 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -98,11 +98,9 @@ export type TypeInputValidateEmailAddress = ( ) => Promise | string | undefined; // centralize error types in order to prevent missing cascading errors -type RegisterOptionsErrorResponse = - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "INVALID_EMAIL_ERROR" }; +type RegisterOptionsErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; -type SignInOptionsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_EMAIL_ERROR" }; +type SignInOptionsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; type SignUpErrorResponse = | { status: "EMAIL_ALREADY_EXISTS_ERROR" } @@ -113,7 +111,7 @@ type SignInErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; type VerifyCredentialsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" } | { status: "INVALID_EMAIL_ERROR" }; +type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; @@ -202,7 +200,6 @@ export type RecipeInterface = { } // | RegisterOptionsErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "INVALID_EMAIL_ERROR" } >; signInOptions(input: { @@ -298,7 +295,6 @@ export type RecipeInterface = { | { status: "OK"; token: string } // | GenerateRecoverAccountTokenErrorResponse | { status: "UNKNOWN_USER_ID_ERROR" } - | { status: "INVALID_EMAIL_ERROR" } >; // make sure the email maps to options email @@ -492,12 +488,9 @@ export type RecipeInterface = { status: "OK"; id: string; relyingPartyId: string; - relyingPartyName: string; origin: string; - userName: string; - userDisplayName: string; + email: string; timeout: string; - attestation: string; challenge: string; } // | GetGeneratedOptionsErrorResponse @@ -518,9 +511,9 @@ export type APIOptions = { type RegisterOptionsPOSTErrorResponse = | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "INVALID_EMAIL_ERROR" }; + | { status: "INVALID_EMAIL_ERROR"; err: string }; -type SignInOptionsPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_EMAIL_ERROR" }; +type SignInOptionsPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; type SignUpPOSTErrorResponse = | { @@ -533,13 +526,10 @@ type SignUpPOSTErrorResponse = type SignInPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type GenerateRecoverAccountTokenPOSTErrorResponse = - | { - status: "RECOVER_ACCOUNT_NOT_ALLOWED"; - reason: string; - } - | { status: "UNKNOWN_USER_ID_ERROR" } - | { status: "INVALID_EMAIL_ERROR" }; +type GenerateRecoverAccountTokenPOSTErrorResponse = { + status: "RECOVER_ACCOUNT_NOT_ALLOWED"; + reason: string; +}; type RecoverAccountPOSTErrorResponse = | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } @@ -589,7 +579,7 @@ export type APIInterface = { | GeneralErrorResponse // | RegisterOptionsPOSTErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "INVALID_EMAIL_ERROR" } + | { status: "INVALID_EMAIL_ERROR"; err: string } >); signInOptionsPOST: @@ -610,7 +600,6 @@ export type APIInterface = { | GeneralErrorResponse // | SignInOptionsPOSTErrorResponse | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_EMAIL_ERROR" } >); signUpPOST: @@ -679,8 +668,6 @@ export type APIInterface = { status: "RECOVER_ACCOUNT_NOT_ALLOWED"; reason: string; } - | { status: "UNKNOWN_USER_ID_ERROR" } - | { status: "INVALID_EMAIL_ERROR" } >); recoverAccountPOST: From ea42bd102d353c33031a535a175483e095bc1926 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Tue, 29 Oct 2024 15:56:08 +0200 Subject: [PATCH 15/36] updated initial recipe implementation --- lib/ts/recipe/webauthn/api/implementation.ts | 3 + lib/ts/recipe/webauthn/constants.ts | 1 + .../recipe/webauthn/recipeImplementation.ts | 424 ++++++++---------- lib/ts/recipe/webauthn/types.ts | 57 +-- 4 files changed, 224 insertions(+), 261 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 844d9385f..cdf15a290 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -13,6 +13,7 @@ import { DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, DEFAULT_SIGNIN_OPTIONS_TIMEOUT, DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, + DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, } from "../constants"; import RecipeUserId from "../../../recipeUserId"; import { getRecoverAccountLink } from "../utils"; @@ -88,6 +89,7 @@ export default function getAPIImplementation(): APIInterface { const requireResidentKey = DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY; const residentKey = DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY; const userVerification = DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION; + const supportedAlgorithmIds = DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS; let response = await options.recipeImplementation.registerOptions({ ...props, @@ -101,6 +103,7 @@ export default function getAPIImplementation(): APIInterface { timeout, tenantId, userContext, + supportedAlgorithmIds, }); if (response.status !== "OK") { diff --git a/lib/ts/recipe/webauthn/constants.ts b/lib/ts/recipe/webauthn/constants.ts index a94993d0f..652ee6e46 100644 --- a/lib/ts/recipe/webauthn/constants.ts +++ b/lib/ts/recipe/webauthn/constants.ts @@ -32,6 +32,7 @@ export const DEFAULT_REGISTER_OPTIONS_ATTESTATION = "none"; export const DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = false; export const DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = "required"; export const DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = "preferred"; +export const DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS = [-8, -7, -257]; export const DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION = "preferred"; diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index f5e23fb40..062cef00d 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -1,11 +1,10 @@ -import { CredentialPayload, RecipeInterface, TypeNormalisedInput } from "./types"; +import { RecipeInterface, TypeNormalisedInput } from "./types"; import AccountLinking from "../accountlinking/recipe"; import { Querier } from "../../querier"; import NormalisedURLPath from "../../normalisedURLPath"; import { getUser } from "../.."; import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; -import { UserContext, User as UserType } from "../../types"; import { LoginMethod, User } from "../../user"; import { AuthUtils } from "../../authUtils"; import * as jose from "jose"; @@ -23,77 +22,21 @@ export default function getRecipeInterface( attestation = "none", tenantId, userContext, + supportedAlgorithmIds, ...rest - }: { - relyingPartyId: string; - relyingPartyName: string; - origin: string; - requireResidentKey: boolean | undefined; // should default to false in order to allow multiple authenticators to be used; see https://auth0.com/blog/a-look-at-webauthn-resident-credentials/ - // default to 'required' in order store the private key locally on the device and not on the server - residentKey: "required" | "preferred" | "discouraged" | undefined; - // default to 'preferred' in order to verify the user (biometrics, pin, etc) based on the device preferences - userVerification: "required" | "preferred" | "discouraged" | undefined; - // default to 'none' in order to allow any authenticator and not verify attestation - attestation: "none" | "indirect" | "direct" | "enterprise" | undefined; - // default to 5 seconds - timeout: number | undefined; - tenantId: string; - userContext: UserContext; - } & ( - | { - recoverAccountToken: string; - } - | { - email: string; - } - )): Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: "public-key"; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: "public-key"; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; - }; - } - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "EMAIL_MISSING_ERROR" } - > { - let email = "email" in rest ? rest.email : undefined; - const recoverAccountToken = "recoverAccountToken" in rest ? rest.recoverAccountToken : undefined; - if (email === undefined && recoverAccountToken === undefined) { - return { - status: "EMAIL_MISSING_ERROR", - }; - } + }) { + const emailInput = "email" in rest ? rest.email : undefined; + const recoverAccountTokenInput = "recoverAccountToken" in rest ? rest.recoverAccountToken : undefined; - // todo check if should decode using Core or using sdk; atm decided on usinng the sdk so to not make another roundtrip to the server - // the actual verification will be done during consumeRecoverAccountToken - if (recoverAccountToken !== undefined) { + let email: string | undefined; + if (emailInput !== undefined) { + email = emailInput; + } else if (recoverAccountTokenInput !== undefined) { + // todo check if should decode using Core or using sdk; atm decided on usinng the sdk so to not make another roundtrip to the server + // the actual verification of the token will be done during consumeRecoverAccountToken let decoded: jose.JWTPayload | undefined; try { - decoded = await jose.decodeJwt(recoverAccountToken); + decoded = await jose.decodeJwt(recoverAccountTokenInput); } catch (e) { console.error(e); @@ -107,7 +50,16 @@ export default function getRecipeInterface( if (!email) { return { - status: "EMAIL_MISSING_ERROR", + status: "INVALID_EMAIL_ERROR", + err: "The email is missing", + }; + } + + const err = await getWebauthnConfig().validateEmailAddress(email, tenantId); + if (err) { + return { + status: "INVALID_EMAIL_ERROR", + err, }; } @@ -122,31 +74,13 @@ export default function getRecipeInterface( origin, timeout, attestation, + supportedAlgorithmIds, }, userContext ); }, - signInOptions: async function ({ - relyingPartyId, - origin, - timeout, - tenantId, - userContext, - }: { - relyingPartyId: string; - origin: string; - userVerification: "required" | "preferred" | "discouraged" | undefined; // see register options - timeout: number | undefined; - tenantId: string; - userContext: UserContext; - }): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: "required" | "preferred" | "discouraged"; - }> { + signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest( new NormalisedURLPath( @@ -164,25 +98,7 @@ export default function getRecipeInterface( signUp: async function ( this: RecipeInterface, { webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, userContext } - ): Promise< - | { - status: "OK"; - user: UserType; - recipeUserId: RecipeUserId; - } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } - | { - 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"; - } - > { + ) { const response = await this.createNewRecipeUser({ credential, webauthnGeneratedOptionsId, @@ -216,57 +132,63 @@ export default function getRecipeInterface( }; }, - createNewRecipeUser: async function (input: { - tenantId: string; - credential: CredentialPayload; - webauthnGeneratedOptionsId: string; - userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { status: "WRONG_CREDENTIALS_ERROR" } - // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - > { - const resp = await querier.sendPostRequest( - new NormalisedURLPath( - `/${input.tenantId === undefined ? DEFAULT_TENANT_ID : input.tenantId}/recipe/webauthn/signup` - ), - { - webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId, - credential: input.credential, - }, - input.userContext - ); + signIn: async function ( + this: RecipeInterface, + { credential, webauthnGeneratedOptionsId, tenantId, session, shouldTryLinkingWithSessionUser, userContext } + ) { + const response = await this.verifyCredentials({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (response.status !== "OK") { + return response; + } - if (resp.status === "OK") { - return { - status: "OK", - user: new User(resp.user), - recipeUserId: new RecipeUserId(resp.recipeUserId), - }; + const loginMethod: LoginMethod = response.user.loginMethods.find( + (lm: LoginMethod) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString() + )!; + + if (!loginMethod.verified) { + await AccountLinking.getInstance().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 getUser(response.recipeUserId!.getAsString(), userContext))!; } - return resp; + const linkResult = await AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + 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 ({ - credential, - webauthnGeneratedOptionsId, - tenantId, - userContext, - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { status: "WRONG_CREDENTIALS_ERROR" } - > { + verifyCredentials: async function ({ credential, webauthnGeneratedOptionsId, tenantId, userContext }) { const response = await querier.sendPostRequest( new NormalisedURLPath( `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/signin` @@ -291,72 +213,30 @@ export default function getRecipeInterface( }; }, - signIn: async function ( - this: RecipeInterface, - { credential, webauthnGeneratedOptionsId, tenantId, session, shouldTryLinkingWithSessionUser, userContext } - ) { - const response = await this.verifyCredentials({ - credential, - webauthnGeneratedOptionsId, - tenantId, - userContext, - }); - - if (response.status === "OK") { - const loginMethod: LoginMethod = response.user.loginMethods.find( - (lm: LoginMethod) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString() - )!; - - if (!loginMethod.verified) { - await AccountLinking.getInstance().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 getUser(response.recipeUserId!.getAsString(), userContext))!; - } + createNewRecipeUser: async function (input) { + const resp = await querier.sendPostRequest( + new NormalisedURLPath( + `/${input.tenantId === undefined ? DEFAULT_TENANT_ID : input.tenantId}/recipe/webauthn/signup` + ), + { + webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId, + credential: input.credential, + }, + input.userContext + ); - const linkResult = await AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }); - if (linkResult.status === "LINKING_TO_SESSION_USER_FAILED") { - return linkResult; - } - response.user = linkResult.user; + if (resp.status === "OK") { + return { + status: "OK", + user: new User(resp.user), + recipeUserId: new RecipeUserId(resp.recipeUserId), + }; } - return response; + return resp; }, - generateRecoverAccountToken: async function ({ - userId, - email, - tenantId, - userContext, - }: { - userId: string; - email: string; - tenantId: string; - userContext: UserContext; - }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + generateRecoverAccountToken: async function ({ userId, email, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest( new NormalisedURLPath( @@ -370,25 +250,12 @@ export default function getRecipeInterface( ); }, - consumeRecoverAccountToken: async function ({ - token, - tenantId, - userContext, - }: { - token: string; - tenantId: string; - userContext: UserContext; - }): Promise< - | { - status: "OK"; - userId: string; - email: string; - } - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - > { + consumeRecoverAccountToken: async function ({ token, tenantId, userContext }) { return await querier.sendPostRequest( new NormalisedURLPath( - `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/paskey/user/recover/token/consume` + `/${ + tenantId === undefined ? DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/user/recover/token/consume` ), { token, @@ -396,5 +263,96 @@ export default function getRecipeInterface( userContext ); }, + + registerCredential: async function ({ webauthnGeneratedOptionsId, credential, userContext, recipeUserId }) { + return await querier.sendPostRequest( + new NormalisedURLPath(`/recipe/webauthn/user/${recipeUserId}/credential/register`), + { + webauthnGeneratedOptionsId, + credential, + }, + userContext + ); + }, + + decodeCredential: async function ({ credential, userContext }) { + const response = await querier.sendPostRequest( + new NormalisedURLPath(`/recipe/webauthn/credential/decode`), + { + credential, + }, + userContext + ); + + if (response.status === "OK") { + return response; + } + + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + }, + + getUserFromRecoverAccountToken: async function ({ token, tenantId, userContext }) { + return await querier.sendGetRequest( + new NormalisedURLPath( + `/${ + tenantId === undefined ? DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/user/recover/token/${token}` + ), + {}, + userContext + ); + }, + + removeCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { + return await querier.sendDeleteRequest( + new NormalisedURLPath(`/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}`), + {}, + {}, + userContext + ); + }, + + getCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { + return await querier.sendGetRequest( + new NormalisedURLPath(`/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}`), + {}, + userContext + ); + }, + + listCredentials: async function ({ recipeUserId, userContext }) { + return await querier.sendGetRequest( + new NormalisedURLPath(`/recipe/webauthn/user/${recipeUserId}/credential/list`), + {}, + userContext + ); + }, + + removeGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { + return await querier.sendDeleteRequest( + new NormalisedURLPath( + `/${ + tenantId === undefined ? DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + ), + {}, + {}, + userContext + ); + }, + + getGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { + return await querier.sendGetRequest( + new NormalisedURLPath( + `/${ + tenantId === undefined ? DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + ), + {}, + userContext + ); + }, }; } diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index f2b24efbd..593998a6d 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -154,6 +154,8 @@ export type RecipeInterface = { userVerification: "required" | "preferred" | "discouraged" | undefined; // default to 'none' in order to allow any authenticator and not verify attestation attestation: "none" | "indirect" | "direct" | "enterprise" | undefined; + // default to [-8, -7, -257] as supported algorithms. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms. + supportedAlgorithmIds: number[] | undefined; // default to 5 seconds timeout: number | undefined; tenantId: string; @@ -200,6 +202,7 @@ export type RecipeInterface = { } // | RegisterOptionsErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "INVALID_EMAIL_ERROR"; err: string } >; signInOptions(input: { @@ -281,6 +284,27 @@ export type RecipeInterface = { | { status: "WRONG_CREDENTIALS_ERROR" } >; + // this function is meant only for creating the recipe in the core and nothing else. + // we added this even though signUp exists cause devs may override signup expecting it + // to be called just during sign up. But we also need a version of signing up which can be + // called during operations like creating a user during password reset flow. + createNewRecipeUser(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + // | CreateNewRecipeUserErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + >; + /** * We pass in the email as well to this function cause the input userId * may not be associated with an webauthn account. In this case, we @@ -319,7 +343,6 @@ export type RecipeInterface = { registerCredential(input: { webauthnGeneratedOptionsId: string; credential: CredentialPayload; - tenantId: string; userContext: UserContext; recipeUserId: RecipeUserId; }): Promise< @@ -331,29 +354,9 @@ export type RecipeInterface = { | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } >; - // this function is meant only for creating the recipe in the core and nothing else. - // we added this even though signUp exists cause devs may override signup expecting it - // to be called just during sign up. But we also need a version of signing up which can be - // called during operations like creating a user during password reset flow. - createNewRecipeUser(input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - // | CreateNewRecipeUserErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - >; - decodeCredential(input: { credential: CredentialPayload; + userContext: UserContext; }): Promise< | { status: "OK"; @@ -445,12 +448,10 @@ export type RecipeInterface = { }): Promise< | { status: "OK"; - credential: { - id: string; - relyingPartyId: string; - recipeUserId: RecipeUserId; - createdAt: number; - }; + id: string; + relyingPartyId: string; + recipeUserId: RecipeUserId; + createdAt: number; } // | GetCredentialErrorResponse | { status: "CREDENTIAL_NOT_FOUND_ERROR" } From b8cffc134545dff749836f72413fd21448e6aff3 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Wed, 6 Nov 2024 15:53:56 +0200 Subject: [PATCH 16/36] fixed implementation --- lib/ts/recipe/webauthn/api/implementation.ts | 41 ++++++++------ lib/ts/recipe/webauthn/api/signup.ts | 8 +-- lib/ts/recipe/webauthn/api/utils.ts | 10 ---- lib/ts/recipe/webauthn/index.ts | 56 ++++++++++++-------- lib/ts/recipe/webauthn/types.ts | 11 +++- lib/ts/types.ts | 2 +- lib/ts/user.ts | 4 +- 7 files changed, 71 insertions(+), 61 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index cdf15a290..61f905625 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -65,8 +65,7 @@ export default function getAPIImplementation(): APIInterface { }; } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "EMAIL_MISSING_ERROR" } - | { status: "REGISTER_OPTIONS_NOT_ALLOWED"; reason: string } + | { status: "INVALID_EMAIL_ERROR"; err: string } > { const relyingPartyId = await options.config.relyingPartyId({ tenantId, @@ -125,10 +124,12 @@ export default function getAPIImplementation(): APIInterface { }, signInOptionsPOST: async function ({ + email, tenantId, options, userContext, }: { + email?: string; tenantId: string; options: APIOptions; userContext: UserContext; @@ -141,6 +142,7 @@ export default function getAPIImplementation(): APIInterface { userVerification: "required" | "preferred" | "discouraged"; } | GeneralErrorResponse + | { status: "WRONG_CREDENTIALS_ERROR" } > { const relyingPartyId = await options.config.relyingPartyId({ tenantId, @@ -159,6 +161,7 @@ export default function getAPIImplementation(): APIInterface { const userVerification = DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION; let response = await options.recipeImplementation.signInOptions({ + email, userVerification, origin, relyingPartyId, @@ -181,7 +184,6 @@ export default function getAPIImplementation(): APIInterface { }, signUpPOST: async function ({ - email, webauthnGeneratedOptionsId, credential, tenantId, @@ -190,28 +192,28 @@ export default function getAPIImplementation(): APIInterface { options, userContext, }: { - email: string; webauthnGeneratedOptionsId: string; credential: CredentialPayload; tenantId: string; - session?: SessionContainerInterface; + session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; + // should also have the email or recoverAccountToken in order to do the preauth checks }): Promise< | { status: "OK"; session: SessionContainerInterface; user: User; } + | GeneralErrorResponse | { status: "SIGN_UP_NOT_ALLOWED"; reason: string; } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | GeneralErrorResponse > { const errorCodeMap = { SIGN_UP_NOT_ALLOWED: @@ -232,13 +234,25 @@ export default function getAPIImplementation(): APIInterface { }, }; + const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (generatedOptions.status !== "OK") { + return { status: "WRONG_CREDENTIALS_ERROR" }; + } + + const email = generatedOptions.email; + // NOTE: Following checks will likely never throw an error as the // check for type is done in a parent function but they are kept // here to be on the safe side. - if (typeof email !== "string") + if (!email) { throw new Error( "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" ); + } const preAuthCheckRes = await AuthUtils.preAuthChecks({ authenticatingAccountInfo: { @@ -353,9 +367,7 @@ export default function getAPIImplementation(): APIInterface { session: SessionContainerInterface; user: User; } - | { - status: "WRONG_CREDENTIALS_ERROR"; - } + | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "SIGN_IN_NOT_ALLOWED"; reason: string; @@ -826,13 +838,9 @@ export default function getAPIImplementation(): APIInterface { email: string; } | GeneralErrorResponse - | { - status: "CONSUME_RECOVER_ACCOUNT_TOKEN_NOT_ALLOWED"; - reason: string; - } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } > { async function markEmailAsVerified(recipeUserId: RecipeUserId, email: string) { const emailVerificationInstance = EmailVerification.getInstance(); @@ -874,7 +882,6 @@ export default function getAPIImplementation(): APIInterface { let updateResponse = await options.recipeImplementation.registerCredential({ recipeUserId, webauthnGeneratedOptionsId, - tenantId, credential, userContext, }); diff --git a/lib/ts/recipe/webauthn/api/signup.ts b/lib/ts/recipe/webauthn/api/signup.ts index 3695cbe3e..2be7713f8 100644 --- a/lib/ts/recipe/webauthn/api/signup.ts +++ b/lib/ts/recipe/webauthn/api/signup.ts @@ -18,11 +18,7 @@ import { getNormalisedShouldTryLinkingWithSessionUserFlag, send200Response, } from "../../../utils"; -import { - validateEmailOrThrowError, - validatewebauthnGeneratedOptionsIdOrThrowError, - validateCredentialOrThrowError, -} from "./utils"; +import { validatewebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; import { APIInterface, APIOptions } from ".."; import STError from "../error"; import { UserContext } from "../../../types"; @@ -39,7 +35,6 @@ export default async function signUpAPI( } const requestBody = await options.req.getJSONBody(); - const email = await validateEmailOrThrowError(requestBody.email); const webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError( requestBody.webauthnGeneratedOptionsId ); @@ -58,7 +53,6 @@ export default async function signUpAPI( } let result = await apiImplementation.signUpPOST({ - email, credential, webauthnGeneratedOptionsId, tenantId, diff --git a/lib/ts/recipe/webauthn/api/utils.ts b/lib/ts/recipe/webauthn/api/utils.ts index 4b1ecaaf8..5e1fa497c 100644 --- a/lib/ts/recipe/webauthn/api/utils.ts +++ b/lib/ts/recipe/webauthn/api/utils.ts @@ -13,16 +13,6 @@ * under the License. */ import STError from "../error"; -import { defaultEmailValidator } from "../utils"; - -export async function validateEmailOrThrowError(email: string): Promise { - const error = await defaultEmailValidator(email); - if (error) { - throw newBadRequestError(error); - } - - return email; -} export async function validatewebauthnGeneratedOptionsIdOrThrowError( webauthnGeneratedOptionsId: string diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 9e53ff0d9..a1da56d89 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -22,10 +22,11 @@ import { getRecoverAccountLink } from "./utils"; import { getRequestFromUserContext, getUser } from "../.."; import { getUserContext } from "../../utils"; import { SessionContainerInterface } from "../session/types"; -import { User, UserContext } from "../../types"; +import { User } from "../../types"; import { DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, + DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, } from "./constants"; @@ -35,8 +36,9 @@ export default class Wrapper { static Error = SuperTokensError; - static registerOptions( - email: string, + static async registerOptions( + email: string | undefined, + recoverAccountToken: string | undefined, relyingPartyId: string, relyingPartyName: string, origin: string, @@ -76,14 +78,24 @@ export default class Wrapper { }; } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "EMAIL_MISSING_ERROR" } - | { status: "INVALID_EMAIL_ERROR" } + | { status: "INVALID_EMAIL_ERROR"; err: string } > { + let payload: { email: string } | { recoverAccountToken: string } | null = email + ? { email } + : recoverAccountToken + ? { recoverAccountToken } + : null; + + if (!payload) { + return { status: "INVALID_EMAIL_ERROR", err: "Email is missing" }; + } + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions({ requireResidentKey: DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, residentKey: DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, userVerification: DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, - email, + supportedAlgorithmIds: DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, + ...payload, relyingPartyId, relyingPartyName, origin, @@ -100,13 +112,16 @@ export default class Wrapper { timeout: number, tenantId: string, userContext: Record - ): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: "required" | "preferred" | "discouraged"; - }> { + ): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + } + | { status: "WRONG_CREDENTIALS_ERROR" } + > { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ userVerification: DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, relyingPartyId, @@ -123,11 +138,7 @@ export default class Wrapper { credential: CredentialPayload, session?: undefined, userContext?: Record - ): Promise< - | { status: "OK"; user: User; recipeUserId: RecipeUserId } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR" } - >; + ): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" }>; static signIn( tenantId: string, webauthnGeneratedOptionsId: string, @@ -137,7 +148,6 @@ export default class Wrapper { ): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR" } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -156,7 +166,6 @@ export default class Wrapper { ): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR" } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -181,7 +190,7 @@ export default class Wrapper { webauthnGeneratedOptionsId: string, credential: CredentialPayload, userContext?: Record - ): Promise<{ status: "OK" } | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR" }> { + ): Promise<{ status: "OK" } | { status: "WRONG_CREDENTIALS_ERROR" }> { const resp = await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ webauthnGeneratedOptionsId, credential, @@ -220,7 +229,7 @@ export default class Wrapper { }); } - static async recoverAccountUsingToken( + static async recoverAccount( tenantId: string, webauthnGeneratedOptionsId: string, token: string, @@ -286,6 +295,7 @@ export default class Wrapper { | { status: "OK" | "WRONG_CREDENTIALS_ERROR"; } + | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } > { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerCredential({ @@ -383,7 +393,7 @@ export let verifyCredentials = Wrapper.verifyCredentials; export let generateRecoverAccountToken = Wrapper.generateRecoverAccountToken; -export let recoverAccountUsingToken = Wrapper.recoverAccountUsingToken; +export let recoverAccount = Wrapper.recoverAccount; export let consumeRecoverAccountToken = Wrapper.consumeRecoverAccountToken; diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 593998a6d..6275e7464 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -525,7 +525,12 @@ type SignUpPOSTErrorResponse = | { status: "WRONG_CREDENTIALS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; -type SignInPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +type SignInPOSTErrorResponse = + | { status: "WRONG_CREDENTIALS_ERROR" } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + }; type GenerateRecoverAccountTokenPOSTErrorResponse = { status: "RECOVER_ACCOUNT_NOT_ALLOWED"; @@ -649,6 +654,10 @@ export type APIInterface = { } | GeneralErrorResponse // | SignInPOSTErrorResponse + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } | { status: "WRONG_CREDENTIALS_ERROR" } >); diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 482d41b9d..308fd0436 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -117,7 +117,7 @@ export type User = { }[]; webauthn: { credentialIds: string[]; - }[]; + }; loginMethods: (RecipeLevelUser & { verified: boolean; hasSameEmailAs: (email: string | undefined) => boolean; diff --git a/lib/ts/user.ts b/lib/ts/user.ts index 87213468e..5a611e340 100644 --- a/lib/ts/user.ts +++ b/lib/ts/user.ts @@ -94,7 +94,7 @@ export class User implements UserType { }[]; public readonly webauthn: { credentialIds: string[]; - }[]; + }; public readonly loginMethods: LoginMethod[]; public readonly timeJoined: number; // minimum timeJoined value from linkedRecipes @@ -143,7 +143,7 @@ export type UserWithoutHelperFunctions = { }[]; webauthn: { credentialIds: string[]; - }[]; + }; loginMethods: { recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; recipeUserId: string; From bf9e00fe8a53713fc6a60cacb781b2184a0c8a2b Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 8 Nov 2024 09:57:03 +0200 Subject: [PATCH 17/36] added basic build --- lib/build/recipe/accountlinking/types.d.ts | 5 +- lib/build/recipe/multifactorauth/index.d.ts | 1 + lib/build/recipe/multifactorauth/types.d.ts | 1 + lib/build/recipe/multifactorauth/types.js | 1 + .../recipe/webauthn/api/emailExists.d.ts | 9 + lib/build/recipe/webauthn/api/emailExists.js | 45 + .../api/generateRecoverAccountToken.d.ts | 9 + .../api/generateRecoverAccountToken.js | 45 + .../recipe/webauthn/api/implementation.d.ts | 3 + .../recipe/webauthn/api/implementation.js | 886 ++++++++++++++++++ .../recipe/webauthn/api/recoverAccount.d.ts | 9 + .../recipe/webauthn/api/recoverAccount.js | 66 ++ .../recipe/webauthn/api/registerOptions.d.ts | 9 + .../recipe/webauthn/api/registerOptions.js | 62 ++ .../recipe/webauthn/api/signInOptions.d.ts | 9 + .../recipe/webauthn/api/signInOptions.js | 30 + lib/build/recipe/webauthn/api/signin.d.ts | 9 + lib/build/recipe/webauthn/api/signin.js | 61 ++ lib/build/recipe/webauthn/api/signup.d.ts | 9 + lib/build/recipe/webauthn/api/signup.js | 80 ++ lib/build/recipe/webauthn/api/utils.d.ts | 5 + lib/build/recipe/webauthn/api/utils.js | 43 + lib/build/recipe/webauthn/constants.d.ts | 16 + lib/build/recipe/webauthn/constants.js | 33 + lib/build/recipe/webauthn/core-mock.d.ts | 3 + lib/build/recipe/webauthn/core-mock.js | 79 ++ lib/build/recipe/webauthn/error.d.ts | 20 + lib/build/recipe/webauthn/error.js | 30 + lib/build/recipe/webauthn/index.d.ts | 244 +++++ lib/build/recipe/webauthn/index.js | 228 +++++ lib/build/recipe/webauthn/recipe.d.ts | 43 + lib/build/recipe/webauthn/recipe.js | 313 +++++++ .../recipe/webauthn/recipeImplementation.d.ts | 7 + .../recipe/webauthn/recipeImplementation.js | 404 ++++++++ lib/build/recipe/webauthn/types.d.ts | 673 +++++++++++++ lib/build/recipe/webauthn/types.js | 16 + lib/build/recipe/webauthn/utils.d.ts | 20 + lib/build/recipe/webauthn/utils.js | 163 ++++ lib/build/types.d.ts | 3 + lib/build/user.d.ts | 12 +- lib/build/user.js | 4 + lib/ts/recipe/webauthn/core-mock.ts | 3 + lib/ts/recipe/webauthn/types.ts | 110 +-- lib/ts/recipe/webauthn/utils.ts | 2 + 44 files changed, 3766 insertions(+), 57 deletions(-) create mode 100644 lib/build/recipe/webauthn/api/emailExists.d.ts create mode 100644 lib/build/recipe/webauthn/api/emailExists.js create mode 100644 lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts create mode 100644 lib/build/recipe/webauthn/api/generateRecoverAccountToken.js create mode 100644 lib/build/recipe/webauthn/api/implementation.d.ts create mode 100644 lib/build/recipe/webauthn/api/implementation.js create mode 100644 lib/build/recipe/webauthn/api/recoverAccount.d.ts create mode 100644 lib/build/recipe/webauthn/api/recoverAccount.js create mode 100644 lib/build/recipe/webauthn/api/registerOptions.d.ts create mode 100644 lib/build/recipe/webauthn/api/registerOptions.js create mode 100644 lib/build/recipe/webauthn/api/signInOptions.d.ts create mode 100644 lib/build/recipe/webauthn/api/signInOptions.js create mode 100644 lib/build/recipe/webauthn/api/signin.d.ts create mode 100644 lib/build/recipe/webauthn/api/signin.js create mode 100644 lib/build/recipe/webauthn/api/signup.d.ts create mode 100644 lib/build/recipe/webauthn/api/signup.js create mode 100644 lib/build/recipe/webauthn/api/utils.d.ts create mode 100644 lib/build/recipe/webauthn/api/utils.js create mode 100644 lib/build/recipe/webauthn/constants.d.ts create mode 100644 lib/build/recipe/webauthn/constants.js create mode 100644 lib/build/recipe/webauthn/core-mock.d.ts create mode 100644 lib/build/recipe/webauthn/core-mock.js create mode 100644 lib/build/recipe/webauthn/error.d.ts create mode 100644 lib/build/recipe/webauthn/error.js create mode 100644 lib/build/recipe/webauthn/index.d.ts create mode 100644 lib/build/recipe/webauthn/index.js create mode 100644 lib/build/recipe/webauthn/recipe.d.ts create mode 100644 lib/build/recipe/webauthn/recipe.js create mode 100644 lib/build/recipe/webauthn/recipeImplementation.d.ts create mode 100644 lib/build/recipe/webauthn/recipeImplementation.js create mode 100644 lib/build/recipe/webauthn/types.d.ts create mode 100644 lib/build/recipe/webauthn/types.js create mode 100644 lib/build/recipe/webauthn/utils.d.ts create mode 100644 lib/build/recipe/webauthn/utils.js diff --git a/lib/build/recipe/accountlinking/types.d.ts b/lib/build/recipe/accountlinking/types.d.ts index 3530940ee..845780080 100644 --- a/lib/build/recipe/accountlinking/types.d.ts +++ b/lib/build/recipe/accountlinking/types.d.ts @@ -182,9 +182,12 @@ export declare type AccountInfo = { id: string; userId: string; }; + webauthn?: { + credentialIds: string[]; + }; }; export declare type AccountInfoWithRecipeId = { - recipeId: "emailpassword" | "thirdparty" | "passwordless"; + recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; } & AccountInfo; export declare type RecipeLevelUser = { tenantIds: string[]; diff --git a/lib/build/recipe/multifactorauth/index.d.ts b/lib/build/recipe/multifactorauth/index.d.ts index b62940921..b2d06cd51 100644 --- a/lib/build/recipe/multifactorauth/index.d.ts +++ b/lib/build/recipe/multifactorauth/index.d.ts @@ -9,6 +9,7 @@ export default class Wrapper { static MultiFactorAuthClaim: import("./multiFactorAuthClaim").MultiFactorAuthClaimClass; static FactorIds: { EMAILPASSWORD: string; + WEBAUTHN: string; OTP_EMAIL: string; OTP_PHONE: string; LINK_EMAIL: string; diff --git a/lib/build/recipe/multifactorauth/types.d.ts b/lib/build/recipe/multifactorauth/types.d.ts index 53f7ac093..e02cbadb4 100644 --- a/lib/build/recipe/multifactorauth/types.d.ts +++ b/lib/build/recipe/multifactorauth/types.d.ts @@ -136,6 +136,7 @@ export declare type GetPhoneNumbersForFactorsFromOtherRecipesFunc = ( }; export declare const FactorIds: { EMAILPASSWORD: string; + WEBAUTHN: string; OTP_EMAIL: string; OTP_PHONE: string; LINK_EMAIL: string; diff --git a/lib/build/recipe/multifactorauth/types.js b/lib/build/recipe/multifactorauth/types.js index ef0ec1829..e4e0f5219 100644 --- a/lib/build/recipe/multifactorauth/types.js +++ b/lib/build/recipe/multifactorauth/types.js @@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.FactorIds = void 0; exports.FactorIds = { EMAILPASSWORD: "emailpassword", + WEBAUTHN: "webauthn", OTP_EMAIL: "otp-email", OTP_PHONE: "otp-phone", LINK_EMAIL: "link-email", diff --git a/lib/build/recipe/webauthn/api/emailExists.d.ts b/lib/build/recipe/webauthn/api/emailExists.d.ts new file mode 100644 index 000000000..2f55b6d3b --- /dev/null +++ b/lib/build/recipe/webauthn/api/emailExists.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from "../"; +import { UserContext } from "../../../types"; +export default function emailExists( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/emailExists.js b/lib/build/recipe/webauthn/api/emailExists.js new file mode 100644 index 000000000..b76ac08c7 --- /dev/null +++ b/lib/build/recipe/webauthn/api/emailExists.js @@ -0,0 +1,45 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../utils"); +const error_1 = __importDefault(require("../error")); +async function emailExists(apiImplementation, tenantId, options, userContext) { + // Logic as per https://github.com/supertokens/supertokens-node/issues/47#issue-751571692 + if (apiImplementation.emailExistsGET === undefined) { + return false; + } + let email = options.req.getKeyValueFromQuery("email"); + if (email === undefined || typeof email !== "string") { + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "Please provide the email as a GET param", + }); + } + let result = await apiImplementation.emailExistsGET({ + email, + tenantId, + options, + userContext, + }); + utils_1.send200Response(options.res, result); + return true; +} +exports.default = emailExists; diff --git a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts new file mode 100644 index 000000000..ca836c5b4 --- /dev/null +++ b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from "../"; +import { UserContext } from "../../../types"; +export default function generateRecoverAccountToken( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js new file mode 100644 index 000000000..651c5a398 --- /dev/null +++ b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js @@ -0,0 +1,45 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../utils"); +const error_1 = __importDefault(require("../error")); +async function generateRecoverAccountToken(apiImplementation, tenantId, options, userContext) { + if (apiImplementation.generateRecoverAccountTokenPOST === undefined) { + return false; + } + const requestBody = await options.req.getJSONBody(); + const email = requestBody.email; + if (email === undefined || typeof email !== "string") { + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "Please provide the email", + }); + } + let result = await apiImplementation.generateRecoverAccountTokenPOST({ + email, + tenantId, + options, + userContext, + }); + utils_1.send200Response(options.res, result); + return true; +} +exports.default = generateRecoverAccountToken; diff --git a/lib/build/recipe/webauthn/api/implementation.d.ts b/lib/build/recipe/webauthn/api/implementation.d.ts new file mode 100644 index 000000000..2f382b449 --- /dev/null +++ b/lib/build/recipe/webauthn/api/implementation.d.ts @@ -0,0 +1,3 @@ +// @ts-nocheck +import { APIInterface } from ".."; +export default function getAPIImplementation(): APIInterface; diff --git a/lib/build/recipe/webauthn/api/implementation.js b/lib/build/recipe/webauthn/api/implementation.js new file mode 100644 index 000000000..6eef006ec --- /dev/null +++ b/lib/build/recipe/webauthn/api/implementation.js @@ -0,0 +1,886 @@ +"use strict"; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const recipe_1 = __importDefault(require("../../accountlinking/recipe")); +const recipe_2 = __importDefault(require("../../emailverification/recipe")); +const authUtils_1 = require("../../../authUtils"); +const utils_1 = require("../../thirdparty/utils"); +const constants_1 = require("../constants"); +const recipeUserId_1 = __importDefault(require("../../../recipeUserId")); +const utils_2 = require("../utils"); +const logger_1 = require("../../../logger"); +const __1 = require("../../.."); +function getAPIImplementation() { + return { + registerOptionsPOST: async function (_a) { + var { tenantId, options, userContext } = _a, + props = __rest(_a, ["tenantId", "options", "userContext"]); + const relyingPartyId = await options.config.relyingPartyId({ + tenantId, + request: options.req, + userContext, + }); + const relyingPartyName = await options.config.relyingPartyName({ + tenantId, + userContext, + }); + const origin = await options.config.getOrigin({ + tenantId, + request: options.req, + userContext, + }); + const timeout = constants_1.DEFAULT_REGISTER_OPTIONS_TIMEOUT; + const attestation = constants_1.DEFAULT_REGISTER_OPTIONS_ATTESTATION; + const requireResidentKey = constants_1.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY; + const residentKey = constants_1.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY; + const userVerification = constants_1.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION; + const supportedAlgorithmIds = constants_1.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS; + let response = await options.recipeImplementation.registerOptions( + Object.assign(Object.assign({}, props), { + attestation, + requireResidentKey, + residentKey, + userVerification, + origin, + relyingPartyId, + relyingPartyName, + timeout, + tenantId, + userContext, + supportedAlgorithmIds, + }) + ); + if (response.status !== "OK") { + return response; + } + return { + status: "OK", + webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, + challenge: response.challenge, + timeout: response.timeout, + attestation: response.attestation, + pubKeyCredParams: response.pubKeyCredParams, + excludeCredentials: response.excludeCredentials, + rp: response.rp, + user: response.user, + authenticatorSelection: response.authenticatorSelection, + }; + }, + signInOptionsPOST: async function ({ email, tenantId, options, userContext }) { + const relyingPartyId = await options.config.relyingPartyId({ + tenantId, + request: options.req, + userContext, + }); + // use this to get the full url instead of only the domain url + const origin = await options.config.getOrigin({ + tenantId, + request: options.req, + userContext, + }); + const timeout = constants_1.DEFAULT_SIGNIN_OPTIONS_TIMEOUT; + const userVerification = constants_1.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION; + let response = await options.recipeImplementation.signInOptions({ + email, + userVerification, + origin, + relyingPartyId, + timeout, + tenantId, + userContext, + }); + if (response.status !== "OK") { + return response; + } + return { + status: "OK", + webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, + challenge: response.challenge, + timeout: response.timeout, + userVerification: response.userVerification, + }; + }, + signUpPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }) { + const errorCodeMap = { + SIGN_UP_NOT_ALLOWED: + "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + INVALID_AUTHENTICATOR_ERROR: { + // TODO: add more cases + }, + WRONG_CREDENTIALS_ERROR: "The sign up credentials are incorrect. Please use a different authenticator.", + LINKING_TO_SESSION_USER_FAILED: { + EMAIL_VERIFICATION_REQUIRED: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", + 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_016)", + }, + }; + const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (generatedOptions.status !== "OK") { + return { status: "WRONG_CREDENTIALS_ERROR" }; + } + const email = generatedOptions.email; + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (!email) { + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + } + const preAuthCheckRes = await authUtils_1.AuthUtils.preAuthChecks({ + authenticatingAccountInfo: { + recipeId: "webauthn", + email, + }, + factorIds: ["webauthn"], + isSignUp: true, + isVerified: utils_1.isFakeEmail(email), + signInVerifiesLoginMethod: false, + skipSessionUserUpdateInCore: false, + authenticatingUser: undefined, + tenantId, + userContext, + session, + shouldTryLinkingWithSessionUser, + }); + if (preAuthCheckRes.status === "SIGN_UP_NOT_ALLOWED") { + const conflictingUsers = await recipe_1.default + .getInstance() + .recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + if ( + conflictingUsers.some((u) => + u.loginMethods.some((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) + ) + ) { + return { + status: "EMAIL_ALREADY_EXISTS_ERROR", + }; + } + } + if (preAuthCheckRes.status !== "OK") { + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthCheckRes, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); + } + if (utils_1.isFakeEmail(email) && preAuthCheckRes.isFirstFactor) { + // Fake emails cannot be used as a first factor + return { + status: "EMAIL_ALREADY_EXISTS_ERROR", + }; + } + // we are using the email from the register options + const signUpResponse = await options.recipeImplementation.signUp({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }); + if (signUpResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { + return signUpResponse; + } + if (signUpResponse.status !== "OK") { + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + signUpResponse, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); + } + const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ + authenticatedUser: signUpResponse.user, + recipeUserId: signUpResponse.recipeUserId, + isSignUp: true, + factorId: "webauthn", + session, + req: options.req, + res: options.res, + tenantId, + userContext, + }); + if (postAuthChecks.status !== "OK") { + // It should never actually come here, but we do it cause of consistency. + // If it does come here (in case there is a bug), it would make this func throw + // anyway, cause there is no SIGN_IN_NOT_ALLOWED in the errorCodeMap. + authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + postAuthChecks, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); + throw new Error("This should never happen"); + } + return { + status: "OK", + session: postAuthChecks.session, + user: postAuthChecks.user, + }; + }, + signInPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }) { + const errorCodeMap = { + SIGN_IN_NOT_ALLOWED: + "Cannot sign in due to security reasons. Please try recovering your account, use a different login method or contact support. (ERR_CODE_008)", + LINKING_TO_SESSION_USER_FAILED: { + EMAIL_VERIFICATION_REQUIRED: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", + 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_012)", + }, + }; + const recipeId = "webauthn"; + // do the verification before in order to retrieve the user email + const verifyCredentialsResponse = await options.recipeImplementation.verifyCredentials({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + const checkCredentialsOnTenant = async () => { + return verifyCredentialsResponse.status === "OK"; + }; + // doing it like this because the email is only available after verifyCredentials is called + let email; + if (verifyCredentialsResponse.status == "OK") { + const loginMethod = verifyCredentialsResponse.user.loginMethods.find((lm) => lm.recipeId === recipeId); + // there should be a webauthn login method and an email when trying to sign in using webauthn + if (!loginMethod || !loginMethod.email) { + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + verifyCredentialsResponse, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); + } + email = loginMethod === null || loginMethod === void 0 ? void 0 : loginMethod.email; + } else { + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( + { + accountInfo: { email }, + userContext, + recipeId, + session, + tenantId, + checkCredentialsOnTenant, + } + ); + const isVerified = authenticatingUser !== undefined && authenticatingUser.loginMethod.verified; + // We check this before preAuthChecks, because that function assumes that if isSignUp is false, + // then authenticatingUser is defined. While it wouldn't technically cause any problems with + // the implementation of that function, this way we can guarantee that either isSignInAllowed or + // isSignUpAllowed will be called as expected. + if (authenticatingUser === undefined) { + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } + const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ + authenticatingAccountInfo: { + recipeId, + email, + }, + factorIds: [recipeId], + isSignUp: false, + authenticatingUser: + authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + isVerified, + signInVerifiesLoginMethod: false, + skipSessionUserUpdateInCore: false, + tenantId, + userContext, + session, + shouldTryLinkingWithSessionUser, + }); + if (preAuthChecks.status === "SIGN_UP_NOT_ALLOWED") { + throw new Error("This should never happen: pre-auth checks should not fail for sign in"); + } + if (preAuthChecks.status !== "OK") { + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthChecks, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); + } + if (utils_1.isFakeEmail(email) && preAuthChecks.isFirstFactor) { + // Fake emails cannot be used as a first factor + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } + const signInResponse = await options.recipeImplementation.signIn({ + webauthnGeneratedOptionsId, + credential, + session, + shouldTryLinkingWithSessionUser, + tenantId, + userContext, + }); + if (signInResponse.status === "WRONG_CREDENTIALS_ERROR") { + return signInResponse; + } + if (signInResponse.status !== "OK") { + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + signInResponse, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); + } + const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ + authenticatedUser: signInResponse.user, + recipeUserId: signInResponse.recipeUserId, + isSignUp: false, + factorId: recipeId, + session, + req: options.req, + res: options.res, + tenantId, + userContext, + }); + if (postAuthChecks.status !== "OK") { + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + postAuthChecks, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); + } + return { + status: "OK", + session: postAuthChecks.session, + user: postAuthChecks.user, + }; + }, + emailExistsGET: async function ({ email, tenantId, userContext }) { + // even if the above returns true, we still need to check if there + // exists an webauthn user with the same email cause the function + // above does not check for that. + let users = await recipe_1.default.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + let webauthnUserExists = + users.find((u) => { + return ( + u.loginMethods.find((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) !== + undefined + ); + }) !== undefined; + return { + status: "OK", + exists: webauthnUserExists, + }; + }, + generateRecoverAccountTokenPOST: async function ({ email, tenantId, options, userContext }) { + // NOTE: Check for email being a non-string value. This check will likely + // never evaluate to `true` as there is an upper-level check for the type + // in validation but kept here to be safe. + if (typeof email !== "string") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + // this function will be reused in different parts of the flow below.. + async function generateAndSendRecoverAccountToken(primaryUserId, recipeUserId) { + // the user ID here can be primary or recipe level. + let response = await options.recipeImplementation.generateRecoverAccountToken({ + tenantId, + userId: recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString(), + email, + userContext, + }); + if (response.status === "UNKNOWN_USER_ID_ERROR") { + logger_1.logDebugMessage( + `Recover account email not sent, unknown user id: ${ + recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() + }` + ); + return { + status: "OK", + }; + } + let recoverAccountLink = utils_2.getRecoverAccountLink({ + appInfo: options.appInfo, + token: response.token, + tenantId, + request: options.req, + userContext, + }); + logger_1.logDebugMessage(`Sending recover account email to ${email}`); + await options.emailDelivery.ingredientInterfaceImpl.sendEmail({ + tenantId, + type: "RECOVER_ACCOUNT", + user: { + id: primaryUserId, + recipeUserId, + email, + }, + recoverAccountLink, + userContext, + }); + return { + status: "OK", + }; + } + /** + * check if primaryUserId is linked with this email + */ + let users = await recipe_1.default.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + // we find the recipe user ID of the webauthn account from the user's list + // for later use. + let webauthnAccount = undefined; + for (let i = 0; i < users.length; i++) { + let webauthnAccountTmp = users[i].loginMethods.find( + (l) => l.recipeId === "webauthn" && l.hasSameEmailAs(email) + ); + if (webauthnAccountTmp !== undefined) { + webauthnAccount = webauthnAccountTmp; + break; + } + } + // we find the primary user ID from the user's list for later use. + let primaryUserAssociatedWithEmail = users.find((u) => u.isPrimaryUser); + // first we check if there even exists a primary user that has the input email + // if not, then we do the regular flow for recover account + if (primaryUserAssociatedWithEmail === undefined) { + if (webauthnAccount === undefined) { + logger_1.logDebugMessage(`Recover account email not sent, unknown user email: ${email}`); + return { + status: "OK", + }; + } + return await generateAndSendRecoverAccountToken( + webauthnAccount.recipeUserId.getAsString(), + webauthnAccount.recipeUserId + ); + } + // Next we check if there is any login method in which the input email is verified. + // If that is the case, then it's proven that the user owns the email and we can + // trust linking of the webauthn account. + let emailVerified = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.hasSameEmailAs(email) && lm.verified; + }) !== undefined; + // finally, we check if the primary user has any other email / phone number + // associated with this account - and if it does, then it means that + // there is a risk of account takeover, so we do not allow the token to be generated + let hasOtherEmailOrPhone = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + }) !== undefined; + if (!emailVerified && hasOtherEmailOrPhone) { + return { + status: "RECOVER_ACCOUNT_NOT_ALLOWED", + reason: + "Recover account link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + }; + } + let shouldDoAccountLinkingResponse = await recipe_1.default + .getInstance() + .config.shouldDoAutomaticAccountLinking( + webauthnAccount !== undefined + ? webauthnAccount + : { + recipeId: "webauthn", + email, + }, + primaryUserAssociatedWithEmail, + undefined, + tenantId, + userContext + ); + // Now we need to check that if there exists any webauthn user at all + // for the input email. If not, then it implies that when the token is consumed, + // then we will create a new user - so we should only generate the token if + // the criteria for the new user is met. + if (webauthnAccount === undefined) { + // this means that there is no webauthn user that exists for the input email. + // So we check for the sign up condition and only go ahead if that condition is + // met. + // But first we must check if account linking is enabled at all - cause if it's + // not, then the new webauthn user that will be created in recover account + // code consume cannot be linked to the primary user - therefore, we should + // not generate a recover account reset token + if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { + logger_1.logDebugMessage( + `Recover account email not sent, since webauthn user didn't exist, and account linking not enabled` + ); + return { + status: "OK", + }; + } + let isSignUpAllowed = await recipe_1.default.getInstance().isSignUpAllowed({ + newUser: { + recipeId: "webauthn", + email, + }, + isVerified: true, + session: undefined, + tenantId, + userContext, + }); + if (isSignUpAllowed) { + // notice that we pass in the primary user ID here. This means that + // we will be creating a new webauthn account when the token + // is consumed and linking it to this primary user. + return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, undefined); + } else { + logger_1.logDebugMessage( + `Recover account email not sent, isSignUpAllowed returned false for email: ${email}` + ); + return { + status: "OK", + }; + } + } + // At this point, we know that some webauthn user exists with this email + // and also some primary user ID exist. We now need to find out if they are linked + // together or not. If they are linked together, then we can just generate the token + // else we check for more security conditions (since we will be linking them post token generation) + let areTheTwoAccountsLinked = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.recipeUserId.getAsString() === webauthnAccount.recipeUserId.getAsString(); + }) !== undefined; + if (areTheTwoAccountsLinked) { + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); + } + // Here we know that the two accounts are NOT linked. We now need to check for an + // extra security measure here to make sure that the input email in the primary user + // is verified, and if not, we need to make sure that there is no other email / phone number + // associated with the primary user account. If there is, then we do not proceed. + /* + This security measure helps prevent the following attack: + An attacker has email A and they create an account using TP and it doesn't matter if A is verified or not. Now they create another account using the webauthn with email A and verifies it. Both these accounts are linked. Now the attacker changes the email for webauthn recipe to B which makes the webauthn account unverified, but it's still linked. + + If the real owner of B tries to signup using webauthn, it will say that the account already exists so they may try to recover the account which should be denied because then they will end up getting access to attacker's account and verify the webauthn account. + + The problem with this situation is if the webauthn account is verified, it will allow further sign-ups with email B which will also be linked to this primary account (that the attacker had created with email A). + + It is important to realize that the attacker had created another account with A because if they hadn't done that, then they wouldn't have access to this account after the real user recovers the account which is why it is important to check there is another non-webauthn account linked to the primary such that the email is not the same as B. + + Exception to the above is that, if there is a third recipe account linked to the above two accounts and has B as verified, then we should allow recover account token generation because user has already proven that the owns the email B + */ + // But first, this only matters it the user cares about checking for email verification status.. + if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { + // here we will go ahead with the token generation cause + // even when the token is consumed, we will not be linking the accounts + // so no need to check for anything + return await generateAndSendRecoverAccountToken( + webauthnAccount.recipeUserId.getAsString(), + webauthnAccount.recipeUserId + ); + } + if (!shouldDoAccountLinkingResponse.shouldRequireVerification) { + // the checks below are related to email verification, and if the user + // does not care about that, then we should just continue with token generation + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); + } + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); + }, + recoverAccountPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + token, + tenantId, + options, + userContext, + }) { + async function markEmailAsVerified(recipeUserId, email) { + const emailVerificationInstance = recipe_2.default.getInstance(); + if (emailVerificationInstance) { + const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( + { + tenantId, + recipeUserId, + email, + userContext, + } + ); + if (tokenResponse.status === "OK") { + await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ + tenantId, + token: tokenResponse.token, + attemptAccountLinking: false, + // we anyway do account linking in this API after this function is + // called. + userContext, + }); + } + } + } + async function doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary(recipeUserId) { + let updateResponse = await options.recipeImplementation.registerCredential({ + recipeUserId, + webauthnGeneratedOptionsId, + credential, + userContext, + }); + // todo decide how to handle these + if (updateResponse.status === "INVALID_AUTHENTICATOR_ERROR") { + // This should happen only cause of a race condition where the user + // might be deleted before token creation and consumption. + return { + status: "INVALID_AUTHENTICATOR_ERROR", + reason: updateResponse.reason, + }; + } else if (updateResponse.status === "WRONG_CREDENTIALS_ERROR") { + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } else { + // status: "OK" + // If the update was successful, we try to mark the email as verified. + // We do this because we assume that the recover account token was delivered by email (and to the appropriate email address) + // so consuming it means that the user actually has access to the emails we send. + // We only do this if the recover account was successful, otherwise the following scenario is possible: + // 1. User M: signs up using the email of user V with their own credential. They can't validate the email, because it is not their own. + // 2. User A: tries signing up but sees the email already exists message + // 3. User A: recovers the account, but somehow this fails + // If we verified (and linked) the existing user with the original credential, User M would get access to the current user and any linked users. + await markEmailAsVerified(recipeUserId, emailForWhomTokenWasGenerated); + // We refresh the user information here, because the verification status may be updated, which is used during linking. + const updatedUserAfterEmailVerification = await __1.getUser( + recipeUserId.getAsString(), + userContext + ); + if (updatedUserAfterEmailVerification === undefined) { + throw new Error("Should never happen - user deleted after during recover account"); + } + if (updatedUserAfterEmailVerification.isPrimaryUser) { + // If the user is already primary, we do not need to do any linking + return { + status: "OK", + email: emailForWhomTokenWasGenerated, + user: updatedUserAfterEmailVerification, + }; + } + // If the user was not primary: + // Now we try and link the accounts. + // The function below will try and also create a primary user of the new account, this can happen if: + // 1. the user was unverified and linking requires verification + // We do not take try linking by session here, since this is supposed to be called without a session + // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking + const linkRes = await recipe_1.default.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ + tenantId, + inputUser: updatedUserAfterEmailVerification, + session: undefined, + userContext, + }); + const userAfterWeTriedLinking = + linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; + return { + status: "OK", + email: emailForWhomTokenWasGenerated, + user: userAfterWeTriedLinking, + }; + } + } + let tokenConsumptionResponse = await options.recipeImplementation.consumeRecoverAccountToken({ + token, + tenantId, + userContext, + }); + // todo decide how to handle these + if (tokenConsumptionResponse.status === "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR") { + return tokenConsumptionResponse; + } + let userIdForWhomTokenWasGenerated = tokenConsumptionResponse.userId; + let emailForWhomTokenWasGenerated = tokenConsumptionResponse.email; + let existingUser = await __1.getUser(tokenConsumptionResponse.userId, userContext); + if (existingUser === undefined) { + // This should happen only cause of a race condition where the user + // might be deleted before token creation and consumption. + // Also note that this being undefined doesn't mean that the webauthn + // user does not exist, but it means that there is no recipe or primary user + // for whom the token was generated. + return { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", + }; + } + // We start by checking if the existingUser is a primary user or not. If it is, + // then we will try and create a new webauthn user and link it to the primary user (if required) + if (existingUser.isPrimaryUser) { + // If this user contains an webauthn account for whom the token was generated, + // then we update that user's credential. + let webauthnUserIsLinkedToExistingUser = + existingUser.loginMethods.find((lm) => { + // we check based on user ID and not email because the only time + // the primary user ID is used for token generation is if the webauthn + // user did not exist - in which case the value of emailPasswordUserExists will + // resolve to false anyway, and that's what we want. + // there is an edge case where if the webauthn recipe user was created + // after the recover account token generation, and it was linked to the + // primary user id (userIdForWhomTokenWasGenerated), in this case, + // we still don't allow credntials update, cause the user should try again + // and the token should be regenerated for the right recipe user. + return ( + lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && + lm.recipeId === "webauthn" + ); + }) !== undefined; + if (webauthnUserIsLinkedToExistingUser) { + return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( + new recipeUserId_1.default(userIdForWhomTokenWasGenerated) + ); + } else { + // this means that the existingUser does not have an webauthn user associated + // with it. It could now mean that no webauthn user exists, or it could mean that + // the the webauthn user exists, but it's not linked to the current account. + // If no webauthn user doesn't exists, we will create one, and link it to the existing account. + // If webauthn user exists, then it means there is some race condition cause + // then the token should have been generated for that user instead of the primary user, + // and it shouldn't have come into this branch. So we can simply send a recover account + // invalid error and the user can try again. + // NOTE: We do not ask the dev if we should do account linking or not here + // cause we already have asked them this when generating an recover account reset token. + // In the edge case that the dev changes account linking allowance from true to false + // when it comes here, only a new recipe user id will be created and not linked + // cause createPrimaryUserIdOrLinkAccounts will disallow linking. This doesn't + // really cause any security issue. + let createUserResponse = await options.recipeImplementation.createNewRecipeUser({ + tenantId, + webauthnGeneratedOptionsId, + credential, + userContext, + }); + // todo decide how to handle these + if (createUserResponse.status === "WRONG_CREDENTIALS_ERROR") { + return createUserResponse; + } else if (createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR") { + return createUserResponse; + } else if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { + // this means that the user already existed and we can just return an invalid + // token (see the above comment) + return { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", + }; + } else { + // we mark the email as verified because recover account also requires + // access to the email to work.. This has a good side effect that + // any other login method with the same email in existingAccount will also get marked + // as verified. + await markEmailAsVerified( + createUserResponse.user.loginMethods[0].recipeUserId, + tokenConsumptionResponse.email + ); + const updatedUser = await __1.getUser(createUserResponse.user.id, userContext); + if (updatedUser === undefined) { + throw new Error("Should never happen - user deleted after during recover account"); + } + createUserResponse.user = updatedUser; + // Now we try and link the accounts. The function below will try and also + // create a primary user of the new account, and if it does that, it's OK.. + // But in most cases, it will end up linking to existing account since the + // email is shared. + // We do not take try linking by session here, since this is supposed to be called without a session + // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking + const linkRes = await recipe_1.default + .getInstance() + .tryLinkingByAccountInfoOrCreatePrimaryUser({ + tenantId, + inputUser: createUserResponse.user, + session: undefined, + userContext, + }); + const userAfterLinking = linkRes.status === "OK" ? linkRes.user : createUserResponse.user; + if (linkRes.status === "OK" && linkRes.user.id !== existingUser.id) { + // this means that the account we just linked to + // was not the one we had expected to link it to. This can happen + // due to some race condition or the other.. Either way, this + // is not an issue and we can just return OK + } + return { + status: "OK", + email: tokenConsumptionResponse.email, + user: userAfterLinking, + }; + } + } + } else { + // This means that the existing user is not a primary account, which implies that + // it must be a non linked webauthn account. In this case, we simply update the credential. + // Linking to an existing account will be done after the user goes through the email + // verification flow once they log in (if applicable). + return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( + new recipeUserId_1.default(userIdForWhomTokenWasGenerated) + ); + } + }, + }; +} +exports.default = getAPIImplementation; diff --git a/lib/build/recipe/webauthn/api/recoverAccount.d.ts b/lib/build/recipe/webauthn/api/recoverAccount.d.ts new file mode 100644 index 000000000..a5038fad1 --- /dev/null +++ b/lib/build/recipe/webauthn/api/recoverAccount.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from "../"; +import { UserContext } from "../../../types"; +export default function recoverAccount( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/recoverAccount.js b/lib/build/recipe/webauthn/api/recoverAccount.js new file mode 100644 index 000000000..9e63a577b --- /dev/null +++ b/lib/build/recipe/webauthn/api/recoverAccount.js @@ -0,0 +1,66 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../utils"); +const utils_2 = require("./utils"); +const error_1 = __importDefault(require("../error")); +async function recoverAccount(apiImplementation, tenantId, options, userContext) { + // Logic as per https://github.com/supertokens/supertokens-node/issues/22#issuecomment-710512442 + if (apiImplementation.recoverAccountPOST === undefined) { + return false; + } + const requestBody = await options.req.getJSONBody(); + let webauthnGeneratedOptionsId = await utils_2.validatewebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); + let credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); + let token = requestBody.token; + if (token === undefined) { + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "Please provide the recover account token", + }); + } + if (typeof token !== "string") { + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "The recover account token must be a string", + }); + } + let result = await apiImplementation.recoverAccountPOST({ + webauthnGeneratedOptionsId, + credential, + token, + tenantId, + options, + userContext, + }); + utils_1.send200Response( + options.res, + result.status === "OK" + ? { + status: "OK", + } + : result + ); + return true; +} +exports.default = recoverAccount; diff --git a/lib/build/recipe/webauthn/api/registerOptions.d.ts b/lib/build/recipe/webauthn/api/registerOptions.d.ts new file mode 100644 index 000000000..6f9f603b6 --- /dev/null +++ b/lib/build/recipe/webauthn/api/registerOptions.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function registerOptions( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/registerOptions.js b/lib/build/recipe/webauthn/api/registerOptions.js new file mode 100644 index 000000000..6ea9bf0ec --- /dev/null +++ b/lib/build/recipe/webauthn/api/registerOptions.js @@ -0,0 +1,62 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../utils"); +const error_1 = __importDefault(require("../error")); +async function registerOptions(apiImplementation, tenantId, options, userContext) { + var _a; + if (apiImplementation.registerOptionsPOST === undefined) { + return false; + } + const requestBody = await options.req.getJSONBody(); + let email = (_a = requestBody.email) === null || _a === void 0 ? void 0 : _a.trim(); + let recoverAccountToken = requestBody.recoverAccountToken; + if ( + (email === undefined || typeof email !== "string") && + (recoverAccountToken === undefined || typeof recoverAccountToken !== "string") + ) { + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "Please provide the email or the recover account token", + }); + } + // same as for passwordless lib/ts/recipe/passwordless/api/createCode.ts + if (email !== undefined) { + const validateError = await options.config.validateEmailAddress(email, tenantId); + if (validateError !== undefined) { + utils_1.send200Response(options.res, { + status: "INVALID_EMAIL_ERROR", + err: validateError, + }); + return true; + } + } + let result = await apiImplementation.registerOptionsPOST({ + email, + recoverAccountToken, + tenantId, + options, + userContext, + }); + utils_1.send200Response(options.res, result); + return true; +} +exports.default = registerOptions; diff --git a/lib/build/recipe/webauthn/api/signInOptions.d.ts b/lib/build/recipe/webauthn/api/signInOptions.d.ts new file mode 100644 index 000000000..1e3bc7b5f --- /dev/null +++ b/lib/build/recipe/webauthn/api/signInOptions.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function signInOptions( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/signInOptions.js b/lib/build/recipe/webauthn/api/signInOptions.js new file mode 100644 index 000000000..3c04d2808 --- /dev/null +++ b/lib/build/recipe/webauthn/api/signInOptions.js @@ -0,0 +1,30 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../utils"); +async function signInOptions(apiImplementation, tenantId, options, userContext) { + if (apiImplementation.signInOptionsPOST === undefined) { + return false; + } + let result = await apiImplementation.signInOptionsPOST({ + tenantId, + options, + userContext, + }); + utils_1.send200Response(options.res, result); + return true; +} +exports.default = signInOptions; diff --git a/lib/build/recipe/webauthn/api/signin.d.ts b/lib/build/recipe/webauthn/api/signin.d.ts new file mode 100644 index 000000000..72cd6e46b --- /dev/null +++ b/lib/build/recipe/webauthn/api/signin.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function signInAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/signin.js b/lib/build/recipe/webauthn/api/signin.js new file mode 100644 index 000000000..44cc8bb6f --- /dev/null +++ b/lib/build/recipe/webauthn/api/signin.js @@ -0,0 +1,61 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../utils"); +const utils_2 = require("./utils"); +const authUtils_1 = require("../../../authUtils"); +async function signInAPI(apiImplementation, tenantId, options, userContext) { + if (apiImplementation.signInPOST === undefined) { + return false; + } + const requestBody = await options.req.getJSONBody(); + const webauthnGeneratedOptionsId = await utils_2.validatewebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); + const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( + options.req, + requestBody + ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); + if (session !== undefined) { + tenantId = session.getTenantId(); + } + let result = await apiImplementation.signInPOST({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }); + if (result.status === "OK") { + utils_1.send200Response( + options.res, + Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) + ); + } else { + utils_1.send200Response(options.res, result); + } + return true; +} +exports.default = signInAPI; diff --git a/lib/build/recipe/webauthn/api/signup.d.ts b/lib/build/recipe/webauthn/api/signup.d.ts new file mode 100644 index 000000000..afc748051 --- /dev/null +++ b/lib/build/recipe/webauthn/api/signup.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function signUpAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/signup.js b/lib/build/recipe/webauthn/api/signup.js new file mode 100644 index 000000000..8eb70b124 --- /dev/null +++ b/lib/build/recipe/webauthn/api/signup.js @@ -0,0 +1,80 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../utils"); +const utils_2 = require("./utils"); +const error_1 = __importDefault(require("../error")); +const authUtils_1 = require("../../../authUtils"); +async function signUpAPI(apiImplementation, tenantId, options, userContext) { + if (apiImplementation.signUpPOST === undefined) { + return false; + } + const requestBody = await options.req.getJSONBody(); + const webauthnGeneratedOptionsId = await utils_2.validatewebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); + const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( + options.req, + requestBody + ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); + if (session !== undefined) { + tenantId = session.getTenantId(); + } + let result = await apiImplementation.signUpPOST({ + credential, + webauthnGeneratedOptionsId, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext: userContext, + }); + if (result.status === "OK") { + utils_1.send200Response( + options.res, + Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) + ); + } else if (result.status === "GENERAL_ERROR") { + utils_1.send200Response(options.res, result); + } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { + throw new error_1.default({ + type: error_1.default.FIELD_ERROR, + payload: [ + { + id: "email", + error: "This email already exists. Please sign in instead.", + }, + ], + message: "Error in input formFields", + }); + } else { + utils_1.send200Response(options.res, result); + } + return true; +} +exports.default = signUpAPI; diff --git a/lib/build/recipe/webauthn/api/utils.d.ts b/lib/build/recipe/webauthn/api/utils.d.ts new file mode 100644 index 000000000..8bd411782 --- /dev/null +++ b/lib/build/recipe/webauthn/api/utils.d.ts @@ -0,0 +1,5 @@ +// @ts-nocheck +export declare function validatewebauthnGeneratedOptionsIdOrThrowError( + webauthnGeneratedOptionsId: string +): Promise; +export declare function validateCredentialOrThrowError(credential: T): Promise; diff --git a/lib/build/recipe/webauthn/api/utils.js b/lib/build/recipe/webauthn/api/utils.js new file mode 100644 index 000000000..bfc2cd2e9 --- /dev/null +++ b/lib/build/recipe/webauthn/api/utils.js @@ -0,0 +1,43 @@ +"use strict"; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.validateCredentialOrThrowError = exports.validatewebauthnGeneratedOptionsIdOrThrowError = void 0; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +const error_1 = __importDefault(require("../error")); +async function validatewebauthnGeneratedOptionsIdOrThrowError(webauthnGeneratedOptionsId) { + if (webauthnGeneratedOptionsId === undefined) { + throw newBadRequestError("webauthnGeneratedOptionsId is required"); + } + return webauthnGeneratedOptionsId; +} +exports.validatewebauthnGeneratedOptionsIdOrThrowError = validatewebauthnGeneratedOptionsIdOrThrowError; +async function validateCredentialOrThrowError(credential) { + if (credential === undefined) { + throw newBadRequestError("credential is required"); + } + return credential; +} +exports.validateCredentialOrThrowError = validateCredentialOrThrowError; +function newBadRequestError(message) { + return new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message, + }); +} diff --git a/lib/build/recipe/webauthn/constants.d.ts b/lib/build/recipe/webauthn/constants.d.ts new file mode 100644 index 000000000..d58df9d60 --- /dev/null +++ b/lib/build/recipe/webauthn/constants.d.ts @@ -0,0 +1,16 @@ +// @ts-nocheck +export declare const REGISTER_OPTIONS_API = "/webauthn/options/register"; +export declare const SIGNIN_OPTIONS_API = "/webauthn/options/signin"; +export declare const SIGN_UP_API = "/webauthn/signup"; +export declare const SIGN_IN_API = "/webauthn/signin"; +export declare const GENERATE_RECOVER_ACCOUNT_TOKEN_API = "/user/webauthn/reset/token"; +export declare const RECOVER_ACCOUNT_API = "/user/webauthn/reset"; +export declare const SIGNUP_EMAIL_EXISTS_API = "/webauthn/email/exists"; +export declare const DEFAULT_REGISTER_OPTIONS_ATTESTATION = "none"; +export declare const DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = false; +export declare const DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = "required"; +export declare const DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = "preferred"; +export declare const DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS: number[]; +export declare const DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION = "preferred"; +export declare const DEFAULT_REGISTER_OPTIONS_TIMEOUT = 5000; +export declare const DEFAULT_SIGNIN_OPTIONS_TIMEOUT = 5000; diff --git a/lib/build/recipe/webauthn/constants.js b/lib/build/recipe/webauthn/constants.js new file mode 100644 index 000000000..2e86a4747 --- /dev/null +++ b/lib/build/recipe/webauthn/constants.js @@ -0,0 +1,33 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DEFAULT_SIGNIN_OPTIONS_TIMEOUT = exports.DEFAULT_REGISTER_OPTIONS_TIMEOUT = exports.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION = exports.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS = exports.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = exports.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = exports.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = exports.DEFAULT_REGISTER_OPTIONS_ATTESTATION = exports.SIGNUP_EMAIL_EXISTS_API = exports.RECOVER_ACCOUNT_API = exports.GENERATE_RECOVER_ACCOUNT_TOKEN_API = exports.SIGN_IN_API = exports.SIGN_UP_API = exports.SIGNIN_OPTIONS_API = exports.REGISTER_OPTIONS_API = void 0; +exports.REGISTER_OPTIONS_API = "/webauthn/options/register"; +exports.SIGNIN_OPTIONS_API = "/webauthn/options/signin"; +exports.SIGN_UP_API = "/webauthn/signup"; +exports.SIGN_IN_API = "/webauthn/signin"; +exports.GENERATE_RECOVER_ACCOUNT_TOKEN_API = "/user/webauthn/reset/token"; +exports.RECOVER_ACCOUNT_API = "/user/webauthn/reset"; +exports.SIGNUP_EMAIL_EXISTS_API = "/webauthn/email/exists"; +// defaults that can be overridden by the developer +exports.DEFAULT_REGISTER_OPTIONS_ATTESTATION = "none"; +exports.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = false; +exports.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = "required"; +exports.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = "preferred"; +exports.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS = [-8, -7, -257]; +exports.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION = "preferred"; +exports.DEFAULT_REGISTER_OPTIONS_TIMEOUT = 5000; +exports.DEFAULT_SIGNIN_OPTIONS_TIMEOUT = 5000; diff --git a/lib/build/recipe/webauthn/core-mock.d.ts b/lib/build/recipe/webauthn/core-mock.d.ts new file mode 100644 index 000000000..0bb5666c4 --- /dev/null +++ b/lib/build/recipe/webauthn/core-mock.d.ts @@ -0,0 +1,3 @@ +// @ts-nocheck +import { Querier } from "../../querier"; +export declare const getMockQuerier: (recipeId: string) => Querier; diff --git a/lib/build/recipe/webauthn/core-mock.js b/lib/build/recipe/webauthn/core-mock.js new file mode 100644 index 000000000..723cce9f8 --- /dev/null +++ b/lib/build/recipe/webauthn/core-mock.js @@ -0,0 +1,79 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getMockQuerier = void 0; +const querier_1 = require("../../querier"); +const getMockQuerier = (recipeId) => { + const querier = querier_1.Querier.getNewInstanceOrThrowError(recipeId); + const sendPostRequest = async (path, body, userContext) => { + console.log("body", body); + console.log("userContext", userContext); + if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { + // @ts-ignore + return { + status: "OK", + webauthnGeneratedOptionsId: "7ab03f6a-61b8-4f65-992f-b8b8469bc18f", + rp: { id: "example.com", name: "Example App" }, + user: { id: "dummy-user-id", name: "user@example.com", displayName: "User" }, + challenge: "dummy-challenge", + timeout: 60000, + excludeCredentials: [], + attestation: "none", + pubKeyCredParams: [{ alg: -7, type: "public-key" }], + authenticatorSelection: { + requireResidentKey: false, + residentKey: "preferred", + userVerification: "preferred", + }, + }; + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + // @ts-ignore + return { + status: "OK", + webauthnGeneratedOptionsId: "18302759-87c6-4d88-990d-c7cab43653cc", + challenge: "dummy-signin-challenge", + timeout: 60000, + userVerification: "preferred", + }; + // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token")) { + // // @ts-ignore + // return { + // status: "OK", + // token: "dummy-recover-token", + // }; + // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token/consume")) { + // // @ts-ignore + // return { + // status: "OK", + // userId: "dummy-user-id", + // email: "user@example.com", + // }; + // } + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { + // @ts-ignore + return { + status: "OK", + user: { + id: "dummy-user-id", + email: "user@example.com", + timeJoined: Date.now(), + }, + recipeUserId: "dummy-recipe-user-id", + }; + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + // @ts-ignore + return { + status: "OK", + user: { + id: "dummy-user-id", + email: "user@example.com", + timeJoined: Date.now(), + }, + recipeUserId: "dummy-recipe-user-id", + }; + } + throw new Error(`Unmocked endpoint: ${path}`); + }; + querier.sendPostRequest = sendPostRequest; + return querier; +}; +exports.getMockQuerier = getMockQuerier; diff --git a/lib/build/recipe/webauthn/error.d.ts b/lib/build/recipe/webauthn/error.d.ts new file mode 100644 index 000000000..d4dc2cf9b --- /dev/null +++ b/lib/build/recipe/webauthn/error.d.ts @@ -0,0 +1,20 @@ +// @ts-nocheck +import STError from "../../error"; +export default class SessionError extends STError { + static FIELD_ERROR: "FIELD_ERROR"; + constructor( + options: + | { + type: "FIELD_ERROR"; + payload: { + id: string; + error: string; + }[]; + message: string; + } + | { + type: "BAD_INPUT_ERROR"; + message: string; + } + ); +} diff --git a/lib/build/recipe/webauthn/error.js b/lib/build/recipe/webauthn/error.js new file mode 100644 index 000000000..9cce55615 --- /dev/null +++ b/lib/build/recipe/webauthn/error.js @@ -0,0 +1,30 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const error_1 = __importDefault(require("../../error")); +class SessionError extends error_1.default { + constructor(options) { + super(Object.assign({}, options)); + this.fromRecipe = "webauthn"; + } +} +exports.default = SessionError; +SessionError.FIELD_ERROR = "FIELD_ERROR"; diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts new file mode 100644 index 000000000..fc33da5b1 --- /dev/null +++ b/lib/build/recipe/webauthn/index.d.ts @@ -0,0 +1,244 @@ +// @ts-nocheck +import Recipe from "./recipe"; +import SuperTokensError from "./error"; +import { RecipeInterface, APIOptions, APIInterface, TypeWebauthnEmailDeliveryInput, CredentialPayload } from "./types"; +import RecipeUserId from "../../recipeUserId"; +import { SessionContainerInterface } from "../session/types"; +import { User } from "../../types"; +export default class Wrapper { + static init: typeof Recipe.init; + static Error: typeof SuperTokensError; + static registerOptions( + email: string | undefined, + recoverAccountToken: string | undefined, + relyingPartyId: string, + relyingPartyName: string, + origin: string, + timeout: number, + attestation: "none" | "indirect" | "direct" | "enterprise" | undefined, + tenantId: string, + userContext: Record + ): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } + >; + static signInOptions( + relyingPartyId: string, + origin: string, + timeout: number, + tenantId: string, + userContext: Record + ): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >; + static signIn( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + session?: undefined, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >; + static signIn( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + session: SessionContainerInterface, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + 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"; + } + >; + static verifyCredentials( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + userContext?: Record + ): Promise< + | { + status: "OK"; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >; + /** + * We do not make email optional here cause we want to + * allow passing in primaryUserId. If we make email optional, + * and if the user provides a primaryUserId, then it may result in two problems: + * - there is no recipeUserId = input primaryUserId, in this case, + * this function will throw an error + * - There is a recipe userId = input primaryUserId, but that recipe has no email, + * or has wrong email compared to what the user wanted to generate a reset token for. + * + * And we want to allow primaryUserId being passed in. + */ + static generateRecoverAccountToken( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + token: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; + static recoverAccount( + tenantId: string, + webauthnGeneratedOptionsId: string, + token: string, + credential: CredentialPayload, + userContext?: Record + ): Promise< + | { + status: "OK" | "WRONG_CREDENTIALS_ERROR" | "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + failureReason: string; + } + >; + static consumeRecoverAccountToken( + tenantId: string, + token: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + >; + static registerCredential(input: { + recipeUserId: RecipeUserId; + tenantId: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + userContext?: Record; + }): Promise< + | { + status: "OK" | "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + >; + static createRecoverAccountLink( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + link: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; + static sendRecoverAccountEmail( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR"; + }>; + static sendEmail( + input: TypeWebauthnEmailDeliveryInput & { + userContext?: Record; + } + ): Promise; +} +export declare let init: typeof Recipe.init; +export declare let Error: typeof SuperTokensError; +export declare let registerOptions: typeof Wrapper.registerOptions; +export declare let signInOptions: typeof Wrapper.signInOptions; +export declare let signIn: typeof Wrapper.signIn; +export declare let verifyCredentials: typeof Wrapper.verifyCredentials; +export declare let generateRecoverAccountToken: typeof Wrapper.generateRecoverAccountToken; +export declare let recoverAccount: typeof Wrapper.recoverAccount; +export declare let consumeRecoverAccountToken: typeof Wrapper.consumeRecoverAccountToken; +export declare let registerCredential: typeof Wrapper.registerCredential; +export type { RecipeInterface, APIOptions, APIInterface }; +export declare let createRecoverAccountLink: typeof Wrapper.createRecoverAccountLink; +export declare let sendRecoverAccountEmail: typeof Wrapper.sendRecoverAccountEmail; +export declare let sendEmail: typeof Wrapper.sendEmail; diff --git a/lib/build/recipe/webauthn/index.js b/lib/build/recipe/webauthn/index.js new file mode 100644 index 000000000..041a297b2 --- /dev/null +++ b/lib/build/recipe/webauthn/index.js @@ -0,0 +1,228 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.sendEmail = exports.sendRecoverAccountEmail = exports.createRecoverAccountLink = exports.registerCredential = exports.consumeRecoverAccountToken = exports.recoverAccount = exports.generateRecoverAccountToken = exports.verifyCredentials = exports.signIn = exports.signInOptions = exports.registerOptions = exports.Error = exports.init = void 0; +const recipe_1 = __importDefault(require("./recipe")); +const error_1 = __importDefault(require("./error")); +const recipeUserId_1 = __importDefault(require("../../recipeUserId")); +const constants_1 = require("../multitenancy/constants"); +const utils_1 = require("./utils"); +const __1 = require("../.."); +const utils_2 = require("../../utils"); +const constants_2 = require("./constants"); +class Wrapper { + static async registerOptions( + email, + recoverAccountToken, + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation = "none", + tenantId, + userContext + ) { + let payload = email ? { email } : recoverAccountToken ? { recoverAccountToken } : null; + if (!payload) { + return { status: "INVALID_EMAIL_ERROR", err: "Email is missing" }; + } + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions( + Object.assign( + Object.assign( + { + requireResidentKey: constants_2.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, + residentKey: constants_2.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, + userVerification: constants_2.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, + supportedAlgorithmIds: constants_2.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, + }, + payload + ), + { + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation, + tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + userContext: utils_2.getUserContext(userContext), + } + ) + ); + } + static signInOptions(relyingPartyId, origin, timeout, tenantId, userContext) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ + userVerification: constants_2.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, + relyingPartyId, + origin, + timeout, + tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + userContext: utils_2.getUserContext(userContext), + }); + } + static signIn(tenantId, webauthnGeneratedOptionsId, credential, session, userContext) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signIn({ + webauthnGeneratedOptionsId, + credential, + session, + shouldTryLinkingWithSessionUser: !!session, + tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + userContext: utils_2.getUserContext(userContext), + }); + } + static async verifyCredentials(tenantId, webauthnGeneratedOptionsId, credential, userContext) { + const resp = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ + webauthnGeneratedOptionsId, + credential, + tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + userContext: utils_2.getUserContext(userContext), + }); + // Here we intentionally skip the user and recipeUserId props, because we do not want apps to accidentally use this to sign in + return { + status: resp.status, + }; + } + /** + * We do not make email optional here cause we want to + * allow passing in primaryUserId. If we make email optional, + * and if the user provides a primaryUserId, then it may result in two problems: + * - there is no recipeUserId = input primaryUserId, in this case, + * this function will throw an error + * - There is a recipe userId = input primaryUserId, but that recipe has no email, + * or has wrong email compared to what the user wanted to generate a reset token for. + * + * And we want to allow primaryUserId being passed in. + */ + static generateRecoverAccountToken(tenantId, userId, email, userContext) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.generateRecoverAccountToken({ + userId, + email, + tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + userContext: utils_2.getUserContext(userContext), + }); + } + static async recoverAccount(tenantId, webauthnGeneratedOptionsId, token, credential, userContext) { + const consumeResp = await Wrapper.consumeRecoverAccountToken(tenantId, token, userContext); + if (consumeResp.status !== "OK") { + return consumeResp; + } + let result = await Wrapper.registerCredential({ + recipeUserId: new recipeUserId_1.default(consumeResp.userId), + webauthnGeneratedOptionsId, + credential, + tenantId, + userContext, + }); + if (result.status === "INVALID_AUTHENTICATOR_ERROR") { + return { + status: "INVALID_AUTHENTICATOR_ERROR", + failureReason: result.reason, + }; + } + return { + status: result.status, + }; + } + static consumeRecoverAccountToken(tenantId, token, userContext) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeRecoverAccountToken({ + token, + tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + userContext: utils_2.getUserContext(userContext), + }); + } + static registerCredential(input) { + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.registerCredential( + Object.assign(Object.assign({}, input), { userContext: utils_2.getUserContext(input.userContext) }) + ); + } + static async createRecoverAccountLink(tenantId, userId, email, userContext) { + const ctx = utils_2.getUserContext(userContext); + let token = await this.generateRecoverAccountToken(tenantId, userId, email, ctx); + if (token.status === "UNKNOWN_USER_ID_ERROR") { + return token; + } + const recipeInstance = recipe_1.default.getInstanceOrThrowError(); + return { + status: "OK", + link: utils_1.getRecoverAccountLink({ + appInfo: recipeInstance.getAppInfo(), + token: token.token, + tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + request: __1.getRequestFromUserContext(ctx), + userContext: ctx, + }), + }; + } + static async sendRecoverAccountEmail(tenantId, userId, email, userContext) { + const user = await __1.getUser(userId, userContext); + if (!user) { + return { status: "UNKNOWN_USER_ID_ERROR" }; + } + const loginMethod = user.loginMethods.find((m) => m.recipeId === "webauthn" && m.hasSameEmailAs(email)); + if (!loginMethod) { + return { status: "UNKNOWN_USER_ID_ERROR" }; + } + let link = await this.createRecoverAccountLink(tenantId, userId, email, userContext); + if (link.status === "UNKNOWN_USER_ID_ERROR") { + return link; + } + await exports.sendEmail({ + recoverAccountLink: link.link, + type: "RECOVER_ACCOUNT", + user: { + id: user.id, + recipeUserId: loginMethod.recipeUserId, + email: loginMethod.email, + }, + tenantId, + userContext, + }); + return { + status: "OK", + }; + } + static async sendEmail(input) { + let recipeInstance = recipe_1.default.getInstanceOrThrowError(); + return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail( + Object.assign(Object.assign({}, input), { + tenantId: input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId, + userContext: utils_2.getUserContext(input.userContext), + }) + ); + } +} +exports.default = Wrapper; +Wrapper.init = recipe_1.default.init; +Wrapper.Error = error_1.default; +exports.init = Wrapper.init; +exports.Error = Wrapper.Error; +exports.registerOptions = Wrapper.registerOptions; +exports.signInOptions = Wrapper.signInOptions; +exports.signIn = Wrapper.signIn; +exports.verifyCredentials = Wrapper.verifyCredentials; +exports.generateRecoverAccountToken = Wrapper.generateRecoverAccountToken; +exports.recoverAccount = Wrapper.recoverAccount; +exports.consumeRecoverAccountToken = Wrapper.consumeRecoverAccountToken; +exports.registerCredential = Wrapper.registerCredential; +exports.createRecoverAccountLink = Wrapper.createRecoverAccountLink; +exports.sendRecoverAccountEmail = Wrapper.sendRecoverAccountEmail; +exports.sendEmail = Wrapper.sendEmail; diff --git a/lib/build/recipe/webauthn/recipe.d.ts b/lib/build/recipe/webauthn/recipe.d.ts new file mode 100644 index 000000000..12c1bf86c --- /dev/null +++ b/lib/build/recipe/webauthn/recipe.d.ts @@ -0,0 +1,43 @@ +// @ts-nocheck +import RecipeModule from "../../recipeModule"; +import { TypeInput, TypeNormalisedInput, RecipeInterface, APIInterface } from "./types"; +import { NormalisedAppinfo, APIHandled, HTTPMethod, RecipeListFunction, UserContext } from "../../types"; +import STError from "./error"; +import NormalisedURLPath from "../../normalisedURLPath"; +import type { BaseRequest, BaseResponse } from "../../framework"; +import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; +import { TypeWebauthnEmailDeliveryInput } from "./types"; +export default class Recipe extends RecipeModule { + private static instance; + static RECIPE_ID: string; + config: TypeNormalisedInput; + recipeInterfaceImpl: RecipeInterface; + apiImpl: APIInterface; + isInServerlessEnv: boolean; + emailDelivery: EmailDeliveryIngredient; + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput | undefined, + ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + } + ); + static getInstanceOrThrowError(): Recipe; + static init(config?: TypeInput): RecipeListFunction; + static reset(): void; + getAPIsHandled: () => APIHandled[]; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ) => Promise; + handleError: (err: STError, _request: BaseRequest, response: BaseResponse) => Promise; + getAllCORSHeaders: () => string[]; + isErrorFromThisRecipe: (err: any) => err is STError; +} diff --git a/lib/build/recipe/webauthn/recipe.js b/lib/build/recipe/webauthn/recipe.js new file mode 100644 index 000000000..f3a82ef7c --- /dev/null +++ b/lib/build/recipe/webauthn/recipe.js @@ -0,0 +1,313 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const recipeModule_1 = __importDefault(require("../../recipeModule")); +const error_1 = __importDefault(require("./error")); +const utils_1 = require("./utils"); +const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); +const constants_1 = require("./constants"); +const signup_1 = __importDefault(require("./api/signup")); +const signin_1 = __importDefault(require("./api/signin")); +const registerOptions_1 = __importDefault(require("./api/registerOptions")); +const signInOptions_1 = __importDefault(require("./api/signInOptions")); +const generateRecoverAccountToken_1 = __importDefault(require("./api/generateRecoverAccountToken")); +const recoverAccount_1 = __importDefault(require("./api/recoverAccount")); +const emailExists_1 = __importDefault(require("./api/emailExists")); +const utils_2 = require("../../utils"); +const recipeImplementation_1 = __importDefault(require("./recipeImplementation")); +const implementation_1 = __importDefault(require("./api/implementation")); +const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); +const emaildelivery_1 = __importDefault(require("../../ingredients/emaildelivery")); +const postSuperTokensInitCallbacks_1 = require("../../postSuperTokensInitCallbacks"); +const recipe_1 = __importDefault(require("../multifactorauth/recipe")); +const recipe_2 = __importDefault(require("../multitenancy/recipe")); +const utils_3 = require("../thirdparty/utils"); +const multifactorauth_1 = require("../multifactorauth"); +const core_mock_1 = require("./core-mock"); +class Recipe extends recipeModule_1.default { + constructor(recipeId, appInfo, isInServerlessEnv, config, ingredients) { + super(recipeId, appInfo); + // abstract instance functions below............... + this.getAPIsHandled = () => { + return [ + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.REGISTER_OPTIONS_API), + id: constants_1.REGISTER_OPTIONS_API, + disabled: this.apiImpl.registerOptionsPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.SIGNIN_OPTIONS_API), + id: constants_1.SIGNIN_OPTIONS_API, + disabled: this.apiImpl.signInOptionsPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.SIGN_UP_API), + id: constants_1.SIGN_UP_API, + disabled: this.apiImpl.signUpPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.SIGN_IN_API), + id: constants_1.SIGN_IN_API, + disabled: this.apiImpl.signInPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default( + constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API + ), + id: constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API, + disabled: this.apiImpl.generateRecoverAccountTokenPOST === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.RECOVER_ACCOUNT_API), + id: constants_1.RECOVER_ACCOUNT_API, + disabled: this.apiImpl.recoverAccountPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.SIGNUP_EMAIL_EXISTS_API), + id: constants_1.SIGNUP_EMAIL_EXISTS_API, + disabled: this.apiImpl.emailExistsGET === undefined, + }, + ]; + }; + this.handleAPIRequest = async (id, tenantId, req, res, _path, _method, userContext) => { + let options = { + config: this.config, + recipeId: this.getRecipeId(), + isInServerlessEnv: this.isInServerlessEnv, + recipeImplementation: this.recipeInterfaceImpl, + req, + res, + emailDelivery: this.emailDelivery, + appInfo: this.getAppInfo(), + }; + if (id === constants_1.REGISTER_OPTIONS_API) { + return await registerOptions_1.default(this.apiImpl, tenantId, options, userContext); + } else if (id === constants_1.SIGNIN_OPTIONS_API) { + return await signInOptions_1.default(this.apiImpl, tenantId, options, userContext); + } else if (id === constants_1.SIGN_UP_API) { + return await signup_1.default(this.apiImpl, tenantId, options, userContext); + } else if (id === constants_1.SIGN_IN_API) { + return await signin_1.default(this.apiImpl, tenantId, options, userContext); + } else if (id === constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API) { + return await generateRecoverAccountToken_1.default(this.apiImpl, tenantId, options, userContext); + } else if (id === constants_1.RECOVER_ACCOUNT_API) { + return await recoverAccount_1.default(this.apiImpl, tenantId, options, userContext); + } else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API) { + return await emailExists_1.default(this.apiImpl, tenantId, options, userContext); + } else return false; + }; + this.handleError = async (err, _request, response) => { + if (err.fromRecipe === Recipe.RECIPE_ID) { + if (err.type === error_1.default.FIELD_ERROR) { + return utils_2.send200Response(response, { + status: "FIELD_ERROR", + formFields: err.payload, + }); + } else { + throw err; + } + } else { + throw err; + } + }; + this.getAllCORSHeaders = () => { + return []; + }; + this.isErrorFromThisRecipe = (err) => { + return error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; + }; + this.isInServerlessEnv = isInServerlessEnv; + this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); + { + const getWebauthnConfig = () => this.config; + // const querier = Querier.getNewInstanceOrThrowError(recipeId); + const querier = core_mock_1.getMockQuerier(recipeId); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier, getWebauthnConfig) + ); + this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); + } + { + let builder = new supertokens_js_override_1.default(implementation_1.default()); + this.apiImpl = builder.override(this.config.override.apis).build(); + } + /** + * emailDelivery will always needs to be declared after isInServerlessEnv + * and recipeInterfaceImpl values are set + */ + this.emailDelivery = + ingredients.emailDelivery === undefined + ? new emaildelivery_1.default(this.config.getEmailDeliveryConfig(this.isInServerlessEnv)) + : ingredients.emailDelivery; + // todo check correctness + postSuperTokensInitCallbacks_1.PostSuperTokensInitCallbacks.addPostInitCallback(() => { + const mfaInstance = recipe_1.default.getInstance(); + if (mfaInstance !== undefined) { + mfaInstance.addFuncToGetAllAvailableSecondaryFactorIdsFromOtherRecipes(() => { + return ["webauthn"]; + }); + mfaInstance.addFuncToGetFactorsSetupForUserFromOtherRecipes(async (user) => { + for (const loginMethod of user.loginMethods) { + // We don't check for tenantId here because if we find the user + // with emailpassword loginMethod from different tenant, then + // we assume the factor is setup for this user. And as part of factor + // completion, we associate that loginMethod with the session's tenantId + if (loginMethod.recipeId === Recipe.RECIPE_ID) { + return ["webauthn"]; + } + } + return []; + }); + mfaInstance.addFuncToGetEmailsForFactorFromOtherRecipes((user, sessionRecipeUserId) => { + // This function is called in the MFA info endpoint API. + // Based on https://github.com/supertokens/supertokens-node/pull/741#discussion_r1432749346 + // preparing some reusable variables for the logic below... + let sessionLoginMethod = user.loginMethods.find((lM) => { + return lM.recipeUserId.getAsString() === sessionRecipeUserId.getAsString(); + }); + if (sessionLoginMethod === undefined) { + // this can happen maybe cause this login method + // was unlinked from the user or deleted entirely... + return { + status: "UNKNOWN_SESSION_RECIPE_USER_ID", + }; + } + // We order the login methods based on timeJoined (oldest first) + const orderedLoginMethodsByTimeJoinedOldestFirst = user.loginMethods.sort((a, b) => { + return a.timeJoined - b.timeJoined; + }); + // Then we take the ones that belong to this recipe + const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter( + (lm) => lm.recipeId === Recipe.RECIPE_ID + ); + let result; + if (recipeLoginMethodsOrderedByTimeJoinedOldestFirst.length !== 0) { + // If there are login methods belonging to this recipe, the factor is set up + // In this case we only list email addresses that have a password associated with them + result = [ + // First we take the verified real emails associated with emailpassword login methods ordered by timeJoined (oldest first) + ...recipeLoginMethodsOrderedByTimeJoinedOldestFirst + .filter((lm) => !utils_3.isFakeEmail(lm.email) && lm.verified === true) + .map((lm) => lm.email), + // Then we take the non-verified real emails associated with emailpassword login methods ordered by timeJoined (oldest first) + ...recipeLoginMethodsOrderedByTimeJoinedOldestFirst + .filter((lm) => !utils_3.isFakeEmail(lm.email) && lm.verified === false) + .map((lm) => lm.email), + // Lastly, fake emails associated with emailpassword login methods ordered by timeJoined (oldest first) + // We also add these into the list because they already have a password added to them so they can be a valid choice when signing in + // We do not want to remove the previously added "MFA password", because a new email password user was linked + // E.g.: + // 1. A discord user adds a password for MFA (which will use the fake email associated with the discord user) + // 2. Later they also sign up and (manually) link a full emailpassword user that they intend to use as a first factor + // 3. The next time they sign in using Discord, they could be asked for a secondary password. + // In this case, they'd be checked against the first user that they originally created for MFA, not the one later linked to the account + ...recipeLoginMethodsOrderedByTimeJoinedOldestFirst + .filter((lm) => utils_3.isFakeEmail(lm.email)) + .map((lm) => lm.email), + ]; + // We handle moving the session email to the top of the list later + } else { + // This factor hasn't been set up, we list all emails belonging to the user + if ( + orderedLoginMethodsByTimeJoinedOldestFirst.some( + (lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email) + ) + ) { + // If there is at least one real email address linked to the user, we only suggest real addresses + result = orderedLoginMethodsByTimeJoinedOldestFirst + .filter((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email)) + .map((lm) => lm.email); + } else { + // Else we use the fake ones + result = orderedLoginMethodsByTimeJoinedOldestFirst + .filter((lm) => lm.email !== undefined && utils_3.isFakeEmail(lm.email)) + .map((lm) => lm.email); + } + // We handle moving the session email to the top of the list later + // Since in this case emails are not guaranteed to be unique, we de-duplicate the results, keeping the oldest one in the list. + // The Set constructor keeps the original insertion order (OrderedByTimeJoinedOldestFirst), but de-duplicates the items, + // keeping the first one added (so keeping the older one if there are two entries with the same email) + // e.g.: [4,2,3,2,1] -> [4,2,3,1] + result = Array.from(new Set(result)); + } + // If the loginmethod associated with the session has an email address, we move it to the top of the list (if it's already in the list) + if (sessionLoginMethod.email !== undefined && result.includes(sessionLoginMethod.email)) { + result = [ + sessionLoginMethod.email, + ...result.filter((email) => email !== sessionLoginMethod.email), + ]; + } + // If the list is empty we generate an email address to make the flow where the user is never asked for + // an email address easier to implement. In many cases when the user adds an email-password factor, they + // actually only want to add a password and do not care about the associated email address. + // Custom implementations can choose to ignore this, and ask the user for the email anyway. + if (result.length === 0) { + result.push(`${sessionRecipeUserId.getAsString()}@stfakeemail.supertokens.com`); + } + return { + status: "OK", + factorIdToEmailsMap: { + webauthn: result, + }, + }; + }); + } + const mtRecipe = recipe_2.default.getInstance(); + if (mtRecipe !== undefined) { + mtRecipe.allAvailableFirstFactors.push(multifactorauth_1.FactorIds.WEBAUTHN); + } + }); + } + static getInstanceOrThrowError() { + if (Recipe.instance !== undefined) { + return Recipe.instance; + } + throw new Error("Initialisation not done. Did you forget to call the Webauthn.init function?"); + } + static init(config) { + return (appInfo, isInServerlessEnv) => { + if (Recipe.instance === undefined) { + Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config, { + emailDelivery: undefined, + }); + return Recipe.instance; + } else { + throw new Error("Webauthn recipe has already been initialised. Please check your code for bugs."); + } + }; + } + static reset() { + if (!utils_2.isTestEnv()) { + throw new Error("calling testing function in non testing env"); + } + Recipe.instance = undefined; + } +} +exports.default = Recipe; +Recipe.instance = undefined; +Recipe.RECIPE_ID = "webauthn"; diff --git a/lib/build/recipe/webauthn/recipeImplementation.d.ts b/lib/build/recipe/webauthn/recipeImplementation.d.ts new file mode 100644 index 000000000..b6421a667 --- /dev/null +++ b/lib/build/recipe/webauthn/recipeImplementation.d.ts @@ -0,0 +1,7 @@ +// @ts-nocheck +import { RecipeInterface, TypeNormalisedInput } from "./types"; +import { Querier } from "../../querier"; +export default function getRecipeInterface( + querier: Querier, + getWebauthnConfig: () => TypeNormalisedInput +): RecipeInterface; diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js new file mode 100644 index 000000000..f664098c1 --- /dev/null +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -0,0 +1,404 @@ +"use strict"; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const recipe_1 = __importDefault(require("../accountlinking/recipe")); +const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); +const __1 = require("../.."); +const recipeUserId_1 = __importDefault(require("../../recipeUserId")); +const constants_1 = require("../multitenancy/constants"); +const user_1 = require("../../user"); +const authUtils_1 = require("../../authUtils"); +const jose = __importStar(require("jose")); +function getRecipeInterface(querier, getWebauthnConfig) { + return { + registerOptions: async function (_a) { + var { + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation = "none", + tenantId, + userContext, + supportedAlgorithmIds, + } = _a, + rest = __rest(_a, [ + "relyingPartyId", + "relyingPartyName", + "origin", + "timeout", + "attestation", + "tenantId", + "userContext", + "supportedAlgorithmIds", + ]); + const emailInput = "email" in rest ? rest.email : undefined; + const recoverAccountTokenInput = "recoverAccountToken" in rest ? rest.recoverAccountToken : undefined; + let email; + if (emailInput !== undefined) { + email = emailInput; + } else if (recoverAccountTokenInput !== undefined) { + // todo check if should decode using Core or using sdk; atm decided on usinng the sdk so to not make another roundtrip to the server + // the actual verification of the token will be done during consumeRecoverAccountToken + let decoded; + try { + decoded = await jose.decodeJwt(recoverAccountTokenInput); + } catch (e) { + console.error(e); + return { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", + }; + } + email = decoded === null || decoded === void 0 ? void 0 : decoded.email; + } + if (!email) { + return { + status: "INVALID_EMAIL_ERROR", + err: "The email is missing", + }; + } + const err = await getWebauthnConfig().validateEmailAddress(email, tenantId); + if (err) { + return { + status: "INVALID_EMAIL_ERROR", + err, + }; + } + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/register` + ), + { + email, + relyingPartyName, + relyingPartyId, + origin, + timeout, + attestation, + supportedAlgorithmIds, + }, + userContext + ); + }, + signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext }) { + // the input user ID can be a recipe or a primary user ID. + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/signin` + ), + { + relyingPartyId, + origin, + timeout, + }, + userContext + ); + }, + signUp: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }) { + const response = await this.createNewRecipeUser({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (response.status !== "OK") { + return response; + } + let updatedUser = response.user; + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( + { + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + } + ); + if (linkResult.status != "OK") { + return linkResult; + } + updatedUser = linkResult.user; + return { + status: "OK", + user: updatedUser, + recipeUserId: response.recipeUserId, + }; + }, + signIn: async function ({ + credential, + webauthnGeneratedOptionsId, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }) { + const response = await this.verifyCredentials({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (response.status !== "OK") { + return response; + } + const loginMethod = response.user.loginMethods.find( + (lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString() + ); + if (!loginMethod.verified) { + await recipe_1.default.getInstance().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 __1.getUser(response.recipeUserId.getAsString(), userContext); + } + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( + { + 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 ({ credential, webauthnGeneratedOptionsId, tenantId, userContext }) { + const response = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/signin` + ), + { + credential, + webauthnGeneratedOptionsId, + }, + 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", + }; + }, + createNewRecipeUser: async function (input) { + const resp = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId + }/recipe/webauthn/signup` + ), + { + webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId, + credential: input.credential, + }, + 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; + }, + generateRecoverAccountToken: async function ({ userId, email, tenantId, userContext }) { + // the input user ID can be a recipe or a primary user ID. + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/user/recover/token` + ), + { + userId, + email, + }, + userContext + ); + }, + consumeRecoverAccountToken: async function ({ token, tenantId, userContext }) { + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/user/recover/token/consume` + ), + { + token, + }, + userContext + ); + }, + registerCredential: async function ({ webauthnGeneratedOptionsId, credential, userContext, recipeUserId }) { + return await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/register`), + { + webauthnGeneratedOptionsId, + credential, + }, + userContext + ); + }, + decodeCredential: async function ({ credential, userContext }) { + const response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/webauthn/credential/decode`), + { + credential, + }, + userContext + ); + if (response.status === "OK") { + return response; + } + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + }, + getUserFromRecoverAccountToken: async function ({ token, tenantId, userContext }) { + return await querier.sendGetRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/user/recover/token/${token}` + ), + {}, + userContext + ); + }, + removeCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { + return await querier.sendDeleteRequest( + new normalisedURLPath_1.default( + `/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}` + ), + {}, + {}, + userContext + ); + }, + getCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { + return await querier.sendGetRequest( + new normalisedURLPath_1.default( + `/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}` + ), + {}, + userContext + ); + }, + listCredentials: async function ({ recipeUserId, userContext }) { + return await querier.sendGetRequest( + new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/list`), + {}, + userContext + ); + }, + removeGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { + return await querier.sendDeleteRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + ), + {}, + {}, + userContext + ); + }, + getGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { + return await querier.sendGetRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + ), + {}, + userContext + ); + }, + }; +} +exports.default = getRecipeInterface; diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts new file mode 100644 index 000000000..ff545f52b --- /dev/null +++ b/lib/build/recipe/webauthn/types.d.ts @@ -0,0 +1,673 @@ +// @ts-nocheck +import type { BaseRequest, BaseResponse } from "../../framework"; +import OverrideableBuilder from "supertokens-js-override"; +import { SessionContainerInterface } from "../session/types"; +import { + TypeInput as EmailDeliveryTypeInput, + TypeInputWithService as EmailDeliveryTypeInputWithService, +} from "../../ingredients/emaildelivery/types"; +import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; +import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; +import RecipeUserId from "../../recipeUserId"; +export declare type TypeNormalisedInput = { + relyingPartyId: TypeNormalisedInputRelyingPartyId; + relyingPartyName: TypeNormalisedInputRelyingPartyName; + getOrigin: TypeNormalisedInputGetOrigin; + getEmailDeliveryConfig: ( + isInServerlessEnv: boolean + ) => EmailDeliveryTypeInputWithService; + validateEmailAddress: TypeNormalisedInputValidateEmailAddress; + override: { + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; +export declare type TypeNormalisedInputRelyingPartyId = (input: { + tenantId: string; + request: BaseRequest | undefined; + userContext: UserContext; +}) => Promise; +export declare type TypeNormalisedInputRelyingPartyName = (input: { + tenantId: string; + userContext: UserContext; +}) => Promise; +export declare type TypeNormalisedInputGetOrigin = (input: { + tenantId: string; + request: BaseRequest; + userContext: UserContext; +}) => Promise; +export declare type TypeNormalisedInputValidateEmailAddress = ( + email: string, + tenantId: string +) => Promise | string | undefined; +export declare type TypeInput = { + emailDelivery?: EmailDeliveryTypeInput; + relyingPartyId?: TypeInputRelyingPartyId; + relyingPartyName?: TypeInputRelyingPartyName; + validateEmailAddress?: TypeInputValidateEmailAddress; + getOrigin?: TypeInputGetOrigin; + override?: { + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; +export declare type TypeInputRelyingPartyId = + | string + | ((input: { tenantId: string; request: BaseRequest | undefined; userContext: UserContext }) => Promise); +export declare type TypeInputRelyingPartyName = + | string + | ((input: { tenantId: string; userContext: UserContext }) => Promise); +export declare type TypeInputGetOrigin = (input: { + tenantId: string; + request: BaseRequest; + userContext: UserContext; +}) => Promise; +export declare type TypeInputValidateEmailAddress = ( + email: string, + tenantId: string +) => Promise | string | undefined; +declare type Base64URLString = string; +export declare type RecipeInterface = { + registerOptions( + input: { + relyingPartyId: string; + relyingPartyName: string; + origin: string; + requireResidentKey: boolean | undefined; + residentKey: "required" | "preferred" | "discouraged" | undefined; + userVerification: "required" | "preferred" | "discouraged" | undefined; + attestation: "none" | "indirect" | "direct" | "enterprise" | undefined; + supportedAlgorithmIds: number[] | undefined; + timeout: number | undefined; + tenantId: string; + userContext: UserContext; + } & ( + | { + recoverAccountToken: string; + } + | { + email: string; + } + ) + ): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } + >; + signInOptions(input: { + email?: string; + relyingPartyId: string; + origin: string; + userVerification: "required" | "preferred" | "discouraged" | undefined; + timeout: number | undefined; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >; + signUp(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + | { + 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"; + } + >; + signIn(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + 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"; + } + >; + verifyCredentials(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >; + createNewRecipeUser(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + >; + /** + * We pass in the email as well to this function cause the input userId + * may not be associated with an webauthn account. In this case, we + * need to know which email to use to create an webauthn account later on. + */ + generateRecoverAccountToken(input: { + userId: string; + email: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + token: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; + consumeRecoverAccountToken(input: { + token: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + >; + registerCredential(input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + userContext: UserContext; + recipeUserId: RecipeUserId; + }): Promise< + | { + status: "OK"; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + >; + decodeCredential(input: { + credential: CredentialPayload; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: { + type: string; + challenge: string; + origin: string; + crossOrigin?: boolean; + tokenBinding?: { + id?: string; + status: "present" | "supported" | "not-supported"; + }; + }; + attestationObject: { + fmt: "packed" | "tpm" | "android-key" | "android-safetynet" | "fido-u2f" | "none"; + authData: { + rpIdHash: string; + flags: { + up: boolean; + uv: boolean; + be: boolean; + bs: boolean; + at: boolean; + ed: boolean; + flagsInt: number; + }; + counter: number; + aaguid?: string; + credentialID?: string; + credentialPublicKey?: string; + extensionsData?: unknown; + }; + attStmt: { + sig?: Base64URLString; + x5c?: Base64URLString[]; + response?: Base64URLString; + alg?: number; + ver?: string; + certInfo?: Base64URLString; + pubArea?: Base64URLString; + size: number; + }; + }; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: string; + }; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >; + getUserFromRecoverAccountToken(input: { + token: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + >; + removeCredential(input: { + webauthnCredentialId: string; + recipeUserId: RecipeUserId; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + } + | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + } + >; + getCredential(input: { + webauthnCredentialId: string; + recipeUserId: RecipeUserId; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + id: string; + relyingPartyId: string; + recipeUserId: RecipeUserId; + createdAt: number; + } + | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + } + >; + listCredentials(input: { + recipeUserId: RecipeUserId; + userContext: UserContext; + }): Promise<{ + status: "OK"; + credentials: { + id: string; + relyingPartyId: string; + createdAt: number; + }[]; + }>; + removeGeneratedOptions(input: { + webauthnGeneratedOptionsId: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + >; + getGeneratedOptions(input: { + webauthnGeneratedOptionsId: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + id: string; + relyingPartyId: string; + origin: string; + email: string; + timeout: string; + challenge: string; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + >; +}; +export declare type APIOptions = { + recipeImplementation: RecipeInterface; + appInfo: NormalisedAppinfo; + config: TypeNormalisedInput; + recipeId: string; + isInServerlessEnv: boolean; + req: BaseRequest; + res: BaseResponse; + emailDelivery: EmailDeliveryIngredient; +}; +export declare type APIInterface = { + registerOptionsPOST: + | undefined + | (( + input: { + tenantId: string; + options: APIOptions; + userContext: UserContext; + } & ( + | { + email: string; + } + | { + recoverAccountToken: string; + } + ) + ) => Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: "required" | "preferred" | "discouraged"; + userVerification: "required" | "preferred" | "discouraged"; + }; + } + | GeneralErrorResponse + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } + >); + signInOptionsPOST: + | undefined + | ((input: { + email?: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: "required" | "preferred" | "discouraged"; + } + | GeneralErrorResponse + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >); + signUpPOST: + | undefined + | ((input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + } + | GeneralErrorResponse + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + >); + signInPOST: + | undefined + | ((input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + } + | GeneralErrorResponse + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >); + generateRecoverAccountTokenPOST: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + | { + status: "RECOVER_ACCOUNT_NOT_ALLOWED"; + reason: string; + } + >); + recoverAccountPOST: + | undefined + | ((input: { + token: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + email: string; + } + | GeneralErrorResponse + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + >); + emailExistsGET: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + exists: boolean; + } + | GeneralErrorResponse + >); +}; +export declare type TypeWebauthnRecoverAccountEmailDeliveryInput = { + type: "RECOVER_ACCOUNT"; + user: { + id: string; + recipeUserId: RecipeUserId | undefined; + email: string; + }; + recoverAccountLink: string; + tenantId: string; +}; +export declare type TypeWebauthnEmailDeliveryInput = TypeWebauthnRecoverAccountEmailDeliveryInput; +export declare type CredentialPayload = { + id: string; + rawId: string; + response: { + clientDataJSON: string; + attestationObject: string; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; +}; +export {}; diff --git a/lib/build/recipe/webauthn/types.js b/lib/build/recipe/webauthn/types.js new file mode 100644 index 000000000..a098ca1d7 --- /dev/null +++ b/lib/build/recipe/webauthn/types.js @@ -0,0 +1,16 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/build/recipe/webauthn/utils.d.ts b/lib/build/recipe/webauthn/utils.d.ts new file mode 100644 index 000000000..e4492d9f0 --- /dev/null +++ b/lib/build/recipe/webauthn/utils.d.ts @@ -0,0 +1,20 @@ +// @ts-nocheck +import Recipe from "./recipe"; +import { TypeInput, TypeNormalisedInput } from "./types"; +import { NormalisedAppinfo, UserContext } from "../../types"; +import { BaseRequest } from "../../framework"; +export declare function validateAndNormaliseUserInput( + recipeInstance: Recipe, + appInfo: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; +export declare function defaultEmailValidator( + value: any +): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; +export declare function getRecoverAccountLink(input: { + appInfo: NormalisedAppinfo; + token: string; + tenantId: string; + request: BaseRequest | undefined; + userContext: UserContext; +}): string; diff --git a/lib/build/recipe/webauthn/utils.js b/lib/build/recipe/webauthn/utils.js new file mode 100644 index 000000000..191551342 --- /dev/null +++ b/lib/build/recipe/webauthn/utils.js @@ -0,0 +1,163 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getRecoverAccountLink = exports.defaultEmailValidator = exports.validateAndNormaliseUserInput = void 0; +function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { + let relyingPartyId = validateAndNormaliseRelyingPartyIdConfig( + recipeInstance, + appInfo, + config === null || config === void 0 ? void 0 : config.relyingPartyId + ); + let relyingPartyName = validateAndNormaliseRelyingPartyNameConfig( + recipeInstance, + appInfo, + config === null || config === void 0 ? void 0 : config.relyingPartyName + ); + let getOrigin = validateAndNormaliseGetOriginConfig( + recipeInstance, + appInfo, + config === null || config === void 0 ? void 0 : config.getOrigin + ); + let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig( + recipeInstance, + appInfo, + config === null || config === void 0 ? void 0 : config.validateEmailAddress + ); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); + function getEmailDeliveryConfig(isInServerlessEnv) { + var _a; + let emailService = + (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 + ? void 0 + : _a.service; + console.log("emailService", emailService); + console.log("isInServerlessEnv", isInServerlessEnv); + /** + * If the user has not passed even that config, we use the default + * createAndSendCustomEmail implementation which calls our supertokens API + */ + // if (emailService === undefined) { + // emailService = new BackwardCompatibilityService(appInfo, isInServerlessEnv); + // } + return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { + /** + * if we do + * let emailDelivery = { + * service: emailService, + * ...config.emailDelivery, + * }; + * + * and if the user has passed service as undefined, + * it it again get set to undefined, so we + * set service at the end + */ + // todo implemenet this + service: null, + }); + } + return { + override, + getOrigin, + relyingPartyId, + relyingPartyName, + validateEmailAddress, + getEmailDeliveryConfig, + }; +} +exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; +function validateAndNormaliseRelyingPartyIdConfig(_, __, relyingPartyIdConfig) { + return (props) => { + if (typeof relyingPartyIdConfig === "string") { + return Promise.resolve(relyingPartyIdConfig); + } else if (typeof relyingPartyIdConfig === "function") { + return relyingPartyIdConfig(props); + } else { + return Promise.resolve( + __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous() + ); + } + }; +} +function validateAndNormaliseRelyingPartyNameConfig(_, __, relyingPartyNameConfig) { + return (props) => { + if (typeof relyingPartyNameConfig === "string") { + return Promise.resolve(relyingPartyNameConfig); + } else if (typeof relyingPartyNameConfig === "function") { + return relyingPartyNameConfig(props); + } else { + return Promise.resolve(__.appName); + } + }; +} +function validateAndNormaliseGetOriginConfig(_, __, getOriginConfig) { + return (props) => { + if (typeof getOriginConfig === "function") { + return getOriginConfig(props); + } else { + return Promise.resolve( + __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous() + ); + } + }; +} +function validateAndNormaliseValidateEmailAddressConfig(_, __, validateEmailAddressConfig) { + return (email, tenantId) => { + if (typeof validateEmailAddressConfig === "function") { + return validateEmailAddressConfig(email, tenantId); + } else { + return defaultEmailValidator(email); + } + }; +} +async function defaultEmailValidator(value) { + // We check if the email syntax is correct + // As per https://github.com/supertokens/supertokens-auth-react/issues/5#issuecomment-709512438 + // Regex from https://stackoverflow.com/a/46181/3867175 + if (typeof value !== "string") { + return "Development bug: Please make sure the email field yields a string"; + } + if ( + value.match( + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ) === null + ) { + return "Email is invalid"; + } + return undefined; +} +exports.defaultEmailValidator = defaultEmailValidator; +function getRecoverAccountLink(input) { + return ( + input.appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + + input.appInfo.websiteBasePath.getAsStringDangerous() + + "/recover-account?token=" + + input.token + + "&tenantId=" + + input.tenantId + ); +} +exports.getRecoverAccountLink = getRecoverAccountLink; diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index c45e6de40..c1abe0c67 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -87,6 +87,9 @@ export declare type User = { id: string; userId: string; }[]; + webauthn: { + credentialIds: string[]; + }; loginMethods: (RecipeLevelUser & { verified: boolean; hasSameEmailAs: (email: string | undefined) => boolean; diff --git a/lib/build/user.d.ts b/lib/build/user.d.ts index ccdddb396..9bc4835a5 100644 --- a/lib/build/user.d.ts +++ b/lib/build/user.d.ts @@ -9,6 +9,7 @@ export declare class LoginMethod implements RecipeLevelUser { readonly email?: string; readonly phoneNumber?: string; readonly thirdParty?: RecipeLevelUser["thirdParty"]; + readonly webauthn?: RecipeLevelUser["webauthn"]; readonly verified: boolean; readonly timeJoined: number; constructor(loginMethod: UserWithoutHelperFunctions["loginMethods"][number]); @@ -27,6 +28,9 @@ export declare class User implements UserType { id: string; userId: string; }[]; + readonly webauthn: { + credentialIds: string[]; + }; readonly loginMethods: LoginMethod[]; readonly timeJoined: number; constructor(user: UserWithoutHelperFunctions); @@ -43,8 +47,11 @@ export declare type UserWithoutHelperFunctions = { id: string; userId: string; }[]; + webauthn: { + credentialIds: string[]; + }; loginMethods: { - recipeId: "emailpassword" | "thirdparty" | "passwordless"; + recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; recipeUserId: string; tenantIds: string[]; email?: string; @@ -53,6 +60,9 @@ export declare type UserWithoutHelperFunctions = { id: string; userId: string; }; + webauthn?: { + credentialIds: string[]; + }; verified: boolean; timeJoined: number; }[]; diff --git a/lib/build/user.js b/lib/build/user.js index 5179ebc6f..fe045dde2 100644 --- a/lib/build/user.js +++ b/lib/build/user.js @@ -16,6 +16,7 @@ class LoginMethod { this.email = loginMethod.email; this.phoneNumber = loginMethod.phoneNumber; this.thirdParty = loginMethod.thirdParty; + this.webauthn = loginMethod.webauthn; this.timeJoined = loginMethod.timeJoined; this.verified = loginMethod.verified; } @@ -61,6 +62,7 @@ class LoginMethod { email: this.email, phoneNumber: this.phoneNumber, thirdParty: this.thirdParty, + webauthn: this.webauthn, timeJoined: this.timeJoined, verified: this.verified, }; @@ -75,6 +77,7 @@ class User { this.emails = user.emails; this.phoneNumbers = user.phoneNumbers; this.thirdParty = user.thirdParty; + this.webauthn = user.webauthn; this.timeJoined = user.timeJoined; this.loginMethods = user.loginMethods.map((m) => new LoginMethod(m)); } @@ -86,6 +89,7 @@ class User { emails: this.emails, phoneNumbers: this.phoneNumbers, thirdParty: this.thirdParty, + webauthn: this.webauthn, loginMethods: this.loginMethods.map((m) => m.toJson()), timeJoined: this.timeJoined, }; diff --git a/lib/ts/recipe/webauthn/core-mock.ts b/lib/ts/recipe/webauthn/core-mock.ts index 7ee65cb53..488b2ba5d 100644 --- a/lib/ts/recipe/webauthn/core-mock.ts +++ b/lib/ts/recipe/webauthn/core-mock.ts @@ -10,6 +10,9 @@ export const getMockQuerier = (recipeId: string) => { body: any, userContext: UserContext ): Promise => { + console.log("body", body); + console.log("userContext", userContext); + if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { // @ts-ignore return { diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 6275e7464..f91c489d1 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -98,44 +98,44 @@ export type TypeInputValidateEmailAddress = ( ) => Promise | string | undefined; // centralize error types in order to prevent missing cascading errors -type RegisterOptionsErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +// type RegisterOptionsErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; -type SignInOptionsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type SignInOptionsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type SignUpErrorResponse = - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; +// type SignUpErrorResponse = +// | { status: "EMAIL_ALREADY_EXISTS_ERROR" } +// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; -type SignInErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type SignInErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type VerifyCredentialsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type VerifyCredentialsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; +// type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; -type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +// type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; -type RegisterCredentialErrorResponse = - | { status: "WRONG_CREDENTIALS_ERROR" } - // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; +// type RegisterCredentialErrorResponse = +// | { status: "WRONG_CREDENTIALS_ERROR" } +// // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct +// | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; -type CreateNewRecipeUserErrorResponse = - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" }; +// type CreateNewRecipeUserErrorResponse = +// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } +// | { status: "EMAIL_ALREADY_EXISTS_ERROR" }; -type DecodeCredentialErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type DecodeCredentialErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; -type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +// type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; -type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; +// type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; -type GetCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; +// type GetCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; -type RemoveGeneratedOptionsErrorResponse = { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; +// type RemoveGeneratedOptionsErrorResponse = { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; -type GetGeneratedOptionsErrorResponse = { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; +// type GetGeneratedOptionsErrorResponse = { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; type Base64URLString = string; @@ -510,37 +510,37 @@ export type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; -type RegisterOptionsPOSTErrorResponse = - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "INVALID_EMAIL_ERROR"; err: string }; - -type SignInOptionsPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; - -type SignUpPOSTErrorResponse = - | { - status: "SIGN_UP_NOT_ALLOWED"; - reason: string; - } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; - -type SignInPOSTErrorResponse = - | { status: "WRONG_CREDENTIALS_ERROR" } - | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - }; - -type GenerateRecoverAccountTokenPOSTErrorResponse = { - status: "RECOVER_ACCOUNT_NOT_ALLOWED"; - reason: string; -}; - -type RecoverAccountPOSTErrorResponse = - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; +// type RegisterOptionsPOSTErrorResponse = +// | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } +// | { status: "INVALID_EMAIL_ERROR"; err: string }; + +// type SignInOptionsPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; + +// type SignUpPOSTErrorResponse = +// | { +// status: "SIGN_UP_NOT_ALLOWED"; +// reason: string; +// } +// | { status: "EMAIL_ALREADY_EXISTS_ERROR" } +// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; + +// type SignInPOSTErrorResponse = +// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { +// status: "SIGN_IN_NOT_ALLOWED"; +// reason: string; +// }; + +// type GenerateRecoverAccountTokenPOSTErrorResponse = { +// status: "RECOVER_ACCOUNT_NOT_ALLOWED"; +// reason: string; +// }; + +// type RecoverAccountPOSTErrorResponse = +// | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } +// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; export type APIInterface = { registerOptionsPOST: diff --git a/lib/ts/recipe/webauthn/utils.ts b/lib/ts/recipe/webauthn/utils.ts index 9f94448c0..7a0a5d733 100644 --- a/lib/ts/recipe/webauthn/utils.ts +++ b/lib/ts/recipe/webauthn/utils.ts @@ -56,6 +56,8 @@ export function validateAndNormaliseUserInput( function getEmailDeliveryConfig(isInServerlessEnv: boolean) { let emailService = config?.emailDelivery?.service; + console.log("emailService", emailService); + console.log("isInServerlessEnv", isInServerlessEnv); /** * If the user has not passed even that config, we use the default * createAndSendCustomEmail implementation which calls our supertokens API From b44752f84c6cc366fd87485db9bbbec4688f54e6 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 8 Nov 2024 11:45:12 +0200 Subject: [PATCH 18/36] added basic build exports --- recipe/webauthn/emaildelivery/index.d.ts | 10 ++++++++++ recipe/webauthn/emaildelivery/index.js | 6 ++++++ recipe/webauthn/index.d.ts | 10 ++++++++++ recipe/webauthn/index.js | 6 ++++++ recipe/webauthn/types/index.d.ts | 10 ++++++++++ recipe/webauthn/types/index.js | 6 ++++++ 6 files changed, 48 insertions(+) create mode 100644 recipe/webauthn/emaildelivery/index.d.ts create mode 100644 recipe/webauthn/emaildelivery/index.js create mode 100644 recipe/webauthn/index.d.ts create mode 100644 recipe/webauthn/index.js create mode 100644 recipe/webauthn/types/index.d.ts create mode 100644 recipe/webauthn/types/index.js diff --git a/recipe/webauthn/emaildelivery/index.d.ts b/recipe/webauthn/emaildelivery/index.d.ts new file mode 100644 index 000000000..f48887942 --- /dev/null +++ b/recipe/webauthn/emaildelivery/index.d.ts @@ -0,0 +1,10 @@ +export * from "../../../lib/build/recipe/webauthn/emaildelivery/services"; +/** + * 'export *' does not re-export a default. + * import NextJS from "supertokens-node/nextjs"; + * the above import statement won't be possible unless either + * - user add "esModuleInterop": true in their tsconfig.json file + * - we do the following change: + */ +import * as _default from "../../../lib/build/recipe/webauthn/emaildelivery/services"; +export default _default; diff --git a/recipe/webauthn/emaildelivery/index.js b/recipe/webauthn/emaildelivery/index.js new file mode 100644 index 000000000..4b6f696e6 --- /dev/null +++ b/recipe/webauthn/emaildelivery/index.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("../../../lib/build/recipe/webauthn/emaildelivery/services")); diff --git a/recipe/webauthn/index.d.ts b/recipe/webauthn/index.d.ts new file mode 100644 index 000000000..d05fed10b --- /dev/null +++ b/recipe/webauthn/index.d.ts @@ -0,0 +1,10 @@ +export * from "../../lib/build/recipe/webauthn"; +/** + * 'export *' does not re-export a default. + * import NextJS from "supertokens-node/nextjs"; + * the above import statement won't be possible unless either + * - user add "esModuleInterop": true in their tsconfig.json file + * - we do the following change: + */ +import * as _default from "../../lib/build/recipe/webauthn"; +export default _default; diff --git a/recipe/webauthn/index.js b/recipe/webauthn/index.js new file mode 100644 index 000000000..16bab5c2b --- /dev/null +++ b/recipe/webauthn/index.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("../../lib/build/recipe/webauthn")); diff --git a/recipe/webauthn/types/index.d.ts b/recipe/webauthn/types/index.d.ts new file mode 100644 index 000000000..688bf1e8d --- /dev/null +++ b/recipe/webauthn/types/index.d.ts @@ -0,0 +1,10 @@ +export * from "../../../lib/build/recipe/webauthn/types"; +/** + * 'export *' does not re-export a default. + * import NextJS from "supertokens-node/nextjs"; + * the above import statement won't be possible unless either + * - user add "esModuleInterop": true in their tsconfig.json file + * - we do the following change: + */ +import * as _default from "../../../lib/build/recipe/webauthn/types"; +export default _default; diff --git a/recipe/webauthn/types/index.js b/recipe/webauthn/types/index.js new file mode 100644 index 000000000..7f828b74f --- /dev/null +++ b/recipe/webauthn/types/index.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("../../../lib/build/recipe/webauthn/types")); From dba5cda9e52d03fe281ccf31420a92dbe3348557 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 8 Nov 2024 17:58:27 +0200 Subject: [PATCH 19/36] pr fixes --- lib/ts/recipe/webauthn/api/implementation.ts | 51 +++++------ lib/ts/recipe/webauthn/api/recoverAccount.ts | 6 +- lib/ts/recipe/webauthn/api/signin.ts | 4 +- lib/ts/recipe/webauthn/api/signup.ts | 14 +-- lib/ts/recipe/webauthn/api/utils.ts | 2 +- lib/ts/recipe/webauthn/error.ts | 19 +---- lib/ts/recipe/webauthn/index.ts | 90 +++++++++++++++++++- lib/ts/recipe/webauthn/types.ts | 37 ++++---- lib/ts/recipe/webauthn/utils.ts | 45 ++++------ 9 files changed, 157 insertions(+), 111 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 61f905625..55657c272 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -20,7 +20,7 @@ import { getRecoverAccountLink } from "../utils"; import { logDebugMessage } from "../../../logger"; import { RecipeLevelUser } from "../../accountlinking/types"; import { getUser } from "../../.."; -import { CredentialPayload } from "../types"; +import { CredentialPayload, ResidentKey, UserVerification } from "../types"; export default function getAPIImplementation(): APIInterface { return { @@ -60,19 +60,19 @@ export default function getAPIImplementation(): APIInterface { }[]; authenticatorSelection: { requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; + residentKey: ResidentKey; + userVerification: UserVerification; }; } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "INVALID_EMAIL_ERROR"; err: string } > { - const relyingPartyId = await options.config.relyingPartyId({ + const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, request: options.req, userContext, }); - const relyingPartyName = await options.config.relyingPartyName({ + const relyingPartyName = await options.config.getRelyingPartyName({ tenantId, userContext, }); @@ -139,12 +139,12 @@ export default function getAPIImplementation(): APIInterface { webauthnGeneratedOptionsId: string; challenge: string; timeout: number; - userVerification: "required" | "preferred" | "discouraged"; + userVerification: UserVerification; } | GeneralErrorResponse | { status: "WRONG_CREDENTIALS_ERROR" } > { - const relyingPartyId = await options.config.relyingPartyId({ + const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, request: options.req, userContext, @@ -391,35 +391,30 @@ export default function getAPIImplementation(): APIInterface { const recipeId = "webauthn"; - // do the verification before in order to retrieve the user email - const verifyCredentialsResponse = await options.recipeImplementation.verifyCredentials({ - credential, + const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId, userContext, }); - const checkCredentialsOnTenant = async () => { - return verifyCredentialsResponse.status === "OK"; - }; - - // doing it like this because the email is only available after verifyCredentials is called - let email: string; - if (verifyCredentialsResponse.status == "OK") { - const loginMethod = verifyCredentialsResponse.user.loginMethods.find((lm) => lm.recipeId === recipeId); - // there should be a webauthn login method and an email when trying to sign in using webauthn - if (!loginMethod || !loginMethod.email) { - return AuthUtils.getErrorStatusResponseWithReason( - verifyCredentialsResponse, - errorCodeMap, - "SIGN_IN_NOT_ALLOWED" - ); - } - email = loginMethod?.email; - } else { + if (generatedOptions.status !== "OK") { return { status: "WRONG_CREDENTIALS_ERROR", }; } + let email = generatedOptions.email; + + const checkCredentialsOnTenant = async () => { + return ( + ( + await options.recipeImplementation.verifyCredentials({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }) + ).status === "OK" + ); + }; const authenticatingUser = await AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ accountInfo: { email }, diff --git a/lib/ts/recipe/webauthn/api/recoverAccount.ts b/lib/ts/recipe/webauthn/api/recoverAccount.ts index dfbc2a476..526a5d707 100644 --- a/lib/ts/recipe/webauthn/api/recoverAccount.ts +++ b/lib/ts/recipe/webauthn/api/recoverAccount.ts @@ -14,7 +14,7 @@ */ import { send200Response } from "../../../utils"; -import { validateCredentialOrThrowError, validatewebauthnGeneratedOptionsIdOrThrowError } from "./utils"; +import { validateCredentialOrThrowError, validateWebauthnGeneratedOptionsIdOrThrowError } from "./utils"; import STError from "../error"; import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; @@ -25,14 +25,12 @@ export default async function recoverAccount( options: APIOptions, userContext: UserContext ): Promise { - // Logic as per https://github.com/supertokens/supertokens-node/issues/22#issuecomment-710512442 - if (apiImplementation.recoverAccountPOST === undefined) { return false; } const requestBody = await options.req.getJSONBody(); - let webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError( + let webauthnGeneratedOptionsId = await validateWebauthnGeneratedOptionsIdOrThrowError( requestBody.webauthnGeneratedOptionsId ); let credential = await validateCredentialOrThrowError(requestBody.credential); diff --git a/lib/ts/recipe/webauthn/api/signin.ts b/lib/ts/recipe/webauthn/api/signin.ts index 1d4eca98c..6d17fce67 100644 --- a/lib/ts/recipe/webauthn/api/signin.ts +++ b/lib/ts/recipe/webauthn/api/signin.ts @@ -18,7 +18,7 @@ import { getNormalisedShouldTryLinkingWithSessionUserFlag, send200Response, } from "../../../utils"; -import { validatewebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; +import { validateWebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; import { AuthUtils } from "../../../authUtils"; @@ -34,7 +34,7 @@ export default async function signInAPI( } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError( + const webauthnGeneratedOptionsId = await validateWebauthnGeneratedOptionsIdOrThrowError( requestBody.webauthnGeneratedOptionsId ); const credential = await validateCredentialOrThrowError(requestBody.credential); diff --git a/lib/ts/recipe/webauthn/api/signup.ts b/lib/ts/recipe/webauthn/api/signup.ts index 2be7713f8..ef4bec5d6 100644 --- a/lib/ts/recipe/webauthn/api/signup.ts +++ b/lib/ts/recipe/webauthn/api/signup.ts @@ -18,7 +18,7 @@ import { getNormalisedShouldTryLinkingWithSessionUserFlag, send200Response, } from "../../../utils"; -import { validatewebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; +import { validateWebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; import { APIInterface, APIOptions } from ".."; import STError from "../error"; import { UserContext } from "../../../types"; @@ -35,7 +35,7 @@ export default async function signUpAPI( } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await validatewebauthnGeneratedOptionsIdOrThrowError( + const webauthnGeneratedOptionsId = await validateWebauthnGeneratedOptionsIdOrThrowError( requestBody.webauthnGeneratedOptionsId ); const credential = await validateCredentialOrThrowError(requestBody.credential); @@ -70,14 +70,8 @@ export default async function signUpAPI( send200Response(options.res, result); } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { throw new STError({ - type: STError.FIELD_ERROR, - payload: [ - { - id: "email", - error: "This email already exists. Please sign in instead.", - }, - ], - message: "Error in input formFields", + type: STError.BAD_INPUT_ERROR, + message: "This email already exists. Please sign in instead.", }); } else { send200Response(options.res, result); diff --git a/lib/ts/recipe/webauthn/api/utils.ts b/lib/ts/recipe/webauthn/api/utils.ts index 5e1fa497c..1311008c7 100644 --- a/lib/ts/recipe/webauthn/api/utils.ts +++ b/lib/ts/recipe/webauthn/api/utils.ts @@ -14,7 +14,7 @@ */ import STError from "../error"; -export async function validatewebauthnGeneratedOptionsIdOrThrowError( +export async function validateWebauthnGeneratedOptionsIdOrThrowError( webauthnGeneratedOptionsId: string ): Promise { if (webauthnGeneratedOptionsId === undefined) { diff --git a/lib/ts/recipe/webauthn/error.ts b/lib/ts/recipe/webauthn/error.ts index 7b984cbaf..e285ee704 100644 --- a/lib/ts/recipe/webauthn/error.ts +++ b/lib/ts/recipe/webauthn/error.ts @@ -16,26 +16,11 @@ import STError from "../../error"; export default class SessionError extends STError { - static FIELD_ERROR: "FIELD_ERROR" = "FIELD_ERROR"; - - constructor( - options: - | { - type: "FIELD_ERROR"; - payload: { - id: string; - error: string; - }[]; - message: string; - } - | { - type: "BAD_INPUT_ERROR"; - message: string; - } - ) { + constructor(options: { type: "BAD_INPUT_ERROR"; message: string }) { super({ ...options, }); + this.fromRecipe = "webauthn"; } } diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index a1da56d89..f3a3b2e92 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -15,7 +15,15 @@ import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { RecipeInterface, APIOptions, APIInterface, TypeWebauthnEmailDeliveryInput, CredentialPayload } from "./types"; +import { + RecipeInterface, + APIOptions, + APIInterface, + TypeWebauthnEmailDeliveryInput, + CredentialPayload, + UserVerification, + ResidentKey, +} from "./types"; import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; import { getRecoverAccountLink } from "./utils"; @@ -73,8 +81,8 @@ export default class Wrapper { }[]; authenticatorSelection: { requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; + residentKey: ResidentKey; + userVerification: UserVerification; }; } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } @@ -118,7 +126,7 @@ export default class Wrapper { webauthnGeneratedOptionsId: string; challenge: string; timeout: number; - userVerification: "required" | "preferred" | "discouraged"; + userVerification: UserVerification; } | { status: "WRONG_CREDENTIALS_ERROR" } > { @@ -132,6 +140,80 @@ export default class Wrapper { }); } + static signUp( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + session?: undefined, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + >; + static signUp( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + session: SessionContainerInterface, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { + 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"; + } + >; + static signUp( + tenantId: string, + webauthnGeneratedOptionsId: string, + credential: CredentialPayload, + session?: SessionContainerInterface, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { + 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"; + } + > { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signUp({ + webauthnGeneratedOptionsId, + credential, + session, + shouldTryLinkingWithSessionUser: !!session, + tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + userContext: getUserContext(userContext), + }); + } + static signIn( tenantId: string, webauthnGeneratedOptionsId: string, diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index f91c489d1..154c75a14 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -25,8 +25,8 @@ import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../. import RecipeUserId from "../../recipeUserId"; export type TypeNormalisedInput = { - relyingPartyId: TypeNormalisedInputRelyingPartyId; - relyingPartyName: TypeNormalisedInputRelyingPartyName; + getRelyingPartyId: TypeNormalisedInputRelyingPartyId; + getRelyingPartyName: TypeNormalisedInputRelyingPartyName; getOrigin: TypeNormalisedInputGetOrigin; getEmailDeliveryConfig: ( isInServerlessEnv: boolean @@ -65,8 +65,8 @@ export type TypeNormalisedInputValidateEmailAddress = ( export type TypeInput = { emailDelivery?: EmailDeliveryTypeInput; - relyingPartyId?: TypeInputRelyingPartyId; - relyingPartyName?: TypeInputRelyingPartyName; + getRelyingPartyId?: TypeInputRelyingPartyId; + getRelyingPartyName?: TypeInputRelyingPartyName; validateEmailAddress?: TypeInputValidateEmailAddress; getOrigin?: TypeInputGetOrigin; override?: { @@ -139,9 +139,11 @@ export type TypeInputValidateEmailAddress = ( type Base64URLString = string; +export type ResidentKey = "required" | "preferred" | "discouraged"; +export type UserVerification = "required" | "preferred" | "discouraged"; +export type Attestation = "none" | "indirect" | "direct" | "enterprise"; + export type RecipeInterface = { - // should have a way to access the user email: passed as a param, through session, or using recoverAccountToken - // it should have at least one of those 3 options registerOptions( input: { relyingPartyId: string; @@ -149,11 +151,11 @@ export type RecipeInterface = { origin: string; requireResidentKey: boolean | undefined; // should default to false in order to allow multiple authenticators to be used; see https://auth0.com/blog/a-look-at-webauthn-resident-credentials/ // default to 'required' in order store the private key locally on the device and not on the server - residentKey: "required" | "preferred" | "discouraged" | undefined; + residentKey: ResidentKey | undefined; // default to 'preferred' in order to verify the user (biometrics, pin, etc) based on the device preferences - userVerification: "required" | "preferred" | "discouraged" | undefined; + userVerification: UserVerification | undefined; // default to 'none' in order to allow any authenticator and not verify attestation - attestation: "none" | "indirect" | "direct" | "enterprise" | undefined; + attestation: Attestation | undefined; // default to [-8, -7, -257] as supported algorithms. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms. supportedAlgorithmIds: number[] | undefined; // default to 5 seconds @@ -172,6 +174,7 @@ export type RecipeInterface = { | { status: "OK"; webauthnGeneratedOptionsId: string; + // for understanding the response, see https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential and https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential rp: { id: string; name: string; @@ -188,7 +191,7 @@ export type RecipeInterface = { type: "public-key"; transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; + attestation: Attestation; pubKeyCredParams: { // we will default to [-8, -7, -257] as supported algorithms. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms alg: number; @@ -196,8 +199,8 @@ export type RecipeInterface = { }[]; authenticatorSelection: { requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; + residentKey: ResidentKey; + userVerification: UserVerification; }; } // | RegisterOptionsErrorResponse @@ -209,7 +212,7 @@ export type RecipeInterface = { email?: string; relyingPartyId: string; origin: string; - userVerification: "required" | "preferred" | "discouraged" | undefined; // see register options + userVerification: UserVerification | undefined; // see register options timeout: number | undefined; tenantId: string; userContext: UserContext; @@ -219,7 +222,7 @@ export type RecipeInterface = { webauthnGeneratedOptionsId: string; challenge: string; timeout: number; - userVerification: "required" | "preferred" | "discouraged"; + userVerification: UserVerification; } // | SignInOptionsErrorResponse | { status: "WRONG_CREDENTIALS_ERROR" } @@ -578,8 +581,8 @@ export type APIInterface = { }[]; authenticatorSelection: { requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; + residentKey: ResidentKey; + userVerification: UserVerification; }; } | GeneralErrorResponse @@ -601,7 +604,7 @@ export type APIInterface = { webauthnGeneratedOptionsId: string; challenge: string; timeout: number; - userVerification: "required" | "preferred" | "discouraged"; + userVerification: UserVerification; } | GeneralErrorResponse // | SignInOptionsPOSTErrorResponse diff --git a/lib/ts/recipe/webauthn/utils.ts b/lib/ts/recipe/webauthn/utils.ts index 7a0a5d733..2731c8c2f 100644 --- a/lib/ts/recipe/webauthn/utils.ts +++ b/lib/ts/recipe/webauthn/utils.ts @@ -31,22 +31,14 @@ import { RecipeInterface, APIInterface } from "./types"; import { BaseRequest } from "../../framework"; export function validateAndNormaliseUserInput( - recipeInstance: Recipe, + _: Recipe, appInfo: NormalisedAppinfo, config?: TypeInput ): TypeNormalisedInput { - let relyingPartyId = validateAndNormaliseRelyingPartyIdConfig(recipeInstance, appInfo, config?.relyingPartyId); - let relyingPartyName = validateAndNormaliseRelyingPartyNameConfig( - recipeInstance, - appInfo, - config?.relyingPartyName - ); - let getOrigin = validateAndNormaliseGetOriginConfig(recipeInstance, appInfo, config?.getOrigin); - let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig( - recipeInstance, - appInfo, - config?.validateEmailAddress - ); + let getRelyingPartyId = validateAndNormaliseRelyingPartyIdConfig(appInfo, config?.getRelyingPartyId); + let getRelyingPartyName = validateAndNormaliseRelyingPartyNameConfig(appInfo, config?.getRelyingPartyName); + let getOrigin = validateAndNormaliseGetOriginConfig(appInfo, config?.getOrigin); + let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig(config?.validateEmailAddress); let override = { functions: (originalImplementation: RecipeInterface) => originalImplementation, @@ -56,8 +48,6 @@ export function validateAndNormaliseUserInput( function getEmailDeliveryConfig(isInServerlessEnv: boolean) { let emailService = config?.emailDelivery?.service; - console.log("emailService", emailService); - console.log("isInServerlessEnv", isInServerlessEnv); /** * If the user has not passed even that config, we use the default * createAndSendCustomEmail implementation which calls our supertokens API @@ -85,16 +75,15 @@ export function validateAndNormaliseUserInput( return { override, getOrigin, - relyingPartyId, - relyingPartyName, + getRelyingPartyId, + getRelyingPartyName, validateEmailAddress, getEmailDeliveryConfig, }; } function validateAndNormaliseRelyingPartyIdConfig( - _: Recipe, - __: NormalisedAppinfo, + normalisedAppinfo: NormalisedAppinfo, relyingPartyIdConfig: TypeInputRelyingPartyId | undefined ): TypeNormalisedInputRelyingPartyId { return (props) => { @@ -104,15 +93,16 @@ function validateAndNormaliseRelyingPartyIdConfig( return relyingPartyIdConfig(props); } else { return Promise.resolve( - __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous() + normalisedAppinfo + .getOrigin({ request: props.request, userContext: props.userContext }) + .getAsStringDangerous() ); } }; } function validateAndNormaliseRelyingPartyNameConfig( - _: Recipe, - __: NormalisedAppinfo, + normalisedAppInfo: NormalisedAppinfo, relyingPartyNameConfig: TypeInputRelyingPartyName | undefined ): TypeNormalisedInputRelyingPartyName { return (props) => { @@ -121,14 +111,13 @@ function validateAndNormaliseRelyingPartyNameConfig( } else if (typeof relyingPartyNameConfig === "function") { return relyingPartyNameConfig(props); } else { - return Promise.resolve(__.appName); + return Promise.resolve(normalisedAppInfo.appName); } }; } function validateAndNormaliseGetOriginConfig( - _: Recipe, - __: NormalisedAppinfo, + normalisedAppinfo: NormalisedAppinfo, getOriginConfig: TypeInputGetOrigin | undefined ): TypeNormalisedInputGetOrigin { return (props) => { @@ -136,15 +125,15 @@ function validateAndNormaliseGetOriginConfig( return getOriginConfig(props); } else { return Promise.resolve( - __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous() + normalisedAppinfo + .getOrigin({ request: props.request, userContext: props.userContext }) + .getAsStringDangerous() ); } }; } function validateAndNormaliseValidateEmailAddressConfig( - _: Recipe, - __: NormalisedAppinfo, validateEmailAddressConfig: TypeInputValidateEmailAddress | undefined ): TypeNormalisedInputValidateEmailAddress { return (email, tenantId) => { From c84a76a8716d6afe59aa0ea680de450afc7b54b4 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 8 Nov 2024 18:02:58 +0200 Subject: [PATCH 20/36] pr fixes --- lib/ts/recipe/webauthn/api/emailExists.ts | 2 +- lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts | 2 +- lib/ts/recipe/webauthn/api/recoverAccount.ts | 2 +- lib/ts/recipe/webauthn/api/registerOptions.ts | 2 +- lib/ts/recipe/webauthn/api/signInOptions.ts | 2 +- lib/ts/recipe/webauthn/api/signin.ts | 2 +- lib/ts/recipe/webauthn/api/signup.ts | 2 +- lib/ts/recipe/webauthn/api/utils.ts | 2 +- lib/ts/recipe/webauthn/constants.ts | 2 +- lib/ts/recipe/webauthn/error.ts | 2 +- lib/ts/recipe/webauthn/index.ts | 2 +- lib/ts/recipe/webauthn/recipe.ts | 2 +- lib/ts/recipe/webauthn/types.ts | 2 +- lib/ts/recipe/webauthn/utils.ts | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/emailExists.ts b/lib/ts/recipe/webauthn/api/emailExists.ts index 3ed5ecab6..3ffe59a33 100644 --- a/lib/ts/recipe/webauthn/api/emailExists.ts +++ b/lib/ts/recipe/webauthn/api/emailExists.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts b/lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts index 4fe1ef211..c224a280e 100644 --- a/lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts +++ b/lib/ts/recipe/webauthn/api/generateRecoverAccountToken.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/api/recoverAccount.ts b/lib/ts/recipe/webauthn/api/recoverAccount.ts index 526a5d707..dfd7df64c 100644 --- a/lib/ts/recipe/webauthn/api/recoverAccount.ts +++ b/lib/ts/recipe/webauthn/api/recoverAccount.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/api/registerOptions.ts b/lib/ts/recipe/webauthn/api/registerOptions.ts index d2f1b1e58..3e2c7b4c9 100644 --- a/lib/ts/recipe/webauthn/api/registerOptions.ts +++ b/lib/ts/recipe/webauthn/api/registerOptions.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/api/signInOptions.ts b/lib/ts/recipe/webauthn/api/signInOptions.ts index 9cf292f9f..f81fde810 100644 --- a/lib/ts/recipe/webauthn/api/signInOptions.ts +++ b/lib/ts/recipe/webauthn/api/signInOptions.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/api/signin.ts b/lib/ts/recipe/webauthn/api/signin.ts index 6d17fce67..1cece7649 100644 --- a/lib/ts/recipe/webauthn/api/signin.ts +++ b/lib/ts/recipe/webauthn/api/signin.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/api/signup.ts b/lib/ts/recipe/webauthn/api/signup.ts index ef4bec5d6..0b59150a6 100644 --- a/lib/ts/recipe/webauthn/api/signup.ts +++ b/lib/ts/recipe/webauthn/api/signup.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/api/utils.ts b/lib/ts/recipe/webauthn/api/utils.ts index 1311008c7..2d68d610c 100644 --- a/lib/ts/recipe/webauthn/api/utils.ts +++ b/lib/ts/recipe/webauthn/api/utils.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/constants.ts b/lib/ts/recipe/webauthn/constants.ts index 652ee6e46..7f9e86afa 100644 --- a/lib/ts/recipe/webauthn/constants.ts +++ b/lib/ts/recipe/webauthn/constants.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/error.ts b/lib/ts/recipe/webauthn/error.ts index e285ee704..36e709cde 100644 --- a/lib/ts/recipe/webauthn/error.ts +++ b/lib/ts/recipe/webauthn/error.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index f3a3b2e92..246dbbc89 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts index 6e0b94302..d24e54ac0 100644 --- a/lib/ts/recipe/webauthn/recipe.ts +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 154c75a14..74deff6f9 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/ts/recipe/webauthn/utils.ts b/lib/ts/recipe/webauthn/utils.ts index 2731c8c2f..0f5111a6b 100644 --- a/lib/ts/recipe/webauthn/utils.ts +++ b/lib/ts/recipe/webauthn/utils.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. From 3649b45deb1f4a0df4dce0c21d86b30513aa0715 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Tue, 12 Nov 2024 18:35:42 +0200 Subject: [PATCH 21/36] pr fixes --- lib/ts/recipe/accountlinking/types.ts | 1 + lib/ts/recipe/webauthn/api/implementation.ts | 85 ++-- lib/ts/recipe/webauthn/core-mock.ts | 3 - lib/ts/recipe/webauthn/index.ts | 441 +++++++++++------- lib/ts/recipe/webauthn/recipe.ts | 9 +- .../recipe/webauthn/recipeImplementation.ts | 19 +- lib/ts/recipe/webauthn/types.ts | 79 ++-- 7 files changed, 396 insertions(+), 241 deletions(-) diff --git a/lib/ts/recipe/accountlinking/types.ts b/lib/ts/recipe/accountlinking/types.ts index 5559796c9..11fede4c3 100644 --- a/lib/ts/recipe/accountlinking/types.ts +++ b/lib/ts/recipe/accountlinking/types.ts @@ -201,6 +201,7 @@ export type AccountInfoWithRecipeId = { recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; } & AccountInfo; +// todo check if possible to split this into returnable (implementation) types because of webauthn credentialIds being an array export type RecipeLevelUser = { tenantIds: string[]; timeJoined: number; diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 55657c272..2a6d5a621 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -33,6 +33,7 @@ export default function getAPIImplementation(): APIInterface { tenantId: string; options: APIOptions; userContext: UserContext; + displayName?: string; } & ({ email: string } | { recoverAccountToken: string })): Promise< | { status: "OK"; @@ -66,6 +67,7 @@ export default function getAPIImplementation(): APIInterface { } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "INVALID_EMAIL_ERROR"; err: string } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } > { const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, @@ -142,7 +144,7 @@ export default function getAPIImplementation(): APIInterface { userVerification: UserVerification; } | GeneralErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } > { const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, @@ -211,17 +213,21 @@ export default function getAPIImplementation(): APIInterface { status: "SIGN_UP_NOT_ALLOWED"; reason: string; } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } > { + // TODO update error codes (ERR_CODE_XXX) after final implementation const errorCodeMap = { SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", INVALID_AUTHENTICATOR_ERROR: { // TODO: add more cases }, - WRONG_CREDENTIALS_ERROR: "The sign up credentials are incorrect. Please use a different authenticator.", + INVALID_CREDENTIALS_ERROR: + "The sign up credentials are incorrect. Please use a different authenticator.", LINKING_TO_SESSION_USER_FAILED: { EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", @@ -240,7 +246,7 @@ export default function getAPIImplementation(): APIInterface { userContext, }); if (generatedOptions.status !== "OK") { - return { status: "WRONG_CREDENTIALS_ERROR" }; + return generatedOptions; } const email = generatedOptions.email; @@ -250,10 +256,11 @@ export default function getAPIImplementation(): APIInterface { // here to be on the safe side. if (!email) { throw new Error( - "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + "Should never come here since we already check that the email value is a string in validateEmailAddress" ); } + // todo familiarize with this method const preAuthCheckRes = await AuthUtils.preAuthChecks({ authenticatingAccountInfo: { recipeId: "webauthn", @@ -318,6 +325,8 @@ export default function getAPIImplementation(): APIInterface { return AuthUtils.getErrorStatusResponseWithReason(signUpResponse, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); } + // todo familiarize with this method + // todo check if we need to remove webauthn credential ids from the type - it is not used atm. const postAuthChecks = await AuthUtils.postAuthChecks({ authenticatedUser: signUpResponse.user, recipeUserId: signUpResponse.recipeUserId, @@ -367,7 +376,7 @@ export default function getAPIImplementation(): APIInterface { session: SessionContainerInterface; user: User; } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } | { status: "SIGN_IN_NOT_ALLOWED"; reason: string; @@ -391,6 +400,16 @@ export default function getAPIImplementation(): APIInterface { const recipeId = "webauthn"; + const verifyResult = await options.recipeImplementation.verifyCredentials({ + credential, + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (verifyResult.status !== "OK") { + return verifyResult; + } + const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId, @@ -398,24 +417,24 @@ export default function getAPIImplementation(): APIInterface { }); if (generatedOptions.status !== "OK") { return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; } let email = generatedOptions.email; const checkCredentialsOnTenant = async () => { - return ( - ( - await options.recipeImplementation.verifyCredentials({ - credential, - webauthnGeneratedOptionsId, - tenantId, - userContext, - }) - ).status === "OK" - ); + return true; }; + // todo familiarize with this method + // todo make sure the section below (from getAuthenticatingUserAndAddToCurrentTenantIfRequired to isVerified) is correct + // const matchingLoginMethodsFromSessionUser = sessionUser.loginMethods.filter( + // (lm) => + // lm.recipeId === recipeId && + // (lm.hasSameEmailAs(accountInfo.email) || + // lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || + // lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) + // ); const authenticatingUser = await AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ accountInfo: { email }, userContext, @@ -432,7 +451,7 @@ export default function getAPIImplementation(): APIInterface { // isSignUpAllowed will be called as expected. if (authenticatingUser === undefined) { return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; } const preAuthChecks = await AuthUtils.preAuthChecks({ @@ -461,7 +480,7 @@ export default function getAPIImplementation(): APIInterface { if (isFakeEmail(email) && preAuthChecks.isFirstFactor) { // Fake emails cannot be used as a first factor return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; } @@ -474,7 +493,7 @@ export default function getAPIImplementation(): APIInterface { userContext, }); - if (signInResponse.status === "WRONG_CREDENTIALS_ERROR") { + if (signInResponse.status === "INVALID_CREDENTIALS_ERROR") { return signInResponse; } if (signInResponse.status !== "OK") { @@ -550,6 +569,11 @@ export default function getAPIImplementation(): APIInterface { tenantId, options, userContext, + }: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; }): Promise< | { status: "OK"; @@ -834,7 +858,9 @@ export default function getAPIImplementation(): APIInterface { } | GeneralErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } > { async function markEmailAsVerified(recipeUserId: RecipeUserId, email: string) { @@ -870,7 +896,7 @@ export default function getAPIImplementation(): APIInterface { user: User; email: string; } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } | GeneralErrorResponse > { @@ -889,9 +915,9 @@ export default function getAPIImplementation(): APIInterface { status: "INVALID_AUTHENTICATOR_ERROR", reason: updateResponse.reason, }; - } else if (updateResponse.status === "WRONG_CREDENTIALS_ERROR") { + } else if (updateResponse.status === "INVALID_CREDENTIALS_ERROR") { return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; } else { // status: "OK" @@ -1025,9 +1051,12 @@ export default function getAPIImplementation(): APIInterface { }); // todo decide how to handle these - if (createUserResponse.status === "WRONG_CREDENTIALS_ERROR") { - return createUserResponse; - } else if (createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR") { + if ( + createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || + createUserResponse.status === "GENERATED_OPTIONS_NOT_FOUND_ERROR" || + createUserResponse.status === "INVALID_GENERATED_OPTIONS_ERROR" || + createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR" + ) { return createUserResponse; } else if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { // this means that the user already existed and we can just return an invalid diff --git a/lib/ts/recipe/webauthn/core-mock.ts b/lib/ts/recipe/webauthn/core-mock.ts index 488b2ba5d..7ee65cb53 100644 --- a/lib/ts/recipe/webauthn/core-mock.ts +++ b/lib/ts/recipe/webauthn/core-mock.ts @@ -10,9 +10,6 @@ export const getMockQuerier = (recipeId: string) => { body: any, userContext: UserContext ): Promise => { - console.log("body", body); - console.log("userContext", userContext); - if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { // @ts-ignore return { diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 246dbbc89..c2b10dbbe 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -17,12 +17,13 @@ import Recipe from "./recipe"; import SuperTokensError from "./error"; import { RecipeInterface, - APIOptions, APIInterface, + APIOptions, TypeWebauthnEmailDeliveryInput, CredentialPayload, UserVerification, ResidentKey, + Attestation, } from "./types"; import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; @@ -37,24 +38,46 @@ import { DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, + DEFAULT_REGISTER_OPTIONS_TIMEOUT, + DEFAULT_REGISTER_OPTIONS_ATTESTATION, + DEFAULT_SIGNIN_OPTIONS_TIMEOUT, } from "./constants"; +import { BaseRequest } from "../../framework"; export default class Wrapper { static init = Recipe.init; static Error = SuperTokensError; - static async registerOptions( - email: string | undefined, - recoverAccountToken: string | undefined, - relyingPartyId: string, - relyingPartyName: string, - origin: string, - timeout: number, - attestation: "none" | "indirect" | "direct" | "enterprise" = "none", - tenantId: string, - userContext: Record - ): Promise< + static async registerOptions({ + requireResidentKey = DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, + residentKey = DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, + userVerification = DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, + attestation = DEFAULT_REGISTER_OPTIONS_ATTESTATION, + supportedAlgorithmIds = DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, + timeout = DEFAULT_REGISTER_OPTIONS_TIMEOUT, + tenantId = DEFAULT_TENANT_ID, + userContext, + ...rest + }: { + requireResidentKey?: boolean; + residentKey?: ResidentKey; + userVerification?: UserVerification; + attestation?: Attestation; + supportedAlgorithmIds?: number[]; + timeout?: number; + tenantId?: string; + userContext?: Record; + } & ( + | { relyingPartyId: string; relyingPartyName: string; origin: string } + | { request: BaseRequest; relyingPartyId?: string; relyingPartyName?: string; origin?: string } + ) & + ( + | { + email: string; + } + | { recoverAccountToken: string } + )): Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; @@ -87,40 +110,92 @@ export default class Wrapper { } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "INVALID_EMAIL_ERROR"; err: string } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } > { - let payload: { email: string } | { recoverAccountToken: string } | null = email - ? { email } - : recoverAccountToken - ? { recoverAccountToken } - : null; - - if (!payload) { + let emailOrRecoverAccountToken: { email: string } | { recoverAccountToken: string }; + if ("email" in rest || "recoverAccountToken" in rest) { + if ("email" in rest) { + emailOrRecoverAccountToken = { email: rest.email }; + } else { + emailOrRecoverAccountToken = { recoverAccountToken: rest.recoverAccountToken }; + } + } else { return { status: "INVALID_EMAIL_ERROR", err: "Email is missing" }; } + let relyingPartyId: string; + let relyingPartyName: string; + let origin: string; + if ("request" in rest) { + origin = + rest.origin || + (await Recipe.getInstanceOrThrowError().config.getOrigin({ + request: rest.request, + tenantId: tenantId, + userContext: getUserContext(userContext), + })); + relyingPartyId = + rest.relyingPartyId || + (await Recipe.getInstanceOrThrowError().config.getRelyingPartyId({ + request: rest.request, + tenantId: tenantId, + userContext: getUserContext(userContext), + })); + relyingPartyName = + rest.relyingPartyName || + (await Recipe.getInstanceOrThrowError().config.getRelyingPartyName({ + tenantId: tenantId, + userContext: getUserContext(userContext), + })); + } else { + if (!rest.origin) { + throw new Error({ type: "BAD_INPUT_ERROR", message: "Origin missing from the input" }); + } + if (!rest.relyingPartyId) { + throw new Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); + } + if (!rest.relyingPartyName) { + throw new Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyName missing from the input" }); + } + + origin = rest.origin; + relyingPartyId = rest.relyingPartyId; + relyingPartyName = rest.relyingPartyName; + } + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions({ - requireResidentKey: DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, - residentKey: DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, - userVerification: DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, - supportedAlgorithmIds: DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, - ...payload, + ...emailOrRecoverAccountToken, + requireResidentKey, + residentKey, + userVerification, + supportedAlgorithmIds, relyingPartyId, relyingPartyName, origin, timeout, attestation, - tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: getUserContext(userContext), }); } - static signInOptions( - relyingPartyId: string, - origin: string, - timeout: number, - tenantId: string, - userContext: Record - ): Promise< + static async signInOptions({ + email, + tenantId = DEFAULT_TENANT_ID, + userVerification = DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, + timeout = DEFAULT_SIGNIN_OPTIONS_TIMEOUT, + userContext, + ...rest + }: { + email?: string; + timeout?: number; + userVerification?: UserVerification; + tenantId?: string; + userContext?: Record; + } & ( + | { relyingPartyId: string; origin: string } + | { request: BaseRequest; relyingPartyId?: string; origin?: string } + )): Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; @@ -128,72 +203,70 @@ export default class Wrapper { timeout: number; userVerification: UserVerification; } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } > { - return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ - userVerification: DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, + let origin: string; + let relyingPartyId: string; + if ("request" in rest) { + relyingPartyId = + rest.relyingPartyId || + (await Recipe.getInstanceOrThrowError().config.getRelyingPartyId({ + request: rest.request, + tenantId: tenantId, + userContext: getUserContext(userContext), + })); + + origin = + rest.origin || + (await Recipe.getInstanceOrThrowError().config.getOrigin({ + request: rest.request, + tenantId: tenantId, + userContext: getUserContext(userContext), + })); + } else { + if (!rest.relyingPartyId) { + throw new Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); + } + if (!rest.origin) { + throw new Error({ type: "BAD_INPUT_ERROR", message: "Origin missing from the input" }); + } + relyingPartyId = rest.relyingPartyId; + origin = rest.origin; + } + + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ + email, relyingPartyId, origin, timeout, - tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + tenantId, + userVerification, userContext: getUserContext(userContext), }); } - static signUp( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - session?: undefined, - userContext?: Record - ): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } - >; - static signUp( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - session: SessionContainerInterface, - userContext?: Record - ): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } - | { - 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"; - } - >; - static signUp( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - session?: SessionContainerInterface, - userContext?: Record - ): Promise< + static signUp({ + tenantId = DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }: { + tenantId?: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + userContext?: Record; + session?: SessionContainerInterface; + }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId; } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } | { status: "LINKING_TO_SESSION_USER_FAILED"; @@ -209,45 +282,26 @@ export default class Wrapper { credential, session, shouldTryLinkingWithSessionUser: !!session, - tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: getUserContext(userContext), }); } - static signIn( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - session?: undefined, - userContext?: Record - ): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" }>; - static signIn( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - session: SessionContainerInterface, - userContext?: Record - ): Promise< - | { status: "OK"; user: User; recipeUserId: RecipeUserId } - | { status: "WRONG_CREDENTIALS_ERROR" } - | { - 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"; - } - >; - static signIn( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - session?: SessionContainerInterface, - userContext?: Record - ): Promise< + static signIn({ + tenantId = DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }: { + tenantId?: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + session?: SessionContainerInterface; + userContext?: Record; + }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -262,21 +316,26 @@ export default class Wrapper { credential, session, shouldTryLinkingWithSessionUser: !!session, - tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: getUserContext(userContext), }); } - static async verifyCredentials( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - userContext?: Record - ): Promise<{ status: "OK" } | { status: "WRONG_CREDENTIALS_ERROR" }> { + static async verifyCredentials({ + tenantId = DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + userContext, + }: { + tenantId?: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + userContext?: Record; + }): Promise<{ status: "OK" } | { status: "INVALID_CREDENTIALS_ERROR" }> { const resp = await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ webauthnGeneratedOptionsId, credential, - tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: getUserContext(userContext), }); @@ -297,33 +356,48 @@ export default class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static generateRecoverAccountToken( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + static generateRecoverAccountToken({ + tenantId = DEFAULT_TENANT_ID, + userId, + email, + userContext, + }: { + tenantId?: string; + userId: string; + email: string; + userContext?: Record; + }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.generateRecoverAccountToken({ userId, email, - tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: getUserContext(userContext), }); } - static async recoverAccount( - tenantId: string, - webauthnGeneratedOptionsId: string, - token: string, - credential: CredentialPayload, - userContext?: Record - ): Promise< + static async recoverAccount({ + tenantId = DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + token, + credential, + userContext, + }: { + tenantId?: string; + webauthnGeneratedOptionsId: string; + token: string; + credential: CredentialPayload; + userContext?: Record; + }): Promise< | { - status: "OK" | "WRONG_CREDENTIALS_ERROR" | "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + status: "OK"; } + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; failureReason: string } > { - const consumeResp = await Wrapper.consumeRecoverAccountToken(tenantId, token, userContext); + const consumeResp = await Wrapper.consumeRecoverAccountToken({ tenantId, token, userContext }); if (consumeResp.status !== "OK") { return consumeResp; @@ -333,26 +407,29 @@ export default class Wrapper { recipeUserId: new RecipeUserId(consumeResp.userId), webauthnGeneratedOptionsId, credential, - tenantId, userContext, }); - if (result.status === "INVALID_AUTHENTICATOR_ERROR") { return { status: "INVALID_AUTHENTICATOR_ERROR", failureReason: result.reason, }; } + return { status: result.status, }; } - static consumeRecoverAccountToken( - tenantId: string, - token: string, - userContext?: Record - ): Promise< + static consumeRecoverAccountToken({ + tenantId = DEFAULT_TENANT_ID, + token, + userContext, + }: { + tenantId?: string; + token: string; + userContext?: Record; + }): Promise< | { status: "OK"; email: string; @@ -362,61 +439,79 @@ export default class Wrapper { > { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.consumeRecoverAccountToken({ token, - tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: getUserContext(userContext), }); } - static registerCredential(input: { + static registerCredential({ + recipeUserId, + webauthnGeneratedOptionsId, + credential, + userContext, + }: { recipeUserId: RecipeUserId; - tenantId: string; webauthnGeneratedOptionsId: string; credential: CredentialPayload; userContext?: Record; }): Promise< | { - status: "OK" | "WRONG_CREDENTIALS_ERROR"; + status: "OK"; } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } > { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerCredential({ - ...input, - userContext: getUserContext(input.userContext), + recipeUserId, + webauthnGeneratedOptionsId, + credential, + userContext: getUserContext(userContext), }); } - static async createRecoverAccountLink( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise<{ status: "OK"; link: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { - const ctx = getUserContext(userContext); - let token = await this.generateRecoverAccountToken(tenantId, userId, email, ctx); + static async createRecoverAccountLink({ + tenantId = DEFAULT_TENANT_ID, + userId, + email, + userContext, + }: { + tenantId?: string; + userId: string; + email: string; + userContext?: Record; + }): Promise<{ status: "OK"; link: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + let token = await this.generateRecoverAccountToken({ tenantId, userId, email, userContext }); if (token.status === "UNKNOWN_USER_ID_ERROR") { return token; } + const ctx = getUserContext(userContext); const recipeInstance = Recipe.getInstanceOrThrowError(); return { status: "OK", link: getRecoverAccountLink({ appInfo: recipeInstance.getAppInfo(), token: token.token, - tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, + tenantId, request: getRequestFromUserContext(ctx), userContext: ctx, }), }; } - static async sendRecoverAccountEmail( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }> { + static async sendRecoverAccountEmail({ + tenantId = DEFAULT_TENANT_ID, + userId, + email, + userContext, + }: { + tenantId?: string; + userId: string; + email: string; + userContext?: Record; + }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }> { const user = await getUser(userId, userContext); if (!user) { return { status: "UNKNOWN_USER_ID_ERROR" }; @@ -427,7 +522,7 @@ export default class Wrapper { return { status: "UNKNOWN_USER_ID_ERROR" }; } - let link = await this.createRecoverAccountLink(tenantId, userId, email, userContext); + let link = await this.createRecoverAccountLink({ tenantId, userId, email, userContext }); if (link.status === "UNKNOWN_USER_ID_ERROR") { return link; } @@ -455,7 +550,7 @@ export default class Wrapper { let recipeInstance = Recipe.getInstanceOrThrowError(); return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail({ ...input, - tenantId: input.tenantId === undefined ? DEFAULT_TENANT_ID : input.tenantId, + tenantId: input.tenantId || DEFAULT_TENANT_ID, userContext: getUserContext(input.userContext), }); } diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts index d24e54ac0..f34ec71cf 100644 --- a/lib/ts/recipe/webauthn/recipe.ts +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -342,14 +342,7 @@ export default class Recipe extends RecipeModule { handleError = async (err: STError, _request: BaseRequest, response: BaseResponse): Promise => { if (err.fromRecipe === Recipe.RECIPE_ID) { - if (err.type === STError.FIELD_ERROR) { - return send200Response(response, { - status: "FIELD_ERROR", - formFields: err.payload, - }); - } else { - throw err; - } + throw err; } else { throw err; } diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 062cef00d..525ee7fca 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -8,6 +8,7 @@ import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; import { LoginMethod, User } from "../../user"; import { AuthUtils } from "../../authUtils"; import * as jose from "jose"; +import { isFakeEmail } from "../thirdparty/utils"; export default function getRecipeInterface( querier: Querier, @@ -63,12 +64,26 @@ export default function getRecipeInterface( }; } + // set a nice default display name + // if the user has a fake email, we use the username part of the email instead (which should be the recipe user id) + let displayName: string; + if (rest.displayName) { + displayName = rest.displayName; + } else { + if (isFakeEmail(email)) { + displayName = email.split("@")[0]; + } else { + displayName = email; + } + } + return await querier.sendPostRequest( new NormalisedURLPath( `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/register` ), { email, + displayName, relyingPartyName, relyingPartyId, origin, @@ -209,7 +224,7 @@ export default function getRecipeInterface( } return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; }, @@ -289,7 +304,7 @@ export default function getRecipeInterface( } return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; }, diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 74deff6f9..2f694f1b1 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -98,34 +98,40 @@ export type TypeInputValidateEmailAddress = ( ) => Promise | string | undefined; // centralize error types in order to prevent missing cascading errors -// type RegisterOptionsErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; +// type RegisterOptionsErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "INVALID_EMAIL_ERROR"; err: string } | { status: "INVALID_GENERATED_OPTIONS_ERROR" }; -// type SignInOptionsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type SignInOptionsErrorResponse = { status: "INVALID_GENERATED_OPTIONS_ERROR" }; // type SignUpErrorResponse = // | { status: "EMAIL_ALREADY_EXISTS_ERROR" } -// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_CREDENTIALS_ERROR" } +// | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } +// | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; -// type SignInErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type SignInErrorResponse = { status: "INVALID_CREDENTIALS_ERROR" }; -// type VerifyCredentialsErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type VerifyCredentialsErrorResponse = { status: "INVALID_CREDENTIALS_ERROR" }; // type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; // type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; // type RegisterCredentialErrorResponse = -// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_CREDENTIALS_ERROR" } +// | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } +// | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // // when the attestation is checked and is not valid or other cases in whcih the authenticator is not correct // | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; // type CreateNewRecipeUserErrorResponse = -// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation +// | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found +// | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired // | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } // | { status: "EMAIL_ALREADY_EXISTS_ERROR" }; -// type DecodeCredentialErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type DecodeCredentialErrorResponse = { status: "INVALID_CREDENTIALS_ERROR" }; // type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; @@ -148,6 +154,7 @@ export type RecipeInterface = { input: { relyingPartyId: string; relyingPartyName: string; + displayName?: string; origin: string; requireResidentKey: boolean | undefined; // should default to false in order to allow multiple authenticators to be used; see https://auth0.com/blog/a-look-at-webauthn-resident-credentials/ // default to 'required' in order store the private key locally on the device and not on the server @@ -206,6 +213,7 @@ export type RecipeInterface = { // | RegisterOptionsErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "INVALID_EMAIL_ERROR"; err: string } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } >; signInOptions(input: { @@ -225,7 +233,7 @@ export type RecipeInterface = { userVerification: UserVerification; } // | SignInOptionsErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } >; signUp(input: { @@ -243,7 +251,9 @@ export type RecipeInterface = { } // | SignUpErrorResponse | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } | { status: "LINKING_TO_SESSION_USER_FAILED"; @@ -265,7 +275,7 @@ export type RecipeInterface = { }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } // | SignInErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } | { status: "LINKING_TO_SESSION_USER_FAILED"; reason: @@ -284,7 +294,7 @@ export type RecipeInterface = { }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId } // | VerifyCredentialsErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } >; // this function is meant only for creating the recipe in the core and nothing else. @@ -303,7 +313,9 @@ export type RecipeInterface = { recipeUserId: RecipeUserId; } // | CreateNewRecipeUserErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } >; @@ -353,7 +365,9 @@ export type RecipeInterface = { status: "OK"; } // | RegisterCredentialErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } >; @@ -416,7 +430,7 @@ export type RecipeInterface = { }; } // | DecodeCredentialErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } >; // used for retrieving the user details (email) from the recover account token @@ -515,21 +529,25 @@ export type APIOptions = { // type RegisterOptionsPOSTErrorResponse = // | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } -// | { status: "INVALID_EMAIL_ERROR"; err: string }; +// | { status: "INVALID_EMAIL_ERROR"; err: string } +// | { status: "INVALID_GENERATED_OPTIONS_ERROR" }; -// type SignInOptionsPOSTErrorResponse = { status: "WRONG_CREDENTIALS_ERROR" }; +// type SignInOptionsPOSTErrorResponse = +// | { status: "INVALID_GENERATED_OPTIONS_ERROR" }; // type SignUpPOSTErrorResponse = // | { // status: "SIGN_UP_NOT_ALLOWED"; // reason: string; // } -// | { status: "EMAIL_ALREADY_EXISTS_ERROR" } -// | { status: "WRONG_CREDENTIALS_ERROR" } -// | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; +// | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation +// | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found +// | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired +// | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } +// | { status: "EMAIL_ALREADY_EXISTS_ERROR" }; // type SignInPOSTErrorResponse = -// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_CREDENTIALS_ERROR" } // | { // status: "SIGN_IN_NOT_ALLOWED"; // reason: string; @@ -542,7 +560,9 @@ export type APIOptions = { // type RecoverAccountPOSTErrorResponse = // | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } -// | { status: "WRONG_CREDENTIALS_ERROR" } +// | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation +// | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found +// | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired // | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; export type APIInterface = { @@ -589,6 +609,7 @@ export type APIInterface = { // | RegisterOptionsPOSTErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "INVALID_EMAIL_ERROR"; err: string } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } >); signInOptionsPOST: @@ -608,7 +629,7 @@ export type APIInterface = { } | GeneralErrorResponse // | SignInOptionsPOSTErrorResponse - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } >); signUpPOST: @@ -634,9 +655,11 @@ export type APIInterface = { status: "SIGN_UP_NOT_ALLOWED"; reason: string; } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } >); signInPOST: @@ -661,7 +684,7 @@ export type APIInterface = { status: "SIGN_IN_NOT_ALLOWED"; reason: string; } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } >); generateRecoverAccountTokenPOST: @@ -701,7 +724,9 @@ export type APIInterface = { | GeneralErrorResponse // | RecoverAccountPOSTErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "WRONG_CREDENTIALS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } >); From 3bb7234cca9563739006949a4a5956e7352ae403 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Tue, 12 Nov 2024 18:37:04 +0200 Subject: [PATCH 22/36] pr fixes --- lib/ts/recipe/webauthn/api/implementation.ts | 1 - lib/ts/recipe/webauthn/recipe.ts | 1 - lib/ts/recipe/webauthn/recipeImplementation.ts | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 2a6d5a621..bc321c4f6 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -1050,7 +1050,6 @@ export default function getAPIImplementation(): APIInterface { userContext, }); - // todo decide how to handle these if ( createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || createUserResponse.status === "GENERATED_OPTIONS_NOT_FOUND_ERROR" || diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts index f34ec71cf..23cd5a717 100644 --- a/lib/ts/recipe/webauthn/recipe.ts +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -97,7 +97,6 @@ export default class Recipe extends RecipeModule { ? new EmailDeliveryIngredient(this.config.getEmailDeliveryConfig(this.isInServerlessEnv)) : ingredients.emailDelivery; - // todo check correctness PostSuperTokensInitCallbacks.addPostInitCallback(() => { const mfaInstance = MultiFactorAuthRecipe.getInstance(); if (mfaInstance !== undefined) { diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 525ee7fca..db6e693f6 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -33,8 +33,7 @@ export default function getRecipeInterface( if (emailInput !== undefined) { email = emailInput; } else if (recoverAccountTokenInput !== undefined) { - // todo check if should decode using Core or using sdk; atm decided on usinng the sdk so to not make another roundtrip to the server - // the actual verification of the token will be done during consumeRecoverAccountToken + // the actual validation of the token will be done during consumeRecoverAccountToken let decoded: jose.JWTPayload | undefined; try { decoded = await jose.decodeJwt(recoverAccountTokenInput); From ce371d1497544fc3e6fc7783e406afd1e62dfe10 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Tue, 12 Nov 2024 18:38:05 +0200 Subject: [PATCH 23/36] pr fixes --- lib/ts/recipe/webauthn/api/implementation.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index bc321c4f6..2a00b910c 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -907,7 +907,6 @@ export default function getAPIImplementation(): APIInterface { userContext, }); - // todo decide how to handle these if (updateResponse.status === "INVALID_AUTHENTICATOR_ERROR") { // This should happen only cause of a race condition where the user // might be deleted before token creation and consumption. @@ -977,7 +976,6 @@ export default function getAPIImplementation(): APIInterface { userContext, }); - // todo decide how to handle these if (tokenConsumptionResponse.status === "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR") { return tokenConsumptionResponse; } From d356701f980208bf9a43a3673e5c2fd6fb8f4abd Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 25 Nov 2024 13:44:43 +0200 Subject: [PATCH 24/36] added missing default email delivery implementation --- .../services/backwardCompatibility/index.ts | 93 ++ .../webauthn/emaildelivery/services/index.ts | 17 + .../emaildelivery/services/smtp/index.ts | 50 + .../services/smtp/recoverAccount.ts | 945 ++++++++++++++++++ .../smtp/serviceImplementation/index.ts | 57 ++ lib/ts/recipe/webauthn/recipe.ts | 5 +- lib/ts/recipe/webauthn/utils.ts | 25 +- 7 files changed, 1181 insertions(+), 11 deletions(-) create mode 100644 lib/ts/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.ts create mode 100644 lib/ts/recipe/webauthn/emaildelivery/services/index.ts create mode 100644 lib/ts/recipe/webauthn/emaildelivery/services/smtp/index.ts create mode 100644 lib/ts/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.ts create mode 100644 lib/ts/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.ts diff --git a/lib/ts/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.ts b/lib/ts/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.ts new file mode 100644 index 000000000..1e2e57d5b --- /dev/null +++ b/lib/ts/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.ts @@ -0,0 +1,93 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { TypeWebauthnEmailDeliveryInput } from "../../../types"; +import { NormalisedAppinfo, UserContext } from "../../../../../types"; +import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; +import { isTestEnv, postWithFetch } from "../../../../../utils"; + +async function createAndSendEmailUsingSupertokensService(input: { + appInfo: NormalisedAppinfo; + // Where the message should be delivered. + user: { email: string; id: string }; + // This has to be entered on the starting device to finish sign in/up + recoverAccountLink: string; +}): Promise { + if (isTestEnv()) { + return; + } + const result = await postWithFetch( + "https://api.supertokens.io/0/st/auth/webauthn/recover", + { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, + { + email: input.user.email, + appName: input.appInfo.appName, + recoverAccountURL: input.recoverAccountLink, + }, + { + successLog: `Email sent to ${input.user.email}`, + errorLogHeader: "Error sending webauthn recover account email", + } + ); + if ("error" in result) { + throw result.error; + } + + if (result.resp && result.resp.status >= 400) { + if (result.resp.body.err) { + /** + * if the error is thrown from API, the response object + * will be of type `{err: string}` + */ + throw new Error(result.resp.body.err); + } else { + throw new Error(`Request failed with status code ${result.resp.status}`); + } + } +} + +export default class BackwardCompatibilityService implements EmailDeliveryInterface { + private isInServerlessEnv: boolean; + private appInfo: NormalisedAppinfo; + + constructor(appInfo: NormalisedAppinfo, isInServerlessEnv: boolean) { + this.isInServerlessEnv = isInServerlessEnv; + this.appInfo = appInfo; + } + + sendEmail = async (input: TypeWebauthnEmailDeliveryInput & { userContext: UserContext }) => { + // we add this here cause the user may have overridden the sendEmail function + // to change the input email and if we don't do this, the input email + // will get reset by the getUserById call above. + try { + if (!this.isInServerlessEnv) { + createAndSendEmailUsingSupertokensService({ + appInfo: this.appInfo, + user: input.user, + recoverAccountLink: input.recoverAccountLink, + }).catch((_) => {}); + } else { + // see https://github.com/supertokens/supertokens-node/pull/135 + await createAndSendEmailUsingSupertokensService({ + appInfo: this.appInfo, + user: input.user, + recoverAccountLink: input.recoverAccountLink, + }); + } + } catch (_) {} + }; +} diff --git a/lib/ts/recipe/webauthn/emaildelivery/services/index.ts b/lib/ts/recipe/webauthn/emaildelivery/services/index.ts new file mode 100644 index 000000000..d70e3f387 --- /dev/null +++ b/lib/ts/recipe/webauthn/emaildelivery/services/index.ts @@ -0,0 +1,17 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import SMTP from "./smtp"; +export let SMTPService = SMTP; diff --git a/lib/ts/recipe/webauthn/emaildelivery/services/smtp/index.ts b/lib/ts/recipe/webauthn/emaildelivery/services/smtp/index.ts new file mode 100644 index 000000000..3498b20f3 --- /dev/null +++ b/lib/ts/recipe/webauthn/emaildelivery/services/smtp/index.ts @@ -0,0 +1,50 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { ServiceInterface, TypeInput } from "../../../../../ingredients/emaildelivery/services/smtp"; +import { TypeWebauthnEmailDeliveryInput } from "../../../types"; +import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; +import { createTransport } from "nodemailer"; +import OverrideableBuilder from "supertokens-js-override"; +import { getServiceImplementation } from "./serviceImplementation"; +import { UserContext } from "../../../../../types"; + +export default class SMTPService implements EmailDeliveryInterface { + serviceImpl: ServiceInterface; + + constructor(config: TypeInput) { + const transporter = createTransport({ + host: config.smtpSettings.host, + port: config.smtpSettings.port, + auth: { + user: config.smtpSettings.authUsername || config.smtpSettings.from.email, + pass: config.smtpSettings.password, + }, + secure: config.smtpSettings.secure, + }); + let builder = new OverrideableBuilder(getServiceImplementation(transporter, config.smtpSettings.from)); + if (config.override !== undefined) { + builder = builder.override(config.override); + } + this.serviceImpl = builder.build(); + } + + sendEmail = async (input: TypeWebauthnEmailDeliveryInput & { userContext: UserContext }) => { + let content = await this.serviceImpl.getContent(input); + await this.serviceImpl.sendRawEmail({ + ...content, + userContext: input.userContext, + }); + }; +} diff --git a/lib/ts/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.ts b/lib/ts/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.ts new file mode 100644 index 000000000..cd95aba07 --- /dev/null +++ b/lib/ts/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.ts @@ -0,0 +1,945 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { TypeWebauthnRecoverAccountEmailDeliveryInput } from "../../../types"; +import { GetContentResult } from "../../../../../ingredients/emaildelivery/services/smtp"; +import Supertokens from "../../../../../supertokens"; + +export default function getRecoverAccountEmailContent( + input: TypeWebauthnRecoverAccountEmailDeliveryInput +): GetContentResult { + let supertokens = Supertokens.getInstanceOrThrowError(); + let appName = supertokens.appInfo.appName; + let body = getRecoverAccountEmailHTML(appName, input.user.email, input.recoverAccountLink); + + return { + body, + toEmail: input.user.email, + subject: "Account recovery instructions", + isHtml: true, + }; +} + +export function getRecoverAccountEmailHTML(appName: string, email: string, resetLink: string) { + return ` + + + + + + + + *|MC:SUBJECT|* + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + +
+ + + + + +
+ +
+ + + + + +
+ + + + + + +
+ + +
+
+ +

+ An account recovery request for your account on + ${appName} has been received. +

+ + +
+
+

+ Alternatively, you can directly paste this link + in your browser
+ ${resetLink} +

+
+
+ + + + +
+ + + + + + +
+

+ This email is meant for ${email} +

+
+
+ +
+ + + + + +
+ +
+ +
+
+ + + + `; +} diff --git a/lib/ts/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.ts b/lib/ts/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.ts new file mode 100644 index 000000000..4f141d7dd --- /dev/null +++ b/lib/ts/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.ts @@ -0,0 +1,57 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { TypeWebauthnEmailDeliveryInput } from "../../../../types"; +import { Transporter } from "nodemailer"; +import { + ServiceInterface, + TypeInputSendRawEmail, + GetContentResult, +} from "../../../../../../ingredients/emaildelivery/services/smtp"; +import getRecoverAccountEmailContent from "../recoverAccount"; +import { UserContext } from "../../../../../../types"; + +export function getServiceImplementation( + transporter: Transporter, + from: { + name: string; + email: string; + } +): ServiceInterface { + return { + sendRawEmail: async function (input: TypeInputSendRawEmail) { + if (input.isHtml) { + await transporter.sendMail({ + from: `${from.name} <${from.email}>`, + to: input.toEmail, + subject: input.subject, + html: input.body, + }); + } else { + await transporter.sendMail({ + from: `${from.name} <${from.email}>`, + to: input.toEmail, + subject: input.subject, + text: input.body, + }); + } + }, + getContent: async function ( + input: TypeWebauthnEmailDeliveryInput & { userContext: UserContext } + ): Promise { + return getRecoverAccountEmailContent(input); + }, + }; +} diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts index 23cd5a717..8620c3fde 100644 --- a/lib/ts/recipe/webauthn/recipe.ts +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -35,7 +35,7 @@ import signInOptionsAPI from "./api/signInOptions"; import generateRecoverAccountTokenAPI from "./api/generateRecoverAccountToken"; import recoverAccountAPI from "./api/recoverAccount"; import emailExistsAPI from "./api/emailExists"; -import { isTestEnv, send200Response } from "../../utils"; +import { isTestEnv } from "../../utils"; import RecipeImplementation from "./recipeImplementation"; import APIImplementation from "./api/implementation"; import type { BaseRequest, BaseResponse } from "../../framework"; @@ -322,6 +322,7 @@ export default class Recipe extends RecipeModule { emailDelivery: this.emailDelivery, appInfo: this.getAppInfo(), }; + if (id === REGISTER_OPTIONS_API) { return await registerOptionsAPI(this.apiImpl, tenantId, options, userContext); } else if (id === SIGNIN_OPTIONS_API) { @@ -339,7 +340,7 @@ export default class Recipe extends RecipeModule { } else return false; }; - handleError = async (err: STError, _request: BaseRequest, response: BaseResponse): Promise => { + handleError = async (err: STError, _request: BaseRequest, _response: BaseResponse): Promise => { if (err.fromRecipe === Recipe.RECIPE_ID) { throw err; } else { diff --git a/lib/ts/recipe/webauthn/utils.ts b/lib/ts/recipe/webauthn/utils.ts index 0f5111a6b..1bf968fc7 100644 --- a/lib/ts/recipe/webauthn/utils.ts +++ b/lib/ts/recipe/webauthn/utils.ts @@ -29,6 +29,7 @@ import { import { NormalisedAppinfo, UserContext } from "../../types"; import { RecipeInterface, APIInterface } from "./types"; import { BaseRequest } from "../../framework"; +import BackwardCompatibilityService from "./emaildelivery/services/backwardCompatibility"; export function validateAndNormaliseUserInput( _: Recipe, @@ -52,9 +53,10 @@ export function validateAndNormaliseUserInput( * If the user has not passed even that config, we use the default * createAndSendCustomEmail implementation which calls our supertokens API */ - // if (emailService === undefined) { - // emailService = new BackwardCompatibilityService(appInfo, isInServerlessEnv); - // } + if (emailService === undefined) { + emailService = new BackwardCompatibilityService(appInfo, isInServerlessEnv); + } + return { ...config?.emailDelivery, /** @@ -69,7 +71,7 @@ export function validateAndNormaliseUserInput( * set service at the end */ // todo implemenet this - service: null as any, + service: emailService, }; } return { @@ -92,11 +94,16 @@ function validateAndNormaliseRelyingPartyIdConfig( } else if (typeof relyingPartyIdConfig === "function") { return relyingPartyIdConfig(props); } else { - return Promise.resolve( - normalisedAppinfo - .getOrigin({ request: props.request, userContext: props.userContext }) - .getAsStringDangerous() - ); + const urlString = normalisedAppinfo + .getOrigin({ request: props.request, userContext: props.userContext }) + .getAsStringDangerous(); + + // should let this throw if the url is invalid + const url = new URL(urlString); + + const hostname = url.hostname; + + return Promise.resolve(hostname); } }; } From e743046fbf8a7af5152617b7791ee86c414d6c3d Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Mon, 25 Nov 2024 13:45:11 +0200 Subject: [PATCH 25/36] added basic tests and mock --- lib/build/recipe/webauthn/api/emailExists.js | 2 +- .../api/generateRecoverAccountToken.js | 2 +- .../recipe/webauthn/api/implementation.js | 83 +- .../recipe/webauthn/api/recoverAccount.js | 5 +- .../recipe/webauthn/api/registerOptions.js | 2 +- .../recipe/webauthn/api/signInOptions.js | 2 +- lib/build/recipe/webauthn/api/signin.js | 4 +- lib/build/recipe/webauthn/api/signup.js | 14 +- lib/build/recipe/webauthn/api/utils.d.ts | 2 +- lib/build/recipe/webauthn/api/utils.js | 8 +- lib/build/recipe/webauthn/constants.js | 2 +- lib/build/recipe/webauthn/core-mock.js | 78 +- .../services/backwardCompatibility/index.d.ts | 14 + .../services/backwardCompatibility/index.js | 66 ++ .../emaildelivery/services/index.d.ts | 3 + .../webauthn/emaildelivery/services/index.js | 24 + .../emaildelivery/services/smtp/index.d.ts | 14 + .../emaildelivery/services/smtp/index.js | 37 + .../services/smtp/recoverAccount.d.ts | 7 + .../services/smtp/recoverAccount.js | 934 ++++++++++++++++++ .../smtp/serviceImplementation/index.d.ts | 11 + .../smtp/serviceImplementation/index.js | 48 + lib/build/recipe/webauthn/error.d.ts | 17 +- lib/build/recipe/webauthn/error.js | 3 +- lib/build/recipe/webauthn/index.d.ts | 306 ++++-- lib/build/recipe/webauthn/index.js | 254 +++-- lib/build/recipe/webauthn/recipe.d.ts | 2 +- lib/build/recipe/webauthn/recipe.js | 14 +- .../recipe/webauthn/recipeImplementation.js | 21 +- lib/build/recipe/webauthn/types.d.ts | 94 +- lib/build/recipe/webauthn/types.js | 2 +- lib/build/recipe/webauthn/utils.d.ts | 2 +- lib/build/recipe/webauthn/utils.js | 61 +- lib/ts/recipe/webauthn/core-mock.ts | 64 +- package-lock.json | 148 +++ package.json | 1 + test/test-server/src/webauthn.ts | 31 + test/utils.js | 2 + test/webauthn/apis.test.js | 149 +++ test/webauthn/config.test.js | 123 +++ 40 files changed, 2317 insertions(+), 339 deletions(-) create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/index.d.ts create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/index.js create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts create mode 100644 lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js create mode 100644 test/test-server/src/webauthn.ts create mode 100644 test/webauthn/apis.test.js create mode 100644 test/webauthn/config.test.js diff --git a/lib/build/recipe/webauthn/api/emailExists.js b/lib/build/recipe/webauthn/api/emailExists.js index b76ac08c7..878fa5c58 100644 --- a/lib/build/recipe/webauthn/api/emailExists.js +++ b/lib/build/recipe/webauthn/api/emailExists.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js index 651c5a398..0bec60d05 100644 --- a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js +++ b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/build/recipe/webauthn/api/implementation.js b/lib/build/recipe/webauthn/api/implementation.js index 6eef006ec..ed63c0ed8 100644 --- a/lib/build/recipe/webauthn/api/implementation.js +++ b/lib/build/recipe/webauthn/api/implementation.js @@ -30,12 +30,12 @@ function getAPIImplementation() { registerOptionsPOST: async function (_a) { var { tenantId, options, userContext } = _a, props = __rest(_a, ["tenantId", "options", "userContext"]); - const relyingPartyId = await options.config.relyingPartyId({ + const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, request: options.req, userContext, }); - const relyingPartyName = await options.config.relyingPartyName({ + const relyingPartyName = await options.config.getRelyingPartyName({ tenantId, userContext, }); @@ -82,7 +82,7 @@ function getAPIImplementation() { }; }, signInOptionsPOST: async function ({ email, tenantId, options, userContext }) { - const relyingPartyId = await options.config.relyingPartyId({ + const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, request: options.req, userContext, @@ -124,13 +124,15 @@ function getAPIImplementation() { options, userContext, }) { + // TODO update error codes (ERR_CODE_XXX) after final implementation const errorCodeMap = { SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", INVALID_AUTHENTICATOR_ERROR: { // TODO: add more cases }, - WRONG_CREDENTIALS_ERROR: "The sign up credentials are incorrect. Please use a different authenticator.", + INVALID_CREDENTIALS_ERROR: + "The sign up credentials are incorrect. Please use a different authenticator.", LINKING_TO_SESSION_USER_FAILED: { EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", @@ -148,7 +150,7 @@ function getAPIImplementation() { userContext, }); if (generatedOptions.status !== "OK") { - return { status: "WRONG_CREDENTIALS_ERROR" }; + return generatedOptions; } const email = generatedOptions.email; // NOTE: Following checks will likely never throw an error as the @@ -156,9 +158,10 @@ function getAPIImplementation() { // here to be on the safe side. if (!email) { throw new Error( - "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + "Should never come here since we already check that the email value is a string in validateEmailAddress" ); } + // todo familiarize with this method const preAuthCheckRes = await authUtils_1.AuthUtils.preAuthChecks({ authenticatingAccountInfo: { recipeId: "webauthn", @@ -228,6 +231,8 @@ function getAPIImplementation() { "SIGN_UP_NOT_ALLOWED" ); } + // todo familiarize with this method + // todo check if we need to remove webauthn credential ids from the type - it is not used atm. const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ authenticatedUser: signUpResponse.user, recipeUserId: signUpResponse.recipeUserId, @@ -280,34 +285,38 @@ function getAPIImplementation() { }, }; const recipeId = "webauthn"; - // do the verification before in order to retrieve the user email - const verifyCredentialsResponse = await options.recipeImplementation.verifyCredentials({ + const verifyResult = await options.recipeImplementation.verifyCredentials({ credential, webauthnGeneratedOptionsId, tenantId, userContext, }); - const checkCredentialsOnTenant = async () => { - return verifyCredentialsResponse.status === "OK"; - }; - // doing it like this because the email is only available after verifyCredentials is called - let email; - if (verifyCredentialsResponse.status == "OK") { - const loginMethod = verifyCredentialsResponse.user.loginMethods.find((lm) => lm.recipeId === recipeId); - // there should be a webauthn login method and an email when trying to sign in using webauthn - if (!loginMethod || !loginMethod.email) { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - verifyCredentialsResponse, - errorCodeMap, - "SIGN_IN_NOT_ALLOWED" - ); - } - email = loginMethod === null || loginMethod === void 0 ? void 0 : loginMethod.email; - } else { + if (verifyResult.status !== "OK") { + return verifyResult; + } + const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (generatedOptions.status !== "OK") { return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; } + let email = generatedOptions.email; + const checkCredentialsOnTenant = async () => { + return true; + }; + // todo familiarize with this method + // todo make sure the section below (from getAuthenticatingUserAndAddToCurrentTenantIfRequired to isVerified) is correct + // const matchingLoginMethodsFromSessionUser = sessionUser.loginMethods.filter( + // (lm) => + // lm.recipeId === recipeId && + // (lm.hasSameEmailAs(accountInfo.email) || + // lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || + // lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) + // ); const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( { accountInfo: { email }, @@ -325,7 +334,7 @@ function getAPIImplementation() { // isSignUpAllowed will be called as expected. if (authenticatingUser === undefined) { return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; } const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ @@ -358,7 +367,7 @@ function getAPIImplementation() { if (utils_1.isFakeEmail(email) && preAuthChecks.isFirstFactor) { // Fake emails cannot be used as a first factor return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; } const signInResponse = await options.recipeImplementation.signIn({ @@ -369,7 +378,7 @@ function getAPIImplementation() { tenantId, userContext, }); - if (signInResponse.status === "WRONG_CREDENTIALS_ERROR") { + if (signInResponse.status === "INVALID_CREDENTIALS_ERROR") { return signInResponse; } if (signInResponse.status !== "OK") { @@ -690,7 +699,6 @@ function getAPIImplementation() { credential, userContext, }); - // todo decide how to handle these if (updateResponse.status === "INVALID_AUTHENTICATOR_ERROR") { // This should happen only cause of a race condition where the user // might be deleted before token creation and consumption. @@ -698,9 +706,9 @@ function getAPIImplementation() { status: "INVALID_AUTHENTICATOR_ERROR", reason: updateResponse.reason, }; - } else if (updateResponse.status === "WRONG_CREDENTIALS_ERROR") { + } else if (updateResponse.status === "INVALID_CREDENTIALS_ERROR") { return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; } else { // status: "OK" @@ -755,7 +763,6 @@ function getAPIImplementation() { tenantId, userContext, }); - // todo decide how to handle these if (tokenConsumptionResponse.status === "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR") { return tokenConsumptionResponse; } @@ -818,10 +825,12 @@ function getAPIImplementation() { credential, userContext, }); - // todo decide how to handle these - if (createUserResponse.status === "WRONG_CREDENTIALS_ERROR") { - return createUserResponse; - } else if (createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR") { + if ( + createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || + createUserResponse.status === "GENERATED_OPTIONS_NOT_FOUND_ERROR" || + createUserResponse.status === "INVALID_GENERATED_OPTIONS_ERROR" || + createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR" + ) { return createUserResponse; } else if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { // this means that the user already existed and we can just return an invalid diff --git a/lib/build/recipe/webauthn/api/recoverAccount.js b/lib/build/recipe/webauthn/api/recoverAccount.js index 9e63a577b..acb883f39 100644 --- a/lib/build/recipe/webauthn/api/recoverAccount.js +++ b/lib/build/recipe/webauthn/api/recoverAccount.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -23,12 +23,11 @@ const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); const error_1 = __importDefault(require("../error")); async function recoverAccount(apiImplementation, tenantId, options, userContext) { - // Logic as per https://github.com/supertokens/supertokens-node/issues/22#issuecomment-710512442 if (apiImplementation.recoverAccountPOST === undefined) { return false; } const requestBody = await options.req.getJSONBody(); - let webauthnGeneratedOptionsId = await utils_2.validatewebauthnGeneratedOptionsIdOrThrowError( + let webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( requestBody.webauthnGeneratedOptionsId ); let credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); diff --git a/lib/build/recipe/webauthn/api/registerOptions.js b/lib/build/recipe/webauthn/api/registerOptions.js index 6ea9bf0ec..9d6ed7036 100644 --- a/lib/build/recipe/webauthn/api/registerOptions.js +++ b/lib/build/recipe/webauthn/api/registerOptions.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/build/recipe/webauthn/api/signInOptions.js b/lib/build/recipe/webauthn/api/signInOptions.js index 3c04d2808..25034546a 100644 --- a/lib/build/recipe/webauthn/api/signInOptions.js +++ b/lib/build/recipe/webauthn/api/signInOptions.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/build/recipe/webauthn/api/signin.js b/lib/build/recipe/webauthn/api/signin.js index 44cc8bb6f..7a9c59de5 100644 --- a/lib/build/recipe/webauthn/api/signin.js +++ b/lib/build/recipe/webauthn/api/signin.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -22,7 +22,7 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { return false; } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await utils_2.validatewebauthnGeneratedOptionsIdOrThrowError( + const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( requestBody.webauthnGeneratedOptionsId ); const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); diff --git a/lib/build/recipe/webauthn/api/signup.js b/lib/build/recipe/webauthn/api/signup.js index 8eb70b124..c196907c7 100644 --- a/lib/build/recipe/webauthn/api/signup.js +++ b/lib/build/recipe/webauthn/api/signup.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -28,7 +28,7 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { return false; } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await utils_2.validatewebauthnGeneratedOptionsIdOrThrowError( + const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( requestBody.webauthnGeneratedOptionsId ); const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); @@ -63,14 +63,8 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { utils_1.send200Response(options.res, result); } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { throw new error_1.default({ - type: error_1.default.FIELD_ERROR, - payload: [ - { - id: "email", - error: "This email already exists. Please sign in instead.", - }, - ], - message: "Error in input formFields", + type: error_1.default.BAD_INPUT_ERROR, + message: "This email already exists. Please sign in instead.", }); } else { utils_1.send200Response(options.res, result); diff --git a/lib/build/recipe/webauthn/api/utils.d.ts b/lib/build/recipe/webauthn/api/utils.d.ts index 8bd411782..881337429 100644 --- a/lib/build/recipe/webauthn/api/utils.d.ts +++ b/lib/build/recipe/webauthn/api/utils.d.ts @@ -1,5 +1,5 @@ // @ts-nocheck -export declare function validatewebauthnGeneratedOptionsIdOrThrowError( +export declare function validateWebauthnGeneratedOptionsIdOrThrowError( webauthnGeneratedOptionsId: string ): Promise; export declare function validateCredentialOrThrowError(credential: T): Promise; diff --git a/lib/build/recipe/webauthn/api/utils.js b/lib/build/recipe/webauthn/api/utils.js index bfc2cd2e9..ae0a5d6fd 100644 --- a/lib/build/recipe/webauthn/api/utils.js +++ b/lib/build/recipe/webauthn/api/utils.js @@ -5,8 +5,8 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.validateCredentialOrThrowError = exports.validatewebauthnGeneratedOptionsIdOrThrowError = void 0; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +exports.validateCredentialOrThrowError = exports.validateWebauthnGeneratedOptionsIdOrThrowError = void 0; +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -21,13 +21,13 @@ exports.validateCredentialOrThrowError = exports.validatewebauthnGeneratedOption * under the License. */ const error_1 = __importDefault(require("../error")); -async function validatewebauthnGeneratedOptionsIdOrThrowError(webauthnGeneratedOptionsId) { +async function validateWebauthnGeneratedOptionsIdOrThrowError(webauthnGeneratedOptionsId) { if (webauthnGeneratedOptionsId === undefined) { throw newBadRequestError("webauthnGeneratedOptionsId is required"); } return webauthnGeneratedOptionsId; } -exports.validatewebauthnGeneratedOptionsIdOrThrowError = validatewebauthnGeneratedOptionsIdOrThrowError; +exports.validateWebauthnGeneratedOptionsIdOrThrowError = validateWebauthnGeneratedOptionsIdOrThrowError; async function validateCredentialOrThrowError(credential) { if (credential === undefined) { throw newBadRequestError("credential is required"); diff --git a/lib/build/recipe/webauthn/constants.js b/lib/build/recipe/webauthn/constants.js index 2e86a4747..2bb0063da 100644 --- a/lib/build/recipe/webauthn/constants.js +++ b/lib/build/recipe/webauthn/constants.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/build/recipe/webauthn/core-mock.js b/lib/build/recipe/webauthn/core-mock.js index 723cce9f8..c0ac30bd0 100644 --- a/lib/build/recipe/webauthn/core-mock.js +++ b/lib/build/recipe/webauthn/core-mock.js @@ -1,39 +1,67 @@ "use strict"; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMockQuerier = void 0; const querier_1 = require("../../querier"); +const server_1 = require("@simplewebauthn/server"); +const crypto_1 = __importDefault(require("crypto")); +const db = { + generatedOptions: {}, +}; +const writeDb = (table, key, value) => { + db[table][key] = value; +}; +// const readDb = (table: keyof typeof db, key: string) => { +// return db[table][key]; +// }; const getMockQuerier = (recipeId) => { const querier = querier_1.Querier.getNewInstanceOrThrowError(recipeId); - const sendPostRequest = async (path, body, userContext) => { - console.log("body", body); - console.log("userContext", userContext); + const sendPostRequest = async (path, body, _userContext) => { if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { - // @ts-ignore - return { - status: "OK", - webauthnGeneratedOptionsId: "7ab03f6a-61b8-4f65-992f-b8b8469bc18f", - rp: { id: "example.com", name: "Example App" }, - user: { id: "dummy-user-id", name: "user@example.com", displayName: "User" }, - challenge: "dummy-challenge", - timeout: 60000, - excludeCredentials: [], - attestation: "none", - pubKeyCredParams: [{ alg: -7, type: "public-key" }], + const registrationOptions = await server_1.generateRegistrationOptions({ + rpID: body.relyingPartyId, + rpName: body.relyingPartyName, + userName: body.email, + timeout: body.timeout, + attestationType: body.attestation || "none", authenticatorSelection: { - requireResidentKey: false, - residentKey: "preferred", - userVerification: "preferred", + userVerification: body.userVerification || "preferred", + requireResidentKey: body.requireResidentKey || false, + residentKey: body.residentKey || "required", }, - }; + supportedAlgorithmIDs: body.supportedAlgorithmIDs || [-8, -7, -257], + userDisplayName: body.displayName || body.email, + }); + const id = crypto_1.default.randomUUID(); + writeDb( + "generatedOptions", + id, + Object.assign(Object.assign({}, registrationOptions), { + id, + origin: body.origin, + tenantId: body.tenantId, + }) + ); + // @ts-ignore + return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, registrationOptions); } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + const signInOptions = await server_1.generateAuthenticationOptions({ + rpID: body.relyingPartyId, + timeout: body.timeout, + userVerification: body.userVerification || "preferred", + }); + const id = crypto_1.default.randomUUID(); + writeDb( + "generatedOptions", + id, + Object.assign(Object.assign({}, signInOptions), { id, origin: body.origin, tenantId: body.tenantId }) + ); // @ts-ignore - return { - status: "OK", - webauthnGeneratedOptionsId: "18302759-87c6-4d88-990d-c7cab43653cc", - challenge: "dummy-signin-challenge", - timeout: 60000, - userVerification: "preferred", - }; + return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, signInOptions); // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token")) { // // @ts-ignore // return { diff --git a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts new file mode 100644 index 000000000..b4ac552b5 --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +import { TypeWebauthnEmailDeliveryInput } from "../../../types"; +import { NormalisedAppinfo, UserContext } from "../../../../../types"; +import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; +export default class BackwardCompatibilityService implements EmailDeliveryInterface { + private isInServerlessEnv; + private appInfo; + constructor(appInfo: NormalisedAppinfo, isInServerlessEnv: boolean); + sendEmail: ( + input: TypeWebauthnEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; +} diff --git a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js new file mode 100644 index 000000000..8c1477788 --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js @@ -0,0 +1,66 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../../../utils"); +async function createAndSendEmailUsingSupertokensService(input) { + if (utils_1.isTestEnv()) { + return; + } + const result = await utils_1.postWithFetch( + "https://api.supertokens.io/0/st/auth/webauthn/recover", + { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, + { + email: input.user.email, + appName: input.appInfo.appName, + recoverAccountURL: input.recoverAccountLink, + }, + { + successLog: `Email sent to ${input.user.email}`, + errorLogHeader: "Error sending webauthn recover account email", + } + ); + if ("error" in result) { + throw result.error; + } + if (result.resp && result.resp.status >= 400) { + if (result.resp.body.err) { + /** + * if the error is thrown from API, the response object + * will be of type `{err: string}` + */ + throw new Error(result.resp.body.err); + } else { + throw new Error(`Request failed with status code ${result.resp.status}`); + } + } +} +class BackwardCompatibilityService { + constructor(appInfo, isInServerlessEnv) { + this.sendEmail = async (input) => { + // we add this here cause the user may have overridden the sendEmail function + // to change the input email and if we don't do this, the input email + // will get reset by the getUserById call above. + try { + if (!this.isInServerlessEnv) { + createAndSendEmailUsingSupertokensService({ + appInfo: this.appInfo, + user: input.user, + recoverAccountLink: input.recoverAccountLink, + }).catch((_) => {}); + } else { + // see https://github.com/supertokens/supertokens-node/pull/135 + await createAndSendEmailUsingSupertokensService({ + appInfo: this.appInfo, + user: input.user, + recoverAccountLink: input.recoverAccountLink, + }); + } + } catch (_) {} + }; + this.isInServerlessEnv = isInServerlessEnv; + this.appInfo = appInfo; + } +} +exports.default = BackwardCompatibilityService; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/index.d.ts new file mode 100644 index 000000000..4de04d983 --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/index.d.ts @@ -0,0 +1,3 @@ +// @ts-nocheck +import SMTP from "./smtp"; +export declare let SMTPService: typeof SMTP; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/index.js b/lib/build/recipe/webauthn/emaildelivery/services/index.js new file mode 100644 index 000000000..91700aeaf --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/index.js @@ -0,0 +1,24 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SMTPService = void 0; +const smtp_1 = __importDefault(require("./smtp")); +exports.SMTPService = smtp_1.default; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts new file mode 100644 index 000000000..d792d04cb --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +import { ServiceInterface, TypeInput } from "../../../../../ingredients/emaildelivery/services/smtp"; +import { TypeWebauthnEmailDeliveryInput } from "../../../types"; +import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; +import { UserContext } from "../../../../../types"; +export default class SMTPService implements EmailDeliveryInterface { + serviceImpl: ServiceInterface; + constructor(config: TypeInput); + sendEmail: ( + input: TypeWebauthnEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; +} diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js new file mode 100644 index 000000000..dedcbe33f --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js @@ -0,0 +1,37 @@ +"use strict"; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const nodemailer_1 = require("nodemailer"); +const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); +const serviceImplementation_1 = require("./serviceImplementation"); +class SMTPService { + constructor(config) { + this.sendEmail = async (input) => { + let content = await this.serviceImpl.getContent(input); + await this.serviceImpl.sendRawEmail( + Object.assign(Object.assign({}, content), { userContext: input.userContext }) + ); + }; + const transporter = nodemailer_1.createTransport({ + host: config.smtpSettings.host, + port: config.smtpSettings.port, + auth: { + user: config.smtpSettings.authUsername || config.smtpSettings.from.email, + pass: config.smtpSettings.password, + }, + secure: config.smtpSettings.secure, + }); + let builder = new supertokens_js_override_1.default( + serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) + ); + if (config.override !== undefined) { + builder = builder.override(config.override); + } + this.serviceImpl = builder.build(); + } +} +exports.default = SMTPService; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts new file mode 100644 index 000000000..3f85c452a --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts @@ -0,0 +1,7 @@ +// @ts-nocheck +import { TypeWebauthnRecoverAccountEmailDeliveryInput } from "../../../types"; +import { GetContentResult } from "../../../../../ingredients/emaildelivery/services/smtp"; +export default function getRecoverAccountEmailContent( + input: TypeWebauthnRecoverAccountEmailDeliveryInput +): GetContentResult; +export declare function getRecoverAccountEmailHTML(appName: string, email: string, resetLink: string): string; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js new file mode 100644 index 000000000..39185d372 --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js @@ -0,0 +1,934 @@ +"use strict"; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getRecoverAccountEmailHTML = void 0; +const supertokens_1 = __importDefault(require("../../../../../supertokens")); +function getRecoverAccountEmailContent(input) { + let supertokens = supertokens_1.default.getInstanceOrThrowError(); + let appName = supertokens.appInfo.appName; + let body = getRecoverAccountEmailHTML(appName, input.user.email, input.recoverAccountLink); + return { + body, + toEmail: input.user.email, + subject: "Account recovery instructions", + isHtml: true, + }; +} +exports.default = getRecoverAccountEmailContent; +function getRecoverAccountEmailHTML(appName, email, resetLink) { + return ` + + + + + + + + *|MC:SUBJECT|* + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + +
+ + + + + +
+ +
+ + + + + +
+ + + + + + +
+ + +
+
+ +

+ An account recovery request for your account on + ${appName} has been received. +

+ + +
+
+

+ Alternatively, you can directly paste this link + in your browser
+ ${resetLink} +

+
+
+ + + + +
+ + + + + + +
+

+ This email is meant for ${email} +

+
+
+ +
+ + + + + +
+ +
+ +
+
+ + + + `; +} +exports.getRecoverAccountEmailHTML = getRecoverAccountEmailHTML; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts new file mode 100644 index 000000000..5eec27f1c --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts @@ -0,0 +1,11 @@ +// @ts-nocheck +import { TypeWebauthnEmailDeliveryInput } from "../../../../types"; +import { Transporter } from "nodemailer"; +import { ServiceInterface } from "../../../../../../ingredients/emaildelivery/services/smtp"; +export declare function getServiceImplementation( + transporter: Transporter, + from: { + name: string; + email: string; + } +): ServiceInterface; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js new file mode 100644 index 000000000..f097d8647 --- /dev/null +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js @@ -0,0 +1,48 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getServiceImplementation = void 0; +const recoverAccount_1 = __importDefault(require("../recoverAccount")); +function getServiceImplementation(transporter, from) { + return { + sendRawEmail: async function (input) { + if (input.isHtml) { + await transporter.sendMail({ + from: `${from.name} <${from.email}>`, + to: input.toEmail, + subject: input.subject, + html: input.body, + }); + } else { + await transporter.sendMail({ + from: `${from.name} <${from.email}>`, + to: input.toEmail, + subject: input.subject, + text: input.body, + }); + } + }, + getContent: async function (input) { + return recoverAccount_1.default(input); + }, + }; +} +exports.getServiceImplementation = getServiceImplementation; diff --git a/lib/build/recipe/webauthn/error.d.ts b/lib/build/recipe/webauthn/error.d.ts index d4dc2cf9b..486758b61 100644 --- a/lib/build/recipe/webauthn/error.d.ts +++ b/lib/build/recipe/webauthn/error.d.ts @@ -1,20 +1,5 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - static FIELD_ERROR: "FIELD_ERROR"; - constructor( - options: - | { - type: "FIELD_ERROR"; - payload: { - id: string; - error: string; - }[]; - message: string; - } - | { - type: "BAD_INPUT_ERROR"; - message: string; - } - ); + constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); } diff --git a/lib/build/recipe/webauthn/error.js b/lib/build/recipe/webauthn/error.js index 9cce55615..7de05e126 100644 --- a/lib/build/recipe/webauthn/error.js +++ b/lib/build/recipe/webauthn/error.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -27,4 +27,3 @@ class SessionError extends error_1.default { } } exports.default = SessionError; -SessionError.FIELD_ERROR = "FIELD_ERROR"; diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts index fc33da5b1..10ece7871 100644 --- a/lib/build/recipe/webauthn/index.d.ts +++ b/lib/build/recipe/webauthn/index.d.ts @@ -1,24 +1,63 @@ // @ts-nocheck import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { RecipeInterface, APIOptions, APIInterface, TypeWebauthnEmailDeliveryInput, CredentialPayload } from "./types"; +import { + RecipeInterface, + APIInterface, + APIOptions, + TypeWebauthnEmailDeliveryInput, + CredentialPayload, + UserVerification, + ResidentKey, + Attestation, +} from "./types"; import RecipeUserId from "../../recipeUserId"; import { SessionContainerInterface } from "../session/types"; import { User } from "../../types"; +import { BaseRequest } from "../../framework"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static registerOptions( - email: string | undefined, - recoverAccountToken: string | undefined, - relyingPartyId: string, - relyingPartyName: string, - origin: string, - timeout: number, - attestation: "none" | "indirect" | "direct" | "enterprise" | undefined, - tenantId: string, - userContext: Record - ): Promise< + static registerOptions({ + requireResidentKey, + residentKey, + userVerification, + attestation, + supportedAlgorithmIds, + timeout, + tenantId, + userContext, + ...rest + }: { + requireResidentKey?: boolean; + residentKey?: ResidentKey; + userVerification?: UserVerification; + attestation?: Attestation; + supportedAlgorithmIds?: number[]; + timeout?: number; + tenantId?: string; + userContext?: Record; + } & ( + | { + relyingPartyId: string; + relyingPartyName: string; + origin: string; + } + | { + request: BaseRequest; + relyingPartyId?: string; + relyingPartyName?: string; + origin?: string; + } + ) & + ( + | { + email: string; + } + | { + recoverAccountToken: string; + } + )): Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; @@ -45,8 +84,8 @@ export default class Wrapper { }[]; authenticatorSelection: { requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; + residentKey: ResidentKey; + userVerification: UserVerification; }; } | { @@ -56,55 +95,108 @@ export default class Wrapper { status: "INVALID_EMAIL_ERROR"; err: string; } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } >; - static signInOptions( - relyingPartyId: string, - origin: string, - timeout: number, - tenantId: string, - userContext: Record - ): Promise< + static signInOptions({ + email, + tenantId, + userVerification, + timeout, + userContext, + ...rest + }: { + email?: string; + timeout?: number; + userVerification?: UserVerification; + tenantId?: string; + userContext?: Record; + } & ( + | { + relyingPartyId: string; + origin: string; + } + | { + request: BaseRequest; + relyingPartyId?: string; + origin?: string; + } + )): Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; challenge: string; timeout: number; - userVerification: "required" | "preferred" | "discouraged"; + userVerification: UserVerification; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_GENERATED_OPTIONS_ERROR"; } >; - static signIn( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - session?: undefined, - userContext?: Record - ): Promise< + static signUp({ + tenantId, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }: { + tenantId?: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + userContext?: Record; + session?: SessionContainerInterface; + }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + | { + 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"; } >; - static signIn( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - session: SessionContainerInterface, - userContext?: Record - ): Promise< + static signIn({ + tenantId, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }: { + tenantId?: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + session?: SessionContainerInterface; + userContext?: Record; + }): Promise< | { status: "OK"; user: User; recipeUserId: RecipeUserId; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; } | { status: "LINKING_TO_SESSION_USER_FAILED"; @@ -115,17 +207,22 @@ export default class Wrapper { | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; } >; - static verifyCredentials( - tenantId: string, - webauthnGeneratedOptionsId: string, - credential: CredentialPayload, - userContext?: Record - ): Promise< + static verifyCredentials({ + tenantId, + webauthnGeneratedOptionsId, + credential, + userContext, + }: { + tenantId?: string; + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + userContext?: Record; + }): Promise< | { status: "OK"; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; } >; /** @@ -139,12 +236,17 @@ export default class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static generateRecoverAccountToken( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise< + static generateRecoverAccountToken({ + tenantId, + userId, + email, + userContext, + }: { + tenantId?: string; + userId: string; + email: string; + userContext?: Record; + }): Promise< | { status: "OK"; token: string; @@ -153,26 +255,48 @@ export default class Wrapper { status: "UNKNOWN_USER_ID_ERROR"; } >; - static recoverAccount( - tenantId: string, - webauthnGeneratedOptionsId: string, - token: string, - credential: CredentialPayload, - userContext?: Record - ): Promise< + static recoverAccount({ + tenantId, + webauthnGeneratedOptionsId, + token, + credential, + userContext, + }: { + tenantId?: string; + webauthnGeneratedOptionsId: string; + token: string; + credential: CredentialPayload; + userContext?: Record; + }): Promise< + | { + status: "OK"; + } | { - status: "OK" | "WRONG_CREDENTIALS_ERROR" | "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; } | { status: "INVALID_AUTHENTICATOR_ERROR"; failureReason: string; } >; - static consumeRecoverAccountToken( - tenantId: string, - token: string, - userContext?: Record - ): Promise< + static consumeRecoverAccountToken({ + tenantId, + token, + userContext, + }: { + tenantId?: string; + token: string; + userContext?: Record; + }): Promise< | { status: "OK"; email: string; @@ -182,30 +306,45 @@ export default class Wrapper { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; } >; - static registerCredential(input: { + static registerCredential({ + recipeUserId, + webauthnGeneratedOptionsId, + credential, + userContext, + }: { recipeUserId: RecipeUserId; - tenantId: string; webauthnGeneratedOptionsId: string; credential: CredentialPayload; userContext?: Record; }): Promise< | { - status: "OK" | "WRONG_CREDENTIALS_ERROR"; + status: "OK"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_GENERATED_OPTIONS_ERROR"; } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; } >; - static createRecoverAccountLink( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise< + static createRecoverAccountLink({ + tenantId, + userId, + email, + userContext, + }: { + tenantId?: string; + userId: string; + email: string; + userContext?: Record; + }): Promise< | { status: "OK"; link: string; @@ -214,12 +353,17 @@ export default class Wrapper { status: "UNKNOWN_USER_ID_ERROR"; } >; - static sendRecoverAccountEmail( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise<{ + static sendRecoverAccountEmail({ + tenantId, + userId, + email, + userContext, + }: { + tenantId?: string; + userId: string; + email: string; + userContext?: Record; + }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR"; }>; static sendEmail( diff --git a/lib/build/recipe/webauthn/index.js b/lib/build/recipe/webauthn/index.js index 041a297b2..35d01c9e8 100644 --- a/lib/build/recipe/webauthn/index.js +++ b/lib/build/recipe/webauthn/index.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -13,6 +13,17 @@ * License for the specific language governing permissions and limitations * under the License. */ +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; var __importDefault = (this && this.__importDefault) || function (mod) { @@ -29,69 +40,182 @@ const __1 = require("../.."); const utils_2 = require("../../utils"); const constants_2 = require("./constants"); class Wrapper { - static async registerOptions( - email, - recoverAccountToken, - relyingPartyId, - relyingPartyName, - origin, - timeout, - attestation = "none", - tenantId, - userContext - ) { - let payload = email ? { email } : recoverAccountToken ? { recoverAccountToken } : null; - if (!payload) { + static async registerOptions(_a) { + var { + requireResidentKey = constants_2.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, + residentKey = constants_2.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, + userVerification = constants_2.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, + attestation = constants_2.DEFAULT_REGISTER_OPTIONS_ATTESTATION, + supportedAlgorithmIds = constants_2.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, + timeout = constants_2.DEFAULT_REGISTER_OPTIONS_TIMEOUT, + tenantId = constants_1.DEFAULT_TENANT_ID, + userContext, + } = _a, + rest = __rest(_a, [ + "requireResidentKey", + "residentKey", + "userVerification", + "attestation", + "supportedAlgorithmIds", + "timeout", + "tenantId", + "userContext", + ]); + let emailOrRecoverAccountToken; + if ("email" in rest || "recoverAccountToken" in rest) { + if ("email" in rest) { + emailOrRecoverAccountToken = { email: rest.email }; + } else { + emailOrRecoverAccountToken = { recoverAccountToken: rest.recoverAccountToken }; + } + } else { return { status: "INVALID_EMAIL_ERROR", err: "Email is missing" }; } - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions( - Object.assign( - Object.assign( - { - requireResidentKey: constants_2.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, - residentKey: constants_2.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, - userVerification: constants_2.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, - supportedAlgorithmIds: constants_2.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, - }, - payload - ), - { - relyingPartyId, - relyingPartyName, - origin, - timeout, - attestation, - tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + let relyingPartyId; + let relyingPartyName; + let origin; + if ("request" in rest) { + origin = + rest.origin || + (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); + relyingPartyId = + rest.relyingPartyId || + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); + relyingPartyName = + rest.relyingPartyName || + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyName({ + tenantId: tenantId, userContext: utils_2.getUserContext(userContext), - } - ) + })); + } else { + if (!rest.origin) { + throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "Origin missing from the input" }); + } + if (!rest.relyingPartyId) { + throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); + } + if (!rest.relyingPartyName) { + throw new exports.Error({ + type: "BAD_INPUT_ERROR", + message: "RelyingPartyName missing from the input", + }); + } + origin = rest.origin; + relyingPartyId = rest.relyingPartyId; + relyingPartyName = rest.relyingPartyName; + } + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions( + Object.assign(Object.assign({}, emailOrRecoverAccountToken), { + requireResidentKey, + residentKey, + userVerification, + supportedAlgorithmIds, + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation, + tenantId, + userContext: utils_2.getUserContext(userContext), + }) ); } - static signInOptions(relyingPartyId, origin, timeout, tenantId, userContext) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ - userVerification: constants_2.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, + static async signInOptions(_a) { + var { + email, + tenantId = constants_1.DEFAULT_TENANT_ID, + userVerification = constants_2.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, + timeout = constants_2.DEFAULT_SIGNIN_OPTIONS_TIMEOUT, + userContext, + } = _a, + rest = __rest(_a, ["email", "tenantId", "userVerification", "timeout", "userContext"]); + let origin; + let relyingPartyId; + if ("request" in rest) { + relyingPartyId = + rest.relyingPartyId || + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); + origin = + rest.origin || + (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); + } else { + if (!rest.relyingPartyId) { + throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); + } + if (!rest.origin) { + throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "Origin missing from the input" }); + } + relyingPartyId = rest.relyingPartyId; + origin = rest.origin; + } + return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ + email, relyingPartyId, origin, timeout, - tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + tenantId, + userVerification, userContext: utils_2.getUserContext(userContext), }); } - static signIn(tenantId, webauthnGeneratedOptionsId, credential, session, userContext) { + static signUp({ + tenantId = constants_1.DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signUp({ + webauthnGeneratedOptionsId, + credential, + session, + shouldTryLinkingWithSessionUser: !!session, + tenantId, + userContext: utils_2.getUserContext(userContext), + }); + } + static signIn({ + tenantId = constants_1.DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signIn({ webauthnGeneratedOptionsId, credential, session, shouldTryLinkingWithSessionUser: !!session, - tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: utils_2.getUserContext(userContext), }); } - static async verifyCredentials(tenantId, webauthnGeneratedOptionsId, credential, userContext) { + static async verifyCredentials({ + tenantId = constants_1.DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + userContext, + }) { const resp = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ webauthnGeneratedOptionsId, credential, - tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: utils_2.getUserContext(userContext), }); // Here we intentionally skip the user and recipeUserId props, because we do not want apps to accidentally use this to sign in @@ -110,16 +234,22 @@ class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static generateRecoverAccountToken(tenantId, userId, email, userContext) { + static generateRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.generateRecoverAccountToken({ userId, email, - tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: utils_2.getUserContext(userContext), }); } - static async recoverAccount(tenantId, webauthnGeneratedOptionsId, token, credential, userContext) { - const consumeResp = await Wrapper.consumeRecoverAccountToken(tenantId, token, userContext); + static async recoverAccount({ + tenantId = constants_1.DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + token, + credential, + userContext, + }) { + const consumeResp = await Wrapper.consumeRecoverAccountToken({ tenantId, token, userContext }); if (consumeResp.status !== "OK") { return consumeResp; } @@ -127,7 +257,6 @@ class Wrapper { recipeUserId: new recipeUserId_1.default(consumeResp.userId), webauthnGeneratedOptionsId, credential, - tenantId, userContext, }); if (result.status === "INVALID_AUTHENTICATOR_ERROR") { @@ -140,39 +269,40 @@ class Wrapper { status: result.status, }; } - static consumeRecoverAccountToken(tenantId, token, userContext) { + static consumeRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, token, userContext }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeRecoverAccountToken({ token, - tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + tenantId, userContext: utils_2.getUserContext(userContext), }); } - static registerCredential(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.registerCredential( - Object.assign(Object.assign({}, input), { userContext: utils_2.getUserContext(input.userContext) }) - ); + static registerCredential({ recipeUserId, webauthnGeneratedOptionsId, credential, userContext }) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerCredential({ + recipeUserId, + webauthnGeneratedOptionsId, + credential, + userContext: utils_2.getUserContext(userContext), + }); } - static async createRecoverAccountLink(tenantId, userId, email, userContext) { - const ctx = utils_2.getUserContext(userContext); - let token = await this.generateRecoverAccountToken(tenantId, userId, email, ctx); + static async createRecoverAccountLink({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { + let token = await this.generateRecoverAccountToken({ tenantId, userId, email, userContext }); if (token.status === "UNKNOWN_USER_ID_ERROR") { return token; } + const ctx = utils_2.getUserContext(userContext); const recipeInstance = recipe_1.default.getInstanceOrThrowError(); return { status: "OK", link: utils_1.getRecoverAccountLink({ appInfo: recipeInstance.getAppInfo(), token: token.token, - tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, + tenantId, request: __1.getRequestFromUserContext(ctx), userContext: ctx, }), }; } - static async sendRecoverAccountEmail(tenantId, userId, email, userContext) { + static async sendRecoverAccountEmail({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { const user = await __1.getUser(userId, userContext); if (!user) { return { status: "UNKNOWN_USER_ID_ERROR" }; @@ -181,7 +311,7 @@ class Wrapper { if (!loginMethod) { return { status: "UNKNOWN_USER_ID_ERROR" }; } - let link = await this.createRecoverAccountLink(tenantId, userId, email, userContext); + let link = await this.createRecoverAccountLink({ tenantId, userId, email, userContext }); if (link.status === "UNKNOWN_USER_ID_ERROR") { return link; } @@ -204,7 +334,7 @@ class Wrapper { let recipeInstance = recipe_1.default.getInstanceOrThrowError(); return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail( Object.assign(Object.assign({}, input), { - tenantId: input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId, + tenantId: input.tenantId || constants_1.DEFAULT_TENANT_ID, userContext: utils_2.getUserContext(input.userContext), }) ); diff --git a/lib/build/recipe/webauthn/recipe.d.ts b/lib/build/recipe/webauthn/recipe.d.ts index 12c1bf86c..1eecfffbe 100644 --- a/lib/build/recipe/webauthn/recipe.d.ts +++ b/lib/build/recipe/webauthn/recipe.d.ts @@ -37,7 +37,7 @@ export default class Recipe extends RecipeModule { _method: HTTPMethod, userContext: UserContext ) => Promise; - handleError: (err: STError, _request: BaseRequest, response: BaseResponse) => Promise; + handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; } diff --git a/lib/build/recipe/webauthn/recipe.js b/lib/build/recipe/webauthn/recipe.js index f3a82ef7c..6d76efb0d 100644 --- a/lib/build/recipe/webauthn/recipe.js +++ b/lib/build/recipe/webauthn/recipe.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -121,16 +121,9 @@ class Recipe extends recipeModule_1.default { return await emailExists_1.default(this.apiImpl, tenantId, options, userContext); } else return false; }; - this.handleError = async (err, _request, response) => { + this.handleError = async (err, _request, _response) => { if (err.fromRecipe === Recipe.RECIPE_ID) { - if (err.type === error_1.default.FIELD_ERROR) { - return utils_2.send200Response(response, { - status: "FIELD_ERROR", - formFields: err.payload, - }); - } else { - throw err; - } + throw err; } else { throw err; } @@ -164,7 +157,6 @@ class Recipe extends recipeModule_1.default { ingredients.emailDelivery === undefined ? new emaildelivery_1.default(this.config.getEmailDeliveryConfig(this.isInServerlessEnv)) : ingredients.emailDelivery; - // todo check correctness postSuperTokensInitCallbacks_1.PostSuperTokensInitCallbacks.addPostInitCallback(() => { const mfaInstance = recipe_1.default.getInstance(); if (mfaInstance !== undefined) { diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js index f664098c1..dc345bee9 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.js +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -60,6 +60,7 @@ const constants_1 = require("../multitenancy/constants"); const user_1 = require("../../user"); const authUtils_1 = require("../../authUtils"); const jose = __importStar(require("jose")); +const utils_1 = require("../thirdparty/utils"); function getRecipeInterface(querier, getWebauthnConfig) { return { registerOptions: async function (_a) { @@ -89,8 +90,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { if (emailInput !== undefined) { email = emailInput; } else if (recoverAccountTokenInput !== undefined) { - // todo check if should decode using Core or using sdk; atm decided on usinng the sdk so to not make another roundtrip to the server - // the actual verification of the token will be done during consumeRecoverAccountToken + // the actual validation of the token will be done during consumeRecoverAccountToken let decoded; try { decoded = await jose.decodeJwt(recoverAccountTokenInput); @@ -115,6 +115,18 @@ function getRecipeInterface(querier, getWebauthnConfig) { err, }; } + // set a nice default display name + // if the user has a fake email, we use the username part of the email instead (which should be the recipe user id) + let displayName; + if (rest.displayName) { + displayName = rest.displayName; + } else { + if (utils_1.isFakeEmail(email)) { + displayName = email.split("@")[0]; + } else { + displayName = email; + } + } return await querier.sendPostRequest( new normalisedURLPath_1.default( `/${ @@ -123,6 +135,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { ), { email, + displayName, relyingPartyName, relyingPartyId, origin, @@ -261,7 +274,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { }; } return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; }, createNewRecipeUser: async function (input) { @@ -336,7 +349,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { return response; } return { - status: "WRONG_CREDENTIALS_ERROR", + status: "INVALID_CREDENTIALS_ERROR", }; }, getUserFromRecoverAccountToken: async function ({ token, tenantId, userContext }) { diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts index ff545f52b..d58b6c2cb 100644 --- a/lib/build/recipe/webauthn/types.d.ts +++ b/lib/build/recipe/webauthn/types.d.ts @@ -10,8 +10,8 @@ import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; export declare type TypeNormalisedInput = { - relyingPartyId: TypeNormalisedInputRelyingPartyId; - relyingPartyName: TypeNormalisedInputRelyingPartyName; + getRelyingPartyId: TypeNormalisedInputRelyingPartyId; + getRelyingPartyName: TypeNormalisedInputRelyingPartyName; getOrigin: TypeNormalisedInputGetOrigin; getEmailDeliveryConfig: ( isInServerlessEnv: boolean @@ -45,8 +45,8 @@ export declare type TypeNormalisedInputValidateEmailAddress = ( ) => Promise | string | undefined; export declare type TypeInput = { emailDelivery?: EmailDeliveryTypeInput; - relyingPartyId?: TypeInputRelyingPartyId; - relyingPartyName?: TypeInputRelyingPartyName; + getRelyingPartyId?: TypeInputRelyingPartyId; + getRelyingPartyName?: TypeInputRelyingPartyName; validateEmailAddress?: TypeInputValidateEmailAddress; getOrigin?: TypeInputGetOrigin; override?: { @@ -73,16 +73,20 @@ export declare type TypeInputValidateEmailAddress = ( tenantId: string ) => Promise | string | undefined; declare type Base64URLString = string; +export declare type ResidentKey = "required" | "preferred" | "discouraged"; +export declare type UserVerification = "required" | "preferred" | "discouraged"; +export declare type Attestation = "none" | "indirect" | "direct" | "enterprise"; export declare type RecipeInterface = { registerOptions( input: { relyingPartyId: string; relyingPartyName: string; + displayName?: string; origin: string; requireResidentKey: boolean | undefined; - residentKey: "required" | "preferred" | "discouraged" | undefined; - userVerification: "required" | "preferred" | "discouraged" | undefined; - attestation: "none" | "indirect" | "direct" | "enterprise" | undefined; + residentKey: ResidentKey | undefined; + userVerification: UserVerification | undefined; + attestation: Attestation | undefined; supportedAlgorithmIds: number[] | undefined; timeout: number | undefined; tenantId: string; @@ -115,15 +119,15 @@ export declare type RecipeInterface = { type: "public-key"; transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; + attestation: Attestation; pubKeyCredParams: { alg: number; type: "public-key"; }[]; authenticatorSelection: { requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; + residentKey: ResidentKey; + userVerification: UserVerification; }; } | { @@ -133,12 +137,15 @@ export declare type RecipeInterface = { status: "INVALID_EMAIL_ERROR"; err: string; } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } >; signInOptions(input: { email?: string; relyingPartyId: string; origin: string; - userVerification: "required" | "preferred" | "discouraged" | undefined; + userVerification: UserVerification | undefined; timeout: number | undefined; tenantId: string; userContext: UserContext; @@ -148,10 +155,10 @@ export declare type RecipeInterface = { webauthnGeneratedOptionsId: string; challenge: string; timeout: number; - userVerification: "required" | "preferred" | "discouraged"; + userVerification: UserVerification; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_GENERATED_OPTIONS_ERROR"; } >; signUp(input: { @@ -171,7 +178,13 @@ export declare type RecipeInterface = { status: "EMAIL_ALREADY_EXISTS_ERROR"; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; } | { status: "INVALID_AUTHENTICATOR_ERROR"; @@ -200,7 +213,7 @@ export declare type RecipeInterface = { recipeUserId: RecipeUserId; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; } | { status: "LINKING_TO_SESSION_USER_FAILED"; @@ -223,7 +236,7 @@ export declare type RecipeInterface = { recipeUserId: RecipeUserId; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; } >; createNewRecipeUser(input: { @@ -238,7 +251,13 @@ export declare type RecipeInterface = { recipeUserId: RecipeUserId; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; } | { status: "INVALID_AUTHENTICATOR_ERROR"; @@ -291,7 +310,13 @@ export declare type RecipeInterface = { status: "OK"; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; } | { status: "INVALID_AUTHENTICATOR_ERROR"; @@ -357,7 +382,7 @@ export declare type RecipeInterface = { }; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; } >; getUserFromRecoverAccountToken(input: { @@ -497,8 +522,8 @@ export declare type APIInterface = { }[]; authenticatorSelection: { requireResidentKey: boolean; - residentKey: "required" | "preferred" | "discouraged"; - userVerification: "required" | "preferred" | "discouraged"; + residentKey: ResidentKey; + userVerification: UserVerification; }; } | GeneralErrorResponse @@ -509,6 +534,9 @@ export declare type APIInterface = { status: "INVALID_EMAIL_ERROR"; err: string; } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } >); signInOptionsPOST: | undefined @@ -523,11 +551,11 @@ export declare type APIInterface = { webauthnGeneratedOptionsId: string; challenge: string; timeout: number; - userVerification: "required" | "preferred" | "discouraged"; + userVerification: UserVerification; } | GeneralErrorResponse | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_GENERATED_OPTIONS_ERROR"; } >); signUpPOST: @@ -552,15 +580,21 @@ export declare type APIInterface = { reason: string; } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_GENERATED_OPTIONS_ERROR"; } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } >); signInPOST: | undefined @@ -584,7 +618,7 @@ export declare type APIInterface = { reason: string; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; } >); generateRecoverAccountTokenPOST: @@ -624,7 +658,13 @@ export declare type APIInterface = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; } | { - status: "WRONG_CREDENTIALS_ERROR"; + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; } | { status: "INVALID_AUTHENTICATOR_ERROR"; diff --git a/lib/build/recipe/webauthn/types.js b/lib/build/recipe/webauthn/types.js index a098ca1d7..9f1237319 100644 --- a/lib/build/recipe/webauthn/types.js +++ b/lib/build/recipe/webauthn/types.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. diff --git a/lib/build/recipe/webauthn/utils.d.ts b/lib/build/recipe/webauthn/utils.d.ts index e4492d9f0..b37ca89ff 100644 --- a/lib/build/recipe/webauthn/utils.d.ts +++ b/lib/build/recipe/webauthn/utils.d.ts @@ -4,7 +4,7 @@ import { TypeInput, TypeNormalisedInput } from "./types"; import { NormalisedAppinfo, UserContext } from "../../types"; import { BaseRequest } from "../../framework"; export declare function validateAndNormaliseUserInput( - recipeInstance: Recipe, + _: Recipe, appInfo: NormalisedAppinfo, config?: TypeInput ): TypeNormalisedInput; diff --git a/lib/build/recipe/webauthn/utils.js b/lib/build/recipe/webauthn/utils.js index 191551342..a6ef86349 100644 --- a/lib/build/recipe/webauthn/utils.js +++ b/lib/build/recipe/webauthn/utils.js @@ -1,5 +1,5 @@ "use strict"; -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -13,27 +13,28 @@ * License for the specific language governing permissions and limitations * under the License. */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRecoverAccountLink = exports.defaultEmailValidator = exports.validateAndNormaliseUserInput = void 0; -function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { - let relyingPartyId = validateAndNormaliseRelyingPartyIdConfig( - recipeInstance, +const backwardCompatibility_1 = __importDefault(require("./emaildelivery/services/backwardCompatibility")); +function validateAndNormaliseUserInput(_, appInfo, config) { + let getRelyingPartyId = validateAndNormaliseRelyingPartyIdConfig( appInfo, - config === null || config === void 0 ? void 0 : config.relyingPartyId + config === null || config === void 0 ? void 0 : config.getRelyingPartyId ); - let relyingPartyName = validateAndNormaliseRelyingPartyNameConfig( - recipeInstance, + let getRelyingPartyName = validateAndNormaliseRelyingPartyNameConfig( appInfo, - config === null || config === void 0 ? void 0 : config.relyingPartyName + config === null || config === void 0 ? void 0 : config.getRelyingPartyName ); let getOrigin = validateAndNormaliseGetOriginConfig( - recipeInstance, appInfo, config === null || config === void 0 ? void 0 : config.getOrigin ); let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig( - recipeInstance, - appInfo, config === null || config === void 0 ? void 0 : config.validateEmailAddress ); let override = Object.assign( @@ -49,15 +50,13 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; - console.log("emailService", emailService); - console.log("isInServerlessEnv", isInServerlessEnv); /** * If the user has not passed even that config, we use the default * createAndSendCustomEmail implementation which calls our supertokens API */ - // if (emailService === undefined) { - // emailService = new BackwardCompatibilityService(appInfo, isInServerlessEnv); - // } + if (emailService === undefined) { + emailService = new backwardCompatibility_1.default(appInfo, isInServerlessEnv); + } return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { /** * if we do @@ -71,55 +70,61 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { * set service at the end */ // todo implemenet this - service: null, + service: emailService, }); } return { override, getOrigin, - relyingPartyId, - relyingPartyName, + getRelyingPartyId, + getRelyingPartyName, validateEmailAddress, getEmailDeliveryConfig, }; } exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; -function validateAndNormaliseRelyingPartyIdConfig(_, __, relyingPartyIdConfig) { +function validateAndNormaliseRelyingPartyIdConfig(normalisedAppinfo, relyingPartyIdConfig) { return (props) => { if (typeof relyingPartyIdConfig === "string") { return Promise.resolve(relyingPartyIdConfig); } else if (typeof relyingPartyIdConfig === "function") { return relyingPartyIdConfig(props); } else { - return Promise.resolve( - __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous() - ); + const urlString = normalisedAppinfo + .getOrigin({ request: props.request, userContext: props.userContext }) + .getAsStringDangerous(); + // should let this throw if the url is invalid + const url = new URL(urlString); + const hostname = url.hostname; + return Promise.resolve(hostname); } }; } -function validateAndNormaliseRelyingPartyNameConfig(_, __, relyingPartyNameConfig) { +function validateAndNormaliseRelyingPartyNameConfig(normalisedAppInfo, relyingPartyNameConfig) { return (props) => { if (typeof relyingPartyNameConfig === "string") { return Promise.resolve(relyingPartyNameConfig); } else if (typeof relyingPartyNameConfig === "function") { return relyingPartyNameConfig(props); } else { - return Promise.resolve(__.appName); + return Promise.resolve(normalisedAppInfo.appName); } }; } -function validateAndNormaliseGetOriginConfig(_, __, getOriginConfig) { +function validateAndNormaliseGetOriginConfig(normalisedAppinfo, getOriginConfig) { return (props) => { if (typeof getOriginConfig === "function") { return getOriginConfig(props); } else { return Promise.resolve( - __.getOrigin({ request: props.request, userContext: props.userContext }).getAsStringDangerous() + normalisedAppinfo + .getOrigin({ request: props.request, userContext: props.userContext }) + .getAsStringDangerous() ); } }; } -function validateAndNormaliseValidateEmailAddressConfig(_, __, validateEmailAddressConfig) { +function validateAndNormaliseValidateEmailAddressConfig(validateEmailAddressConfig) { return (email, tenantId) => { if (typeof validateEmailAddressConfig === "function") { return validateEmailAddressConfig(email, tenantId); diff --git a/lib/ts/recipe/webauthn/core-mock.ts b/lib/ts/recipe/webauthn/core-mock.ts index 7ee65cb53..ecf81c0d8 100644 --- a/lib/ts/recipe/webauthn/core-mock.ts +++ b/lib/ts/recipe/webauthn/core-mock.ts @@ -1,6 +1,18 @@ import NormalisedURLPath from "../../normalisedURLPath"; import { Querier } from "../../querier"; import { UserContext } from "../../types"; +import { generateAuthenticationOptions, generateRegistrationOptions } from "@simplewebauthn/server"; +import crypto from "crypto"; + +const db = { + generatedOptions: {} as Record, +}; +const writeDb = (table: keyof typeof db, key: string, value: any) => { + db[table][key] = value; +}; +// const readDb = (table: keyof typeof db, key: string) => { +// return db[table][key]; +// }; export const getMockQuerier = (recipeId: string) => { const querier = Querier.getNewInstanceOrThrowError(recipeId); @@ -8,34 +20,50 @@ export const getMockQuerier = (recipeId: string) => { const sendPostRequest = async ( path: NormalisedURLPath, body: any, - userContext: UserContext + _userContext: UserContext ): Promise => { if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { + const registrationOptions = await generateRegistrationOptions({ + rpID: body.relyingPartyId, + rpName: body.relyingPartyName, + userName: body.email, + timeout: body.timeout, + attestationType: body.attestation || "none", + authenticatorSelection: { + userVerification: body.userVerification || "preferred", + requireResidentKey: body.requireResidentKey || false, + residentKey: body.residentKey || "required", + }, + supportedAlgorithmIDs: body.supportedAlgorithmIDs || [-8, -7, -257], + userDisplayName: body.displayName || body.email, + }); + const id = crypto.randomUUID(); + writeDb("generatedOptions", id, { + ...registrationOptions, + id, + origin: body.origin, + tenantId: body.tenantId, + }); // @ts-ignore return { status: "OK", - webauthnGeneratedOptionsId: "7ab03f6a-61b8-4f65-992f-b8b8469bc18f", - rp: { id: "example.com", name: "Example App" }, - user: { id: "dummy-user-id", name: "user@example.com", displayName: "User" }, - challenge: "dummy-challenge", - timeout: 60000, - excludeCredentials: [], - attestation: "none", - pubKeyCredParams: [{ alg: -7, type: "public-key" }], - authenticatorSelection: { - requireResidentKey: false, - residentKey: "preferred", - userVerification: "preferred", - }, + webauthnGeneratedOptionsId: id, + ...registrationOptions, }; } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + const signInOptions = await generateAuthenticationOptions({ + rpID: body.relyingPartyId, + timeout: body.timeout, + userVerification: body.userVerification || "preferred", + }); + const id = crypto.randomUUID(); + writeDb("generatedOptions", id, { ...signInOptions, id, origin: body.origin, tenantId: body.tenantId }); + // @ts-ignore return { status: "OK", - webauthnGeneratedOptionsId: "18302759-87c6-4d88-990d-c7cab43653cc", - challenge: "dummy-signin-challenge", - timeout: 60000, - userVerification: "preferred", + webauthnGeneratedOptionsId: id, + ...signInOptions, }; // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token")) { // // @ts-ignore diff --git a/package-lock.json b/package-lock.json index 918e552e8..5e765d6c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@loopback/core": "2.16.2", "@loopback/repository": "3.7.1", "@loopback/rest": "9.3.0", + "@simplewebauthn/server": "^11.0.0", "@types/aws-lambda": "8.10.77", "@types/brotli": "^1.3.4", "@types/co-body": "^5.1.1", @@ -799,6 +800,12 @@ "@hapi/hoek": "9.x.x" } }, + "node_modules/@hexagon/base64": { + "version": "1.1.28", + "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", + "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -963,6 +970,12 @@ "node": ">= 8.0.0" } }, + "node_modules/@levischuck/tiny-cbor": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.2.tgz", + "integrity": "sha512-f5CnPw997Y2GQ8FAvtuVVC19FX8mwNNC+1XJcIi16n/LTJifKO6QBgGLgN3YEmqtGMk17SKSuoWES3imJVxAVw==", + "dev": true + }, "node_modules/@loopback/context": { "version": "3.18.0", "resolved": "https://registry.npmjs.org/@loopback/context/-/context-3.18.0.tgz", @@ -1227,6 +1240,74 @@ "node": ">=12" } }, + "node_modules/@peculiar/asn1-android": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.13.tgz", + "integrity": "sha512-0VTNazDGKrLS6a3BwTDZanqq6DR/I3SbvmDMuS8Be+OYpvM6x1SRDh9AGDsHVnaCOIztOspCPc6N1m+iUv1Xxw==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.14.tgz", + "integrity": "sha512-zWPyI7QZto6rnLv6zPniTqbGaLh6zBpJyI46r1yS/bVHJXT2amdMHCRRnbV5yst2H8+ppXG6uXu/M6lKakiQ8w==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/asn1-x509": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.13.tgz", + "integrity": "sha512-wBNQqCyRtmqvXkGkL4DR3WxZhHy8fDiYtOjTeCd7SFE5F6GBeafw3EJ94PX/V0OJJrjQ40SkRY2IZu3ZSyBqcg==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/asn1-x509": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", + "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", + "dev": true, + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.13.tgz", + "integrity": "sha512-PfeLQl2skXmxX2/AFFCVaWU8U6FKW1Db43mgBhShCOFS1bVxqtvusq1hVjfuEcuSQGedrLdCSvTgabluwN/M9A==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "asn1js": "^3.0.5", + "ipaddr.js": "^2.1.0", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/asn1-x509/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -1248,6 +1329,41 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "dev": true }, + "node_modules/@simplewebauthn/server": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-11.0.0.tgz", + "integrity": "sha512-zu8dxKcPiRUNSN2kmrnNOzNbRI8VaR/rL4ENCHUfC6PEE7SAAdIql9g5GBOd/wOVZolIsaZz3ccFxuGoVP0iaw==", + "dev": true, + "dependencies": { + "@hexagon/base64": "^1.1.27", + "@levischuck/tiny-cbor": "^0.2.2", + "@peculiar/asn1-android": "^2.3.10", + "@peculiar/asn1-ecc": "^2.3.8", + "@peculiar/asn1-rsa": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "@simplewebauthn/types": "^11.0.0", + "cross-fetch": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@simplewebauthn/server/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/@simplewebauthn/types": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-11.0.0.tgz", + "integrity": "sha512-b2o0wC5u2rWts31dTgBkAtSNKGX0cvL6h8QedNsKmj8O4QoLFQFR3DBVBUlpyVEhYKA+mXGUaXbcOc4JdQ3HzA==", + "dev": true + }, "node_modules/@sinonjs/commons": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", @@ -1861,6 +1977,20 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dev": true, + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -6500,6 +6630,24 @@ "node": ">=6" } }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/qs": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", diff --git a/package.json b/package.json index 335447feb..0e4bc1c15 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "@loopback/core": "2.16.2", "@loopback/repository": "3.7.1", "@loopback/rest": "9.3.0", + "@simplewebauthn/server": "^11.0.0", "@types/aws-lambda": "8.10.77", "@types/brotli": "^1.3.4", "@types/co-body": "^5.1.1", diff --git a/test/test-server/src/webauthn.ts b/test/test-server/src/webauthn.ts new file mode 100644 index 000000000..d2747ea2b --- /dev/null +++ b/test/test-server/src/webauthn.ts @@ -0,0 +1,31 @@ +import { Router } from "express"; +import EmailPassword from "../../../recipe/emailpassword"; +import Webauthn from "../../../recipe/webauthn"; +import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeResponse, serializeUser } from "./utils"; +import * as supertokens from "../../../lib/build"; +import { logger } from "./logger"; + +const namespace = "com.supertokens:node-test-server:emailpassword"; +const { logDebugMessage } = logger(namespace); + +const router = Router().post("/registeroptions", async (req, res, next) => { + try { + logDebugMessage("Webauthn:registerOptions %j", req.body); + const response = await Webauthn.registerOptions( + req.body.email, + req.body.recoverAccountToken, + req.body.relyingPartyId, + req.body.relyingPartyName, + req.body.origin, + req.body.timeout, + req.body.attestation, + req.body.tenantId || "public", + req.body.userContext + ); + res.json(response); + } catch (e) { + next(e); + } +}); + +export default router; diff --git a/test/utils.js b/test/utils.js index f679a6c3a..63a38025c 100644 --- a/test/utils.js +++ b/test/utils.js @@ -32,6 +32,7 @@ let MultitenancyRecipe = require("../lib/build/recipe/multitenancy/recipe").defa let MultiFactorAuthRecipe = require("../lib/build/recipe/multifactorauth/recipe").default; const UserRolesRecipe = require("../lib/build/recipe/userroles/recipe").default; const OAuth2Recipe = require("../lib/build/recipe/oauth2provider/recipe").default; +const WebAuthnRecipe = require("../lib/build/recipe/webauthn/recipe").default; let { ProcessState } = require("../lib/build/processState"); let { Querier } = require("../lib/build/querier"); let { maxVersion } = require("../lib/build/utils"); @@ -268,6 +269,7 @@ module.exports.resetAll = function (disableLogging = true) { TotpRecipe.reset(); MultiFactorAuthRecipe.reset(); OAuth2Recipe.reset(); + WebAuthnRecipe.reset(); if (disableLogging) { debug.disable(); } diff --git a/test/webauthn/apis.test.js b/test/webauthn/apis.test.js new file mode 100644 index 000000000..85d26917b --- /dev/null +++ b/test/webauthn/apis.test.js @@ -0,0 +1,149 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +const { + printPath, + setupST, + startST, + startSTWithMultitenancy, + killAllST, + cleanST, + setKeyValueInConfig, + stopST, +} = require("../utils"); +let STExpress = require("../../"); +let Session = require("../../recipe/session"); +let WebAuthn = require("../../recipe/webauthn"); +let assert = require("assert"); +let { ProcessState } = require("../../lib/build/processState"); +let SuperTokens = require("../../lib/build/supertokens").default; +const request = require("supertest"); +const express = require("express"); +let { middleware, errorHandler } = require("../../framework/express"); +let { isCDIVersionCompatible } = require("../utils"); +const { default: RecipeUserId } = require("../../lib/build/recipeUserId"); + +describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function () { + beforeEach(async function () { + await killAllST(); + await setupST(); + ProcessState.getInstance().reset(); + }); + + after(async function () { + await killAllST(); + await cleanST(); + }); + + it("test registerOptionsAPI with default values", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokensplm", + websiteDomain: "supertokens.io", + }, + recipeList: [WebAuthn.init()], + }); + + // run test if current CDI version >= 2.11 + if (!(await isCDIVersionCompatible("2.11"))) return; + + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + // passing valid field + let validCreateCodeResponse = await new Promise((resolve) => + request(app) + .post("/auth/webauthn/options/register") + .send({ + email: "test@example.com", + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + resolve(undefined); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + console.log(validCreateCodeResponse); + + assert(validCreateCodeResponse.status === "OK"); + + assert(typeof validCreateCodeResponse.challenge === "string"); + assert(validCreateCodeResponse.attestation === "none"); + assert(validCreateCodeResponse.rp.id === "supertokens.io"); + assert(validCreateCodeResponse.rp.name === "SuperTokensplm"); + assert(validCreateCodeResponse.user.name === "test@example.com"); + assert(validCreateCodeResponse.user.displayName === "test@example.com"); + assert(Number.isInteger(validCreateCodeResponse.timeout)); + assert(validCreateCodeResponse.authenticatorSelection.userVerification === "preferred"); + assert(validCreateCodeResponse.authenticatorSelection.requireResidentKey === true); + assert(validCreateCodeResponse.authenticatorSelection.residentKey === "required"); + }); +}); + +function checkConsumeResponse(validUserInputCodeResponse, { email, phoneNumber, isNew, isPrimary }) { + assert.strictEqual(validUserInputCodeResponse.status, "OK"); + assert.strictEqual(validUserInputCodeResponse.createdNewRecipeUser, isNew); + + assert.strictEqual(typeof validUserInputCodeResponse.user.id, "string"); + assert.strictEqual(typeof validUserInputCodeResponse.user.timeJoined, "number"); + assert.strictEqual(validUserInputCodeResponse.user.isPrimaryUser, isPrimary); + + assert(validUserInputCodeResponse.user.emails instanceof Array); + if (email !== undefined) { + assert.strictEqual(validUserInputCodeResponse.user.emails.length, 1); + assert.strictEqual(validUserInputCodeResponse.user.emails[0], email); + } else { + assert.strictEqual(validUserInputCodeResponse.user.emails.length, 0); + } + + assert(validUserInputCodeResponse.user.phoneNumbers instanceof Array); + if (phoneNumber !== undefined) { + assert.strictEqual(validUserInputCodeResponse.user.phoneNumbers.length, 1); + assert.strictEqual(validUserInputCodeResponse.user.phoneNumbers[0], phoneNumber); + } else { + assert.strictEqual(validUserInputCodeResponse.user.phoneNumbers.length, 0); + } + + assert.strictEqual(validUserInputCodeResponse.user.thirdParty.length, 0); + + assert.strictEqual(validUserInputCodeResponse.user.loginMethods.length, 1); + const loginMethod = { + recipeId: "passwordless", + recipeUserId: validUserInputCodeResponse.user.id, + timeJoined: validUserInputCodeResponse.user.timeJoined, + verified: true, + tenantIds: ["public"], + }; + if (email) { + loginMethod.email = email; + } + if (phoneNumber) { + loginMethod.phoneNumber = phoneNumber; + } + assert.deepStrictEqual(validUserInputCodeResponse.user.loginMethods, [loginMethod]); + + assert.strictEqual(Object.keys(validUserInputCodeResponse.user).length, 8); + assert.strictEqual(Object.keys(validUserInputCodeResponse).length, 3); +} diff --git a/test/webauthn/config.test.js b/test/webauthn/config.test.js new file mode 100644 index 000000000..a6331db28 --- /dev/null +++ b/test/webauthn/config.test.js @@ -0,0 +1,123 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +const { printPath, setupST, startST, stopST, killAllST, cleanST, resetAll } = require("../utils"); +let STExpress = require("../../"); +let Session = require("../../recipe/session"); +let SessionRecipe = require("../../lib/build/recipe/session/recipe").default; +let assert = require("assert"); +let { ProcessState } = require("../../lib/build/processState"); +let { normaliseURLPathOrThrowError } = require("../../lib/build/normalisedURLPath"); +let { normaliseURLDomainOrThrowError } = require("../../lib/build/normalisedURLDomain"); +let { normaliseSessionScopeOrThrowError } = require("../../lib/build/recipe/session/utils"); +const { Querier } = require("../../lib/build/querier"); +let WebAuthn = require("../../recipe/webauthn"); +let WebAuthnRecipe = require("../../lib/build/recipe/webauthn/recipe").default; +let utils = require("../../lib/build/recipe/webauthn/utils"); +let { middleware, errorHandler } = require("../../framework/express"); + +describe(`configTest: ${printPath("[test/webauthn/config.test.js]")}`, function () { + beforeEach(async function () { + await killAllST(); + await setupST(); + ProcessState.getInstance().reset(); + }); + + after(async function () { + await killAllST(); + await cleanST(); + }); + + // test config for emailpassword module + // Failure condition: passing custom data or data of invalid type/ syntax to the module + it("test default config for webauthn module", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [WebAuthn.init()], + debug: true, + }); + + let webauthn = await WebAuthnRecipe.getInstanceOrThrowError(); + + assert(!!webauthn); + const origin = await webauthn.config.getOrigin({ userContext: {} }); + const relyingPartyId = await webauthn.config.getRelyingPartyId({ userContext: {} }); + const relyingPartyName = await webauthn.config.getRelyingPartyName({ userContext: {} }); + + assert(origin === "https://supertokens.io"); + assert(relyingPartyId === "supertokens.io"); + assert(relyingPartyName === "SuperTokens"); + + assert((await webauthn.config.validateEmailAddress("aaaaa")) === "Email is invalid"); + assert((await webauthn.config.validateEmailAddress("aaaaaa@aaaaaa")) === "Email is invalid"); + assert((await webauthn.config.validateEmailAddress("random User @randomMail.com")) === "Email is invalid"); + assert((await webauthn.config.validateEmailAddress("*@*")) === "Email is invalid"); + assert((await webauthn.config.validateEmailAddress("validmail@gmail.com")) === undefined); + assert( + (await webauthn.config.validateEmailAddress()) === + "Development bug: Please make sure the email field yields a string" + ); + }); + + // Failure condition: passing data of invalid type/ syntax to the module + it("test config for webauthn module", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [ + WebAuthn.init({ + getOrigin: () => { + return "testOrigin"; + }, + getRelyingPartyId: () => { + return "testId"; + }, + getRelyingPartyName: () => { + return "testName"; + }, + validateEmailAddress: (email) => { + return email === "test"; + }, + }), + ], + }); + + let webauthn = await WebAuthnRecipe.getInstanceOrThrowError(); + const origin = webauthn.config.getOrigin(); + const relyingPartyId = webauthn.config.getRelyingPartyId(); + const relyingPartyName = webauthn.config.getRelyingPartyName(); + + assert(origin === "testOrigin"); + assert(relyingPartyId === "testId"); + assert(relyingPartyName === "testName"); + assert(await webauthn.config.validateEmailAddress("test")); + assert(!(await webauthn.config.validateEmailAddress("test!"))); + }); +}); From fbb2d4b283e796bd4e40a0411ea8f10859bdb671 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 3 Dec 2024 16:45:39 +0200 Subject: [PATCH 26/36] feat: added API testing * added missing tests * add reject support for api calls * added todos for updating to latest cdi version when skipping tests --------- Co-authored-by: Victor Bojica --- .../{recipe/webauthn => }/core-mock.d.ts | 2 +- lib/build/core-mock.js | 217 +++++ .../recipe/webauthn/api/implementation.js | 2 +- .../recipe/webauthn/api/signInOptions.js | 16 + lib/build/recipe/webauthn/core-mock.js | 107 --- lib/build/recipe/webauthn/index.d.ts | 25 +- lib/build/recipe/webauthn/index.js | 10 +- lib/build/recipe/webauthn/recipe.js | 2 +- .../recipe/webauthn/recipeImplementation.js | 3 +- lib/build/recipe/webauthn/types.d.ts | 4 +- lib/ts/core-mock.ts | 248 ++++++ lib/ts/recipe/webauthn/api/implementation.ts | 4 +- lib/ts/recipe/webauthn/api/signInOptions.ts | 12 + lib/ts/recipe/webauthn/core-mock.ts | 112 --- lib/ts/recipe/webauthn/index.ts | 20 +- lib/ts/recipe/webauthn/recipe.ts | 2 +- .../recipe/webauthn/recipeImplementation.ts | 3 +- lib/ts/recipe/webauthn/types.ts | 4 +- package-lock.json | 22 +- test/webauthn/apis.test.js | 839 +++++++++++++++--- test/webauthn/wasm_exec.js | 639 +++++++++++++ test/webauthn/webauthn.wasm | Bin 0 -> 5458724 bytes 22 files changed, 1951 insertions(+), 342 deletions(-) rename lib/build/{recipe/webauthn => }/core-mock.d.ts (66%) create mode 100644 lib/build/core-mock.js delete mode 100644 lib/build/recipe/webauthn/core-mock.js create mode 100644 lib/ts/core-mock.ts delete mode 100644 lib/ts/recipe/webauthn/core-mock.ts create mode 100644 test/webauthn/wasm_exec.js create mode 100755 test/webauthn/webauthn.wasm diff --git a/lib/build/recipe/webauthn/core-mock.d.ts b/lib/build/core-mock.d.ts similarity index 66% rename from lib/build/recipe/webauthn/core-mock.d.ts rename to lib/build/core-mock.d.ts index 0bb5666c4..8471fde1a 100644 --- a/lib/build/recipe/webauthn/core-mock.d.ts +++ b/lib/build/core-mock.d.ts @@ -1,3 +1,3 @@ // @ts-nocheck -import { Querier } from "../../querier"; +import { Querier } from "./querier"; export declare const getMockQuerier: (recipeId: string) => Querier; diff --git a/lib/build/core-mock.js b/lib/build/core-mock.js new file mode 100644 index 000000000..9a511d842 --- /dev/null +++ b/lib/build/core-mock.js @@ -0,0 +1,217 @@ +"use strict"; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getMockQuerier = void 0; +const querier_1 = require("./querier"); +const server_1 = require("@simplewebauthn/server"); +const crypto_1 = __importDefault(require("crypto")); +const db = { + generatedOptions: {}, + credentials: {}, + users: {}, +}; +const writeDb = (table, key, value) => { + db[table][key] = value; +}; +const readDb = (table, key) => { + return db[table][key]; +}; +// const readDbBy = (table: keyof typeof db, func: (value: any) => boolean) => { +// return Object.values(db[table]).find(func); +// }; +const getMockQuerier = (recipeId) => { + const querier = querier_1.Querier.getNewInstanceOrThrowError(recipeId); + const sendPostRequest = async (path, body, _userContext) => { + var _a, _b; + if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { + const registrationOptions = await server_1.generateRegistrationOptions({ + rpID: body.relyingPartyId, + rpName: body.relyingPartyName, + userName: body.email, + timeout: body.timeout, + attestationType: body.attestation || "none", + authenticatorSelection: { + userVerification: body.userVerification || "preferred", + requireResidentKey: body.requireResidentKey || false, + residentKey: body.residentKey || "required", + }, + supportedAlgorithmIDs: body.supportedAlgorithmIDs || [-8, -7, -257], + userDisplayName: body.displayName || body.email, + }); + const id = crypto_1.default.randomUUID(); + const now = new Date(); + writeDb( + "generatedOptions", + id, + Object.assign(Object.assign({}, registrationOptions), { + id, + origin: body.origin, + tenantId: body.tenantId, + email: body.email, + rpId: registrationOptions.rp.id, + createdAt: now.getTime(), + expiresAt: now.getTime() + body.timeout * 1000, + }) + ); + // @ts-ignore + return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, registrationOptions); + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + const signInOptions = await server_1.generateAuthenticationOptions({ + rpID: body.relyingPartyId, + timeout: body.timeout, + userVerification: body.userVerification || "preferred", + }); + const id = crypto_1.default.randomUUID(); + const now = new Date(); + writeDb( + "generatedOptions", + id, + Object.assign(Object.assign({}, signInOptions), { + id, + origin: body.origin, + tenantId: body.tenantId, + email: body.email, + createdAt: now.getTime(), + expiresAt: now.getTime() + body.timeout * 1000, + }) + ); + // @ts-ignore + return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, signInOptions); + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { + const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + const registrationVerification = await server_1.verifyRegistrationResponse({ + expectedChallenge: options.challenge, + expectedOrigin: options.origin, + expectedRPID: options.rpId, + response: body.credential, + }); + if (!registrationVerification.verified) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const credentialId = body.credential.id; + if (!credentialId) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const recipeUserId = crypto_1.default.randomUUID(); + const now = new Date(); + writeDb("credentials", credentialId, { + id: credentialId, + userId: recipeUserId, + counter: 0, + publicKey: + (_a = registrationVerification.registrationInfo) === null || _a === void 0 + ? void 0 + : _a.credential.publicKey.toString(), + rpId: options.rpId, + transports: + (_b = registrationVerification.registrationInfo) === null || _b === void 0 + ? void 0 + : _b.credential.transports, + createdAt: now.toISOString(), + }); + const user = { + id: recipeUserId, + timeJoined: now.getTime(), + isPrimaryUser: true, + tenantIds: [body.tenantId], + emails: [options.email], + phoneNumbers: [], + thirdParty: [], + webauthn: { + credentialIds: [credentialId], + }, + loginMethods: [ + { + recipeId: "webauthn", + recipeUserId, + tenantIds: [body.tenantId], + verified: true, + timeJoined: now.getTime(), + webauthn: { + credentialIds: [credentialId], + }, + email: options.email, + }, + ], + }; + writeDb("users", recipeUserId, user); + const response = { + status: "OK", + user: user, + recipeUserId, + }; + // @ts-ignore + return response; + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const credentialId = body.credential.id; + const credential = readDb("credentials", credentialId); + if (!credential) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const authenticationVerification = await server_1.verifyAuthenticationResponse({ + expectedChallenge: options.challenge, + expectedOrigin: options.origin, + expectedRPID: options.rpId, + response: body.credential, + credential: { + publicKey: new Uint8Array(credential.publicKey.split(",").map((byte) => parseInt(byte))), + transports: credential.transports, + counter: credential.counter, + id: credential.id, + }, + }); + if (!authenticationVerification.verified) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const user = readDb("users", credential.userId); + if (!user) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + // @ts-ignore + return { + status: "OK", + user, + recipeUserId: user.id, + }; + } + throw new Error(`Unmocked endpoint: ${path}`); + }; + const sendGetRequest = async (path, _body, _userContext) => { + if (path.getAsStringDangerous().includes("/recipe/webauthn/options")) { + const webauthnGeneratedOptionsId = path.getAsStringDangerous().split("/").pop(); + if (!webauthnGeneratedOptionsId) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + const options = readDb("generatedOptions", webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + return Object.assign({ status: "OK" }, options); + } + throw new Error(`Unmocked endpoint: ${path}`); + }; + querier.sendPostRequest = sendPostRequest; + querier.sendGetRequest = sendGetRequest; + return querier; +}; +exports.getMockQuerier = getMockQuerier; diff --git a/lib/build/recipe/webauthn/api/implementation.js b/lib/build/recipe/webauthn/api/implementation.js index ed63c0ed8..412737cf8 100644 --- a/lib/build/recipe/webauthn/api/implementation.js +++ b/lib/build/recipe/webauthn/api/implementation.js @@ -354,7 +354,7 @@ function getAPIImplementation() { session, shouldTryLinkingWithSessionUser, }); - if (preAuthChecks.status === "SIGN_UP_NOT_ALLOWED") { + if (preAuthChecks.status === "SIGN_IN_NOT_ALLOWED") { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); } if (preAuthChecks.status !== "OK") { diff --git a/lib/build/recipe/webauthn/api/signInOptions.js b/lib/build/recipe/webauthn/api/signInOptions.js index 25034546a..49f3445a5 100644 --- a/lib/build/recipe/webauthn/api/signInOptions.js +++ b/lib/build/recipe/webauthn/api/signInOptions.js @@ -13,13 +13,29 @@ * License for the specific language governing permissions and limitations * under the License. */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); +const error_1 = __importDefault(require("../error")); async function signInOptions(apiImplementation, tenantId, options, userContext) { + var _a; if (apiImplementation.signInOptionsPOST === undefined) { return false; } + const requestBody = await options.req.getJSONBody(); + let email = (_a = requestBody.email) === null || _a === void 0 ? void 0 : _a.trim(); + if (email === undefined || typeof email !== "string") { + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "Please provide the email", + }); + } let result = await apiImplementation.signInOptionsPOST({ + email, tenantId, options, userContext, diff --git a/lib/build/recipe/webauthn/core-mock.js b/lib/build/recipe/webauthn/core-mock.js deleted file mode 100644 index c0ac30bd0..000000000 --- a/lib/build/recipe/webauthn/core-mock.js +++ /dev/null @@ -1,107 +0,0 @@ -"use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getMockQuerier = void 0; -const querier_1 = require("../../querier"); -const server_1 = require("@simplewebauthn/server"); -const crypto_1 = __importDefault(require("crypto")); -const db = { - generatedOptions: {}, -}; -const writeDb = (table, key, value) => { - db[table][key] = value; -}; -// const readDb = (table: keyof typeof db, key: string) => { -// return db[table][key]; -// }; -const getMockQuerier = (recipeId) => { - const querier = querier_1.Querier.getNewInstanceOrThrowError(recipeId); - const sendPostRequest = async (path, body, _userContext) => { - if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { - const registrationOptions = await server_1.generateRegistrationOptions({ - rpID: body.relyingPartyId, - rpName: body.relyingPartyName, - userName: body.email, - timeout: body.timeout, - attestationType: body.attestation || "none", - authenticatorSelection: { - userVerification: body.userVerification || "preferred", - requireResidentKey: body.requireResidentKey || false, - residentKey: body.residentKey || "required", - }, - supportedAlgorithmIDs: body.supportedAlgorithmIDs || [-8, -7, -257], - userDisplayName: body.displayName || body.email, - }); - const id = crypto_1.default.randomUUID(); - writeDb( - "generatedOptions", - id, - Object.assign(Object.assign({}, registrationOptions), { - id, - origin: body.origin, - tenantId: body.tenantId, - }) - ); - // @ts-ignore - return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, registrationOptions); - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { - const signInOptions = await server_1.generateAuthenticationOptions({ - rpID: body.relyingPartyId, - timeout: body.timeout, - userVerification: body.userVerification || "preferred", - }); - const id = crypto_1.default.randomUUID(); - writeDb( - "generatedOptions", - id, - Object.assign(Object.assign({}, signInOptions), { id, origin: body.origin, tenantId: body.tenantId }) - ); - // @ts-ignore - return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, signInOptions); - // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token")) { - // // @ts-ignore - // return { - // status: "OK", - // token: "dummy-recover-token", - // }; - // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token/consume")) { - // // @ts-ignore - // return { - // status: "OK", - // userId: "dummy-user-id", - // email: "user@example.com", - // }; - // } - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { - // @ts-ignore - return { - status: "OK", - user: { - id: "dummy-user-id", - email: "user@example.com", - timeJoined: Date.now(), - }, - recipeUserId: "dummy-recipe-user-id", - }; - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { - // @ts-ignore - return { - status: "OK", - user: { - id: "dummy-user-id", - email: "user@example.com", - timeJoined: Date.now(), - }, - recipeUserId: "dummy-recipe-user-id", - }; - } - throw new Error(`Unmocked endpoint: ${path}`); - }; - querier.sendPostRequest = sendPostRequest; - return querier; -}; -exports.getMockQuerier = getMockQuerier; diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts index 10ece7871..154a2c439 100644 --- a/lib/build/recipe/webauthn/index.d.ts +++ b/lib/build/recipe/webauthn/index.d.ts @@ -107,7 +107,7 @@ export default class Wrapper { userContext, ...rest }: { - email?: string; + email: string; timeout?: number; userVerification?: UserVerification; tenantId?: string; @@ -134,6 +134,28 @@ export default class Wrapper { status: "INVALID_GENERATED_OPTIONS_ERROR"; } >; + static getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext, + }: { + webauthnGeneratedOptionsId: string; + tenantId?: string; + userContext?: Record; + }): Promise< + | { + status: "OK"; + id: string; + relyingPartyId: string; + origin: string; + email: string; + timeout: string; + challenge: string; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + >; static signUp({ tenantId, webauthnGeneratedOptionsId, @@ -386,3 +408,4 @@ export type { RecipeInterface, APIOptions, APIInterface }; export declare let createRecoverAccountLink: typeof Wrapper.createRecoverAccountLink; export declare let sendRecoverAccountEmail: typeof Wrapper.sendRecoverAccountEmail; export declare let sendEmail: typeof Wrapper.sendEmail; +export declare let getGeneratedOptions: typeof Wrapper.getGeneratedOptions; diff --git a/lib/build/recipe/webauthn/index.js b/lib/build/recipe/webauthn/index.js index 35d01c9e8..cd54ae5f2 100644 --- a/lib/build/recipe/webauthn/index.js +++ b/lib/build/recipe/webauthn/index.js @@ -30,7 +30,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.sendEmail = exports.sendRecoverAccountEmail = exports.createRecoverAccountLink = exports.registerCredential = exports.consumeRecoverAccountToken = exports.recoverAccount = exports.generateRecoverAccountToken = exports.verifyCredentials = exports.signIn = exports.signInOptions = exports.registerOptions = exports.Error = exports.init = void 0; +exports.getGeneratedOptions = exports.sendEmail = exports.sendRecoverAccountEmail = exports.createRecoverAccountLink = exports.registerCredential = exports.consumeRecoverAccountToken = exports.recoverAccount = exports.generateRecoverAccountToken = exports.verifyCredentials = exports.signIn = exports.signInOptions = exports.registerOptions = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); const error_1 = __importDefault(require("./error")); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); @@ -174,6 +174,13 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } + static getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId = constants_1.DEFAULT_TENANT_ID, userContext }) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext: utils_2.getUserContext(userContext), + }); + } static signUp({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, @@ -356,3 +363,4 @@ exports.registerCredential = Wrapper.registerCredential; exports.createRecoverAccountLink = Wrapper.createRecoverAccountLink; exports.sendRecoverAccountEmail = Wrapper.sendRecoverAccountEmail; exports.sendEmail = Wrapper.sendEmail; +exports.getGeneratedOptions = Wrapper.getGeneratedOptions; diff --git a/lib/build/recipe/webauthn/recipe.js b/lib/build/recipe/webauthn/recipe.js index 6d76efb0d..1f6b7689d 100644 --- a/lib/build/recipe/webauthn/recipe.js +++ b/lib/build/recipe/webauthn/recipe.js @@ -41,7 +41,7 @@ const recipe_1 = __importDefault(require("../multifactorauth/recipe")); const recipe_2 = __importDefault(require("../multitenancy/recipe")); const utils_3 = require("../thirdparty/utils"); const multifactorauth_1 = require("../multifactorauth"); -const core_mock_1 = require("./core-mock"); +const core_mock_1 = require("../../core-mock"); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config, ingredients) { super(recipeId, appInfo); diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js index dc345bee9..089433503 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.js +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -146,7 +146,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { userContext ); }, - signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext }) { + signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext, email }) { // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest( new normalisedURLPath_1.default( @@ -155,6 +155,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { }/recipe/webauthn/options/signin` ), { + email, relyingPartyId, origin, timeout, diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts index d58b6c2cb..86bde2d7a 100644 --- a/lib/build/recipe/webauthn/types.d.ts +++ b/lib/build/recipe/webauthn/types.d.ts @@ -142,7 +142,7 @@ export declare type RecipeInterface = { } >; signInOptions(input: { - email?: string; + email: string; relyingPartyId: string; origin: string; userVerification: UserVerification | undefined; @@ -541,7 +541,7 @@ export declare type APIInterface = { signInOptionsPOST: | undefined | ((input: { - email?: string; + email: string; tenantId: string; options: APIOptions; userContext: UserContext; diff --git a/lib/ts/core-mock.ts b/lib/ts/core-mock.ts new file mode 100644 index 000000000..20fca1520 --- /dev/null +++ b/lib/ts/core-mock.ts @@ -0,0 +1,248 @@ +import NormalisedURLPath from "./normalisedURLPath"; +import { Querier } from "./querier"; +import { UserContext } from "./types"; +import { + generateAuthenticationOptions, + generateRegistrationOptions, + verifyAuthenticationResponse, + verifyRegistrationResponse, +} from "@simplewebauthn/server"; +import crypto from "crypto"; + +const db = { + generatedOptions: {} as Record, + credentials: {} as Record, + users: {} as Record, +}; +const writeDb = (table: keyof typeof db, key: string, value: any) => { + db[table][key] = value; +}; +const readDb = (table: keyof typeof db, key: string) => { + return db[table][key]; +}; +// const readDbBy = (table: keyof typeof db, func: (value: any) => boolean) => { +// return Object.values(db[table]).find(func); +// }; + +export const getMockQuerier = (recipeId: string) => { + const querier = Querier.getNewInstanceOrThrowError(recipeId); + + const sendPostRequest = async ( + path: NormalisedURLPath, + body: any, + _userContext: UserContext + ): Promise => { + if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { + const registrationOptions = await generateRegistrationOptions({ + rpID: body.relyingPartyId, + rpName: body.relyingPartyName, + userName: body.email, + timeout: body.timeout, + attestationType: body.attestation || "none", + authenticatorSelection: { + userVerification: body.userVerification || "preferred", + requireResidentKey: body.requireResidentKey || false, + residentKey: body.residentKey || "required", + }, + supportedAlgorithmIDs: body.supportedAlgorithmIDs || [-8, -7, -257], + userDisplayName: body.displayName || body.email, + }); + + const id = crypto.randomUUID(); + const now = new Date(); + + writeDb("generatedOptions", id, { + ...registrationOptions, + id, + origin: body.origin, + tenantId: body.tenantId, + email: body.email, + rpId: registrationOptions.rp.id, + createdAt: now.getTime(), + expiresAt: now.getTime() + body.timeout * 1000, + }); + + // @ts-ignore + return { + status: "OK", + webauthnGeneratedOptionsId: id, + ...registrationOptions, + }; + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + const signInOptions = await generateAuthenticationOptions({ + rpID: body.relyingPartyId, + timeout: body.timeout, + userVerification: body.userVerification || "preferred", + }); + + const id = crypto.randomUUID(); + const now = new Date(); + + writeDb("generatedOptions", id, { + ...signInOptions, + id, + origin: body.origin, + tenantId: body.tenantId, + email: body.email, + createdAt: now.getTime(), + expiresAt: now.getTime() + body.timeout * 1000, + }); + + // @ts-ignore + return { + status: "OK", + webauthnGeneratedOptionsId: id, + ...signInOptions, + }; + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { + const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + + const registrationVerification = await verifyRegistrationResponse({ + expectedChallenge: options.challenge, + expectedOrigin: options.origin, + expectedRPID: options.rpId, + response: body.credential, + }); + + if (!registrationVerification.verified) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const credentialId = body.credential.id; + if (!credentialId) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const recipeUserId = crypto.randomUUID(); + const now = new Date(); + + writeDb("credentials", credentialId, { + id: credentialId, + userId: recipeUserId, + counter: 0, + publicKey: registrationVerification.registrationInfo?.credential.publicKey.toString(), + rpId: options.rpId, + transports: registrationVerification.registrationInfo?.credential.transports, + createdAt: now.toISOString(), + }); + + const user = { + id: recipeUserId, + timeJoined: now.getTime(), + isPrimaryUser: true, + tenantIds: [body.tenantId], + emails: [options.email], + phoneNumbers: [], + thirdParty: [], + webauthn: { + credentialIds: [credentialId], + }, + loginMethods: [ + { + recipeId: "webauthn", + recipeUserId, + tenantIds: [body.tenantId], + verified: true, + timeJoined: now.getTime(), + webauthn: { + credentialIds: [credentialId], + }, + email: options.email, + }, + ], + }; + writeDb("users", recipeUserId, user); + + const response = { + status: "OK", + user: user, + recipeUserId, + }; + + // @ts-ignore + return response; + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const credentialId = body.credential.id; + const credential = readDb("credentials", credentialId); + if (!credential) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const authenticationVerification = await verifyAuthenticationResponse({ + expectedChallenge: options.challenge, + expectedOrigin: options.origin, + expectedRPID: options.rpId, + response: body.credential, + credential: { + publicKey: new Uint8Array(credential.publicKey.split(",").map((byte: string) => parseInt(byte))), + transports: credential.transports, + counter: credential.counter, + id: credential.id, + }, + }); + + if (!authenticationVerification.verified) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const user = readDb("users", credential.userId); + + if (!user) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + // @ts-ignore + return { + status: "OK", + user, + recipeUserId: user.id, + }; + } + + throw new Error(`Unmocked endpoint: ${path}`); + }; + + const sendGetRequest = async ( + path: NormalisedURLPath, + _body: any, + _userContext: UserContext + ): Promise => { + if (path.getAsStringDangerous().includes("/recipe/webauthn/options")) { + const webauthnGeneratedOptionsId = path.getAsStringDangerous().split("/").pop(); + if (!webauthnGeneratedOptionsId) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + + const options = readDb("generatedOptions", webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + + return { status: "OK", ...options }; + } + + throw new Error(`Unmocked endpoint: ${path}`); + }; + + querier.sendPostRequest = sendPostRequest; + querier.sendGetRequest = sendGetRequest; + + return querier; +}; diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 2a00b910c..4056cda5c 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -131,7 +131,7 @@ export default function getAPIImplementation(): APIInterface { options, userContext, }: { - email?: string; + email: string; tenantId: string; options: APIOptions; userContext: UserContext; @@ -470,7 +470,7 @@ export default function getAPIImplementation(): APIInterface { session, shouldTryLinkingWithSessionUser, }); - if (preAuthChecks.status === "SIGN_UP_NOT_ALLOWED") { + if (preAuthChecks.status === "SIGN_IN_NOT_ALLOWED") { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); } if (preAuthChecks.status !== "OK") { diff --git a/lib/ts/recipe/webauthn/api/signInOptions.ts b/lib/ts/recipe/webauthn/api/signInOptions.ts index f81fde810..4e0802657 100644 --- a/lib/ts/recipe/webauthn/api/signInOptions.ts +++ b/lib/ts/recipe/webauthn/api/signInOptions.ts @@ -16,6 +16,7 @@ import { send200Response } from "../../../utils"; import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; +import STError from "../error"; export default async function signInOptions( apiImplementation: APIInterface, @@ -26,8 +27,19 @@ export default async function signInOptions( if (apiImplementation.signInOptionsPOST === undefined) { return false; } + const requestBody = await options.req.getJSONBody(); + + let email = requestBody.email?.trim(); + + if (email === undefined || typeof email !== "string") { + throw new STError({ + type: STError.BAD_INPUT_ERROR, + message: "Please provide the email", + }); + } let result = await apiImplementation.signInOptionsPOST({ + email, tenantId, options, userContext, diff --git a/lib/ts/recipe/webauthn/core-mock.ts b/lib/ts/recipe/webauthn/core-mock.ts deleted file mode 100644 index ecf81c0d8..000000000 --- a/lib/ts/recipe/webauthn/core-mock.ts +++ /dev/null @@ -1,112 +0,0 @@ -import NormalisedURLPath from "../../normalisedURLPath"; -import { Querier } from "../../querier"; -import { UserContext } from "../../types"; -import { generateAuthenticationOptions, generateRegistrationOptions } from "@simplewebauthn/server"; -import crypto from "crypto"; - -const db = { - generatedOptions: {} as Record, -}; -const writeDb = (table: keyof typeof db, key: string, value: any) => { - db[table][key] = value; -}; -// const readDb = (table: keyof typeof db, key: string) => { -// return db[table][key]; -// }; - -export const getMockQuerier = (recipeId: string) => { - const querier = Querier.getNewInstanceOrThrowError(recipeId); - - const sendPostRequest = async ( - path: NormalisedURLPath, - body: any, - _userContext: UserContext - ): Promise => { - if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { - const registrationOptions = await generateRegistrationOptions({ - rpID: body.relyingPartyId, - rpName: body.relyingPartyName, - userName: body.email, - timeout: body.timeout, - attestationType: body.attestation || "none", - authenticatorSelection: { - userVerification: body.userVerification || "preferred", - requireResidentKey: body.requireResidentKey || false, - residentKey: body.residentKey || "required", - }, - supportedAlgorithmIDs: body.supportedAlgorithmIDs || [-8, -7, -257], - userDisplayName: body.displayName || body.email, - }); - const id = crypto.randomUUID(); - writeDb("generatedOptions", id, { - ...registrationOptions, - id, - origin: body.origin, - tenantId: body.tenantId, - }); - // @ts-ignore - return { - status: "OK", - webauthnGeneratedOptionsId: id, - ...registrationOptions, - }; - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { - const signInOptions = await generateAuthenticationOptions({ - rpID: body.relyingPartyId, - timeout: body.timeout, - userVerification: body.userVerification || "preferred", - }); - const id = crypto.randomUUID(); - writeDb("generatedOptions", id, { ...signInOptions, id, origin: body.origin, tenantId: body.tenantId }); - - // @ts-ignore - return { - status: "OK", - webauthnGeneratedOptionsId: id, - ...signInOptions, - }; - // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token")) { - // // @ts-ignore - // return { - // status: "OK", - // token: "dummy-recover-token", - // }; - // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token/consume")) { - // // @ts-ignore - // return { - // status: "OK", - // userId: "dummy-user-id", - // email: "user@example.com", - // }; - // } - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { - // @ts-ignore - return { - status: "OK", - user: { - id: "dummy-user-id", - email: "user@example.com", - timeJoined: Date.now(), - }, - recipeUserId: "dummy-recipe-user-id", - }; - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { - // @ts-ignore - return { - status: "OK", - user: { - id: "dummy-user-id", - email: "user@example.com", - timeJoined: Date.now(), - }, - recipeUserId: "dummy-recipe-user-id", - }; - } - - throw new Error(`Unmocked endpoint: ${path}`); - }; - - querier.sendPostRequest = sendPostRequest; - - return querier; -}; diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index c2b10dbbe..e5b9c6903 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -187,7 +187,7 @@ export default class Wrapper { userContext, ...rest }: { - email?: string; + email: string; timeout?: number; userVerification?: UserVerification; tenantId?: string; @@ -245,6 +245,22 @@ export default class Wrapper { }); } + static getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId = DEFAULT_TENANT_ID, + userContext, + }: { + webauthnGeneratedOptionsId: string; + tenantId?: string; + userContext?: Record; + }) { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext: getUserContext(userContext), + }); + } + static signUp({ tenantId = DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, @@ -583,3 +599,5 @@ export let createRecoverAccountLink = Wrapper.createRecoverAccountLink; export let sendRecoverAccountEmail = Wrapper.sendRecoverAccountEmail; export let sendEmail = Wrapper.sendEmail; + +export let getGeneratedOptions = Wrapper.getGeneratedOptions; diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts index 8620c3fde..533dbc293 100644 --- a/lib/ts/recipe/webauthn/recipe.ts +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -48,7 +48,7 @@ import MultitenancyRecipe from "../multitenancy/recipe"; import { User } from "../../user"; import { isFakeEmail } from "../thirdparty/utils"; import { FactorIds } from "../multifactorauth"; -import { getMockQuerier } from "./core-mock"; +import { getMockQuerier } from "../../core-mock"; export default class Recipe extends RecipeModule { private static instance: Recipe | undefined = undefined; diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index db6e693f6..9c3361c2f 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -94,13 +94,14 @@ export default function getRecipeInterface( ); }, - signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext }) { + signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext, email }) { // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest( new NormalisedURLPath( `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/signin` ), { + email, relyingPartyId, origin, timeout, diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 2f694f1b1..b1a6b4540 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -217,7 +217,7 @@ export type RecipeInterface = { >; signInOptions(input: { - email?: string; + email: string; relyingPartyId: string; origin: string; userVerification: UserVerification | undefined; // see register options @@ -615,7 +615,7 @@ export type APIInterface = { signInOptionsPOST: | undefined | ((input: { - email?: string; + email: string; tenantId: string; options: APIOptions; userContext: UserContext; diff --git a/package-lock.json b/package-lock.json index 5e765d6c9..35d08da5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2172,9 +2172,10 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", + "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3788,6 +3789,21 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/test/webauthn/apis.test.js b/test/webauthn/apis.test.js index 85d26917b..c7eba3f13 100644 --- a/test/webauthn/apis.test.js +++ b/test/webauthn/apis.test.js @@ -12,27 +12,23 @@ * License for the specific language governing permissions and limitations * under the License. */ -const { - printPath, - setupST, - startST, - startSTWithMultitenancy, - killAllST, - cleanST, - setKeyValueInConfig, - stopST, -} = require("../utils"); +const { printPath, setupST, startST, killAllST, cleanST, stopST } = require("../utils"); +let assert = require("assert"); + +const request = require("supertest"); +const express = require("express"); + let STExpress = require("../../"); let Session = require("../../recipe/session"); let WebAuthn = require("../../recipe/webauthn"); -let assert = require("assert"); let { ProcessState } = require("../../lib/build/processState"); let SuperTokens = require("../../lib/build/supertokens").default; -const request = require("supertest"); -const express = require("express"); let { middleware, errorHandler } = require("../../framework/express"); let { isCDIVersionCompatible } = require("../utils"); -const { default: RecipeUserId } = require("../../lib/build/recipeUserId"); +const { readFile } = require("fs/promises"); +const nock = require("nock"); + +require("./wasm_exec"); describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function () { beforeEach(async function () { @@ -46,104 +42,737 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function await cleanST(); }); - it("test registerOptionsAPI with default values", async function () { - const connectionURI = await startST(); - - STExpress.init({ - supertokens: { - connectionURI, - }, - appInfo: { - apiDomain: "api.supertokens.io", - appName: "SuperTokensplm", - websiteDomain: "supertokens.io", - }, - recipeList: [WebAuthn.init()], + describe("[registerOptions]", function () { + it("test registerOptions with default values", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokensplm", + websiteDomain: "supertokens.io", + }, + recipeList: [WebAuthn.init()], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + // passing valid field + let registerOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/register") + .send({ + email: "test@example.com", + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(registerOptionsResponse.status === "OK"); + + assert(typeof registerOptionsResponse.challenge === "string"); + assert(registerOptionsResponse.attestation === "none"); + assert(registerOptionsResponse.rp.id === "supertokens.io"); + assert(registerOptionsResponse.rp.name === "SuperTokensplm"); + assert(registerOptionsResponse.user.name === "test@example.com"); + assert(registerOptionsResponse.user.displayName === "test@example.com"); + assert(Number.isInteger(registerOptionsResponse.timeout)); + assert(registerOptionsResponse.authenticatorSelection.userVerification === "preferred"); + assert(registerOptionsResponse.authenticatorSelection.requireResidentKey === true); + assert(registerOptionsResponse.authenticatorSelection.residentKey === "required"); + + const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( + { + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + } + ); + + assert(generatedOptions.origin === "https://supertokens.io"); + }); + + it("test registerOptions with custom values", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokensplm", + websiteDomain: "supertokens.io", + }, + recipeList: [ + WebAuthn.init({ + getOrigin: () => { + return "testOrigin.com"; + }, + getRelyingPartyId: () => { + return "testId.com"; + }, + getRelyingPartyName: () => { + return "testName"; + }, + validateEmailAddress: (email) => { + return email === "test@example.com" ? undefined : "Invalid email"; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + // passing valid field + let registerOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/register") + .send({ + email: "test@example.com", + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(registerOptionsResponse.status === "OK"); + + assert(typeof registerOptionsResponse.challenge === "string"); + assert(registerOptionsResponse.attestation === "none"); + assert(registerOptionsResponse.rp.id === "testId.com"); + assert(registerOptionsResponse.rp.name === "testName"); + assert(registerOptionsResponse.user.name === "test@example.com"); + assert(registerOptionsResponse.user.displayName === "test@example.com"); + assert(Number.isInteger(registerOptionsResponse.timeout)); + assert(registerOptionsResponse.authenticatorSelection.userVerification === "preferred"); + assert(registerOptionsResponse.authenticatorSelection.requireResidentKey === true); + assert(registerOptionsResponse.authenticatorSelection.residentKey === "required"); + + const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( + { + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + } + ); + assert(generatedOptions.origin === "testOrigin.com"); }); + }); + + describe("[signInOptions]", function () { + it("test signInOptions with default values", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokensplm", + websiteDomain: "supertokens.io", + }, + recipeList: [WebAuthn.init()], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + // passing valid field + let signInOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/signin") + .send({ email: "test@example.com" }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(signInOptionsResponse.status === "OK"); + + assert(typeof signInOptionsResponse.challenge === "string"); + assert(Number.isInteger(signInOptionsResponse.timeout)); + assert(signInOptionsResponse.userVerification === "preferred"); + + const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( + { + webauthnGeneratedOptionsId: signInOptionsResponse.webauthnGeneratedOptionsId, + } + ); + + assert(generatedOptions.rpId === "supertokens.io"); + assert(generatedOptions.origin === "https://supertokens.io"); + }); + + it("test signInOptions with custom values", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokensplm", + websiteDomain: "supertokens.io", + }, + recipeList: [ + WebAuthn.init({ + getOrigin: () => { + return "testOrigin.com"; + }, + getRelyingPartyId: () => { + return "testId.com"; + }, + getRelyingPartyName: () => { + return "testName"; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + // passing valid field + let signInOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/signin") + .send({ email: "test@example.com" }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(signInOptionsResponse.status === "OK"); + + assert(typeof signInOptionsResponse.challenge === "string"); + assert(Number.isInteger(signInOptionsResponse.timeout)); + assert(signInOptionsResponse.userVerification === "preferred"); + + const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( + { + webauthnGeneratedOptionsId: signInOptionsResponse.webauthnGeneratedOptionsId, + } + ); + + assert(generatedOptions.rpId === "testId.com"); + assert(generatedOptions.origin === "testOrigin.com"); + }); + }); + + describe("[signUp]", function () { + it("test signUp with no account linking", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokensplm"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokensplm", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); - // run test if current CDI version >= 2.11 - if (!(await isCDIVersionCompatible("2.11"))) return; + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; - const app = express(); - app.use(middleware()); - app.use(errorHandler()); + const app = express(); + app.use(middleware()); + app.use(errorHandler()); - // passing valid field - let validCreateCodeResponse = await new Promise((resolve) => - request(app) - .post("/auth/webauthn/options/register") - .send({ - email: "test@example.com", + const email = `${Math.random().toString().slice(2)}@supertokens.com`; + let registerOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + assert(registerOptionsResponse.status === "OK"); + + const { createCredential } = await getWebauthnLib(); + const credential = createCredential(registerOptionsResponse, { + rpId, + rpName, + origin, + userNotPresent: false, + userNotVerified: false, + }); + + let signUpResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/signup") + .send({ + credential, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + shouldTryLinkingWithSessionUser: false, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(signUpResponse.status === "OK"); + + assert(signUpResponse?.user?.id !== undefined); + assert(signUpResponse?.user?.emails?.length === 1); + assert(signUpResponse?.user?.emails?.[0] === email); + assert(signUpResponse?.user?.webauthn?.credentialIds?.length === 1); + assert(signUpResponse?.user?.webauthn?.credentialIds?.[0] === credential.id); + }); + }); + + describe("[signIn]", function () { + it("test signIn with no account linking", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokensplm"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokensplm", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + const email = `${Math.random().toString().slice(2)}@supertokens.com`; + let registerOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + assert(registerOptionsResponse.status === "OK"); + + let signInOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/signin") + .send({ email }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + assert(signInOptionsResponse.status === "OK"); + + const { createAndAssertCredential } = await getWebauthnLib(); + const credential = createAndAssertCredential(registerOptionsResponse, signInOptionsResponse, { + rpId, + rpName, + origin, + userNotPresent: false, + userNotVerified: false, + }); + + let signUpResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/signup") + .send({ + credential: credential.attestation, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + shouldTryLinkingWithSessionUser: false, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(signUpResponse.status === "OK"); + + // todo remove this when the core is implemented + // mock the core to return the user + nock("http://localhost:8080/", { allowUnmocked: true }) + .get("/public/users/by-accountinfo") + .query({ email, doUnionOfAccountInfo: true }) + .reply(200, (uri, body) => { + return { status: "OK", users: [signUpResponse.user] }; }) - .expect(200) - .end((err, res) => { - if (err) { - console.log(err); - resolve(undefined); - } else { - resolve(JSON.parse(res.text)); - } + .get("/user/id") + .query({ userId: signUpResponse.user.id }) + .reply(200, (uri, body) => { + return { status: "OK", user: signUpResponse.user }; + }); + + let signInResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/signin") + .send({ + credential: credential.assertion, + webauthnGeneratedOptionsId: signInOptionsResponse.webauthnGeneratedOptionsId, + shouldTryLinkingWithSessionUser: false, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(signInResponse.status === "OK"); + + assert(signInResponse?.user?.id !== undefined); + assert(signInResponse?.user?.emails?.length === 1); + assert(signInResponse?.user?.emails?.[0] === email); + assert(signInResponse?.user?.webauthn?.credentialIds?.length === 1); + assert(signInResponse?.user?.webauthn?.credentialIds?.[0] === credential.attestation.id); + }); + + it("test signIn fail with wrong credential", async function () { + const connectionURI = await startST(); + + const origin = "https://supertokens.io"; + const rpId = "supertokens.io"; + const rpName = "SuperTokensplm"; + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokensplm", + websiteDomain: "supertokens.io", + }, + recipeList: [ + Session.init(), + WebAuthn.init({ + getOrigin: async () => { + return origin; + }, + getRelyingPartyId: async () => { + return rpId; + }, + getRelyingPartyName: async () => { + return rpName; + }, + }), + ], + }); + + // run test if current CDI version >= 2.11 + // todo update this to crrect version + if (!(await isCDIVersionCompatible("2.11"))) return; + + const app = express(); + app.use(middleware()); + app.use(errorHandler()); + + const email = `${Math.random().toString().slice(2)}@supertokens.com`; + let registerOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/register") + .send({ + email, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + assert(registerOptionsResponse.status === "OK"); + + let signInOptionsResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/options/signin") + .send({ email: email + "wrong" }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + assert(signInOptionsResponse.status === "OK"); + + const { createAndAssertCredential } = await getWebauthnLib(); + const credential = createAndAssertCredential(registerOptionsResponse, signInOptionsResponse, { + rpId, + rpName, + origin, + userNotPresent: false, + userNotVerified: false, + }); + + let signUpResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/signup") + .send({ + credential: credential.attestation, + webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + shouldTryLinkingWithSessionUser: false, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(signUpResponse.status === "OK"); + + // todo remove this when the core is implemented + // mock the core to return the user + nock("http://localhost:8080/", { allowUnmocked: true }) + .get("/public/users/by-accountinfo") + .query({ email, doUnionOfAccountInfo: true }) + .reply(200, (uri, body) => { + return { status: "OK", users: [signUpResponse.user] }; }) - ); - console.log(validCreateCodeResponse); - - assert(validCreateCodeResponse.status === "OK"); - - assert(typeof validCreateCodeResponse.challenge === "string"); - assert(validCreateCodeResponse.attestation === "none"); - assert(validCreateCodeResponse.rp.id === "supertokens.io"); - assert(validCreateCodeResponse.rp.name === "SuperTokensplm"); - assert(validCreateCodeResponse.user.name === "test@example.com"); - assert(validCreateCodeResponse.user.displayName === "test@example.com"); - assert(Number.isInteger(validCreateCodeResponse.timeout)); - assert(validCreateCodeResponse.authenticatorSelection.userVerification === "preferred"); - assert(validCreateCodeResponse.authenticatorSelection.requireResidentKey === true); - assert(validCreateCodeResponse.authenticatorSelection.residentKey === "required"); + .get("/user/id") + .query({ userId: signUpResponse.user.id }) + .reply(200, (uri, body) => { + return { status: "OK", user: signUpResponse.user }; + }); + + let signInResponse = await new Promise((resolve, reject) => + request(app) + .post("/auth/webauthn/signin") + .send({ + credential: credential.assertion, + webauthnGeneratedOptionsId: signInOptionsResponse.webauthnGeneratedOptionsId, + shouldTryLinkingWithSessionUser: false, + }) + .expect(200) + .end((err, res) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + + assert(signInResponse.status === "INVALID_CREDENTIALS_ERROR"); + }); }); }); -function checkConsumeResponse(validUserInputCodeResponse, { email, phoneNumber, isNew, isPrimary }) { - assert.strictEqual(validUserInputCodeResponse.status, "OK"); - assert.strictEqual(validUserInputCodeResponse.createdNewRecipeUser, isNew); - - assert.strictEqual(typeof validUserInputCodeResponse.user.id, "string"); - assert.strictEqual(typeof validUserInputCodeResponse.user.timeJoined, "number"); - assert.strictEqual(validUserInputCodeResponse.user.isPrimaryUser, isPrimary); - - assert(validUserInputCodeResponse.user.emails instanceof Array); - if (email !== undefined) { - assert.strictEqual(validUserInputCodeResponse.user.emails.length, 1); - assert.strictEqual(validUserInputCodeResponse.user.emails[0], email); - } else { - assert.strictEqual(validUserInputCodeResponse.user.emails.length, 0); - } - - assert(validUserInputCodeResponse.user.phoneNumbers instanceof Array); - if (phoneNumber !== undefined) { - assert.strictEqual(validUserInputCodeResponse.user.phoneNumbers.length, 1); - assert.strictEqual(validUserInputCodeResponse.user.phoneNumbers[0], phoneNumber); - } else { - assert.strictEqual(validUserInputCodeResponse.user.phoneNumbers.length, 0); - } - - assert.strictEqual(validUserInputCodeResponse.user.thirdParty.length, 0); - - assert.strictEqual(validUserInputCodeResponse.user.loginMethods.length, 1); - const loginMethod = { - recipeId: "passwordless", - recipeUserId: validUserInputCodeResponse.user.id, - timeJoined: validUserInputCodeResponse.user.timeJoined, - verified: true, - tenantIds: ["public"], +const getWebauthnLib = async () => { + const wasmBuffer = await readFile(__dirname + "/webauthn.wasm"); + + // Set up the WebAssembly module instance + const go = new Go(); + const { instance } = await WebAssembly.instantiate(wasmBuffer, go.importObject); + go.run(instance); + + // Export extractURL from the global object + const createCredential = ( + registerOptions, + { userNotPresent = true, userNotVerified = true, rpId, rpName, origin } + ) => { + const registerOptionsString = JSON.stringify(registerOptions); + const result = global.createCredential( + registerOptionsString, + rpId, + rpName, + origin, + userNotPresent, + userNotVerified + ); + + if (!result) { + throw new Error("Failed to create credential"); + } + + try { + const credential = JSON.parse(result); + return credential; + } catch (e) { + throw new Error("Failed to parse credential"); + } + }; + + const createAndAssertCredential = ( + registerOptions, + signInOptions, + { userNotPresent = false, userNotVerified = false, rpId, rpName, origin } + ) => { + const registerOptionsString = JSON.stringify(registerOptions); + const signInOptionsString = JSON.stringify(signInOptions); + + const result = global.createAndAssertCredential( + registerOptionsString, + signInOptionsString, + rpId, + rpName, + origin, + userNotPresent, + userNotVerified + ); + + if (!result) { + throw new Error("Failed to create/assert credential"); + } + + try { + const parsedResult = JSON.parse(result); + return { attestation: parsedResult.attestation, assertion: parsedResult.assertion }; + } catch (e) { + throw new Error("Failed to parse result"); + } }; - if (email) { - loginMethod.email = email; - } - if (phoneNumber) { - loginMethod.phoneNumber = phoneNumber; - } - assert.deepStrictEqual(validUserInputCodeResponse.user.loginMethods, [loginMethod]); - - assert.strictEqual(Object.keys(validUserInputCodeResponse.user).length, 8); - assert.strictEqual(Object.keys(validUserInputCodeResponse).length, 3); -} + + return { createCredential, createAndAssertCredential }; +}; + +const log = ({ ...args }) => { + Object.keys(args).forEach((key) => { + console.log(); + console.log("------------------------------------------------"); + console.log(`${key}`); + console.log("------------------------------------------------"); + console.log(JSON.stringify(args[key], null, 2)); + console.log("================================================"); + console.log(); + }); +}; diff --git a/test/webauthn/wasm_exec.js b/test/webauthn/wasm_exec.js new file mode 100644 index 000000000..5a1d281ec --- /dev/null +++ b/test/webauthn/wasm_exec.js @@ -0,0 +1,639 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +"use strict"; + +(() => { + const enosys = () => { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + return err; + }; + + if (!globalThis.fs) { + let outputBuf = ""; + globalThis.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substring(0, nl)); + outputBuf = outputBuf.substring(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + chmod(path, mode, callback) { + callback(enosys()); + }, + chown(path, uid, gid, callback) { + callback(enosys()); + }, + close(fd, callback) { + callback(enosys()); + }, + fchmod(fd, mode, callback) { + callback(enosys()); + }, + fchown(fd, uid, gid, callback) { + callback(enosys()); + }, + fstat(fd, callback) { + callback(enosys()); + }, + fsync(fd, callback) { + callback(null); + }, + ftruncate(fd, length, callback) { + callback(enosys()); + }, + lchown(path, uid, gid, callback) { + callback(enosys()); + }, + link(path, link, callback) { + callback(enosys()); + }, + lstat(path, callback) { + callback(enosys()); + }, + mkdir(path, perm, callback) { + callback(enosys()); + }, + open(path, flags, mode, callback) { + callback(enosys()); + }, + read(fd, buffer, offset, length, position, callback) { + callback(enosys()); + }, + readdir(path, callback) { + callback(enosys()); + }, + readlink(path, callback) { + callback(enosys()); + }, + rename(from, to, callback) { + callback(enosys()); + }, + rmdir(path, callback) { + callback(enosys()); + }, + stat(path, callback) { + callback(enosys()); + }, + symlink(path, link, callback) { + callback(enosys()); + }, + truncate(path, length, callback) { + callback(enosys()); + }, + unlink(path, callback) { + callback(enosys()); + }, + utimes(path, atime, mtime, callback) { + callback(enosys()); + }, + }; + } + + if (!globalThis.process) { + globalThis.process = { + getuid() { + return -1; + }, + getgid() { + return -1; + }, + geteuid() { + return -1; + }, + getegid() { + return -1; + }, + getgroups() { + throw enosys(); + }, + pid: -1, + ppid: -1, + umask() { + throw enosys(); + }, + cwd() { + throw enosys(); + }, + chdir() { + throw enosys(); + }, + }; + } + + if (!globalThis.path) { + globalThis.path = { + resolve(...pathSegments) { + return pathSegments.join("/"); + }, + }; + } + + if (!globalThis.crypto) { + throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); + } + + if (!globalThis.performance) { + throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); + } + + if (!globalThis.TextEncoder) { + throw new Error("globalThis.TextEncoder is not available, polyfill required"); + } + + if (!globalThis.TextDecoder) { + throw new Error("globalThis.TextDecoder is not available, polyfill required"); + } + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + + globalThis.Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve; + }); + this._pendingEvent = null; + this._scheduledTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); + }; + + const setInt32 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + }; + + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true); + const high = this.mem.getInt32(addr + 4, true); + return low + high * 4294967296; + }; + + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = this.mem.getUint32(addr, true); + return this._values[id]; + }; + + const storeValue = (addr, v) => { + const nanHead = 0x7ff80000; + + if (typeof v === "number" && v !== 0) { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 0, true); + return; + } + this.mem.setFloat64(addr, v, true); + return; + } + + if (v === undefined) { + this.mem.setFloat64(addr, 0, true); + return; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = this._values.length; + } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 0; + switch (typeof v) { + case "object": + if (v !== null) { + typeFlag = 1; + } + break; + case "string": + typeFlag = 2; + break; + case "symbol": + typeFlag = 3; + break; + case "function": + typeFlag = 4; + break; + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true); + this.mem.setUint32(addr, id, true); + }; + + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); + }; + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + }; + + const loadString = (addr) => { + const saddr = getInt64(addr + 0); + const len = getInt64(addr + 8); + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + }; + + const testCallExport = (a, b) => { + this._inst.exports.testExport0(); + return this._inst.exports.testExport(a, b); + }; + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + _gotest: { + add: (a, b) => a + b, + callExport: testCallExport, + }, + gojs: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + sp >>>= 0; + const code = this.mem.getInt32(sp + 8, true); + this.exited = true; + delete this._inst; + delete this._values; + delete this._goRefCounts; + delete this._ids; + delete this._idPool; + this.exit(code); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + sp >>>= 0; + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = this.mem.getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func resetMemoryDataView() + "runtime.resetMemoryDataView": (sp) => { + sp >>>= 0; + this.mem = new DataView(this._inst.exports.mem.buffer); + }, + + // func nanotime1() int64 + "runtime.nanotime1": (sp) => { + sp >>>= 0; + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { + sp >>>= 0; + const msec = new Date().getTime(); + setInt64(sp + 8, msec / 1000); + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleTimeoutEvent(delay int64) int32 + "runtime.scheduleTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._scheduledTimeouts.set( + id, + setTimeout(() => { + this._resume(); + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn("scheduleTimeoutEvent: missed timeout event"); + this._resume(); + } + }, getInt64(sp + 8)) + ); + this.mem.setInt32(sp + 16, id, true); + }, + + // func clearTimeoutEvent(id int32) + "runtime.clearTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this.mem.getInt32(sp + 8, true); + clearTimeout(this._scheduledTimeouts.get(id)); + this._scheduledTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + sp >>>= 0; + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (sp) => { + sp >>>= 0; + const id = this.mem.getUint32(sp + 8, true); + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + sp >>>= 0; + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + sp >>>= 0; + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 32, result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueDelete(v ref, p string) + "syscall/js.valueDelete": (sp) => { + sp >>>= 0; + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + sp >>>= 0; + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + const result = Reflect.apply(m, v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, result); + this.mem.setUint8(sp + 64, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, err); + this.mem.setUint8(sp + 64, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.apply(v, undefined, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.construct(v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + sp >>>= 0; + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + sp >>>= 0; + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + sp >>>= 0; + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (sp) => { + sp >>>= 0; + this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16) ? 1 : 0); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + "syscall/js.copyBytesToGo": (sp) => { + sp >>>= 0; + const dst = loadSlice(sp + 8); + const src = loadValue(sp + 32); + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + "syscall/js.copyBytesToJS": (sp) => { + sp >>>= 0; + const dst = loadValue(sp + 8); + const src = loadSlice(sp + 16); + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + debug: (value) => { + console.log(value); + }, + }, + }; + } + + async run(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error("Go.run: WebAssembly.Instance expected"); + } + this._inst = instance; + this.mem = new DataView(this._inst.exports.mem.buffer); + this._values = [ + // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + globalThis, + this, + ]; + this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ + // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [globalThis, 5], + [this, 6], + ]); + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + const ptr = offset; + const bytes = encoder.encode(str + "\0"); + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); + offset += bytes.length; + if (offset % 8 !== 0) { + offset += 8 - (offset % 8); + } + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + argvPtrs.push(0); + + const keys = Object.keys(this.env).sort(); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + argvPtrs.push(0); + + const argv = offset; + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true); + this.mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192; + if (offset >= wasmMinDataAddr) { + throw new Error("total length of command line and environment variables exceeds limit"); + } + + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); + } + await this._exitPromise; + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } + }; +})(); diff --git a/test/webauthn/webauthn.wasm b/test/webauthn/webauthn.wasm new file mode 100755 index 0000000000000000000000000000000000000000..c1e114cdee5caf19ed1565bd756d4b20ef313ce5 GIT binary patch literal 5458724 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

u&Gn81|uXrqID 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%0Dc^Re<( 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

a)yw=4RM^ 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 z

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

uhiO?r62I7_9u{&TpXJoW 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

$SY_^gvi%0W7qCyKoXv?$ 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 {WM7z*sp{urvEL|)MGJ}kDeO$1-ZkrUm%h6H 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

h5NAQS>#lJY^jXKI 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 z zMG+yC>QKNKo887*lh_X0+C-}Xo94$_8#$>NA!`dUzz9uVET$4a$a{wW!zAZG6I#Sb z5MWl#5CoJ9C-hNtOl`x+PU1hjhoiVBKWo4L)6eRn|=YDoEF z(CPHWCOq+R{0T!Z#^p&DHg@9!S$1=L7|I@Qe3L1OeSU7cuyGj{;VBtLajC3D{E?-? zU277QKpc-$kn@T*$vup#lThQ19k3&A`?-eGo)eNQ5KN@x>h63j0AG zgXst|Hb%*o#CEO?e3uJt4A`EJHtkGmUY%F$VdrzagOl*Wnk}~#5q>P8zjQx<#}GOj zBnv;m*zRM|qHazNSpfLaeDduGBH36UphrK+M^(10bGv;Nk7I|>Q0L=zuVW8ue+jn< zO?Dot!1Jg1_BpsQ)VW0M#>-d-AL{&E>DJkkor_2@e8UO+$9z@0YKSM!aiQ$;a1L0E zv$Y8|!0@d0y7-bbLaM{IErV;&oHZ}PN96U=eiZ=v#w5hJdSW<_(x45RGp!Ex)rsC%5pSJD zBlW)#6ATL$1qy|N9>eC8PVZCcxGnz^wZuScX-jP?VnA<$S!#dH=9nTjli%_`@m5=p zqcR!zv&VdZm;fy3n?heYz9iTm+4;A1VqZ|77ZHkCWCKAmZ8$ZWwO0nj0=T9Hfv8L| zl>+=>-`syWC_iai$lBmni^ADb36jTD9T2fD>{o=vfyEnQ=6`aGObF9T{Ag=S4yIC! z{}aDvq-m(NRYL7wcmw%d21B>`LWDgDO&8kW05ww)87LbLIn2Zk+$LI_`%tx=7e%zB zy;DR>T5&%iv_MuFb6ZVQYdy=Bf6I*SnN(Qu!?01k|1fN(aj@pt727v38<2)0E+IJe z08%jxVL*sV_i1g9vr5`Av|-Ty{t%SFum`sCfDXPQ`9Ll#ZN)I66YC1gsvyb|h@z*h zN+Vnh%-tQz-Mz`m$xi@(AnX#WfW#jCxPas(TlhM7ZspavmCsK|+`vRfBS0GcE0gJL zu>YD!I9O(A%qZLWm)n2HRSsV0oKjFl21wRNX=*W?Wz#G}adn2`maT$<0snF^3%ZIB zL3Nhc$|h-U>NUBkKe7Q?@F?5;AK^;V$d%hi;GQxgiJx3);wM*{_=%M!JcneY zkt?C>hq2;>hDKh7jlO-PdmlhCfDg+M-*P_80wy1{HlI&}Dl^BcyJ@KJn-rt&5~G6q z&jNG8&hK=mZK9loHd>o++4e6&36c@96%5i1xn$=wR+|uMZsODAHwhWia*$c@n%uFc zBbX=TE-c#Oa+P6BRtVF;pAcC69jx_QqPvy(RdT!5$B0ZRjAc9aQoaoQjd8;@EVl6} zWNPCqhkgTc=#!-0g_EFxb=5UW9BraFlD0~>8CAFdIO+7hQVoROn8)zGrjW-6L)L6Z z<&m9!j99m*o2GG*q{mFY0D}#P%cBDE6(DW|t+_oAJjRd-{SL_vX$fcx`ARTR-fW6na9?DfP?UPqg>a&Em;N=m=1!4vIB$$`-Y1C+B;Kt2S?uQpMu%P zqhx>sZNmT2$@U)-&qxZMlwYGzf^W@-4&$LQ`?veSJ|<@G=JS>Y91kFlgvcOeN^6ok zHYO?1A9!VJ^dOQeo}g7nm2#)Q#9bPQ9pCx(Q(9x(%`x8Epa*o$2NNNNxcLKT3BjYJ z#8tECV7N7T046q)pt=9adVq+5a^!tGPK2eO_4Y)(jWQIzT}lP7Jov86GdMzk8X!(M zh)W#UDP<6`;RIR+AOc~Z<@YB~wsnXDQfL`)9fvsIFg4UZ7hf?optSe4TFBvxHL_R< zV}!?;{cXHs)}LOEeF)JFEz{Em;zb~T4Q;{>W=@ay><@5eUN zM4632{d^<86x`!TG!TdM#do^48qdbdz^ZlBwcWwR{Wp~L%UE9WQ}Kqu zPZyJ+pn@%<-~M#7*-Hyz|ADk~&$Txb&-onI2__IB+JJu>@o!S*#go+NdN_Xw5RYlJOlp896~I3U{Q%v8*3{Yxrwf|kcjz=^7QfxZEWj$E88V+FN6<+#Ujv3yMFEhc>Xy z4bo4NhZ}4|A=f_5A~cy~%GxA%hWe%&)Qrk&Ic!Y{|JMAD%cn>2o!sc#^8+4CVZ44E z?8)dYm;O-1QA>Ussw}DuTjKeCXVF_Skj|n^7)HPccB1vJI9y_tQ!+sv)4M=$nzfg$I$^3pSV#8}OR;mKBl0 zs~fwF*}<0Bcm=IMD0(t4cVAf^Z$U4+Ue83iJ&(6!mI2p&NSV^EdCv7!-HcKL7&HY# zPdB3wD+W&qVV*K~G$}?ZPX&Jp6?5F@gsdmCP>B}X?N1D2`chvWRhz4hb}YJbNAr?y z9`T#xNUY4t)eJF2WQM4s*a}fau@!ir*b41Tu@&0g#a7?MmSr7Dl(=0Jo`lOa6HgR2 z%khT13El{2L>>ih9I+=d-rtk)?p}qhWv;mNUv}x=*OCcO+;m0a&J-3$gvn)RswJ0u zM}4UkUJ12;Gzi)n0ZZZVMZB}Ym=f94{eF`uCMhW&Gp6SA^p>?_@v6kY)rf}wU?u^-W)O|8wC zsi(3&7gyAge+&h?NmUA&%}d@QYm%LM`~>x9`3VYfC72vVCMUPd@DrSJ1VJXWNioGw zo^)q-A_A-_>|8>w3vMLW1*ehgf&(+xWsdl8<_OP&pwxDyX&M zoH6U1al|NLp89&yWLq8yGJRPL zlPB4;csvpo+9UXS>tt(7ETPawV{pLMUSM8qaV}QTBswpfoqs#K_mkGA(yvwA0Gy`K_GB#QRWxTZeR=21-cBzB@4@s^L#6-yk<{=MjeS--9!aywXk3<&>Y3aF}#g< z4H)MKaXn%!P{Xo?1!@wsAoBt@%?H32DV#;L;kp+phcgP-b8*dC74nF)DoDgx6@PP9 z#eCtc3Jt1R6&IXUdCjYM!C95QSKsA-DY851o3y?@Almbz;9@&YuEnVh7u(?_L|2P}^!vP3io2 zB-G-(j2&m3aRz+caXEh=KV@rk8qw&gQ&O_X+{)Pgy*EU?jdxmI`L|<L=F zd9);)=+4qN)R!(^PQg8-VIkuDEEMD>YLYiN2sJlkfTs;L7VW3ez;-VetR8X>Nun^b zI(2hMp=MV*11YZ4IuMdTad6R@kkc_7g*^vb6;82L2Dfo>AycValvlFbU=1wACNyT@ zo&{0`{}Rx|h#*k<8w5&!Q$PaZqaiR`xVGVB;=MWJ;zpBSmZSw?>BI{&&7>EEEAK;i zQC&zv$r$6`XuEb~B)xV9`NhnO&GK=iwiJRA-qsiz_5&yvG3`WRHf#K5-QmI2X2QZ1 zDBa#>U9~{S7FG^8q2Jz#X@=sTfMyd!%qoT8Jf=8dqjSz9%En z#mGa1$f6j5$uE%%Gd~?9vTtfg(6DSuxjSQamArATKwk0SdS&mgbYnSYt=-UVG z_vq(tuoF_ci3jk-H1J1UclimPSpY(4|6=}g!5$o_wM#mjTxiiV$?!Ev;w!6gI4d^r zof$x1p@h>2V7l%`FSxTb4QEMdv&?1VK1h&So^7En3mqcuMfLBoqLw_|ds&->CE!ny zRefbM*790o zCUaKD49~M#qK;i036Tjg=;AapYCn_WJti`_u#{%Jv=leRF%i#3M9+H27U#hmRy|4{ zD3^-j5t)ZJy7oPc3c%CSXnjs;O-#iu%3Qwgr21Tj)hCs0ar8;0$<%?`vmOzas7@h4 zAYLSBg+dgU##91*!NeIoU#xY>X46#C6bcevYI6s?-@}|?5123hnafcu*qXl0`d1{Fb9eQ8>&-naW2Djv00^*b^fOlcORS5BC`NivOF+01M zy@})Y8(_~ru)!ZVo6HY`n>ju{VfErrnSzy@-Q_&5zF%#oVvLig+Y5QW+ES_30}Wcn z5-_wQvDKc=CnE{Chk8f|{fkI_Oe4xH-bS!SFW^hwk^m@A?qj;ah?2c%0*l!CWGO^ z>*aVJvXR#-aV?)3H_jRN7*4i za%`Vyz;H>HTp4!^H=z?qqg+aU95-QblVA)vXqs!v^KW3XX4ul_urLpBJ6ADmEk2bN5Oy+O=p=OgBwm%OvU=b zpG6jK+51=nUc-74S$LQ>Ia+RSYAr{W*oH%4r_j5~0W>}wZVoIZn-1Fx@jX;nSk5D^k$ z$TSTB58G}D2mF*dpvR`IIu)}rQ^3BDjyX*wcrmYfybf|X$cg+Cf^`%74H-nY3oKgR zcM8B#6*N5q&(1<3m`Nh`i{YU~pK3`QF7Zadq)JpFp;!2giD}X?V@E zo(X>$(DW>`|hyA~_Ez0?l; zLh_lKffp#UGbDSNPCT?c^)*2Svybx=%szen3_vHRNB~!-NDx633BrjYAt6N(BitGksjNcvYO7Fp=8@J?+wR(QjMbs_l=0y2 zprS8mG^C=rl9V(w3qq?Nv6k zb~oGqSTBN>=5n^SB+_|BRViNPGM9J$V9kXu&435bAex@jyf0!+7u0>g}!H?95{n<=+#{ktO#Kyp6)nvV2L ztw%On7CM*OfH_cnM+t}KC_*mA2bh?lx!`3^eN!^U7?|2iA!DEJZgD16VD>Ef_UWDs zOrP!xX5nW`=!OjYTWzYIYNSK7j;Od89xxS|Rms&V817DoqrjE01d~?6L|vipaEE)y z!mH{IP?u&{m6(JXWy}E(7nGRQfEE zZje34nTn`caz*UTOxC+H-h6+ne9dDjj*sN`YKr534MTAZub)=1t%08=BR$F~C}tGY zWcynxvR&x}%MFzjm4M#s>j#y50tI6zf*JNe*v*BqWf^F?&?L|V$9$19LQLT%CMw^f z)~`>Ty)In$7Gx`=;9z&;nr?#LC#sne#c8t_%;_o07h9Y%7?6@Q2uQ{cmc`piYPlrc z<8*er$$(69Gz!?c7`5_tnG0T

!!~etwtp1S4~sT3xiU7QVgtP%u?{LVvUy@Dds< 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 z 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 z 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�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 z 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%0D?garP+HS@ia^nFM1-LBpgm_-L1!U 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 nvm!5VQBC?NjIRia7T?_x}id-X#J5uK|CT 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%0Ai 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

$R6G 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 Date: Wed, 8 Jan 2025 13:25:24 +0530 Subject: [PATCH 27/36] Add support for correct types for signup and signin endpoints (#967) --- lib/ts/recipe/webauthn/api/implementation.ts | 8 +-- lib/ts/recipe/webauthn/index.ts | 5 +- lib/ts/recipe/webauthn/types.ts | 51 +++++++++++++++----- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 4056cda5c..9d7ec3065 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -20,7 +20,7 @@ import { getRecoverAccountLink } from "../utils"; import { logDebugMessage } from "../../../logger"; import { RecipeLevelUser } from "../../accountlinking/types"; import { getUser } from "../../.."; -import { CredentialPayload, ResidentKey, UserVerification } from "../types"; +import { AuthenticationPayload, RegistrationPayload, ResidentKey, UserVerification } from "../types"; export default function getAPIImplementation(): APIInterface { return { @@ -195,7 +195,7 @@ export default function getAPIImplementation(): APIInterface { userContext, }: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; tenantId: string; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; @@ -364,7 +364,7 @@ export default function getAPIImplementation(): APIInterface { userContext, }: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; tenantId: string; session?: SessionContainerInterface; shouldTryLinkingWithSessionUser: boolean | undefined; @@ -846,7 +846,7 @@ export default function getAPIImplementation(): APIInterface { }: { token: string; webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; tenantId: string; options: APIOptions; userContext: UserContext; diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index e5b9c6903..c18f3a7ef 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -24,6 +24,7 @@ import { UserVerification, ResidentKey, Attestation, + AuthenticationPayload, } from "./types"; import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; @@ -312,7 +313,7 @@ export default class Wrapper { }: { tenantId?: string; webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; session?: SessionContainerInterface; userContext?: Record; }): Promise< @@ -345,7 +346,7 @@ export default class Wrapper { }: { tenantId?: string; webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; userContext?: Record; }): Promise<{ status: "OK" } | { status: "INVALID_CREDENTIALS_ERROR" }> { const resp = await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index b1a6b4540..b3037fa4c 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -238,7 +238,7 @@ export type RecipeInterface = { signUp(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; @@ -267,7 +267,7 @@ export type RecipeInterface = { signIn(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; @@ -288,7 +288,7 @@ export type RecipeInterface = { verifyCredentials(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; tenantId: string; userContext: UserContext; }): Promise< @@ -303,7 +303,7 @@ export type RecipeInterface = { // called during operations like creating a user during password reset flow. createNewRecipeUser(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; tenantId: string; userContext: UserContext; }): Promise< @@ -357,7 +357,7 @@ export type RecipeInterface = { // (in consumeRecoverAccountToken invalidating the token and in registerOptions for storing the email in the generated options) registerCredential(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; userContext: UserContext; recipeUserId: RecipeUserId; }): Promise< @@ -636,7 +636,7 @@ export type APIInterface = { | undefined | ((input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; tenantId: string; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; @@ -666,7 +666,7 @@ export type APIInterface = { | undefined | ((input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; tenantId: string; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; @@ -711,7 +711,7 @@ export type APIInterface = { | ((input: { token: string; webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; tenantId: string; options: APIOptions; userContext: UserContext; @@ -760,16 +760,43 @@ export type TypeWebauthnRecoverAccountEmailDeliveryInput = { export type TypeWebauthnEmailDeliveryInput = TypeWebauthnRecoverAccountEmailDeliveryInput; -export type CredentialPayload = { +export type CredentialPayloadBase = { id: string; rawId: string; + authenticatorAttachment?: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; +}; + +export type AuthenticatorAssertionResponseJSON = { + clientDataJSON: Base64URLString; + authenticatorData: Base64URLString; + signature: Base64URLString; + userHandle?: Base64URLString; +}; + +export type AuthenticatorAttestationResponseJSON = { + clientDataJSON: Base64URLString; + attestationObject: Base64URLString; + authenticatorData?: Base64URLString; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + publicKeyAlgorithm?: COSEAlgorithmIdentifier; + publicKey?: Base64URLString; +}; + +export type AuthenticationPayload = CredentialPayloadBase & { + response: AuthenticatorAssertionResponseJSON; +}; + +export type RegistrationPayload = CredentialPayloadBase & { + response: AuthenticatorAttestationResponseJSON; +}; + +export type CredentialPayload = CredentialPayloadBase & { response: { clientDataJSON: string; attestationObject: string; transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; userHandle: string; }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; }; From 8dcca2db25c1460c4dc11ef1b823fa1604837e4e Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 8 Jan 2025 10:07:50 +0200 Subject: [PATCH 28/36] WebAuthn API testing updates (#971) --------- Co-authored-by: Victor Bojica --- lib/ts/recipe/webauthn/api/implementation.ts | 3 --- lib/ts/recipe/webauthn/constants.ts | 1 - lib/ts/recipe/webauthn/index.ts | 4 ---- lib/ts/recipe/webauthn/recipeImplementation.ts | 4 ++++ lib/ts/recipe/webauthn/types.ts | 1 - lib/ts/recipe/webauthn/utils.ts | 4 +--- test/webauthn/apis.test.js | 15 +++++++++++++++ 7 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 9d7ec3065..e7319b034 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -8,7 +8,6 @@ import { SessionContainerInterface } from "../../session/types"; import { DEFAULT_REGISTER_OPTIONS_ATTESTATION, DEFAULT_REGISTER_OPTIONS_TIMEOUT, - DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, DEFAULT_SIGNIN_OPTIONS_TIMEOUT, @@ -87,7 +86,6 @@ export default function getAPIImplementation(): APIInterface { const timeout = DEFAULT_REGISTER_OPTIONS_TIMEOUT; const attestation = DEFAULT_REGISTER_OPTIONS_ATTESTATION; - const requireResidentKey = DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY; const residentKey = DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY; const userVerification = DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION; const supportedAlgorithmIds = DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS; @@ -95,7 +93,6 @@ export default function getAPIImplementation(): APIInterface { let response = await options.recipeImplementation.registerOptions({ ...props, attestation, - requireResidentKey, residentKey, userVerification, origin, diff --git a/lib/ts/recipe/webauthn/constants.ts b/lib/ts/recipe/webauthn/constants.ts index 7f9e86afa..ec9be9a81 100644 --- a/lib/ts/recipe/webauthn/constants.ts +++ b/lib/ts/recipe/webauthn/constants.ts @@ -29,7 +29,6 @@ export const SIGNUP_EMAIL_EXISTS_API = "/webauthn/email/exists"; // defaults that can be overridden by the developer export const DEFAULT_REGISTER_OPTIONS_ATTESTATION = "none"; -export const DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = false; export const DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = "required"; export const DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = "preferred"; export const DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS = [-8, -7, -257]; diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index c18f3a7ef..75b0abbe8 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -34,7 +34,6 @@ import { getUserContext } from "../../utils"; import { SessionContainerInterface } from "../session/types"; import { User } from "../../types"; import { - DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, @@ -51,7 +50,6 @@ export default class Wrapper { static Error = SuperTokensError; static async registerOptions({ - requireResidentKey = DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, residentKey = DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, userVerification = DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, attestation = DEFAULT_REGISTER_OPTIONS_ATTESTATION, @@ -61,7 +59,6 @@ export default class Wrapper { userContext, ...rest }: { - requireResidentKey?: boolean; residentKey?: ResidentKey; userVerification?: UserVerification; attestation?: Attestation; @@ -166,7 +163,6 @@ export default class Wrapper { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions({ ...emailOrRecoverAccountToken, - requireResidentKey, residentKey, userVerification, supportedAlgorithmIds, diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 9c3361c2f..2cb7e6d5e 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -24,6 +24,8 @@ export default function getRecipeInterface( tenantId, userContext, supportedAlgorithmIds, + userVerification, + residentKey, ...rest }) { const emailInput = "email" in rest ? rest.email : undefined; @@ -89,6 +91,8 @@ export default function getRecipeInterface( timeout, attestation, supportedAlgorithmIds, + userVerification, + residentKey, }, userContext ); diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index b3037fa4c..cdf7c6914 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -156,7 +156,6 @@ export type RecipeInterface = { relyingPartyName: string; displayName?: string; origin: string; - requireResidentKey: boolean | undefined; // should default to false in order to allow multiple authenticators to be used; see https://auth0.com/blog/a-look-at-webauthn-resident-credentials/ // default to 'required' in order store the private key locally on the device and not on the server residentKey: ResidentKey | undefined; // default to 'preferred' in order to verify the user (biometrics, pin, etc) based on the device preferences diff --git a/lib/ts/recipe/webauthn/utils.ts b/lib/ts/recipe/webauthn/utils.ts index 1bf968fc7..a644332d8 100644 --- a/lib/ts/recipe/webauthn/utils.ts +++ b/lib/ts/recipe/webauthn/utils.ts @@ -94,9 +94,7 @@ function validateAndNormaliseRelyingPartyIdConfig( } else if (typeof relyingPartyIdConfig === "function") { return relyingPartyIdConfig(props); } else { - const urlString = normalisedAppinfo - .getOrigin({ request: props.request, userContext: props.userContext }) - .getAsStringDangerous(); + const urlString = normalisedAppinfo.apiDomain.getAsStringDangerous(); // should let this throw if the url is invalid const url = new URL(urlString); diff --git a/test/webauthn/apis.test.js b/test/webauthn/apis.test.js index c7eba3f13..1bb80bf4d 100644 --- a/test/webauthn/apis.test.js +++ b/test/webauthn/apis.test.js @@ -132,6 +132,21 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function validateEmailAddress: (email) => { return email === "test@example.com" ? undefined : "Invalid email"; }, + override: { + functions: (originalImplementation) => { + return { + ...originalImplementation, + signInOptions: (input) => { + return originalImplementation.signInOptions({ + ...input, + timeout: 10 * 1000, + userVerification: "required", + relyingPartyId: "testId.com", + }); + }, + }; + }, + }, }), ], }); From 00f03b53edde4c5fb472b876d1a7c6ef8351bc27 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Tue, 14 Jan 2025 12:32:15 +0200 Subject: [PATCH 29/36] added expires at and created at and minor fixes and register crendetial endpoiint --- lib/ts/recipe/accountlinking/recipe.ts | 4 +- lib/ts/recipe/webauthn/api/implementation.ts | 91 +++++++- .../recipe/webauthn/api/registerCredential.ts | 72 ++++++ lib/ts/recipe/webauthn/core-mock.ts | 208 +++++++++++++++--- lib/ts/recipe/webauthn/types.ts | 43 ++++ 5 files changed, 384 insertions(+), 34 deletions(-) create mode 100644 lib/ts/recipe/webauthn/api/registerCredential.ts diff --git a/lib/ts/recipe/accountlinking/recipe.ts b/lib/ts/recipe/accountlinking/recipe.ts index a29c6bc7d..c494a21fa 100644 --- a/lib/ts/recipe/accountlinking/recipe.ts +++ b/lib/ts/recipe/accountlinking/recipe.ts @@ -149,8 +149,10 @@ export default class Recipe extends RecipeModule { // then, we try and find a primary user based on the email / phone number / third party ID. let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo: user.loginMethods[0], + accountInfo: user.loginMethods[0], // todo we might need to omit the credental id here + doUnionOfAccountInfo: true, + userContext, }); diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 2a00b910c..5e6ba757a 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -38,6 +38,8 @@ export default function getAPIImplementation(): APIInterface { | { status: "OK"; webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; rp: { id: string; name: string; @@ -114,6 +116,8 @@ export default function getAPIImplementation(): APIInterface { return { status: "OK", webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, + createdAt: response.createdAt, + expiresAt: response.expiresAt, challenge: response.challenge, timeout: response.timeout, attestation: response.attestation, @@ -139,6 +143,8 @@ export default function getAPIImplementation(): APIInterface { | { status: "OK"; webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; challenge: string; timeout: number; userVerification: UserVerification; @@ -179,6 +185,8 @@ export default function getAPIImplementation(): APIInterface { return { status: "OK", webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, + createdAt: response.createdAt, + expiresAt: response.expiresAt, challenge: response.challenge, timeout: response.timeout, userVerification: response.userVerification, @@ -287,6 +295,8 @@ export default function getAPIImplementation(): APIInterface { doUnionOfAccountInfo: false, userContext, }); + + // this isn't mandatory to if ( conflictingUsers.some((u) => u.loginMethods.some((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) @@ -470,7 +480,7 @@ export default function getAPIImplementation(): APIInterface { session, shouldTryLinkingWithSessionUser, }); - if (preAuthChecks.status === "SIGN_UP_NOT_ALLOWED") { + if (preAuthChecks.status === "SIGN_IN_NOT_ALLOWED") { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); } if (preAuthChecks.status !== "OK") { @@ -1112,5 +1122,84 @@ export default function getAPIImplementation(): APIInterface { ); } }, + + registerCredentialPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + options, + userContext, + session, + }: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + options: APIOptions; + userContext: UserContext; + session: SessionContainerInterface; + }): Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + | { + status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; + reason: string; + } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + > { + // TODO update error codes (ERR_CODE_XXX) after final implementation + const errorCodeMap = { + REGISTER_CREDENTIAL_NOT_ALLOWED: + "Cannot register credential due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + INVALID_AUTHENTICATOR_ERROR: { + // TODO: add more cases + }, + INVALID_CREDENTIALS_ERROR: "The credentials are incorrect. Please use a different authenticator.", + }; + + const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (generatedOptions.status !== "OK") { + return generatedOptions; + } + + const email = generatedOptions.email; + + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (!email) { + throw new Error( + "Should never come here since we already check that the email value is a string in validateEmailAddress" + ); + } + + // we are using the email from the register options + const registerCredentialResponse = await options.recipeImplementation.registerCredential({ + webauthnGeneratedOptionsId, + credential, + userContext, + recipeUserId: session.getRecipeUserId(), + }); + + if (registerCredentialResponse.status !== "OK") { + return AuthUtils.getErrorStatusResponseWithReason( + registerCredentialResponse, + errorCodeMap, + "REGISTER_CREDENTIAL_NOT_ALLOWED" + ); + } + + return { + status: "OK", + }; + }, }; } diff --git a/lib/ts/recipe/webauthn/api/registerCredential.ts b/lib/ts/recipe/webauthn/api/registerCredential.ts new file mode 100644 index 000000000..4aacd8bd9 --- /dev/null +++ b/lib/ts/recipe/webauthn/api/registerCredential.ts @@ -0,0 +1,72 @@ +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { + getBackwardsCompatibleUserInfo, + getNormalisedShouldTryLinkingWithSessionUserFlag, + send200Response, +} from "../../../utils"; +import { validateWebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; +import { APIInterface, APIOptions } from ".."; +import STError from "../error"; +import { UserContext } from "../../../types"; +import { AuthUtils } from "../../../authUtils"; + +export default async function registerCredentialAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.registerCredentialPOST === undefined) { + return false; + } + + const requestBody = await options.req.getJSONBody(); + const webauthnGeneratedOptionsId = await validateWebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); + const credential = await validateCredentialOrThrowError(requestBody.credential); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, undefined, userContext); + + if (session === undefined) { + throw new STError({ + type: STError.BAD_INPUT_ERROR, + message: "A valid session is required to register a credential", + }); + } + + let result = await apiImplementation.registerCredentialPOST({ + credential, + webauthnGeneratedOptionsId, + tenantId, + options, + userContext: userContext, + session, + }); + + if (result.status === "OK") { + send200Response(options.res, { + status: "OK", + }); + } else if (result.status === "GENERAL_ERROR") { + send200Response(options.res, result); + } else { + send200Response(options.res, result); + } + + return true; +} diff --git a/lib/ts/recipe/webauthn/core-mock.ts b/lib/ts/recipe/webauthn/core-mock.ts index ecf81c0d8..daf4a6eb8 100644 --- a/lib/ts/recipe/webauthn/core-mock.ts +++ b/lib/ts/recipe/webauthn/core-mock.ts @@ -1,17 +1,27 @@ import NormalisedURLPath from "../../normalisedURLPath"; import { Querier } from "../../querier"; import { UserContext } from "../../types"; -import { generateAuthenticationOptions, generateRegistrationOptions } from "@simplewebauthn/server"; +import { + generateAuthenticationOptions, + generateRegistrationOptions, + verifyAuthenticationResponse, + verifyRegistrationResponse, +} from "@simplewebauthn/server"; import crypto from "crypto"; const db = { generatedOptions: {} as Record, + credentials: {} as Record, + users: {} as Record, }; const writeDb = (table: keyof typeof db, key: string, value: any) => { db[table][key] = value; }; -// const readDb = (table: keyof typeof db, key: string) => { -// return db[table][key]; +const readDb = (table: keyof typeof db, key: string) => { + return db[table][key]; +}; +// const readDbBy = (table: keyof typeof db, func: (value: any) => boolean) => { +// return Object.values(db[table]).find(func); // }; export const getMockQuerier = (recipeId: string) => { @@ -37,17 +47,29 @@ export const getMockQuerier = (recipeId: string) => { supportedAlgorithmIDs: body.supportedAlgorithmIDs || [-8, -7, -257], userDisplayName: body.displayName || body.email, }); + const id = crypto.randomUUID(); + const now = new Date(); + + const createdAt = now.getTime(); + const expiresAt = createdAt + body.timeout * 1000; writeDb("generatedOptions", id, { ...registrationOptions, id, origin: body.origin, tenantId: body.tenantId, + email: body.email, + rpId: registrationOptions.rp.id, + createdAt, + expiresAt, }); + // @ts-ignore return { status: "OK", webauthnGeneratedOptionsId: id, + createdAt, + expiresAt, ...registrationOptions, }; } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { @@ -56,57 +78,179 @@ export const getMockQuerier = (recipeId: string) => { timeout: body.timeout, userVerification: body.userVerification || "preferred", }); + const id = crypto.randomUUID(); - writeDb("generatedOptions", id, { ...signInOptions, id, origin: body.origin, tenantId: body.tenantId }); + const now = new Date(); + + const createdAt = now.getTime(); + const expiresAt = createdAt + body.timeout * 1000; + writeDb("generatedOptions", id, { + ...signInOptions, + id, + origin: body.origin, + tenantId: body.tenantId, + email: body.email, + createdAt, + expiresAt, + }); // @ts-ignore return { status: "OK", webauthnGeneratedOptionsId: id, + createdAt, + expiresAt, ...signInOptions, }; - // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token")) { - // // @ts-ignore - // return { - // status: "OK", - // token: "dummy-recover-token", - // }; - // } else if (path.getAsStringDangerous().includes("/recipe/webauthn/user/recover/token/consume")) { - // // @ts-ignore - // return { - // status: "OK", - // userId: "dummy-user-id", - // email: "user@example.com", - // }; - // } } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { - // @ts-ignore - return { - status: "OK", - user: { - id: "dummy-user-id", - email: "user@example.com", - timeJoined: Date.now(), + const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + + const registrationVerification = await verifyRegistrationResponse({ + expectedChallenge: options.challenge, + expectedOrigin: options.origin, + expectedRPID: options.rpId, + response: body.credential, + }); + + if (!registrationVerification.verified) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const credentialId = body.credential.id; + if (!credentialId) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const recipeUserId = crypto.randomUUID(); + const now = new Date(); + + writeDb("credentials", credentialId, { + id: credentialId, + userId: recipeUserId, + counter: 0, + publicKey: registrationVerification.registrationInfo?.credential.publicKey.toString(), + rpId: options.rpId, + transports: registrationVerification.registrationInfo?.credential.transports, + createdAt: now.toISOString(), + }); + + const user = { + id: recipeUserId, + timeJoined: now.getTime(), + isPrimaryUser: true, + tenantIds: [body.tenantId], + emails: [options.email], + phoneNumbers: [], + thirdParty: [], + webauthn: { + credentialIds: [credentialId], }, - recipeUserId: "dummy-recipe-user-id", + loginMethods: [ + { + recipeId: "webauthn", + recipeUserId, + tenantIds: [body.tenantId], + verified: true, + timeJoined: now.getTime(), + webauthn: { + credentialIds: [credentialId], + }, + email: options.email, + }, + ], }; + writeDb("users", recipeUserId, user); + + const response = { + status: "OK", + user: user, + recipeUserId, + }; + + // @ts-ignore + return response; } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const credentialId = body.credential.id; + const credential = readDb("credentials", credentialId); + if (!credential) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const authenticationVerification = await verifyAuthenticationResponse({ + expectedChallenge: options.challenge, + expectedOrigin: options.origin, + expectedRPID: options.rpId, + response: body.credential, + credential: { + publicKey: new Uint8Array(credential.publicKey.split(",").map((byte: string) => parseInt(byte))), + transports: credential.transports, + counter: credential.counter, + id: credential.id, + }, + }); + + if (!authenticationVerification.verified) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + + const user = readDb("users", credential.userId); + + if (!user) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + // @ts-ignore return { status: "OK", - user: { - id: "dummy-user-id", - email: "user@example.com", - timeJoined: Date.now(), - }, - recipeUserId: "dummy-recipe-user-id", + user, + recipeUserId: user.id, }; } throw new Error(`Unmocked endpoint: ${path}`); }; + const sendGetRequest = async ( + path: NormalisedURLPath, + _body: any, + _userContext: UserContext + ): Promise => { + if (path.getAsStringDangerous().includes("/recipe/webauthn/options")) { + const webauthnGeneratedOptionsId = path.getAsStringDangerous().split("/").pop(); + if (!webauthnGeneratedOptionsId) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + + const options = readDb("generatedOptions", webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + + return { status: "OK", ...options }; + } + + throw new Error(`Unmocked endpoint: ${path}`); + }; + querier.sendPostRequest = sendPostRequest; + querier.sendGetRequest = sendGetRequest; return querier; }; diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 2f694f1b1..ea104c44e 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -181,6 +181,8 @@ export type RecipeInterface = { | { status: "OK"; webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; // for understanding the response, see https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential and https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential rp: { id: string; @@ -228,6 +230,8 @@ export type RecipeInterface = { | { status: "OK"; webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; challenge: string; timeout: number; userVerification: UserVerification; @@ -565,6 +569,16 @@ export type APIOptions = { // | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired // | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; +// type RegisterCredentialPOSTErrorResponse = +// | { +// status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; +// reason: string; +// } +// | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation +// | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found +// | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired +// | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; + export type APIInterface = { registerOptionsPOST: | undefined @@ -578,6 +592,8 @@ export type APIInterface = { | { status: "OK"; webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; rp: { id: string; name: string; @@ -623,6 +639,8 @@ export type APIInterface = { | { status: "OK"; webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; challenge: string; timeout: number; userVerification: UserVerification; @@ -730,6 +748,31 @@ export type APIInterface = { | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } >); + registerCredentialPOST: + | undefined + | ((input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + // | RegisterCredentialPOSTErrorResponse + | { + status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; + reason: string; + } + | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation + | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found + | { status: "INVALID_GENERATED_OPTIONS_ERROR" } // i.e. timeout expired + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } + >); + // used for checking if the email already exists before generating the credential emailExistsGET: | undefined From 217bbd30744b31896e97351cbdd5e5ed63d63d9d Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Tue, 14 Jan 2025 12:33:19 +0200 Subject: [PATCH 30/36] fix --- lib/ts/recipe/webauthn/recipe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts index 533dbc293..8620c3fde 100644 --- a/lib/ts/recipe/webauthn/recipe.ts +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -48,7 +48,7 @@ import MultitenancyRecipe from "../multitenancy/recipe"; import { User } from "../../user"; import { isFakeEmail } from "../thirdparty/utils"; import { FactorIds } from "../multifactorauth"; -import { getMockQuerier } from "../../core-mock"; +import { getMockQuerier } from "./core-mock"; export default class Recipe extends RecipeModule { private static instance: Recipe | undefined = undefined; From eafcf8eaa92899e0ecf632292a9555a25e0bbbdb Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 21 Jan 2025 11:58:39 +0200 Subject: [PATCH 31/36] Split listUsersByAccountInfo input type from AccountInfo (#973) * split listUsersByAccountInfo input type from AccountInfo * remove webauthn when linking users * build files * added comment explaining the missing webauthn credential id from account linking --------- Co-authored-by: Victor Bojica --- lib/build/authUtils.d.ts | 300 ++--- lib/build/authUtils.js | 595 +++------- lib/build/combinedRemoteJWKSet.d.ts | 5 +- lib/build/combinedRemoteJWKSet.js | 17 +- lib/build/core-mock.js | 54 +- lib/build/customFramework.d.ts | 34 +- lib/build/customFramework.js | 39 +- lib/build/error.d.ts | 22 +- lib/build/framework/awsLambda/framework.d.ts | 23 +- lib/build/framework/awsLambda/framework.js | 81 +- lib/build/framework/awsLambda/index.d.ts | 4 +- lib/build/framework/constants.d.ts | 3 +- lib/build/framework/constants.js | 3 +- lib/build/framework/custom/framework.d.ts | 66 +- lib/build/framework/custom/framework.js | 26 +- lib/build/framework/custom/index.d.ts | 31 +- lib/build/framework/custom/index.js | 14 +- lib/build/framework/custom/nodeHeaders.js | 101 +- lib/build/framework/express/framework.d.ts | 11 +- lib/build/framework/express/framework.js | 32 +- lib/build/framework/express/index.d.ts | 13 +- lib/build/framework/fastify/framework.d.ts | 11 +- lib/build/framework/fastify/framework.js | 37 +- lib/build/framework/fastify/index.d.ts | 12 +- lib/build/framework/fastify/types.d.ts | 12 +- lib/build/framework/hapi/framework.d.ts | 18 +- lib/build/framework/hapi/framework.js | 29 +- lib/build/framework/index.js | 69 +- lib/build/framework/koa/framework.d.ts | 11 +- lib/build/framework/koa/framework.js | 20 +- lib/build/framework/loopback/framework.d.ts | 11 +- lib/build/framework/loopback/framework.js | 23 +- lib/build/framework/request.js | 12 +- lib/build/framework/response.d.ts | 11 +- lib/build/framework/utils.d.ts | 41 +- lib/build/framework/utils.js | 172 ++- lib/build/index.d.ts | 61 +- lib/build/index.js | 62 +- lib/build/ingredients/emaildelivery/index.js | 8 +- .../emaildelivery/services/smtp.d.ts | 8 +- .../ingredients/emaildelivery/types.d.ts | 20 +- lib/build/ingredients/smsdelivery/index.js | 8 +- .../smsdelivery/services/twilio.d.ts | 45 +- .../smsdelivery/services/twilio.js | 9 +- lib/build/ingredients/smsdelivery/types.d.ts | 20 +- lib/build/logger.js | 14 +- lib/build/nextjs.d.ts | 28 +- lib/build/nextjs.js | 8 +- lib/build/normalisedURLDomain.js | 18 +- lib/build/normalisedURLPath.js | 18 +- lib/build/processState.d.ts | 2 +- lib/build/processState.js | 27 +- lib/build/querier.d.ts | 40 +- lib/build/querier.js | 621 +++++----- lib/build/recipe/accountlinking/index.d.ts | 172 +-- lib/build/recipe/accountlinking/index.js | 8 +- lib/build/recipe/accountlinking/recipe.d.ts | 100 +- lib/build/recipe/accountlinking/recipe.js | 460 +++----- .../accountlinking/recipeImplementation.d.ts | 6 +- .../accountlinking/recipeImplementation.js | 154 +-- lib/build/recipe/accountlinking/types.d.ts | 189 ++-- lib/build/recipe/accountlinking/utils.js | 14 +- lib/build/recipe/dashboard/api/analytics.d.ts | 7 +- lib/build/recipe/dashboard/api/analytics.js | 18 +- .../recipe/dashboard/api/apiKeyProtector.d.ts | 8 +- .../recipe/dashboard/api/apiKeyProtector.js | 11 +- lib/build/recipe/dashboard/api/dashboard.d.ts | 6 +- .../recipe/dashboard/api/implementation.js | 27 +- .../createOrUpdateThirdPartyConfig.d.ts | 28 +- .../createOrUpdateThirdPartyConfig.js | 58 +- .../api/multitenancy/createTenant.d.ts | 28 +- .../api/multitenancy/createTenant.js | 36 +- .../api/multitenancy/deleteTenant.d.ts | 21 +- .../api/multitenancy/deleteTenant.js | 11 +- .../multitenancy/deleteThirdPartyConfig.d.ts | 21 +- .../multitenancy/deleteThirdPartyConfig.js | 36 +- .../api/multitenancy/getTenantInfo.d.ts | 45 +- .../api/multitenancy/getTenantInfo.js | 96 +- .../api/multitenancy/getThirdPartyConfig.d.ts | 29 +- .../api/multitenancy/getThirdPartyConfig.js | 157 +-- .../listAllTenantsWithLoginMethods.d.ts | 7 +- .../listAllTenantsWithLoginMethods.js | 8 +- .../multitenancy/updateTenantCoreConfig.d.ts | 26 +- .../multitenancy/updateTenantCoreConfig.js | 11 +- .../multitenancy/updateTenantFirstFactor.d.ts | 26 +- .../multitenancy/updateTenantFirstFactor.js | 35 +- .../updateTenantSecondaryFactor.d.ts | 33 +- .../updateTenantSecondaryFactor.js | 35 +- .../dashboard/api/multitenancy/utils.d.ts | 8 +- .../dashboard/api/multitenancy/utils.js | 46 +- .../recipe/dashboard/api/search/tagsGet.d.ts | 7 +- .../recipe/dashboard/api/search/tagsGet.js | 14 +- lib/build/recipe/dashboard/api/signIn.js | 20 +- lib/build/recipe/dashboard/api/signOut.d.ts | 7 +- lib/build/recipe/dashboard/api/signOut.js | 21 +- .../api/user/create/emailpasswordUser.d.ts | 37 +- .../api/user/create/emailpasswordUser.js | 18 +- .../api/user/create/passwordlessUser.d.ts | 39 +- .../api/user/create/passwordlessUser.js | 30 +- .../dashboard/api/userdetails/userDelete.js | 11 +- .../api/userdetails/userEmailVerifyGet.js | 17 +- .../api/userdetails/userEmailVerifyPut.d.ts | 7 +- .../api/userdetails/userEmailVerifyPut.js | 31 +- .../userdetails/userEmailVerifyTokenPost.d.ts | 7 +- .../userdetails/userEmailVerifyTokenPost.js | 16 +- .../dashboard/api/userdetails/userGet.js | 21 +- .../api/userdetails/userMetadataGet.js | 11 +- .../api/userdetails/userMetadataPut.d.ts | 7 +- .../api/userdetails/userMetadataPut.js | 11 +- .../api/userdetails/userPasswordPut.d.ts | 21 +- .../api/userdetails/userPasswordPut.js | 17 +- .../dashboard/api/userdetails/userPut.d.ts | 52 +- .../dashboard/api/userdetails/userPut.js | 54 +- .../api/userdetails/userSessionsGet.js | 35 +- .../api/userdetails/userSessionsPost.d.ts | 7 +- .../api/userdetails/userSessionsPost.js | 8 +- .../api/userdetails/userUnlinkGet.d.ts | 7 +- .../api/userdetails/userUnlinkGet.js | 8 +- .../api/userroles/addRoleToUser.d.ts | 20 +- .../dashboard/api/userroles/addRoleToUser.js | 11 +- .../api/userroles/getRolesForUser.d.ts | 20 +- .../api/userroles/getRolesForUser.js | 11 +- .../permissions/getPermissionsForRole.d.ts | 20 +- .../permissions/getPermissionsForRole.js | 11 +- .../permissions/removePermissions.d.ts | 7 +- .../permissions/removePermissions.js | 11 +- .../api/userroles/removeUserRole.d.ts | 20 +- .../dashboard/api/userroles/removeUserRole.js | 11 +- .../roles/createRoleOrAddPermissions.d.ts | 20 +- .../roles/createRoleOrAddPermissions.js | 11 +- .../api/userroles/roles/deleteRole.d.ts | 20 +- .../api/userroles/roles/deleteRole.js | 11 +- .../api/userroles/roles/getAllRoles.js | 11 +- .../recipe/dashboard/api/usersCountGet.d.ts | 7 +- .../recipe/dashboard/api/usersCountGet.js | 8 +- lib/build/recipe/dashboard/api/usersGet.d.ts | 4 +- lib/build/recipe/dashboard/api/usersGet.js | 67 +- lib/build/recipe/dashboard/api/validateKey.js | 3 +- lib/build/recipe/dashboard/error.js | 8 +- lib/build/recipe/dashboard/index.js | 11 +- lib/build/recipe/dashboard/recipe.d.ts | 10 +- lib/build/recipe/dashboard/recipe.js | 255 ++--- .../recipe/dashboard/recipeImplementation.js | 35 +- lib/build/recipe/dashboard/types.d.ts | 26 +- lib/build/recipe/dashboard/utils.d.ts | 6 +- lib/build/recipe/dashboard/utils.js | 49 +- .../recipe/emailpassword/api/emailExists.d.ts | 7 +- .../recipe/emailpassword/api/emailExists.js | 8 +- .../api/generatePasswordResetToken.d.ts | 7 +- .../api/generatePasswordResetToken.js | 7 +- .../emailpassword/api/implementation.js | 389 ++----- .../emailpassword/api/passwordReset.d.ts | 7 +- .../recipe/emailpassword/api/passwordReset.js | 28 +- .../recipe/emailpassword/api/signin.d.ts | 7 +- lib/build/recipe/emailpassword/api/signin.js | 22 +- .../recipe/emailpassword/api/signup.d.ts | 7 +- lib/build/recipe/emailpassword/api/signup.js | 41 +- lib/build/recipe/emailpassword/api/utils.d.ts | 15 +- lib/build/recipe/emailpassword/api/utils.js | 15 +- .../services/backwardCompatibility/index.d.ts | 11 +- .../services/backwardCompatibility/index.js | 16 +- .../emaildelivery/services/index.js | 8 +- .../emaildelivery/services/smtp/index.d.ts | 8 +- .../emaildelivery/services/smtp/index.js | 16 +- .../services/smtp/passwordReset.d.ts | 4 +- .../services/smtp/passwordReset.js | 8 +- .../smtp/serviceImplementation/index.d.ts | 11 +- .../smtp/serviceImplementation/index.js | 11 +- lib/build/recipe/emailpassword/error.d.ts | 26 +- lib/build/recipe/emailpassword/error.js | 8 +- lib/build/recipe/emailpassword/index.d.ts | 239 ++-- lib/build/recipe/emailpassword/index.js | 25 +- .../emailpassword/passwordResetFunctions.d.ts | 12 +- .../emailpassword/passwordResetFunctions.js | 27 +- lib/build/recipe/emailpassword/recipe.d.ts | 22 +- lib/build/recipe/emailpassword/recipe.js | 56 +- .../emailpassword/recipeImplementation.d.ts | 5 +- .../emailpassword/recipeImplementation.js | 144 +-- lib/build/recipe/emailpassword/types.d.ts | 359 +++--- lib/build/recipe/emailpassword/utils.d.ts | 21 +- lib/build/recipe/emailpassword/utils.js | 103 +- .../emailverification/api/emailVerify.d.ts | 7 +- .../emailverification/api/emailVerify.js | 28 +- .../api/generateEmailVerifyToken.d.ts | 6 +- .../api/generateEmailVerifyToken.js | 15 +- .../emailverification/api/implementation.js | 115 +- .../emailVerificationClaim.js | 51 +- .../emailVerificationFunctions.d.ts | 6 +- .../emailVerificationFunctions.js | 27 +- .../services/backwardCompatibility/index.d.ts | 11 +- .../services/backwardCompatibility/index.js | 16 +- .../emaildelivery/services/index.js | 8 +- .../services/smtp/emailVerify.js | 8 +- .../emaildelivery/services/smtp/index.d.ts | 8 +- .../emaildelivery/services/smtp/index.js | 16 +- .../services/smtp/serviceImplementation.d.ts | 11 +- .../services/smtp/serviceImplementation.js | 11 +- lib/build/recipe/emailverification/error.d.ts | 5 +- lib/build/recipe/emailverification/error.js | 8 +- lib/build/recipe/emailverification/index.d.ts | 114 +- lib/build/recipe/emailverification/index.js | 49 +- .../recipe/emailverification/recipe.d.ts | 22 +- lib/build/recipe/emailverification/recipe.js | 103 +- .../recipeImplementation.d.ts | 5 +- .../emailverification/recipeImplementation.js | 80 +- lib/build/recipe/emailverification/types.d.ts | 191 ++-- lib/build/recipe/emailverification/utils.d.ts | 6 +- lib/build/recipe/emailverification/utils.js | 37 +- lib/build/recipe/jwt/api/getJWKS.d.ts | 6 +- lib/build/recipe/jwt/api/getJWKS.js | 3 +- lib/build/recipe/jwt/api/implementation.js | 2 +- lib/build/recipe/jwt/index.d.ts | 24 +- lib/build/recipe/jwt/index.js | 8 +- lib/build/recipe/jwt/recipe.d.ts | 10 +- lib/build/recipe/jwt/recipe.js | 19 +- .../recipe/jwt/recipeImplementation.d.ts | 6 +- lib/build/recipe/jwt/recipeImplementation.js | 38 +- lib/build/recipe/jwt/types.d.ts | 42 +- lib/build/recipe/jwt/utils.d.ts | 6 +- lib/build/recipe/jwt/utils.js | 13 +- .../multifactorauth/api/implementation.js | 42 +- .../api/resyncSessionAndFetchMFAInfo.d.ts | 6 +- .../api/resyncSessionAndFetchMFAInfo.js | 15 +- lib/build/recipe/multifactorauth/index.d.ts | 29 +- lib/build/recipe/multifactorauth/index.js | 49 +- .../multifactorauth/multiFactorAuthClaim.d.ts | 46 +- .../multifactorauth/multiFactorAuthClaim.js | 48 +- lib/build/recipe/multifactorauth/recipe.d.ts | 59 +- lib/build/recipe/multifactorauth/recipe.js | 41 +- .../multifactorauth/recipeImplementation.js | 88 +- lib/build/recipe/multifactorauth/types.d.ts | 106 +- lib/build/recipe/multifactorauth/utils.d.ts | 25 +- lib/build/recipe/multifactorauth/utils.js | 153 ++- .../multitenancy/allowedDomainsClaim.js | 8 +- .../recipe/multitenancy/api/implementation.js | 25 +- .../recipe/multitenancy/api/loginMethods.d.ts | 7 +- lib/build/recipe/multitenancy/error.d.ts | 5 +- lib/build/recipe/multitenancy/error.js | 8 +- lib/build/recipe/multitenancy/index.d.ts | 88 +- lib/build/recipe/multitenancy/index.js | 15 +- lib/build/recipe/multitenancy/recipe.d.ts | 10 +- lib/build/recipe/multitenancy/recipe.js | 22 +- .../multitenancy/recipeImplementation.js | 100 +- lib/build/recipe/multitenancy/types.d.ts | 87 +- lib/build/recipe/multitenancy/utils.d.ts | 28 +- lib/build/recipe/multitenancy/utils.js | 74 +- .../recipe/oauth2client/api/implementation.js | 28 +- lib/build/recipe/oauth2client/api/signin.d.ts | 7 +- lib/build/recipe/oauth2client/api/signin.js | 25 +- lib/build/recipe/oauth2client/index.d.ts | 19 +- lib/build/recipe/oauth2client/index.js | 8 +- lib/build/recipe/oauth2client/recipe.d.ts | 18 +- lib/build/recipe/oauth2client/recipe.js | 15 +- .../oauth2client/recipeImplementation.js | 56 +- lib/build/recipe/oauth2client/types.d.ts | 85 +- lib/build/recipe/oauth2client/utils.d.ts | 5 +- lib/build/recipe/oauth2client/utils.js | 12 +- .../recipe/oauth2provider/OAuth2Client.d.ts | 29 +- .../recipe/oauth2provider/OAuth2Client.js | 29 +- lib/build/recipe/oauth2provider/api/auth.d.ts | 6 +- lib/build/recipe/oauth2provider/api/auth.js | 31 +- .../recipe/oauth2provider/api/endSession.d.ts | 12 +- .../recipe/oauth2provider/api/endSession.js | 27 +- .../oauth2provider/api/implementation.js | 6 +- .../oauth2provider/api/introspectToken.d.ts | 6 +- .../recipe/oauth2provider/api/login.d.ts | 6 +- lib/build/recipe/oauth2provider/api/login.js | 36 +- .../recipe/oauth2provider/api/loginInfo.d.ts | 6 +- .../recipe/oauth2provider/api/loginInfo.js | 13 +- .../recipe/oauth2provider/api/logout.d.ts | 6 +- lib/build/recipe/oauth2provider/api/logout.js | 17 +- .../oauth2provider/api/revokeToken.d.ts | 6 +- .../recipe/oauth2provider/api/revokeToken.js | 9 +- .../recipe/oauth2provider/api/token.d.ts | 6 +- lib/build/recipe/oauth2provider/api/token.js | 3 +- .../recipe/oauth2provider/api/userInfo.d.ts | 7 +- .../recipe/oauth2provider/api/userInfo.js | 27 +- .../recipe/oauth2provider/api/utils.d.ts | 68 +- lib/build/recipe/oauth2provider/api/utils.js | 71 +- lib/build/recipe/oauth2provider/index.d.ts | 173 +-- lib/build/recipe/oauth2provider/index.js | 35 +- lib/build/recipe/oauth2provider/recipe.d.ts | 42 +- lib/build/recipe/oauth2provider/recipe.js | 51 +- .../oauth2provider/recipeImplementation.d.ts | 9 +- .../oauth2provider/recipeImplementation.js | 449 +++----- lib/build/recipe/oauth2provider/types.d.ts | 500 ++++---- lib/build/recipe/oauth2provider/utils.d.ts | 6 +- lib/build/recipe/oauth2provider/utils.js | 8 +- .../api/getOpenIdDiscoveryConfiguration.d.ts | 6 +- .../api/getOpenIdDiscoveryConfiguration.js | 3 +- lib/build/recipe/openid/index.d.ts | 4 +- lib/build/recipe/openid/index.js | 8 +- lib/build/recipe/openid/recipe.d.ts | 10 +- lib/build/recipe/openid/recipe.js | 18 +- .../recipe/openid/recipeImplementation.js | 17 +- lib/build/recipe/openid/types.d.ts | 64 +- lib/build/recipe/openid/utils.js | 8 +- .../recipe/passwordless/api/consumeCode.d.ts | 7 +- .../recipe/passwordless/api/consumeCode.js | 65 +- .../recipe/passwordless/api/createCode.d.ts | 7 +- .../recipe/passwordless/api/createCode.js | 38 +- .../recipe/passwordless/api/emailExists.d.ts | 7 +- .../recipe/passwordless/api/emailExists.js | 8 +- .../recipe/passwordless/api/implementation.js | 468 +++----- .../passwordless/api/phoneNumberExists.d.ts | 7 +- .../passwordless/api/phoneNumberExists.js | 8 +- .../recipe/passwordless/api/resendCode.d.ts | 7 +- .../recipe/passwordless/api/resendCode.js | 15 +- .../services/backwardCompatibility/index.d.ts | 11 +- .../services/backwardCompatibility/index.js | 36 +- .../emaildelivery/services/index.js | 8 +- .../emaildelivery/services/smtp/index.d.ts | 8 +- .../emaildelivery/services/smtp/index.js | 16 +- .../services/smtp/passwordlessLogin.d.ts | 8 +- .../services/smtp/passwordlessLogin.js | 31 +- .../services/smtp/serviceImplementation.d.ts | 11 +- .../services/smtp/serviceImplementation.js | 11 +- lib/build/recipe/passwordless/error.d.ts | 5 +- lib/build/recipe/passwordless/error.js | 8 +- lib/build/recipe/passwordless/index.d.ts | 398 +++---- lib/build/recipe/passwordless/index.js | 103 +- lib/build/recipe/passwordless/recipe.d.ts | 80 +- lib/build/recipe/passwordless/recipe.js | 229 ++-- .../passwordless/recipeImplementation.js | 107 +- .../services/backwardCompatibility/index.d.ts | 8 +- .../services/backwardCompatibility/index.js | 55 +- .../smsdelivery/services/index.js | 8 +- .../smsdelivery/services/supertokens/index.js | 47 +- .../smsdelivery/services/twilio/index.d.ts | 8 +- .../smsdelivery/services/twilio/index.js | 35 +- .../services/twilio/passwordlessLogin.js | 14 +- .../twilio/serviceImplementation.d.ts | 4 +- .../services/twilio/serviceImplementation.js | 11 +- lib/build/recipe/passwordless/types.d.ts | 517 ++++----- lib/build/recipe/passwordless/utils.d.ts | 6 +- lib/build/recipe/passwordless/utils.js | 87 +- lib/build/recipe/session/accessToken.d.ts | 6 +- lib/build/recipe/session/accessToken.js | 191 ++-- .../recipe/session/api/implementation.js | 17 +- lib/build/recipe/session/api/refresh.d.ts | 6 +- lib/build/recipe/session/api/signout.d.ts | 6 +- .../session/claimBaseClasses/booleanClaim.js | 5 +- .../claimBaseClasses/primitiveArrayClaim.d.ts | 14 +- .../claimBaseClasses/primitiveArrayClaim.js | 59 +- .../claimBaseClasses/primitiveClaim.d.ts | 14 +- .../claimBaseClasses/primitiveClaim.js | 9 +- lib/build/recipe/session/claims.js | 28 +- .../recipe/session/cookieAndHeaders.d.ts | 50 +- lib/build/recipe/session/cookieAndHeaders.js | 75 +- lib/build/recipe/session/error.d.ts | 57 +- lib/build/recipe/session/error.js | 21 +- .../recipe/session/framework/awsLambda.js | 11 +- .../recipe/session/framework/custom.d.ts | 8 +- lib/build/recipe/session/framework/custom.js | 14 +- .../recipe/session/framework/express.d.ts | 4 +- lib/build/recipe/session/framework/express.js | 14 +- .../recipe/session/framework/fastify.d.ts | 4 +- lib/build/recipe/session/framework/fastify.js | 11 +- lib/build/recipe/session/framework/hapi.d.ts | 4 +- lib/build/recipe/session/framework/hapi.js | 14 +- lib/build/recipe/session/framework/index.js | 55 +- lib/build/recipe/session/framework/koa.d.ts | 4 +- lib/build/recipe/session/framework/koa.js | 14 +- .../recipe/session/framework/loopback.js | 14 +- lib/build/recipe/session/index.d.ts | 226 +--- lib/build/recipe/session/index.js | 56 +- lib/build/recipe/session/jwt.js | 31 +- lib/build/recipe/session/recipe.d.ts | 34 +- lib/build/recipe/session/recipe.js | 108 +- .../recipe/session/recipeImplementation.d.ts | 7 +- .../recipe/session/recipeImplementation.js | 306 ++--- lib/build/recipe/session/sessionClass.d.ts | 15 +- lib/build/recipe/session/sessionClass.js | 186 +-- .../recipe/session/sessionFunctions.d.ts | 79 +- lib/build/recipe/session/sessionFunctions.js | 233 ++-- .../session/sessionRequestFunctions.d.ts | 44 +- .../recipe/session/sessionRequestFunctions.js | 221 ++-- lib/build/recipe/session/types.d.ts | 182 ++- lib/build/recipe/session/utils.d.ts | 80 +- lib/build/recipe/session/utils.js | 186 +-- .../recipe/thirdparty/api/appleRedirect.d.ts | 6 +- .../thirdparty/api/authorisationUrl.d.ts | 7 +- .../recipe/thirdparty/api/authorisationUrl.js | 8 +- .../recipe/thirdparty/api/implementation.js | 93 +- lib/build/recipe/thirdparty/api/signinup.d.ts | 7 +- lib/build/recipe/thirdparty/api/signinup.js | 37 +- lib/build/recipe/thirdparty/error.d.ts | 10 +- lib/build/recipe/thirdparty/error.js | 8 +- lib/build/recipe/thirdparty/index.d.ts | 90 +- lib/build/recipe/thirdparty/index.js | 18 +- .../thirdparty/providers/activeDirectory.js | 12 +- .../recipe/thirdparty/providers/apple.js | 117 +- .../recipe/thirdparty/providers/bitbucket.js | 49 +- .../recipe/thirdparty/providers/boxySaml.js | 15 +- .../thirdparty/providers/configUtils.d.ts | 26 +- .../thirdparty/providers/configUtils.js | 76 +- .../recipe/thirdparty/providers/custom.js | 126 +-- .../recipe/thirdparty/providers/discord.js | 15 +- .../recipe/thirdparty/providers/facebook.js | 39 +- .../recipe/thirdparty/providers/github.js | 50 +- .../recipe/thirdparty/providers/gitlab.js | 15 +- .../recipe/thirdparty/providers/google.js | 17 +- .../thirdparty/providers/googleWorkspaces.js | 33 +- .../recipe/thirdparty/providers/index.js | 8 +- .../recipe/thirdparty/providers/linkedin.js | 22 +- lib/build/recipe/thirdparty/providers/okta.js | 12 +- .../recipe/thirdparty/providers/twitter.js | 94 +- .../recipe/thirdparty/providers/utils.d.ts | 34 +- .../recipe/thirdparty/providers/utils.js | 77 +- lib/build/recipe/thirdparty/recipe.d.ts | 19 +- lib/build/recipe/thirdparty/recipe.js | 34 +- .../recipe/thirdparty/recipeImplementation.js | 103 +- lib/build/recipe/thirdparty/types.d.ts | 241 ++-- lib/build/recipe/thirdparty/utils.d.ts | 5 +- lib/build/recipe/thirdparty/utils.js | 13 +- lib/build/recipe/totp/api/createDevice.d.ts | 6 +- lib/build/recipe/totp/api/createDevice.js | 15 +- lib/build/recipe/totp/api/implementation.js | 23 +- lib/build/recipe/totp/api/listDevices.d.ts | 6 +- lib/build/recipe/totp/api/listDevices.js | 15 +- lib/build/recipe/totp/api/removeDevice.d.ts | 6 +- lib/build/recipe/totp/api/removeDevice.js | 15 +- lib/build/recipe/totp/api/verifyDevice.d.ts | 6 +- lib/build/recipe/totp/api/verifyDevice.js | 15 +- lib/build/recipe/totp/api/verifyTOTP.d.ts | 6 +- lib/build/recipe/totp/api/verifyTOTP.js | 15 +- lib/build/recipe/totp/index.d.ts | 115 +- lib/build/recipe/totp/index.js | 8 +- lib/build/recipe/totp/recipe.d.ts | 10 +- lib/build/recipe/totp/recipe.js | 27 +- lib/build/recipe/totp/recipeImplementation.js | 114 +- lib/build/recipe/totp/types.d.ts | 273 ++--- lib/build/recipe/totp/utils.d.ts | 5 +- lib/build/recipe/totp/utils.js | 23 +- lib/build/recipe/usermetadata/index.d.ts | 16 +- lib/build/recipe/usermetadata/index.js | 8 +- lib/build/recipe/usermetadata/recipe.d.ts | 9 +- lib/build/recipe/usermetadata/recipe.js | 19 +- .../usermetadata/recipeImplementation.js | 37 +- lib/build/recipe/usermetadata/types.d.ts | 10 +- lib/build/recipe/usermetadata/utils.d.ts | 6 +- lib/build/recipe/usermetadata/utils.js | 8 +- lib/build/recipe/userroles/index.d.ts | 109 +- lib/build/recipe/userroles/index.js | 22 +- lib/build/recipe/userroles/permissionClaim.js | 8 +- lib/build/recipe/userroles/recipe.d.ts | 9 +- lib/build/recipe/userroles/recipe.js | 81 +- .../recipe/userroles/recipeImplementation.js | 78 +- lib/build/recipe/userroles/types.d.ts | 70 +- lib/build/recipe/userroles/userRoleClaim.js | 8 +- lib/build/recipe/userroles/utils.d.ts | 6 +- lib/build/recipe/userroles/utils.js | 14 +- .../recipe/webauthn/api/emailExists.d.ts | 7 +- lib/build/recipe/webauthn/api/emailExists.js | 8 +- .../api/generateRecoverAccountToken.d.ts | 7 +- .../api/generateRecoverAccountToken.js | 8 +- .../recipe/webauthn/api/implementation.js | 474 ++++---- .../recipe/webauthn/api/recoverAccount.d.ts | 7 +- .../recipe/webauthn/api/recoverAccount.js | 25 +- .../webauthn/api/registerCredential.d.ts | 4 + .../recipe/webauthn/api/registerCredential.js | 59 + .../recipe/webauthn/api/registerOptions.d.ts | 7 +- .../recipe/webauthn/api/registerOptions.js | 14 +- .../recipe/webauthn/api/signInOptions.d.ts | 7 +- .../recipe/webauthn/api/signInOptions.js | 8 +- lib/build/recipe/webauthn/api/signin.d.ts | 7 +- lib/build/recipe/webauthn/api/signin.js | 24 +- lib/build/recipe/webauthn/api/signup.d.ts | 7 +- lib/build/recipe/webauthn/api/signup.js | 38 +- lib/build/recipe/webauthn/api/utils.d.ts | 4 +- lib/build/recipe/webauthn/api/utils.js | 8 +- lib/build/recipe/webauthn/constants.d.ts | 1 - lib/build/recipe/webauthn/constants.js | 3 +- lib/build/recipe/webauthn/core-mock.d.ts | 3 + lib/build/recipe/webauthn/core-mock.js | 197 ++++ .../services/backwardCompatibility/index.d.ts | 8 +- .../services/backwardCompatibility/index.js | 38 +- .../webauthn/emaildelivery/services/index.js | 8 +- .../emaildelivery/services/smtp/index.d.ts | 8 +- .../emaildelivery/services/smtp/index.js | 16 +- .../services/smtp/recoverAccount.d.ts | 4 +- .../services/smtp/recoverAccount.js | 8 +- .../smtp/serviceImplementation/index.d.ts | 11 +- .../smtp/serviceImplementation/index.js | 11 +- lib/build/recipe/webauthn/error.d.ts | 5 +- lib/build/recipe/webauthn/error.js | 8 +- lib/build/recipe/webauthn/index.d.ts | 494 +++----- lib/build/recipe/webauthn/index.js | 199 ++-- lib/build/recipe/webauthn/recipe.d.ts | 22 +- lib/build/recipe/webauthn/recipe.js | 62 +- .../recipe/webauthn/recipeImplementation.d.ts | 5 +- .../recipe/webauthn/recipeImplementation.js | 364 ++---- lib/build/recipe/webauthn/types.d.ts | 1004 ++++++++--------- lib/build/recipe/webauthn/utils.d.ts | 10 +- lib/build/recipe/webauthn/utils.js | 97 +- lib/build/recipeModule.d.ts | 34 +- lib/build/recipeModule.js | 15 +- lib/build/supertokens.d.ts | 53 +- lib/build/supertokens.js | 241 ++-- lib/build/thirdpartyUtils.d.ts | 34 +- lib/build/thirdpartyUtils.js | 79 +- lib/build/types.d.ts | 20 +- lib/build/user.d.ts | 5 +- lib/build/user.js | 17 +- lib/build/utils.d.ts | 61 +- lib/build/utils.js | 112 +- lib/ts/authUtils.ts | 17 +- lib/ts/index.ts | 4 +- lib/ts/recipe/accountlinking/recipe.ts | 29 +- .../accountlinking/recipeImplementation.ts | 4 +- lib/ts/recipe/accountlinking/types.ts | 8 +- lib/ts/recipe/webauthn/api/implementation.ts | 2 +- .../recipe/webauthn/api/registerCredential.ts | 6 +- 513 files changed, 8526 insertions(+), 15858 deletions(-) create mode 100644 lib/build/recipe/webauthn/api/registerCredential.d.ts create mode 100644 lib/build/recipe/webauthn/api/registerCredential.js create mode 100644 lib/build/recipe/webauthn/core-mock.d.ts create mode 100644 lib/build/recipe/webauthn/core-mock.js diff --git a/lib/build/authUtils.d.ts b/lib/build/authUtils.d.ts index 98ff4ad41..339c35930 100644 --- a/lib/build/authUtils.d.ts +++ b/lib/build/authUtils.d.ts @@ -20,14 +20,10 @@ export declare const AuthUtils: { * } * ``` */ - getErrorStatusResponseWithReason( - resp: { - status: string; - reason?: string; - }, - errorCodeMap: Record | string | undefined>, - errorStatus: T - ): { + getErrorStatusResponseWithReason(resp: { + status: string; + reason?: string; + }, errorCodeMap: Record | string | undefined>, errorStatus: T): { status: T; reason: string; }; @@ -44,19 +40,7 @@ export declare const AuthUtils: { * - 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: ({ - authenticatingAccountInfo, - tenantId, - isSignUp, - isVerified, - signInVerifiesLoginMethod, - authenticatingUser, - factorIds, - skipSessionUserUpdateInCore, - session, - shouldTryLinkingWithSessionUser, - userContext, - }: { + preAuthChecks: ({ authenticatingAccountInfo, tenantId, isSignUp, isVerified, signInVerifiesLoginMethod, authenticatingUser, factorIds, skipSessionUserUpdateInCore, session, shouldTryLinkingWithSessionUser, userContext, }: { authenticatingAccountInfo: AccountInfoWithRecipeId; authenticatingUser: User | undefined; tenantId: string; @@ -68,23 +52,18 @@ export declare const AuthUtils: { session?: SessionContainerInterface | undefined; 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"; - } - >; + }) => 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 @@ -102,17 +81,7 @@ export declare const AuthUtils: { * - 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: ({ - authenticatedUser, - recipeUserId, - isSignUp, - factorId, - session, - req, - res, - tenantId, - userContext, - }: { + postAuthChecks: ({ authenticatedUser, recipeUserId, isSignUp, factorId, session, req, res, tenantId, userContext, }: { authenticatedUser: User; recipeUserId: RecipeUserId; tenantId: string; @@ -122,16 +91,13 @@ export declare const AuthUtils: { userContext: UserContext; req: BaseRequest; res: BaseResponse; - }) => Promise< - | { - status: "OK"; - session: SessionContainerInterface; - user: User; - } - | { - status: "SIGN_IN_NOT_ALLOWED"; - } - >; + }) => 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 @@ -142,45 +108,42 @@ export declare const AuthUtils: { * 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: ({ - recipeId, - accountInfo, - checkCredentialsOnTenant, - tenantId, - session, - userContext, - }: { + getAuthenticatingUserAndAddToCurrentTenantIfRequired: ({ recipeId, accountInfo, checkCredentialsOnTenant, tenantId, session, userContext, }: { recipeId: string; - accountInfo: - | { - email: string; - thirdParty?: undefined; - phoneNumber?: undefined; - } - | { - email?: undefined; - thirdParty?: undefined; - phoneNumber: string; - } - | { - email?: undefined; - thirdParty: { - id: string; - userId: string; - }; - phoneNumber?: undefined; - }; + 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; userContext: UserContext; - }) => Promise< - | { - user: User; - loginMethod: LoginMethod; - } - | undefined - >; + }) => 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): @@ -194,36 +157,24 @@ export declare const AuthUtils: { * - 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: ( - 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"; - } - >; + checkAuthTypeAndLinkingStatus: (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 @@ -238,34 +189,20 @@ export declare const AuthUtils: { * - 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: ({ - tenantId, - inputUser, - recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }: { + linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo: ({ tenantId, inputUser, recipeUserId, session, shouldTryLinkingWithSessionUser, userContext, }: { 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"; - } - >; + }) => 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: @@ -276,22 +213,14 @@ export declare const AuthUtils: { * * It throws INVALID_CLAIM_ERROR if shouldDoAutomaticAccountLinking returned `{ shouldAutomaticallyLink: false }` but the email verification status was wrong */ - tryAndMakeSessionUserIntoAPrimaryUser: ( - 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"; - } - >; + tryAndMakeSessionUserIntoAPrimaryUser: (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 * @@ -305,45 +234,22 @@ export declare const AuthUtils: { * - 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: ({ - linkingToSessionUserRequiresVerification, - authLoginMethod, - authenticatedUser, - sessionUser, - userContext, - }: { + tryLinkingBySession: ({ linkingToSessionUserRequiresVerification, authLoginMethod, authenticatedUser, sessionUser, userContext, }: { 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: ( - factorIds: string[], - tenantId: string, - hasSession: boolean, - userContext: UserContext - ) => Promise; - loadSessionInAuthAPIIfNeeded: ( - req: BaseRequest, - res: BaseResponse, - shouldTryLinkingWithSessionUser: boolean | undefined, - userContext: UserContext - ) => Promise; + }) => 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: (factorIds: string[], tenantId: string, hasSession: boolean, userContext: UserContext) => Promise; + loadSessionInAuthAPIIfNeeded: (req: BaseRequest, res: BaseResponse, shouldTryLinkingWithSessionUser: boolean | undefined, userContext: UserContext) => Promise; }; diff --git a/lib/build/authUtils.js b/lib/build/authUtils.js index b03e8cb94..84a29bb06 100644 --- a/lib/build/authUtils.js +++ b/lib/build/authUtils.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthUtils = void 0; const recipe_1 = __importDefault(require("./recipe/accountlinking/recipe")); @@ -43,7 +41,8 @@ exports.AuthUtils = { status: errorStatus, reason: reasons, }; - } else if (typeof reasons === "object" && resp.reason !== undefined) { + } + else if (typeof reasons === "object" && resp.reason !== undefined) { if (reasons[resp.reason]) { return { status: errorStatus, @@ -68,19 +67,7 @@ exports.AuthUtils = { * - 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: async function ({ - authenticatingAccountInfo, - tenantId, - isSignUp, - isVerified, - signInVerifiesLoginMethod, - authenticatingUser, - factorIds, - skipSessionUserUpdateInCore, - session, - shouldTryLinkingWithSessionUser, - userContext, - }) { + preAuthChecks: async function ({ authenticatingAccountInfo, tenantId, isSignUp, isVerified, signInVerifiesLoginMethod, authenticatingUser, factorIds, skipSessionUserUpdateInCore, session, shouldTryLinkingWithSessionUser, userContext, }) { let validFactorIds; // This would be an implementation error on our part, we only check it because TS doesn't if (factorIds.length === 0) { @@ -90,14 +77,7 @@ exports.AuthUtils = { // First we check if the app intends to link the inputUser or not, // to decide if this is a first factor auth or not and if it'll link to the session user // We also load the session user here if it is available. - const authTypeInfo = await exports.AuthUtils.checkAuthTypeAndLinkingStatus( - session, - shouldTryLinkingWithSessionUser, - authenticatingAccountInfo, - authenticatingUser, - skipSessionUserUpdateInCore, - userContext - ); + const authTypeInfo = await exports.AuthUtils.checkAuthTypeAndLinkingStatus(session, shouldTryLinkingWithSessionUser, authenticatingAccountInfo, authenticatingUser, skipSessionUserUpdateInCore, userContext); if (authTypeInfo.status !== "OK") { logger_1.logDebugMessage(`preAuthChecks returning ${authTypeInfo.status} from checkAuthType results`); return authTypeInfo; @@ -112,66 +92,47 @@ exports.AuthUtils = { // The filtered list can be used to select email templates. As an example: // If the flowType for passwordless is USER_INPUT_CODE_AND_MAGIC_LINK and firstFactors for the tenant we only have otp-email // then we do not want to include a link in the email. - const validFirstFactors = await exports.AuthUtils.filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid( - factorIds, - tenantId, - session !== undefined, - userContext - ); + const validFirstFactors = await exports.AuthUtils.filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid(factorIds, tenantId, session !== undefined, userContext); validFactorIds = validFirstFactors; - } else { + } + else { logger_1.logDebugMessage("preAuthChecks getting valid secondary factors"); // In this case the app will try to link the session user and the authenticating user after auth, // so we need to check if this is allowed by the MFA recipe (if initialized). - validFactorIds = await filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid( - factorIds, - authTypeInfo.inputUserAlreadyLinkedToSessionUser, - authTypeInfo.sessionUser, - session, - userContext - ); + validFactorIds = await filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid(factorIds, authTypeInfo.inputUserAlreadyLinkedToSessionUser, authTypeInfo.sessionUser, session, userContext); } if (!isSignUp && authenticatingUser === undefined) { - throw new Error( - "This should never happen: preAuthChecks called with isSignUp: false, authenticatingUser: undefined" - ); + throw new Error("This should never happen: preAuthChecks called with isSignUp: false, authenticatingUser: undefined"); } // If this is a sign up we check that the sign up is allowed if (isSignUp) { // We need this check in case the session user has verified an email address and now tries to add a password for it. - let verifiedInSessionUser = - !authTypeInfo.isFirstFactor && - authTypeInfo.sessionUser.loginMethods.some( - (lm) => - lm.verified && - (lm.hasSameEmailAs(authenticatingAccountInfo.email) || - lm.hasSamePhoneNumberAs(authenticatingAccountInfo.phoneNumber)) - ); + let verifiedInSessionUser = !authTypeInfo.isFirstFactor && + authTypeInfo.sessionUser.loginMethods.some((lm) => lm.verified && + (lm.hasSameEmailAs(authenticatingAccountInfo.email) || + lm.hasSamePhoneNumberAs(authenticatingAccountInfo.phoneNumber))); logger_1.logDebugMessage("preAuthChecks checking if the user is allowed to sign up"); - if ( - !(await recipe_1.default.getInstance().isSignUpAllowed({ - newUser: authenticatingAccountInfo, - isVerified: isVerified || signInVerifiesLoginMethod || verifiedInSessionUser, - tenantId, - session, - userContext, - })) - ) { + if (!(await recipe_1.default.getInstance().isSignUpAllowed({ + newUser: authenticatingAccountInfo, + isVerified: isVerified || signInVerifiesLoginMethod || verifiedInSessionUser, + tenantId, + session, + userContext, + }))) { return { status: "SIGN_UP_NOT_ALLOWED" }; } - } else if (authenticatingUser !== undefined) { + } + else if (authenticatingUser !== undefined) { // for sign ins, this is checked after the credentials have been verified logger_1.logDebugMessage("preAuthChecks checking if the user is allowed to sign in"); - if ( - !(await recipe_1.default.getInstance().isSignInAllowed({ - user: authenticatingUser, - accountInfo: authenticatingAccountInfo, - signInVerifiesLoginMethod, - tenantId, - session, - userContext, - })) - ) { + if (!(await recipe_1.default.getInstance().isSignInAllowed({ + user: authenticatingUser, + accountInfo: authenticatingAccountInfo, + signInVerifiesLoginMethod, + tenantId, + session, + userContext, + }))) { return { status: "SIGN_IN_NOT_ALLOWED" }; } } @@ -200,28 +161,12 @@ exports.AuthUtils = { * - 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: async function ({ - authenticatedUser, - recipeUserId, - isSignUp, - factorId, - session, - req, - res, - tenantId, - userContext, - }) { - logger_1.logDebugMessage( - `postAuthChecks called ${session !== undefined ? "with" : "without"} a session to ${ - isSignUp ? "sign in" : "sign up" - } with ${factorId}` - ); + postAuthChecks: async function ({ authenticatedUser, recipeUserId, isSignUp, factorId, session, req, res, tenantId, userContext, }) { + logger_1.logDebugMessage(`postAuthChecks called ${session !== undefined ? "with" : "without"} a session to ${isSignUp ? "sign in" : "sign up"} with ${factorId}`); const mfaInstance = recipe_2.default.getInstance(); let respSession = session; if (session !== undefined) { - const authenticatedUserLinkedToSessionUser = authenticatedUser.loginMethods.some( - (lm) => lm.recipeUserId.getAsString() === session.getRecipeUserId(userContext).getAsString() - ); + const authenticatedUserLinkedToSessionUser = authenticatedUser.loginMethods.some((lm) => lm.recipeUserId.getAsString() === session.getRecipeUserId(userContext).getAsString()); if (authenticatedUserLinkedToSessionUser) { logger_1.logDebugMessage(`postAuthChecks session and input user got linked`); if (mfaInstance !== undefined) { @@ -230,48 +175,28 @@ exports.AuthUtils = { // we mark it as completed in the session. await multifactorauth_1.default.markFactorAsCompleteInSession(respSession, factorId, userContext); } - } else { + } + else { logger_1.logDebugMessage(`postAuthChecks checking overwriteSessionDuringSignInUp`); // If the new user wasn't linked to the current one, we check the config and overwrite the session if required // Note: we could also get here if MFA is enabled, but the app didn't want to link the user to the session user. // This is intentional, since the MFA and overwriteSessionDuringSignInUp configs should work independently. - let overwriteSessionDuringSignInUp = recipe_3.default - .getInstanceOrThrowError() - .getNormalisedOverwriteSessionDuringSignInUp(req); + let overwriteSessionDuringSignInUp = recipe_3.default.getInstanceOrThrowError().getNormalisedOverwriteSessionDuringSignInUp(req); if (overwriteSessionDuringSignInUp) { - respSession = await session_1.default.createNewSession( - req, - res, - tenantId, - recipeUserId, - {}, - {}, - userContext - ); + respSession = await session_1.default.createNewSession(req, res, tenantId, recipeUserId, {}, {}, userContext); if (mfaInstance !== undefined) { - await multifactorauth_1.default.markFactorAsCompleteInSession( - respSession, - factorId, - userContext - ); + await multifactorauth_1.default.markFactorAsCompleteInSession(respSession, factorId, userContext); } } } - } else { + } + else { // We do not have to care about overwriting the session here, since we either: // - have overwriteSessionDuringSignInUp true and we didn't even try to load the session because we ignore it anyway // - have overwriteSessionDuringSignInUp false and we checked in the api imlp that there is no session logger_1.logDebugMessage(`postAuthChecks creating session for first factor sign in/up`); // If there is no input session, we do not need to do anything other checks and create a new session - respSession = await session_1.default.createNewSession( - req, - res, - tenantId, - recipeUserId, - {}, - {}, - userContext - ); + respSession = await session_1.default.createNewSession(req, res, tenantId, recipeUserId, {}, {}, userContext); // Here we can always mark the factor as completed, since we just created the session if (mfaInstance !== undefined) { await multifactorauth_1.default.markFactorAsCompleteInSession(respSession, factorId, userContext); @@ -289,47 +214,29 @@ exports.AuthUtils = { * 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: async ({ - recipeId, - accountInfo, - checkCredentialsOnTenant, - tenantId, - session, - userContext, - }) => { + getAuthenticatingUserAndAddToCurrentTenantIfRequired: async ({ recipeId, accountInfo, checkCredentialsOnTenant, tenantId, session, userContext, }) => { let i = 0; while (i++ < 300) { - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired called with ${JSON.stringify(accountInfo)}` - ); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired called with ${JSON.stringify(accountInfo)}`); const existingUsers = await recipe_1.default.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, accountInfo, doUnionOfAccountInfo: true, userContext: userContext, }); - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired got ${existingUsers.length} users from the core resp` - ); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired got ${existingUsers.length} users from the core resp`); const usersWithMatchingLoginMethods = existingUsers .map((user) => ({ - user, - loginMethod: user.loginMethods.find( - (lm) => - lm.recipeId === recipeId && - ((accountInfo.email !== undefined && lm.hasSameEmailAs(accountInfo.email)) || - lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || - lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) - ), - })) + user, + loginMethod: user.loginMethods.find((lm) => lm.recipeId === recipeId && + ((accountInfo.email !== undefined && lm.hasSameEmailAs(accountInfo.email)) || + lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || + lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty))), + })) .filter(({ loginMethod }) => loginMethod !== undefined); - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired got ${usersWithMatchingLoginMethods.length} users with matching login methods` - ); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired got ${usersWithMatchingLoginMethods.length} users with matching login methods`); if (usersWithMatchingLoginMethods.length > 1) { - throw new Error( - "You have found a bug. Please report it on https://github.com/supertokens/supertokens-node/issues" - ); + throw new Error("You have found a bug. Please report it on https://github.com/supertokens/supertokens-node/issues"); } let authenticatingUser = usersWithMatchingLoginMethods[0]; if (authenticatingUser === undefined && session !== undefined) { @@ -345,25 +252,16 @@ exports.AuthUtils = { // and we know that that login method is associated with the current tenant. // This means that the user has no loginMethods we need to check (that only belong to other tenantIds) if (!sessionUser.isPrimaryUser) { - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired session user is non-primary so returning early without checking other tenants` - ); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired session user is non-primary so returning early without checking other tenants`); return undefined; } - const matchingLoginMethodsFromSessionUser = sessionUser.loginMethods.filter( - (lm) => - lm.recipeId === recipeId && - (lm.hasSameEmailAs(accountInfo.email) || - lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || - lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) - ); - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired session has ${matchingLoginMethodsFromSessionUser.length} matching login methods` - ); + const matchingLoginMethodsFromSessionUser = sessionUser.loginMethods.filter((lm) => lm.recipeId === recipeId && + (lm.hasSameEmailAs(accountInfo.email) || + lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || + lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty))); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired session has ${matchingLoginMethodsFromSessionUser.length} matching login methods`); if (matchingLoginMethodsFromSessionUser.some((lm) => lm.tenantIds.includes(tenantId))) { - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired session has ${matchingLoginMethodsFromSessionUser.length} matching login methods` - ); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired session has ${matchingLoginMethodsFromSessionUser.length} matching login methods`); // This can happen in a race condition where a user was created and linked with the session user // between listing the existing users and loading the session user // We can return early, this only means that someone did the same sharing this function was aiming to do @@ -375,33 +273,21 @@ exports.AuthUtils = { } let goToRetry = false; for (const lm of matchingLoginMethodsFromSessionUser) { - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired session checking credentials on ${lm.tenantIds[0]}` - ); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired session checking credentials on ${lm.tenantIds[0]}`); if (await checkCredentialsOnTenant(lm.tenantIds[0])) { - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired associating user from ${lm.tenantIds[0]} with current tenant` - ); - const associateRes = await multitenancy_1.default.associateUserToTenant( - tenantId, - lm.recipeUserId, - userContext - ); - logger_1.logDebugMessage( - `getAuthenticatingUserAndAddToCurrentTenantIfRequired associating returned ${associateRes.status}` - ); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired associating user from ${lm.tenantIds[0]} with current tenant`); + const associateRes = await multitenancy_1.default.associateUserToTenant(tenantId, lm.recipeUserId, userContext); + logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired associating returned ${associateRes.status}`); if (associateRes.status === "OK") { // We know that this is what happens lm.tenantIds.push(tenantId); return { user: sessionUser, loginMethod: lm }; } - if ( - associateRes.status === "UNKNOWN_USER_ID_ERROR" || // This means that the recipe user was deleted + if (associateRes.status === "UNKNOWN_USER_ID_ERROR" || // This means that the recipe user was deleted // All below conditions mean that both the account list and the session user we loaded is outdated associateRes.status === "EMAIL_ALREADY_EXISTS_ERROR" || associateRes.status === "PHONE_NUMBER_ALREADY_EXISTS_ERROR" || - associateRes.status === "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" - ) { + associateRes.status === "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR") { // In these cases we retry, because we know some info we are using is outdated // while some of these cases we could handle locally, it's cleaner to restart the process. goToRetry = true; @@ -426,9 +312,7 @@ exports.AuthUtils = { } return authenticatingUser; } - throw new Error( - "This should never happen: ran out of retries for getAuthenticatingUserAndAddToCurrentTenantIfRequired" - ); + throw new Error("This should never happen: ran out of retries for getAuthenticatingUserAndAddToCurrentTenantIfRequired"); }, /** * This function checks if the current authentication attempt should be considered a first factor or not. @@ -443,14 +327,7 @@ exports.AuthUtils = { * - 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: async function ( - session, - shouldTryLinkingWithSessionUser, - accountInfo, - inputUser, - skipSessionUserUpdateInCore, - userContext - ) { + checkAuthTypeAndLinkingStatus: async function (session, shouldTryLinkingWithSessionUser, accountInfo, inputUser, skipSessionUserUpdateInCore, userContext) { logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus called`); let sessionUser = undefined; if (session === undefined) { @@ -460,35 +337,28 @@ exports.AuthUtils = { message: "Session not found but shouldTryLinkingWithSessionUser is true", }); } - logger_1.logDebugMessage( - `checkAuthTypeAndLinkingStatus returning first factor because there is no session` - ); + logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus returning first factor because there is no session`); // If there is no active session we have nothing to link to - so this has to be a first factor sign in return { status: "OK", isFirstFactor: true }; - } else { + } + else { if (shouldTryLinkingWithSessionUser === false) { - logger_1.logDebugMessage( - `checkAuthTypeAndLinkingStatus returning first factor because shouldTryLinkingWithSessionUser is false` - ); + logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus returning first factor because shouldTryLinkingWithSessionUser is false`); // In our normal flows this should never happen - but some user overrides might do this. // Anyway, since shouldTryLinkingWithSessionUser explicitly set to false, it's safe to consider this a firstFactor return { status: "OK", isFirstFactor: true }; } if (!utils_3.recipeInitDefinedShouldDoAutomaticAccountLinking(recipe_1.default.getInstance().config)) { if (shouldTryLinkingWithSessionUser === true) { - throw new Error( - "Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA" - ); - } else { + throw new Error("Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA"); + } + else { // This is the legacy case where shouldTryLinkingWithSessionUser is undefined if (recipe_2.default.getInstance() !== undefined) { - throw new Error( - "Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA" - ); - } else { - logger_1.logDebugMessage( - `checkAuthTypeAndLinkingStatus (legacy behaviour) returning first factor because MFA is not initialised and shouldDoAutomaticAccountLinking is not defined` - ); + throw new Error("Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA"); + } + else { + logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus (legacy behaviour) returning first factor because MFA is not initialised and shouldDoAutomaticAccountLinking is not defined`); return { status: "OK", isFirstFactor: true }; } } @@ -500,9 +370,7 @@ exports.AuthUtils = { // - MFA may or may not be initialized // If the input and the session user are the same if (inputUser !== undefined && inputUser.id === session.getUserId()) { - logger_1.logDebugMessage( - `checkAuthTypeAndLinkingStatus returning secondary factor, session and input user are the same` - ); + logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus returning secondary factor, session and input user are the same`); // Then this is basically a user logging in with an already linked secondary account // Which is basically a factor completion in MFA terms. // Since the sessionUser and the inputUser are the same in this case, we can just return early @@ -513,23 +381,14 @@ exports.AuthUtils = { sessionUser: inputUser, }; } - logger_1.logDebugMessage( - `checkAuthTypeAndLinkingStatus loading session user, ${ - inputUser === null || inputUser === void 0 ? void 0 : inputUser.id - } === ${session.getUserId()}` - ); + logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus loading session user, ${inputUser === null || inputUser === void 0 ? void 0 : inputUser.id} === ${session.getUserId()}`); // We have to load the session user in order to get the account linking info - const sessionUserResult = await exports.AuthUtils.tryAndMakeSessionUserIntoAPrimaryUser( - session, - skipSessionUserUpdateInCore, - userContext - ); + const sessionUserResult = await exports.AuthUtils.tryAndMakeSessionUserIntoAPrimaryUser(session, skipSessionUserUpdateInCore, userContext); if (sessionUserResult.status === "SHOULD_AUTOMATICALLY_LINK_FALSE") { if (shouldTryLinkingWithSessionUser === true) { // tryAndMakeSessionUserIntoAPrimaryUser throws if it is an email verification iss throw new _1.Error({ - message: - "shouldDoAutomaticAccountLinking returned false when making the session user primary but shouldTryLinkingWithSessionUser is true", + message: "shouldDoAutomaticAccountLinking returned false when making the session user primary but shouldTryLinkingWithSessionUser is true", type: "BAD_INPUT_ERROR", }); } @@ -537,9 +396,8 @@ exports.AuthUtils = { status: "OK", isFirstFactor: true, }; - } else if ( - sessionUserResult.status === "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" - ) { + } + else if (sessionUserResult.status === "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { return { status: "LINKING_TO_SESSION_USER_FAILED", reason: "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", @@ -548,30 +406,18 @@ exports.AuthUtils = { sessionUser = sessionUserResult.sessionUser; // We check if the app intends to link these two accounts // Note: in some cases if the accountInfo already belongs to a primary user - const shouldLink = await recipe_1.default - .getInstance() - .config.shouldDoAutomaticAccountLinking( - accountInfo, - sessionUser, - session, - session.getTenantId(), - userContext - ); - logger_1.logDebugMessage( - `checkAuthTypeAndLinkingStatus session user <-> input user shouldDoAutomaticAccountLinking returned ${JSON.stringify( - shouldLink - )}` - ); + const shouldLink = await recipe_1.default.getInstance().config.shouldDoAutomaticAccountLinking(accountInfo, sessionUser, session, session.getTenantId(), userContext); + logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus session user <-> input user shouldDoAutomaticAccountLinking returned ${JSON.stringify(shouldLink)}`); if (shouldLink.shouldAutomaticallyLink === false) { if (shouldTryLinkingWithSessionUser === true) { throw new _1.Error({ - message: - "shouldDoAutomaticAccountLinking returned false when making the session user primary but shouldTryLinkingWithSessionUser is true", + message: "shouldDoAutomaticAccountLinking returned false when making the session user primary but shouldTryLinkingWithSessionUser is true", type: "BAD_INPUT_ERROR", }); } return { status: "OK", isFirstFactor: true }; - } else { + } + else { return { status: "OK", isFirstFactor: false, @@ -596,14 +442,7 @@ exports.AuthUtils = { * - 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: async function ({ - tenantId, - inputUser, - recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }) { + linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo: async function ({ tenantId, inputUser, recipeUserId, session, shouldTryLinkingWithSessionUser, userContext, }) { logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo called"); const retry = () => { logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo retrying...."); @@ -618,35 +457,20 @@ exports.AuthUtils = { }; // If we got here, we have a session and a primary session user // We can not assume the inputUser is non-primary, since we'll only check that seeing if the app wants to link to the session user or not. - const authLoginMethod = inputUser.loginMethods.find( - (lm) => lm.recipeUserId.getAsString() === recipeUserId.getAsString() - ); + const authLoginMethod = inputUser.loginMethods.find((lm) => lm.recipeUserId.getAsString() === recipeUserId.getAsString()); if (authLoginMethod === undefined) { - throw new Error( - "This should never happen: the recipeUserId and user is inconsistent in createPrimaryUserIdOrLinkByAccountInfo params" - ); + throw new Error("This should never happen: the recipeUserId and user is inconsistent in createPrimaryUserIdOrLinkByAccountInfo params"); } - const authTypeRes = await exports.AuthUtils.checkAuthTypeAndLinkingStatus( - session, - shouldTryLinkingWithSessionUser, - authLoginMethod, - inputUser, - false, - userContext - ); + const authTypeRes = await exports.AuthUtils.checkAuthTypeAndLinkingStatus(session, shouldTryLinkingWithSessionUser, authLoginMethod, inputUser, false, userContext); if (authTypeRes.status !== "OK") { return authTypeRes; } if (authTypeRes.isFirstFactor) { if (!utils_3.recipeInitDefinedShouldDoAutomaticAccountLinking(recipe_1.default.getInstance().config)) { - logger_1.logDebugMessage( - "linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo skipping link by account info because this is a first factor auth and the app hasn't defined shouldDoAutomaticAccountLinking" - ); + logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo skipping link by account info because this is a first factor auth and the app hasn't defined shouldDoAutomaticAccountLinking"); return { status: "OK", user: inputUser }; } - logger_1.logDebugMessage( - "linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo trying to link by account info because this is a first factor auth" - ); + logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo trying to link by account info because this is a first factor auth"); // We try and list all users that can be linked to the input user based on the account info // later we can use these when trying to link or when checking if linking to the session user is possible. const linkRes = await recipe_1.default.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ @@ -669,9 +493,7 @@ exports.AuthUtils = { user: authTypeRes.sessionUser, }; } - logger_1.logDebugMessage( - "linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo trying to link by session info" - ); + logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo trying to link by session info"); const sessionLinkingRes = await exports.AuthUtils.tryLinkingBySession({ sessionUser: authTypeRes.sessionUser, authenticatedUser: inputUser, @@ -684,10 +506,12 @@ exports.AuthUtils = { // This means that although we made the session user primary above, some race condition undid that (e.g.: calling unlink concurrently with this func) // We can retry in this case, since we start by trying to make it into a primary user and throwing if we can't return retry(); - } else { + } + else { return sessionLinkingRes; } - } else { + } + else { // If we get here the status is OK, so we can just return it return sessionLinkingRes; } @@ -715,62 +539,38 @@ exports.AuthUtils = { logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser session user already primary`); // if the session user was already primary we can just return it return { status: "OK", sessionUser }; - } else { + } + else { // if the session user is not primary we try and make it one logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser not primary user yet`); // We could check here if the session user can even become a primary user, but that'd only mean one extra core call // without any added benefits, since the core already checks all pre-conditions // We do this check here instead of using the shouldBecomePrimaryUser util, because // here we handle the shouldRequireVerification case differently - const shouldDoAccountLinking = await recipe_1.default - .getInstance() - .config.shouldDoAutomaticAccountLinking( - sessionUser.loginMethods[0], - undefined, - session, - session.getTenantId(userContext), - userContext - ); - logger_1.logDebugMessage( - `tryAndMakeSessionUserIntoAPrimaryUser shouldDoAccountLinking: ${JSON.stringify( - shouldDoAccountLinking - )}` - ); + const shouldDoAccountLinking = await recipe_1.default.getInstance().config.shouldDoAutomaticAccountLinking(sessionUser.loginMethods[0], undefined, session, session.getTenantId(userContext), userContext); + logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser shouldDoAccountLinking: ${JSON.stringify(shouldDoAccountLinking)}`); if (shouldDoAccountLinking.shouldAutomaticallyLink) { if (skipSessionUserUpdateInCore) { return { status: "OK", sessionUser: sessionUser }; } if (shouldDoAccountLinking.shouldRequireVerification && !sessionUser.loginMethods[0].verified) { // We force-update the claim value if it is not set or different from what we just fetched from the DB - if ( - (await session.getClaimValue(emailverification_1.EmailVerificationClaim, userContext)) !== false - ) { - logger_1.logDebugMessage( - `tryAndMakeSessionUserIntoAPrimaryUser updating emailverification status in session` - ); + if ((await session.getClaimValue(emailverification_1.EmailVerificationClaim, userContext)) !== false) { + logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser updating emailverification status in session`); // This will let the frontend know if the value has been updated in the background await session.setClaimValue(emailverification_1.EmailVerificationClaim, false, userContext); } logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser throwing validation error`); // Then run the validation expecting it to fail. We run assertClaims instead of throwing the error locally // to make sure the error shape in the response will match what we'd return normally - await session.assertClaims( - [emailverification_1.EmailVerificationClaim.validators.isVerified()], - userContext - ); - throw new Error( - "This should never happen: email verification claim validator passed after setting value to false" - ); + await session.assertClaims([emailverification_1.EmailVerificationClaim.validators.isVerified()], userContext); + throw new Error("This should never happen: email verification claim validator passed after setting value to false"); } - const createPrimaryUserRes = await recipe_1.default - .getInstance() - .recipeInterfaceImpl.createPrimaryUser({ - recipeUserId: sessionUser.loginMethods[0].recipeUserId, - userContext, - }); - logger_1.logDebugMessage( - `tryAndMakeSessionUserIntoAPrimaryUser createPrimaryUser returned ${createPrimaryUserRes.status}` - ); + const createPrimaryUserRes = await recipe_1.default.getInstance().recipeInterfaceImpl.createPrimaryUser({ + recipeUserId: sessionUser.loginMethods[0].recipeUserId, + userContext, + }); + logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser createPrimaryUser returned ${createPrimaryUserRes.status}`); if (createPrimaryUserRes.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR") { // This means that the session user got primary since we loaded the session user info above // but this status means that the user id has also changed, so the session should be invalid @@ -778,14 +578,17 @@ exports.AuthUtils = { type: error_1.default.UNAUTHORISED, message: "Session user not found", }); - } else if (createPrimaryUserRes.status === "OK") { + } + else if (createPrimaryUserRes.status === "OK") { return { status: "OK", sessionUser: createPrimaryUserRes.user }; - } else { + } + else { // All other statuses signify that we can't make the session user primary // Which means we can't continue return createPrimaryUserRes; } - } else { + } + else { // This means that the app doesn't want to make the session user primary return { status: "SHOULD_AUTOMATICALLY_LINK_FALSE" }; } @@ -804,13 +607,7 @@ exports.AuthUtils = { * - 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: async function ({ - linkingToSessionUserRequiresVerification, - authLoginMethod, - authenticatedUser, - sessionUser, - userContext, - }) { + tryLinkingBySession: async function ({ linkingToSessionUserRequiresVerification, authLoginMethod, authenticatedUser, sessionUser, userContext, }) { logger_1.logDebugMessage("tryLinkingBySession called"); // If the input user has another user (and it's not the session user) it could be linked to based on account info then we can't link it to the session user. // However, we do not need to check this as the linkAccounts check will fail anyway and we do not want the extra core call in case it succeeds @@ -818,13 +615,9 @@ exports.AuthUtils = { // then we don't want to ask them to verify it again. // This is different from linking based on account info, but the presence of a session shows that the user has access to both accounts, // and intends to link these two accounts. - const sessionUserHasVerifiedAccountInfo = sessionUser.loginMethods.some( - (lm) => - (lm.hasSameEmailAs(authLoginMethod.email) || lm.hasSamePhoneNumberAs(authLoginMethod.phoneNumber)) && - lm.verified - ); - const canLinkBasedOnVerification = - !linkingToSessionUserRequiresVerification || authLoginMethod.verified || sessionUserHasVerifiedAccountInfo; + const sessionUserHasVerifiedAccountInfo = sessionUser.loginMethods.some((lm) => (lm.hasSameEmailAs(authLoginMethod.email) || lm.hasSamePhoneNumberAs(authLoginMethod.phoneNumber)) && + lm.verified); + const canLinkBasedOnVerification = !linkingToSessionUserRequiresVerification || authLoginMethod.verified || sessionUserHasVerifiedAccountInfo; if (!canLinkBasedOnVerification) { return { status: "LINKING_TO_SESSION_USER_FAILED", reason: "EMAIL_VERIFICATION_REQUIRED" }; } @@ -839,25 +632,22 @@ exports.AuthUtils = { if (linkAccountsResult.status === "OK") { logger_1.logDebugMessage("tryLinkingBySession successfully linked input user to session user"); return { status: "OK", user: linkAccountsResult.user }; - } else if (linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { + } + else if (linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { // this can happen because of a race condition wherein the recipe user ID get's linked to // some other primary user whilst the linking is going on. - logger_1.logDebugMessage( - "tryLinkingBySession linking to session user failed because of a race condition - input user linked to another user" - ); + logger_1.logDebugMessage("tryLinkingBySession linking to session user failed because of a race condition - input user linked to another user"); return { status: "LINKING_TO_SESSION_USER_FAILED", reason: linkAccountsResult.status }; - } else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { - logger_1.logDebugMessage( - "tryLinkingBySession linking to session user failed because of a race condition - INPUT_USER_IS_NOT_A_PRIMARY_USER, should retry" - ); + } + else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { + logger_1.logDebugMessage("tryLinkingBySession linking to session user failed because of a race condition - INPUT_USER_IS_NOT_A_PRIMARY_USER, should retry"); // This can be possible during a race condition wherein the primary user we created above // is somehow no more a primary user. This can happen if the unlink function was called in parallel // on that user. We can just retry, as that will try and make it a primary user again. return { status: "LINKING_TO_SESSION_USER_FAILED", reason: linkAccountsResult.status }; - } else { - logger_1.logDebugMessage( - "tryLinkingBySession linking to session user failed because of a race condition - input user has another primary user it can be linked to" - ); + } + else { + logger_1.logDebugMessage("tryLinkingBySession linking to session user failed because of a race condition - input user has another primary user it can be linked to"); // Status can only be "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" // It can come here if the recipe user ID can't be linked to the primary user ID because the email / phone number is associated with // some other primary user ID. @@ -877,10 +667,12 @@ exports.AuthUtils = { type: error_1.default.UNAUTHORISED, message: "Tenant not found", }); - } else { + } + else { throw new Error("Tenant not found error."); } - } else if (validRes.status === "OK") { + } + else if (validRes.status === "OK") { validFactorIds.push(id); } } @@ -890,72 +682,44 @@ exports.AuthUtils = { type: error_1.default.UNAUTHORISED, message: "A valid session is required to authenticate with secondary factors", }); - } else { + } + else { throw new error_2.default({ type: error_2.default.BAD_INPUT_ERROR, - message: - "First factor sign in/up called for a non-first factor with an active session. This might indicate that you are trying to use this as a secondary factor, but disabled account linking.", + message: "First factor sign in/up called for a non-first factor with an active session. This might indicate that you are trying to use this as a secondary factor, but disabled account linking.", }); } } return validFactorIds; }, loadSessionInAuthAPIIfNeeded: async function (req, res, shouldTryLinkingWithSessionUser, userContext) { - const overwriteSessionDuringSignInUp = recipe_3.default - .getInstanceOrThrowError() - .getNormalisedOverwriteSessionDuringSignInUp(req); + const overwriteSessionDuringSignInUp = recipe_3.default.getInstanceOrThrowError().getNormalisedOverwriteSessionDuringSignInUp(req); if (shouldTryLinkingWithSessionUser !== false) { - logger_1.logDebugMessage( - "loadSessionInAuthAPIIfNeeded: loading session because shouldTryLinkingWithSessionUser is not set to false so we may want to link later" - ); - return await session_1.default.getSession( - req, - res, - { - // This is optional only if shouldTryLinkingWithSessionUser is undefined - // in the (old) 3.0 FDI, this flag didn't exist and we linking was based on the session presence and shouldDoAutomaticAccountLinking - sessionRequired: shouldTryLinkingWithSessionUser === true, - overrideGlobalClaimValidators: () => [], - }, - userContext - ); + logger_1.logDebugMessage("loadSessionInAuthAPIIfNeeded: loading session because shouldTryLinkingWithSessionUser is not set to false so we may want to link later"); + return await session_1.default.getSession(req, res, { + // This is optional only if shouldTryLinkingWithSessionUser is undefined + // in the (old) 3.0 FDI, this flag didn't exist and we linking was based on the session presence and shouldDoAutomaticAccountLinking + sessionRequired: shouldTryLinkingWithSessionUser === true, + overrideGlobalClaimValidators: () => [], + }, userContext); } if (overwriteSessionDuringSignInUp === false) { - logger_1.logDebugMessage( - "loadSessionInAuthAPIIfNeeded: loading session in optional mode because overwriteSessionDuringSignInUp is false so if it is not found we will skip session creation" - ); - return await session_1.default.getSession( - req, - res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, - userContext - ); + logger_1.logDebugMessage("loadSessionInAuthAPIIfNeeded: loading session in optional mode because overwriteSessionDuringSignInUp is false so if it is not found we will skip session creation"); + return await session_1.default.getSession(req, res, { + sessionRequired: false, + overrideGlobalClaimValidators: () => [], + }, userContext); } - logger_1.logDebugMessage( - "loadSessionInAuthAPIIfNeeded: skipping session loading because we are not linking and we would overwrite it anyway" - ); + logger_1.logDebugMessage("loadSessionInAuthAPIIfNeeded: skipping session loading because we are not linking and we would overwrite it anyway"); return undefined; }, }; -async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid( - factorIds, - inputUserAlreadyLinkedToSessionUser, - sessionUser, - session, - userContext -) { +async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid(factorIds, inputUserAlreadyLinkedToSessionUser, sessionUser, session, userContext) { if (session === undefined) { - throw new Error( - "This should never happen: filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called without a session" - ); + throw new Error("This should never happen: filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called without a session"); } if (sessionUser === undefined) { - throw new Error( - "This should never happen: filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called without a sessionUser" - ); + throw new Error("This should never happen: filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called without a sessionUser"); } logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called for ${factorIds.join(", ")}`); const mfaInstance = recipe_2.default.getInstance(); @@ -990,9 +754,7 @@ async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid( }; // If we are linking the input user to the session user, then we need to check if MFA allows it // From an MFA perspective this is a factor setup - logger_1.logDebugMessage( - `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid checking if linking is allowed by the mfa recipe` - ); + logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid checking if linking is allowed by the mfa recipe`); let caughtSetupFactorError; const validFactorIds = []; // In all apis besides passwordless createCode, we know exactly which factor we are signing into, so in those cases, @@ -1003,46 +765,35 @@ async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid( // If the flowType for passwordless is USER_INPUT_CODE_AND_MAGIC_LINK and but only the otp-email factor is allowed to be set up // then we do not want to include a link in the email. for (const id of factorIds) { - logger_1.logDebugMessage( - `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid checking assertAllowedToSetupFactorElseThrowInvalidClaimError` - ); + logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid checking assertAllowedToSetupFactorElseThrowInvalidClaimError`); try { memoizedAllowedToSetupFactorInput.factorId = id; - await mfaInstance.recipeInterfaceImpl.assertAllowedToSetupFactorElseThrowInvalidClaimError( - memoizedAllowedToSetupFactorInput - ); - logger_1.logDebugMessage( - `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid ${id} valid because assertAllowedToSetupFactorElseThrowInvalidClaimError passed` - ); + await mfaInstance.recipeInterfaceImpl.assertAllowedToSetupFactorElseThrowInvalidClaimError(memoizedAllowedToSetupFactorInput); + logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid ${id} valid because assertAllowedToSetupFactorElseThrowInvalidClaimError passed`); // we add it to the valid factor ids list since it is either already set up or allowed to be set up validFactorIds.push(id); - } catch (err) { - logger_1.logDebugMessage( - `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid assertAllowedToSetupFactorElseThrowInvalidClaimError failed for ${id}` - ); + } + catch (err) { + logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid assertAllowedToSetupFactorElseThrowInvalidClaimError failed for ${id}`); caughtSetupFactorError = err; } } if (validFactorIds.length === 0) { - logger_1.logDebugMessage( - `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid rethrowing error from assertAllowedToSetupFactorElseThrowInvalidClaimError because we found no valid factors` - ); + logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid rethrowing error from assertAllowedToSetupFactorElseThrowInvalidClaimError because we found no valid factors`); // we can safely re-throw this since this should be an InvalidClaimError // if it's anything else, we do not really have a way of handling it anyway. throw caughtSetupFactorError; } return validFactorIds; - } else { + } + else { // If signing in will not change the user (no linking), then we can let the sign-in/up happen (if allowed by account linking checks) - logger_1.logDebugMessage( - `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid allowing all factors because it'll not create new link` - ); + logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid allowing all factors because it'll not create new link`); return factorIds; } - } else { - logger_1.logDebugMessage( - `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid allowing all factors because MFA is not enabled` - ); + } + else { + logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid allowing all factors because MFA is not enabled`); // If MFA is not enabled, we allow the user to connect any secondary account to the session user. return factorIds; } diff --git a/lib/build/combinedRemoteJWKSet.d.ts b/lib/build/combinedRemoteJWKSet.d.ts index d36ad16ae..539a6d328 100644 --- a/lib/build/combinedRemoteJWKSet.d.ts +++ b/lib/build/combinedRemoteJWKSet.d.ts @@ -15,7 +15,4 @@ export declare function resetCombinedJWKS(): void; */ export declare function getCombinedJWKS(config: { jwksRefreshIntervalSec: number; -}): ( - protectedHeader?: import("jose").JWSHeaderParameters | undefined, - token?: import("jose").FlattenedJWSInput | undefined -) => Promise; +}): (protectedHeader?: import("jose").JWSHeaderParameters | undefined, token?: import("jose").FlattenedJWSInput | undefined) => Promise; diff --git a/lib/build/combinedRemoteJWKSet.js b/lib/build/combinedRemoteJWKSet.js index 86d75b126..0e5d73d2f 100644 --- a/lib/build/combinedRemoteJWKSet.js +++ b/lib/build/combinedRemoteJWKSet.js @@ -26,24 +26,21 @@ function getCombinedJWKS(config) { if (combinedJWKS === undefined) { const JWKS = querier_1.Querier.getNewInstanceOrThrowError(undefined) .getAllCoreUrlsForPath("/.well-known/jwks.json") - .map((url) => - jose_1.createRemoteJWKSet(new URL(url), { - cacheMaxAge: config.jwksRefreshIntervalSec * 1000, - cooldownDuration: constants_1.JWKCacheCooldownInMs, - }) - ); + .map((url) => jose_1.createRemoteJWKSet(new URL(url), { + cacheMaxAge: config.jwksRefreshIntervalSec * 1000, + cooldownDuration: constants_1.JWKCacheCooldownInMs, + })); combinedJWKS = async (...args) => { let lastError = undefined; if (JWKS.length === 0) { - throw Error( - "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." - ); + throw Error("No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using."); } for (const jwks of JWKS) { try { // We await before returning to make sure we catch the error return await jwks(...args); - } catch (ex) { + } + catch (ex) { lastError = ex; } } diff --git a/lib/build/core-mock.js b/lib/build/core-mock.js index 9a511d842..0d727271c 100644 --- a/lib/build/core-mock.js +++ b/lib/build/core-mock.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMockQuerier = void 0; const querier_1 = require("./querier"); @@ -44,22 +42,11 @@ const getMockQuerier = (recipeId) => { }); const id = crypto_1.default.randomUUID(); const now = new Date(); - writeDb( - "generatedOptions", - id, - Object.assign(Object.assign({}, registrationOptions), { - id, - origin: body.origin, - tenantId: body.tenantId, - email: body.email, - rpId: registrationOptions.rp.id, - createdAt: now.getTime(), - expiresAt: now.getTime() + body.timeout * 1000, - }) - ); + writeDb("generatedOptions", id, Object.assign(Object.assign({}, registrationOptions), { id, origin: body.origin, tenantId: body.tenantId, email: body.email, rpId: registrationOptions.rp.id, createdAt: now.getTime(), expiresAt: now.getTime() + body.timeout * 1000 })); // @ts-ignore return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, registrationOptions); - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + } + else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { const signInOptions = await server_1.generateAuthenticationOptions({ rpID: body.relyingPartyId, timeout: body.timeout, @@ -67,21 +54,11 @@ const getMockQuerier = (recipeId) => { }); const id = crypto_1.default.randomUUID(); const now = new Date(); - writeDb( - "generatedOptions", - id, - Object.assign(Object.assign({}, signInOptions), { - id, - origin: body.origin, - tenantId: body.tenantId, - email: body.email, - createdAt: now.getTime(), - expiresAt: now.getTime() + body.timeout * 1000, - }) - ); + writeDb("generatedOptions", id, Object.assign(Object.assign({}, signInOptions), { id, origin: body.origin, tenantId: body.tenantId, email: body.email, createdAt: now.getTime(), expiresAt: now.getTime() + body.timeout * 1000 })); // @ts-ignore return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, signInOptions); - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { + } + else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); if (!options) { // @ts-ignore @@ -108,15 +85,9 @@ const getMockQuerier = (recipeId) => { id: credentialId, userId: recipeUserId, counter: 0, - publicKey: - (_a = registrationVerification.registrationInfo) === null || _a === void 0 - ? void 0 - : _a.credential.publicKey.toString(), + publicKey: (_a = registrationVerification.registrationInfo) === null || _a === void 0 ? void 0 : _a.credential.publicKey.toString(), rpId: options.rpId, - transports: - (_b = registrationVerification.registrationInfo) === null || _b === void 0 - ? void 0 - : _b.credential.transports, + transports: (_b = registrationVerification.registrationInfo) === null || _b === void 0 ? void 0 : _b.credential.transports, createdAt: now.toISOString(), }); const user = { @@ -152,7 +123,8 @@ const getMockQuerier = (recipeId) => { }; // @ts-ignore return response; - } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + } + else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); if (!options) { // @ts-ignore diff --git a/lib/build/customFramework.d.ts b/lib/build/customFramework.d.ts index d7abeb160..9b944d2cb 100644 --- a/lib/build/customFramework.d.ts +++ b/lib/build/customFramework.d.ts @@ -14,12 +14,8 @@ export interface ParsableRequest { formData: () => Promise; json: () => Promise; } -export declare function getCookieFromRequest( - request: RequestType -): Record; -export declare function getQueryFromRequest( - request: RequestType -): Record; +export declare function getCookieFromRequest(request: RequestType): Record; +export declare function getQueryFromRequest(request: RequestType): Record; export declare function handleAuthAPIRequest(): (req: Request) => Promise; /** * A helper function to retrieve session details on the server side. @@ -28,33 +24,15 @@ export declare function handleAuthAPIRequest(): (req: Request) => Promise( - request: RequestType -): Promise<{ +export declare function getSessionForSSR(request: RequestType): Promise<{ accessTokenPayload: JWTPayload | undefined; hasToken: boolean; error: Error | undefined; }>; -export declare function getSessionForSSRUsingAccessToken( - accessToken: string | undefined -): Promise<{ +export declare function getSessionForSSRUsingAccessToken(accessToken: string | undefined): Promise<{ accessTokenPayload: JWTPayload | undefined; hasToken: boolean; error: Error | undefined; }>; -export declare function withSession< - RequestType extends ParsableRequest = Request, - ResponseType extends Response = Response ->( - request: RequestType, - handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, - options?: VerifySessionOptions, - userContext?: Record -): Promise; -export declare function withPreParsedRequestResponse< - RequestType extends ParsableRequest = Request, - ResponseType extends Response = Response ->( - req: RequestType, - handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise -): Promise; +export declare function withSession(request: RequestType, handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, options?: VerifySessionOptions, userContext?: Record): Promise; +export declare function withPreParsedRequestResponse(req: RequestType, handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise): Promise; diff --git a/lib/build/customFramework.js b/lib/build/customFramework.js index 25064135d..482dd3d08 100644 --- a/lib/build/customFramework.js +++ b/lib/build/customFramework.js @@ -4,11 +4,9 @@ * that can be used to easily integrate the SDK with most * frameworks if they are not directly supported. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.withPreParsedRequestResponse = exports.withSession = exports.getSessionForSSRUsingAccessToken = exports.getSessionForSSR = exports.handleAuthAPIRequest = exports.getQueryFromRequest = exports.getCookieFromRequest = void 0; const cookie_1 = require("cookie"); @@ -123,10 +121,12 @@ async function getSessionForSSRUsingAccessToken(accessToken) { return { accessTokenPayload: decoded.userData, hasToken, error: undefined }; } return { accessTokenPayload: undefined, hasToken, error: undefined }; - } catch (error) { + } + catch (error) { return { accessTokenPayload: undefined, hasToken, error: undefined }; } - } catch (error) { + } + catch (error) { return { accessTokenPayload: undefined, hasToken, error: error }; } } @@ -137,7 +137,8 @@ async function withSession(request, handler, options, userContext) { const session = await session_1.default.getSession(baseRequest, baseResponse, options, userContext); return handler(undefined, session); }); - } catch (error) { + } + catch (error) { return await handler(error, undefined); } } @@ -148,7 +149,8 @@ async function withPreParsedRequestResponse(req, handler) { let userResponse; try { userResponse = await handler(baseRequest, baseResponse); - } catch (err) { + } + catch (err) { userResponse = await handleError(err, baseRequest, baseResponse); } return addCookies(baseResponse, userResponse); @@ -162,17 +164,14 @@ function addCookies(baseResponse, userResponse) { let didAddHeaders = false; for (const respCookie of baseResponse.cookies) { didAddCookies = true; - userResponse.headers.append( - "Set-Cookie", - cookie_1.serialize(respCookie.key, respCookie.value, { - domain: respCookie.domain, - expires: new Date(respCookie.expires), - httpOnly: respCookie.httpOnly, - path: respCookie.path, - sameSite: respCookie.sameSite, - secure: respCookie.secure, - }) - ); + userResponse.headers.append("Set-Cookie", cookie_1.serialize(respCookie.key, respCookie.value, { + domain: respCookie.domain, + expires: new Date(respCookie.expires), + httpOnly: respCookie.httpOnly, + path: respCookie.path, + sameSite: respCookie.sameSite, + secure: respCookie.secure, + })); } baseResponse.headers.forEach((value, key) => { didAddHeaders = true; diff --git a/lib/build/error.d.ts b/lib/build/error.d.ts index 41083aef0..354527199 100644 --- a/lib/build/error.d.ts +++ b/lib/build/error.d.ts @@ -6,18 +6,14 @@ export default class SuperTokensError extends Error { payload: any; fromRecipe: string | undefined; private errMagic; - constructor( - options: - | { - message: string; - payload?: any; - type: string; - } - | { - message: string; - type: "BAD_INPUT_ERROR"; - payload: undefined; - } - ); + constructor(options: { + message: string; + payload?: any; + type: string; + } | { + message: string; + type: "BAD_INPUT_ERROR"; + payload: undefined; + }); static isErrorFromSuperTokens(obj: any): obj is SuperTokensError; } diff --git a/lib/build/framework/awsLambda/framework.d.ts b/lib/build/framework/awsLambda/framework.d.ts index c5945ac37..ae555f8f7 100644 --- a/lib/build/framework/awsLambda/framework.d.ts +++ b/lib/build/framework/awsLambda/framework.d.ts @@ -1,11 +1,5 @@ // @ts-nocheck -import type { - APIGatewayProxyEventV2, - APIGatewayProxyEvent, - APIGatewayProxyResult, - APIGatewayProxyStructuredResultV2, - Handler, -} from "aws-lambda"; +import type { APIGatewayProxyEventV2, APIGatewayProxyEvent, APIGatewayProxyResult, APIGatewayProxyStructuredResultV2, Handler } from "aws-lambda"; import { HTTPMethod } from "../../types"; import { BaseRequest } from "../request"; import { BaseResponse } from "../response"; @@ -56,24 +50,13 @@ export declare class AWSResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: ( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" - ) => void; + setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; /** * @param {number} statusCode */ setStatusCode: (statusCode: number) => void; sendJSONResponse: (content: any) => void; - sendResponse: ( - response?: APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 | undefined - ) => APIGatewayProxyResult | APIGatewayProxyStructuredResultV2; + sendResponse: (response?: APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 | undefined) => APIGatewayProxyResult | APIGatewayProxyStructuredResultV2; } export interface SessionEventV2 extends SupertokensLambdaEventV2 { session?: SessionContainerInterface; diff --git a/lib/build/framework/awsLambda/framework.js b/lib/build/framework/awsLambda/framework.js index 9ad56d91b..db261c799 100644 --- a/lib/build/framework/awsLambda/framework.js +++ b/lib/build/framework/awsLambda/framework.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.AWSWrapper = exports.middleware = exports.AWSResponse = exports.AWSRequest = void 0; const utils_1 = require("../../utils"); @@ -19,11 +17,13 @@ class AWSRequest extends request_1.BaseRequest { this.getFormDataFromRequestBody = async () => { if (this.event.body === null || this.event.body === undefined) { return {}; - } else { + } + else { try { const parsedUrlEncodedFormData = Object.fromEntries(new URLSearchParams(this.event.body).entries()); return parsedUrlEncodedFormData === undefined ? {} : parsedUrlEncodedFormData; - } catch (err) { + } + catch (err) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "API input error: Please make sure to pass valid url encoded form in the request body", @@ -34,7 +34,8 @@ class AWSRequest extends request_1.BaseRequest { this.getJSONFromRequestBody = async () => { if (this.event.body === null || this.event.body === undefined) { return {}; - } else { + } + else { const parsedJSONBody = JSON.parse(this.event.body); return parsedJSONBody === undefined ? {} : parsedJSONBody; } @@ -58,20 +59,15 @@ class AWSRequest extends request_1.BaseRequest { }; this.getCookieValue = (key) => { let cookies = this.event.cookies; - if ( - (this.event.headers === undefined || this.event.headers === null) && - (cookies === undefined || cookies === null) - ) { + if ((this.event.headers === undefined || this.event.headers === null) && + (cookies === undefined || cookies === null)) { return undefined; } let value = utils_2.getCookieValueFromHeaders(this.event.headers, key); if (value === undefined && cookies !== undefined && cookies !== null) { - value = utils_2.getCookieValueFromHeaders( - { - cookie: cookies.join(";"), - }, - key - ); + value = utils_2.getCookieValueFromHeaders({ + cookie: cookies.join(";"), + }, key); } return value; }; @@ -122,21 +118,10 @@ class AWSResponse extends response_1.BaseResponse { }); }; this.removeHeader = (key) => { - this.event.supertokens.response.headers = this.event.supertokens.response.headers.filter( - (header) => header.key.toLowerCase() !== key.toLowerCase() - ); + this.event.supertokens.response.headers = this.event.supertokens.response.headers.filter((header) => header.key.toLowerCase() !== key.toLowerCase()); }; this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { - let serialisedCookie = utils_2.serializeCookieValue( - key, - value, - domain, - secure, - httpOnly, - expires, - path, - sameSite - ); + let serialisedCookie = utils_2.serializeCookieValue(key, value, domain, secure, httpOnly, expires, path, sameSite); this.event.supertokens.response.cookies = [ ...this.event.supertokens.response.cookies.filter((c) => !c.startsWith(key + "=")), serialisedCookie, @@ -190,14 +175,13 @@ class AWSResponse extends response_1.BaseResponse { For example if the caller is trying to add front token to the access control exposed headers property we do not want to append if something else had already added it */ - if ( - typeof currentValue !== "string" || - !currentValue.includes(supertokensHeaders[i].value.toString()) - ) { + if (typeof currentValue !== "string" || + !currentValue.includes(supertokensHeaders[i].value.toString())) { let newValue = `${currentValue}, ${supertokensHeaders[i].value}`; headers[supertokensHeaders[i].key] = newValue; } - } else { + } + else { headers[supertokensHeaders[i].key] = supertokensHeaders[i].value; } } @@ -207,28 +191,26 @@ class AWSResponse extends response_1.BaseResponse { cookies = []; } cookies.push(...supertokensCookies); - let result = Object.assign(Object.assign({}, response), { cookies, body, statusCode, headers }); + let result = Object.assign(Object.assign({}, response), { cookies, + body, + statusCode, + headers }); return result; - } else { + } + else { let multiValueHeaders = response.multiValueHeaders; if (multiValueHeaders === undefined) { multiValueHeaders = {}; } let headsersInMultiValueHeaders = Object.keys(multiValueHeaders); - let cookieHeader = headsersInMultiValueHeaders.find( - (h) => h.toLowerCase() === constants_1.COOKIE_HEADER.toLowerCase() - ); + let cookieHeader = headsersInMultiValueHeaders.find((h) => h.toLowerCase() === constants_1.COOKIE_HEADER.toLowerCase()); if (cookieHeader === undefined) { multiValueHeaders[constants_1.COOKIE_HEADER] = supertokensCookies; - } else { + } + else { multiValueHeaders[cookieHeader].push(...supertokensCookies); } - let result = Object.assign(Object.assign({}, response), { - multiValueHeaders, - body: body, - statusCode: statusCode, - headers, - }); + let result = Object.assign(Object.assign({}, response), { multiValueHeaders, body: body, statusCode: statusCode, headers }); return result; } }; @@ -271,7 +253,8 @@ const middleware = (handler) => { error: `The middleware couldn't serve the API path ${request.getOriginalURL()}, method: ${request.getMethod()}. If this is an unexpected behaviour, please create an issue here: https://github.com/supertokens/supertokens-node/issues`, }); return response.sendResponse(); - } catch (err) { + } + catch (err) { await supertokens.errorHandler(err, request, response, userContext); if (response.responseSet) { return response.sendResponse(); diff --git a/lib/build/framework/awsLambda/index.d.ts b/lib/build/framework/awsLambda/index.d.ts index 1028174f0..4ec1183d8 100644 --- a/lib/build/framework/awsLambda/index.d.ts +++ b/lib/build/framework/awsLambda/index.d.ts @@ -1,7 +1,5 @@ // @ts-nocheck export type { SessionEvent, SessionEventV2 } from "./framework"; -export declare const middleware: ( - handler?: import("aws-lambda").Handler | undefined -) => import("aws-lambda").Handler; +export declare const middleware: (handler?: import("aws-lambda").Handler | undefined) => import("aws-lambda").Handler; export declare const wrapRequest: (unwrapped: any) => import("..").BaseRequest; export declare const wrapResponse: (unwrapped: any) => import("..").BaseResponse; diff --git a/lib/build/framework/constants.d.ts b/lib/build/framework/constants.d.ts index 79db4b944..e9b796f53 100644 --- a/lib/build/framework/constants.d.ts +++ b/lib/build/framework/constants.d.ts @@ -1,4 +1,3 @@ // @ts-nocheck export declare const COOKIE_HEADER = "Set-Cookie"; -export declare const BROTLI_DECOMPRESSION_ERROR_MESSAGE = - "Brotli decompression not implement, Please add a middleware that handles decompression before the SuperTokens middleware."; +export declare const BROTLI_DECOMPRESSION_ERROR_MESSAGE = "Brotli decompression not implement, Please add a middleware that handles decompression before the SuperTokens middleware."; diff --git a/lib/build/framework/constants.js b/lib/build/framework/constants.js index 1038b6933..ae0e26999 100644 --- a/lib/build/framework/constants.js +++ b/lib/build/framework/constants.js @@ -17,5 +17,4 @@ exports.BROTLI_DECOMPRESSION_ERROR_MESSAGE = exports.COOKIE_HEADER = void 0; */ exports.COOKIE_HEADER = "Set-Cookie"; // Define error message for brotli decompression not being supported -exports.BROTLI_DECOMPRESSION_ERROR_MESSAGE = - "Brotli decompression not implement, Please add a middleware that handles decompression before the SuperTokens middleware."; +exports.BROTLI_DECOMPRESSION_ERROR_MESSAGE = "Brotli decompression not implement, Please add a middleware that handles decompression before the SuperTokens middleware."; diff --git a/lib/build/framework/custom/framework.d.ts b/lib/build/framework/custom/framework.d.ts index 6b313019f..0505a06d1 100644 --- a/lib/build/framework/custom/framework.d.ts +++ b/lib/build/framework/custom/framework.d.ts @@ -46,16 +46,7 @@ export declare class CollectingResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: ( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" - ) => void; + setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; /** * @param {number} statusCode */ @@ -63,47 +54,22 @@ export declare class CollectingResponse extends BaseResponse { sendJSONResponse: (content: any) => void; } export declare type NextFunction = (err?: any) => void; -export declare const middleware: ( - wrapRequest?: (req: OrigReqType) => BaseRequest, - wrapResponse?: (req: OrigRespType) => BaseResponse -) => ( - request: OrigReqType, - response: OrigRespType, - next?: NextFunction | undefined -) => Promise< - | { - handled: boolean; - error?: undefined; - } - | { - error: any; - handled?: undefined; - } ->; -export declare const errorHandler: () => ( - err: any, - request: BaseRequest, - response: BaseResponse, - next: NextFunction -) => Promise; +export declare const middleware: (wrapRequest?: (req: OrigReqType) => BaseRequest, wrapResponse?: (req: OrigRespType) => BaseResponse) => (request: OrigReqType, response: OrigRespType, next?: NextFunction | undefined) => Promise<{ + handled: boolean; + error?: undefined; +} | { + error: any; + handled?: undefined; +}>; +export declare const errorHandler: () => (err: any, request: BaseRequest, response: BaseResponse, next: NextFunction) => Promise; export declare const CustomFrameworkWrapper: { - middleware: ( - wrapRequest?: (req: OrigReqType) => BaseRequest, - wrapResponse?: (req: OrigRespType) => BaseResponse - ) => ( - request: OrigReqType, - response: OrigRespType, - next?: NextFunction | undefined - ) => Promise< - | { - handled: boolean; - error?: undefined; - } - | { - error: any; - handled?: undefined; - } - >; + middleware: (wrapRequest?: (req: OrigReqType) => BaseRequest, wrapResponse?: (req: OrigRespType) => BaseResponse) => (request: OrigReqType, response: OrigRespType, next?: NextFunction | undefined) => Promise<{ + handled: boolean; + error?: undefined; + } | { + error: any; + handled?: undefined; + }>; errorHandler: () => (err: any, request: BaseRequest, response: BaseResponse, next: NextFunction) => Promise; }; export {}; diff --git a/lib/build/framework/custom/framework.js b/lib/build/framework/custom/framework.js index a321c2ffb..2bb6b38a1 100644 --- a/lib/build/framework/custom/framework.js +++ b/lib/build/framework/custom/framework.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.CustomFrameworkWrapper = exports.errorHandler = exports.middleware = exports.CollectingResponse = exports.PreParsedRequest = void 0; const utils_1 = require("../../utils"); @@ -89,7 +87,8 @@ class CollectingResponse extends response_1.BaseResponse { if (!((_a = this.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(value))) { this.headers.append(key, value); } - } else { + } + else { this.headers.set(key, value); } }; @@ -113,7 +112,8 @@ class CollectingResponse extends response_1.BaseResponse { // Still, if available we are using the built-in (node 18+) if (typeof Headers === "undefined") { this.headers = new nodeHeaders_1.default(null); - } else { + } + else { this.headers = new Headers(); } this.statusCode = 200; @@ -138,18 +138,21 @@ const middleware = (wrapRequest = identity, wrapResponse = identity) => { return { handled: false }; } return { handled: true }; - } catch (err) { + } + catch (err) { if (supertokens) { try { await supertokens.errorHandler(err, wrappedReq, wrappedResp, userContext); return { handled: true }; - } catch (_a) { + } + catch (_a) { if (next) { next(err); } return { error: err }; } - } else { + } + else { if (next) { next(err); } @@ -166,7 +169,8 @@ const errorHandler = () => { try { await supertokens.errorHandler(err, request, response, userContext); return next(); - } catch (err) { + } + catch (err) { return next(err); } }; diff --git a/lib/build/framework/custom/index.d.ts b/lib/build/framework/custom/index.d.ts index 7c3b1f1fa..490f51f3a 100644 --- a/lib/build/framework/custom/index.d.ts +++ b/lib/build/framework/custom/index.d.ts @@ -1,25 +1,10 @@ // @ts-nocheck export { PreParsedRequest, CollectingResponse } from "./framework"; -export declare const middleware: ( - wrapRequest?: (req: OrigReqType) => import("..").BaseRequest, - wrapResponse?: (req: OrigRespType) => import("..").BaseResponse -) => ( - request: OrigReqType, - response: OrigRespType, - next?: import("./framework").NextFunction | undefined -) => Promise< - | { - handled: boolean; - error?: undefined; - } - | { - error: any; - handled?: undefined; - } ->; -export declare const errorHandler: () => ( - err: any, - request: import("..").BaseRequest, - response: import("..").BaseResponse, - next: import("./framework").NextFunction -) => Promise; +export declare const middleware: (wrapRequest?: (req: OrigReqType) => import("..").BaseRequest, wrapResponse?: (req: OrigRespType) => import("..").BaseResponse) => (request: OrigReqType, response: OrigRespType, next?: import("./framework").NextFunction | undefined) => Promise<{ + handled: boolean; + error?: undefined; +} | { + error: any; + handled?: undefined; +}>; +export declare const errorHandler: () => (err: any, request: import("..").BaseRequest, response: import("..").BaseResponse, next: import("./framework").NextFunction) => Promise; diff --git a/lib/build/framework/custom/index.js b/lib/build/framework/custom/index.js index 31170e351..acd4ab523 100644 --- a/lib/build/framework/custom/index.js +++ b/lib/build/framework/custom/index.js @@ -17,17 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.errorHandler = exports.middleware = exports.CollectingResponse = exports.PreParsedRequest = void 0; const framework_1 = require("./framework"); var framework_2 = require("./framework"); -Object.defineProperty(exports, "PreParsedRequest", { - enumerable: true, - get: function () { - return framework_2.PreParsedRequest; - }, -}); -Object.defineProperty(exports, "CollectingResponse", { - enumerable: true, - get: function () { - return framework_2.CollectingResponse; - }, -}); +Object.defineProperty(exports, "PreParsedRequest", { enumerable: true, get: function () { return framework_2.PreParsedRequest; } }); +Object.defineProperty(exports, "CollectingResponse", { enumerable: true, get: function () { return framework_2.CollectingResponse; } }); exports.middleware = framework_1.CustomFrameworkWrapper.middleware; exports.errorHandler = framework_1.CustomFrameworkWrapper.errorHandler; diff --git a/lib/build/framework/custom/nodeHeaders.js b/lib/build/framework/custom/nodeHeaders.js index 88f417cd1..47c5664cf 100644 --- a/lib/build/framework/custom/nodeHeaders.js +++ b/lib/build/framework/custom/nodeHeaders.js @@ -57,16 +57,19 @@ class Headers extends URLSearchParams { for (const [name, values] of Object.entries(raw)) { result.push(...values.map((value) => [name, value])); } - } else if (init == null) { + } + else if (init == null) { // eslint-disable-line no-eq-null, eqeqeq // No op - } else if (typeof init === "object" && !utils_1.isBoxedPrimitive(init)) { + } + else if (typeof init === "object" && !utils_1.isBoxedPrimitive(init)) { const method = init[Symbol.iterator]; // eslint-disable-next-line no-eq-null, eqeqeq if (method == null) { // Record result.push(...Object.entries(init)); - } else { + } + else { if (typeof method !== "function") { throw new TypeError("Header pairs must be iterable"); } @@ -74,31 +77,30 @@ class Headers extends URLSearchParams { // Note: per spec we have to first exhaust the lists then process them result = [...init] .map((pair) => { - if (typeof pair !== "object" || utils_1.isBoxedPrimitive(pair)) { - throw new TypeError("Each header pair must be an iterable object"); - } - return [...pair]; - }) + if (typeof pair !== "object" || utils_1.isBoxedPrimitive(pair)) { + throw new TypeError("Each header pair must be an iterable object"); + } + return [...pair]; + }) .map((pair) => { - if (pair.length !== 2) { - throw new TypeError("Each header pair must be a name/value tuple"); - } - return [...pair]; - }); + if (pair.length !== 2) { + throw new TypeError("Each header pair must be a name/value tuple"); + } + return [...pair]; + }); } - } else { - throw new TypeError( - "Failed to construct 'Headers': The provided value is not of type '(sequence> or record)" - ); + } + else { + throw new TypeError("Failed to construct 'Headers': The provided value is not of type '(sequence> or record)"); } // Validate and lowercase result = result.length > 0 ? result.map(([name, value]) => { - validateHeaderName(name); - validateHeaderValue(name, String(value)); - return [String(name).toLowerCase(), String(value)]; - }) + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return [String(name).toLowerCase(), String(value)]; + }) : undefined; super(result); // Returning a Proxy that will lowercase key names, validate parameters and sort keys @@ -111,11 +113,7 @@ class Headers extends URLSearchParams { return (name, value) => { validateHeaderName(name); validateHeaderValue(name, String(value)); - return URLSearchParams.prototype[p].call( - receiver, - String(name).toLowerCase(), - String(value) - ); + return URLSearchParams.prototype[p].call(receiver, String(name).toLowerCase(), String(value)); }; case "delete": case "has": @@ -195,7 +193,8 @@ class Headers extends URLSearchParams { // This hack makes specifying custom Host header possible. if (key === "host") { result[key] = values[0]; - } else { + } + else { result[key] = values.length > 1 ? values : values[0]; } return result; @@ -207,37 +206,33 @@ exports.default = Headers; * Re-shaping object for Web IDL tests * Only need to do it for overridden methods */ -Object.defineProperties( - Headers.prototype, - ["get", "entries", "forEach", "values"].reduce((result, property) => { - result[property] = { enumerable: true }; - return result; - }, {}) -); +Object.defineProperties(Headers.prototype, ["get", "entries", "forEach", "values"].reduce((result, property) => { + result[property] = { enumerable: true }; + return result; +}, {})); /** * Create a Headers object from an http.IncomingMessage.rawHeaders, ignoring those that do * not conform to HTTP grammar productions. * @param {import('http').IncomingMessage['rawHeaders']} headers */ function fromRawHeaders(headers = []) { - return new Headers( - headers - // Split into pairs - .reduce((result, value, index, array) => { - if (index % 2 === 0) { - result.push(array.slice(index, index + 2)); - } - return result; - }, []) - .filter(([name, value]) => { - try { - validateHeaderName(name); - validateHeaderValue(name, String(value)); - return true; - } catch (_a) { - return false; - } - }) - ); + return new Headers(headers + // Split into pairs + .reduce((result, value, index, array) => { + if (index % 2 === 0) { + result.push(array.slice(index, index + 2)); + } + return result; + }, []) + .filter(([name, value]) => { + try { + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return true; + } + catch (_a) { + return false; + } + })); } exports.fromRawHeaders = fromRawHeaders; diff --git a/lib/build/framework/express/framework.d.ts b/lib/build/framework/express/framework.d.ts index 40cbcad7b..b98f2fbe6 100644 --- a/lib/build/framework/express/framework.d.ts +++ b/lib/build/framework/express/framework.d.ts @@ -23,16 +23,7 @@ export declare class ExpressResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: ( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" - ) => void; + setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; /** * @param {number} statusCode */ diff --git a/lib/build/framework/express/framework.js b/lib/build/framework/express/framework.js index d8a58729b..09cf076ba 100644 --- a/lib/build/framework/express/framework.js +++ b/lib/build/framework/express/framework.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExpressWrapper = exports.errorHandler = exports.middleware = exports.ExpressResponse = exports.ExpressRequest = void 0; const utils_1 = require("../../utils"); @@ -86,17 +84,7 @@ class ExpressResponse extends response_1.BaseResponse { this.response.removeHeader(key); }; this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { - utils_2.setCookieForServerResponse( - this.response, - key, - value, - domain, - secure, - httpOnly, - expires, - path, - sameSite - ); + utils_2.setCookieForServerResponse(this.response, key, value, domain, secure, httpOnly, expires, path, sameSite); }; /** * @param {number} statusCode @@ -129,14 +117,17 @@ const middleware = () => { if (!result) { return next(); } - } catch (err) { + } + catch (err) { if (supertokens) { try { await supertokens.errorHandler(err, request, response, userContext); - } catch (_a) { + } + catch (_a) { next(err); } - } else { + } + else { next(err); } } @@ -151,7 +142,8 @@ const errorHandler = () => { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { await supertokens.errorHandler(err, request, response, userContext); - } catch (err) { + } + catch (err) { return next(err); } }; diff --git a/lib/build/framework/express/index.d.ts b/lib/build/framework/express/index.d.ts index 9bf873aa2..8ff22112c 100644 --- a/lib/build/framework/express/index.d.ts +++ b/lib/build/framework/express/index.d.ts @@ -1,15 +1,6 @@ // @ts-nocheck export type { SessionRequest } from "./framework"; -export declare const middleware: () => ( - req: import("express").Request, - res: import("express").Response, - next: import("express").NextFunction -) => Promise; -export declare const errorHandler: () => ( - err: any, - req: import("express").Request, - res: import("express").Response, - next: import("express").NextFunction -) => Promise; +export declare const middleware: () => (req: import("express").Request, res: import("express").Response, next: import("express").NextFunction) => Promise; +export declare const errorHandler: () => (err: any, req: import("express").Request, res: import("express").Response, next: import("express").NextFunction) => Promise; export declare const wrapRequest: (unwrapped: any) => import("..").BaseRequest; export declare const wrapResponse: (unwrapped: any) => import("..").BaseResponse; diff --git a/lib/build/framework/fastify/framework.d.ts b/lib/build/framework/fastify/framework.d.ts index 074d5ebe3..d299d1196 100644 --- a/lib/build/framework/fastify/framework.d.ts +++ b/lib/build/framework/fastify/framework.d.ts @@ -23,16 +23,7 @@ export declare class FastifyResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: ( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" - ) => void; + setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; /** * @param {number} statusCode */ diff --git a/lib/build/framework/fastify/framework.js b/lib/build/framework/fastify/framework.js index 5bce10130..2bffba3c6 100644 --- a/lib/build/framework/fastify/framework.js +++ b/lib/build/framework/fastify/framework.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.FastifyWrapper = exports.errorHandler = exports.FastifyResponse = exports.FastifyRequest = void 0; const utils_1 = require("../../utils"); @@ -78,7 +76,8 @@ class FastifyResponse extends response_1.BaseResponse { // we have the this.response.header for compatibility with nextJS if (existingValue === undefined) { this.response.header(key, value); - } else if (allowDuplicateKey) { + } + else if (allowDuplicateKey) { /** We only want to append if it does not already exist For example if the caller is trying to add front token to the access control exposed headers property @@ -87,11 +86,13 @@ class FastifyResponse extends response_1.BaseResponse { if (typeof existingValue !== "string" || !existingValue.includes(value)) { this.response.header(key, existingValue + ", " + value); } - } else { + } + else { // we overwrite the current one with the new one this.response.header(key, value); } - } catch (err) { + } + catch (err) { throw new Error("Error while setting header with key: " + key + " and value: " + value); } }; @@ -99,19 +100,12 @@ class FastifyResponse extends response_1.BaseResponse { this.response.removeHeader(key); }; this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { - let serialisedCookie = utils_2.serializeCookieValue( - key, - value, - domain, - secure, - httpOnly, - expires, - path, - sameSite - ); + let serialisedCookie = utils_2.serializeCookieValue(key, value, domain, secure, httpOnly, expires, path, sameSite); let oldHeaders = this.response.getHeader(constants_1.COOKIE_HEADER); - if (oldHeaders === undefined) oldHeaders = []; - else if (!(oldHeaders instanceof Array)) oldHeaders = [oldHeaders.toString()]; + if (oldHeaders === undefined) + oldHeaders = []; + else if (!(oldHeaders instanceof Array)) + oldHeaders = [oldHeaders.toString()]; this.response.removeHeader(constants_1.COOKIE_HEADER); this.response.header(constants_1.COOKIE_HEADER, [ ...oldHeaders.filter((h) => !h.startsWith(key + "=")), @@ -149,7 +143,8 @@ function plugin(fastify, _, done) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { await supertokens.middleware(request, response, userContext); - } catch (err) { + } + catch (err) { await supertokens.errorHandler(err, request, response, userContext); } }); diff --git a/lib/build/framework/fastify/index.d.ts b/lib/build/framework/fastify/index.d.ts index 878d1ec23..d1d0e9664 100644 --- a/lib/build/framework/fastify/index.d.ts +++ b/lib/build/framework/fastify/index.d.ts @@ -1,14 +1,6 @@ // @ts-nocheck export type { SessionRequest } from "./framework"; -export declare const plugin: import("./types").FastifyPluginCallback>; -export declare const errorHandler: () => ( - err: any, - req: import("./types").FastifyRequest, - res: import("./types").FastifyReply -) => Promise; +export declare const plugin: import("./types").FastifyPluginCallback>; +export declare const errorHandler: () => (err: any, req: import("./types").FastifyRequest, res: import("./types").FastifyReply) => Promise; export declare const wrapRequest: (unwrapped: any) => import("..").BaseRequest; export declare const wrapResponse: (unwrapped: any) => import("..").BaseResponse; diff --git a/lib/build/framework/fastify/types.d.ts b/lib/build/framework/fastify/types.d.ts index 86c117df5..08a02b14d 100644 --- a/lib/build/framework/fastify/types.d.ts +++ b/lib/build/framework/fastify/types.d.ts @@ -19,15 +19,7 @@ export interface FastifyReply { getHeader(key: any): number | string | string[] | undefined; type(contentType: string): FastifyReply; } -export interface FastifyInstance< - Instance = unknown, - Request extends FastifyRequest = FastifyRequest, - Reply extends FastifyReply = FastifyReply -> { +export interface FastifyInstance { addHook(this: Instance, name: string, hook: (req: Request, reply: Reply) => void): Instance; } -export declare type FastifyPluginCallback = ( - instance: Instance, - opts: unknown, - done: (err?: Error) => void -) => void; +export declare type FastifyPluginCallback = (instance: Instance, opts: unknown, done: (err?: Error) => void) => void; diff --git a/lib/build/framework/hapi/framework.d.ts b/lib/build/framework/hapi/framework.d.ts index c7dafc2da..9cbdd0324 100644 --- a/lib/build/framework/hapi/framework.d.ts +++ b/lib/build/framework/hapi/framework.d.ts @@ -17,12 +17,7 @@ export declare class HapiRequest extends BaseRequest { getOriginalURL: () => string; } export interface ExtendedResponseToolkit extends ResponseToolkit { - lazyHeaderBindings: ( - h: ResponseToolkit, - key: string, - value: string | undefined, - allowDuplicateKey: boolean - ) => void; + lazyHeaderBindings: (h: ResponseToolkit, key: string, value: string | undefined, allowDuplicateKey: boolean) => void; } export declare class HapiResponse extends BaseResponse { private response; @@ -34,16 +29,7 @@ export declare class HapiResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: ( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" - ) => void; + setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; /** * @param {number} statusCode */ diff --git a/lib/build/framework/hapi/framework.js b/lib/build/framework/hapi/framework.js index 7be6275ce..8c0936f90 100644 --- a/lib/build/framework/hapi/framework.js +++ b/lib/build/framework/hapi/framework.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.HapiWrapper = exports.HapiResponse = exports.HapiRequest = void 0; const utils_1 = require("../../utils"); @@ -75,7 +73,8 @@ class HapiResponse extends response_1.BaseResponse { this.setHeader = (key, value, allowDuplicateKey) => { try { this.response.lazyHeaderBindings(this.response, key, value, allowDuplicateKey); - } catch (err) { + } + catch (err) { throw new Error("Error while setting header with key: " + key + " and value: " + value); } }; @@ -90,11 +89,12 @@ class HapiResponse extends response_1.BaseResponse { path: path, domain, ttl: expires - now, - isSameSite: sameSite === "lax" ? "Lax" : sameSite === "none" ? "None" : "Strict", + isSameSite: (sameSite === "lax" ? "Lax" : sameSite === "none" ? "None" : "Strict"), }; if (expires > now) { this.response.state(key, value, cookieOptions); - } else { + } + else { this.response.unstate(key, cookieOptions); } }; @@ -149,7 +149,8 @@ const plugin = { (request.app.lazyHeaders || []).forEach(({ key, value, allowDuplicateKey }) => { if (request.response.isBoom) { request.response.output.headers[key] = value; - } else { + } + else { request.response.header(key, value, { append: allowDuplicateKey }); } }); @@ -169,7 +170,8 @@ const plugin = { return resObj.takeover(); } return h.continue; - } catch (e) { + } + catch (e) { return h.continue; } } @@ -180,10 +182,9 @@ const plugin = { const anyApp = h.request.app; anyApp.lazyHeaders = anyApp.lazyHeaders || []; if (value === undefined) { - anyApp.lazyHeaders = anyApp.lazyHeaders.filter( - (header) => header.key.toLowerCase() !== key.toLowerCase() - ); - } else { + anyApp.lazyHeaders = anyApp.lazyHeaders.filter((header) => header.key.toLowerCase() !== key.toLowerCase()); + } + else { anyApp.lazyHeaders.push({ key, value, allowDuplicateKey }); } }); diff --git a/lib/build/framework/index.js b/lib/build/framework/index.js index 28dc9790d..f9017266b 100644 --- a/lib/build/framework/index.js +++ b/lib/build/framework/index.js @@ -1,40 +1,23 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.awsLambda = exports.koa = exports.loopback = exports.hapi = exports.fastify = exports.express = exports.BaseResponse = exports.BaseRequest = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -52,19 +35,9 @@ exports.awsLambda = exports.koa = exports.loopback = exports.hapi = exports.fast * under the License. */ var request_1 = require("./request"); -Object.defineProperty(exports, "BaseRequest", { - enumerable: true, - get: function () { - return request_1.BaseRequest; - }, -}); +Object.defineProperty(exports, "BaseRequest", { enumerable: true, get: function () { return request_1.BaseRequest; } }); var response_1 = require("./response"); -Object.defineProperty(exports, "BaseResponse", { - enumerable: true, - get: function () { - return response_1.BaseResponse; - }, -}); +Object.defineProperty(exports, "BaseResponse", { enumerable: true, get: function () { return response_1.BaseResponse; } }); const expressFramework = __importStar(require("./express")); const fastifyFramework = __importStar(require("./fastify")); const hapiFramework = __importStar(require("./hapi")); diff --git a/lib/build/framework/koa/framework.d.ts b/lib/build/framework/koa/framework.d.ts index a9dff8f43..1df307ab7 100644 --- a/lib/build/framework/koa/framework.d.ts +++ b/lib/build/framework/koa/framework.d.ts @@ -24,16 +24,7 @@ export declare class KoaResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: ( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" - ) => void; + setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; /** * @param {number} statusCode */ diff --git a/lib/build/framework/koa/framework.js b/lib/build/framework/koa/framework.js index c108eb50d..5b4157368 100644 --- a/lib/build/framework/koa/framework.js +++ b/lib/build/framework/koa/framework.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.KoaWrapper = exports.middleware = exports.KoaResponse = exports.KoaRequest = void 0; const utils_1 = require("../../utils"); @@ -80,7 +78,8 @@ class KoaResponse extends response_1.BaseResponse { let existingValue = existingHeaders[key.toLowerCase()]; if (existingValue === undefined) { this.ctx.set(key, value); - } else if (allowDuplicateKey) { + } + else if (allowDuplicateKey) { /** We only want to append if it does not already exist For example if the caller is trying to add front token to the access control exposed headers property @@ -89,11 +88,13 @@ class KoaResponse extends response_1.BaseResponse { if (typeof existingValue !== "string" || !existingValue.includes(value)) { this.ctx.set(key, existingValue + ", " + value); } - } else { + } + else { // we overwrite the current one with the new one this.ctx.set(key, value); } - } catch (err) { + } + catch (err) { throw new Error("Error while setting header with key: " + key + " and value: " + value); } }; @@ -142,7 +143,8 @@ const middleware = () => { if (!result) { return await next(); } - } catch (err) { + } + catch (err) { return await supertokens.errorHandler(err, request, response, userContext); } }; diff --git a/lib/build/framework/loopback/framework.d.ts b/lib/build/framework/loopback/framework.d.ts index 12eb9c293..1ffe0dba4 100644 --- a/lib/build/framework/loopback/framework.d.ts +++ b/lib/build/framework/loopback/framework.d.ts @@ -23,16 +23,7 @@ export declare class LoopbackResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: ( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" - ) => void; + setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; setStatusCode: (statusCode: number) => void; sendJSONResponse: (content: any) => void; } diff --git a/lib/build/framework/loopback/framework.js b/lib/build/framework/loopback/framework.js index fc4097b3f..986b51a4a 100644 --- a/lib/build/framework/loopback/framework.js +++ b/lib/build/framework/loopback/framework.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.LoopbackWrapper = exports.middleware = exports.LoopbackResponse = exports.LoopbackRequest = void 0; const utils_1 = require("../../utils"); @@ -79,17 +77,7 @@ class LoopbackResponse extends response_1.BaseResponse { this.response.removeHeader(key); }; this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { - utils_2.setCookieForServerResponse( - this.response, - key, - value, - domain, - secure, - httpOnly, - expires, - path, - sameSite - ); + utils_2.setCookieForServerResponse(this.response, key, value, domain, secure, httpOnly, expires, path, sameSite); }; this.setStatusCode = (statusCode) => { if (!this.response.writableEnded) { @@ -118,7 +106,8 @@ const middleware = async (ctx, next) => { return await next(); } return; - } catch (err) { + } + catch (err) { return await supertokens.errorHandler(err, request, response, userContext); } }; diff --git a/lib/build/framework/request.js b/lib/build/framework/request.js index edaa6e780..c99c91879 100644 --- a/lib/build/framework/request.js +++ b/lib/build/framework/request.js @@ -46,16 +46,20 @@ class BaseRequest { if (contentType) { if (contentType.startsWith("application/json")) { return await this.getJSONBody(); - } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + } + else if (contentType.startsWith("application/x-www-form-urlencoded")) { return await this.getFormData(); } - } else { + } + else { try { return await this.getJSONBody(); - } catch (_a) { + } + catch (_a) { try { return await this.getFormData(); - } catch (_b) { + } + catch (_b) { throw new Error("Unable to parse body as JSON or Form Data."); } } diff --git a/lib/build/framework/response.d.ts b/lib/build/framework/response.d.ts index 8cf7a67d3..328749bf0 100644 --- a/lib/build/framework/response.d.ts +++ b/lib/build/framework/response.d.ts @@ -5,16 +5,7 @@ export declare abstract class BaseResponse { constructor(); abstract setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; abstract removeHeader: (key: string) => void; - abstract setCookie: ( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" - ) => void; + abstract setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; abstract setStatusCode: (statusCode: number) => void; abstract sendJSONResponse: (content: any) => void; abstract sendHTMLResponse: (html: string) => void; diff --git a/lib/build/framework/utils.d.ts b/lib/build/framework/utils.d.ts index 4ef5fc0f5..f64b37767 100644 --- a/lib/build/framework/utils.d.ts +++ b/lib/build/framework/utils.d.ts @@ -10,17 +10,9 @@ export declare function getHeaderValueFromIncomingMessage(request: IncomingMessa export declare function normalizeHeaderValue(value: string | string[] | undefined): string | undefined; export declare function parseJSONBodyFromRequest(req: IncomingMessage): Promise; export declare function parseURLEncodedFormData(req: IncomingMessage): Promise; -export declare function assertThatBodyParserHasBeenUsedForExpressLikeRequest( - method: HTTPMethod, - request: Request -): Promise; +export declare function assertThatBodyParserHasBeenUsedForExpressLikeRequest(method: HTTPMethod, request: Request): Promise; export declare function assertFormDataBodyParserHasBeenUsedForExpressLikeRequest(request: Request): Promise; -export declare function setHeaderForExpressLikeResponse( - res: Response, - key: string, - value: string, - allowDuplicateKey: boolean -): void; +export declare function setHeaderForExpressLikeResponse(res: Response, key: string, value: string, allowDuplicateKey: boolean): void; /** * * @param res @@ -32,30 +24,7 @@ export declare function setHeaderForExpressLikeResponse( * @param expires * @param path */ -export declare function setCookieForServerResponse( - res: ServerResponse, - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" -): ServerResponse; -export declare function getCookieValueToSetInHeader( - prev: string | string[] | undefined, - val: string | string[], - key: string -): string | string[]; -export declare function serializeCookieValue( - key: string, - value: string, - domain: string | undefined, - secure: boolean, - httpOnly: boolean, - expires: number, - path: string, - sameSite: "strict" | "lax" | "none" -): string; +export declare function setCookieForServerResponse(res: ServerResponse, key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): ServerResponse; +export declare function getCookieValueToSetInHeader(prev: string | string[] | undefined, val: string | string[], key: string): string | string[]; +export declare function serializeCookieValue(key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): string; export declare function isBoxedPrimitive(value: any): boolean; diff --git a/lib/build/framework/utils.js b/lib/build/framework/utils.js index eb78c07df..ed30fa0ec 100644 --- a/lib/build/framework/utils.js +++ b/lib/build/framework/utils.js @@ -13,43 +13,16 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __asyncValues = - (this && this.__asyncValues) || - function (o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], - i; - return m - ? m.call(o) - : ((o = typeof __values === "function" ? __values(o) : o[Symbol.iterator]()), - (i = {}), - verb("next"), - verb("throw"), - verb("return"), - (i[Symbol.asyncIterator] = function () { - return this; - }), - i); - function verb(n) { - i[n] = - o[n] && - function (v) { - return new Promise(function (resolve, reject) { - (v = o[n](v)), settle(resolve, reject, v.done, v.value); - }); - }; - } - function settle(resolve, reject, d, v) { - Promise.resolve(v).then(function (v) { - resolve({ value: v, done: d }); - }, reject); - } - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.isBoxedPrimitive = exports.serializeCookieValue = exports.getCookieValueToSetInHeader = exports.setCookieForServerResponse = exports.setHeaderForExpressLikeResponse = exports.assertFormDataBodyParserHasBeenUsedForExpressLikeRequest = exports.assertThatBodyParserHasBeenUsedForExpressLikeRequest = exports.parseURLEncodedFormData = exports.parseJSONBodyFromRequest = exports.normalizeHeaderValue = exports.getHeaderValueFromIncomingMessage = exports.getCookieValueFromIncomingMessage = exports.getCookieValueFromHeaders = void 0; const cookie_1 = require("cookie"); @@ -68,52 +41,45 @@ async function inflate(stream) { if (encoding === "gzip" || encoding === "deflate") { const inflator = new pako_1.default.Inflate(); try { - for ( - var stream_1 = __asyncValues(stream), stream_1_1; - (stream_1_1 = await stream_1.next()), !stream_1_1.done; - - ) { + for (var stream_1 = __asyncValues(stream), stream_1_1; stream_1_1 = await stream_1.next(), !stream_1_1.done;) { const chunk = stream_1_1.value; inflator.push(chunk, false); } - } catch (e_1_1) { - e_1 = { error: e_1_1 }; - } finally { + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { try { if (stream_1_1 && !stream_1_1.done && (_a = stream_1.return)) await _a.call(stream_1); - } finally { - if (e_1) throw e_1.error; } + finally { if (e_1) throw e_1.error; } } if (inflator.err) { throw new Error(`Decompression error: ${inflator.msg}`); } decompressedData = inflator.result; - } else if (encoding === "br") { + } + else if (encoding === "br") { throw new Error(constants_1.BROTLI_DECOMPRESSION_ERROR_MESSAGE); - } else { + } + else { // Handle identity or unsupported encoding decompressedData = utils_1.getBuffer().concat([]); try { - for ( - var stream_2 = __asyncValues(stream), stream_2_1; - (stream_2_1 = await stream_2.next()), !stream_2_1.done; - - ) { + for (var stream_2 = __asyncValues(stream), stream_2_1; stream_2_1 = await stream_2.next(), !stream_2_1.done;) { const chunk = stream_2_1.value; decompressedData = utils_1.getBuffer().concat([decompressedData, chunk]); } - } catch (e_2_1) { - e_2 = { error: e_2_1 }; - } finally { + } + catch (e_2_1) { e_2 = { error: e_2_1 }; } + finally { try { if (stream_2_1 && !stream_2_1.done && (_b = stream_2.return)) await _b.call(stream_2); - } finally { - if (e_2) throw e_2.error; } + finally { if (e_2) throw e_2.error; } } } - if (typeof decompressedData === "string") return decompressedData; + if (typeof decompressedData === "string") + return decompressedData; return new TextDecoder().decode(decompressedData); } function getCookieValueFromHeaders(headers, key) { @@ -164,7 +130,8 @@ function JSONCookie(str) { } try { return JSON.parse(str.slice(2)); - } catch (err) { + } + catch (err) { return undefined; } } @@ -191,7 +158,8 @@ function JSONCookies(obj) { function getCharset(req) { try { return (content_type_1.default.parse(req).parameters.charset || "").toLowerCase(); - } catch (e) { + } + catch (e) { return undefined; } } @@ -218,10 +186,12 @@ async function parseURLEncodedFormData(req) { if (key in body) { if (body[key] instanceof Array) { body[key].push(val); - } else { + } + else { body[key] = [body[key], val]; } - } else { + } + else { body[key] = val; } } @@ -234,25 +204,27 @@ async function assertThatBodyParserHasBeenUsedForExpressLikeRequest(method, requ if (typeof request.body === "string") { try { request.body = JSON.parse(request.body); - } catch (err) { + } + catch (err) { if (request.body === "") { request.body = {}; - } else { + } + else { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "API input error: Please make sure to pass a valid JSON input in the request body", }); } } - } else if ( - request.body === undefined || + } + else if (request.body === undefined || utils_1.isBuffer(request.body) || - (Object.keys(request.body).length === 0 && request.readable) - ) { + (Object.keys(request.body).length === 0 && request.readable)) { try { // parsing it again to make sure that the request is parsed atleast once by a json parser request.body = await parseJSONBodyFromRequest(request); - } catch (err) { + } + catch (err) { // If the error message matches the brotli decompression // related error, then throw that error. if (err.message === constants_1.BROTLI_DECOMPRESSION_ERROR_MESSAGE) { @@ -274,25 +246,27 @@ async function assertFormDataBodyParserHasBeenUsedForExpressLikeRequest(request) if (typeof request.body === "string") { try { request.body = Object.fromEntries(new URLSearchParams(request.body).entries()); - } catch (err) { + } + catch (err) { if (request.body === "") { request.body = {}; - } else { + } + else { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "API input error: Please make sure to pass valid url encoded form in the request body", }); } } - } else if ( - request.body === undefined || + } + else if (request.body === undefined || utils_1.isBuffer(request.body) || - (Object.keys(request.body).length === 0 && request.readable) - ) { + (Object.keys(request.body).length === 0 && request.readable)) { try { // parsing it again to make sure that the request is parsed atleast once by a form data parser request.body = await parseURLEncodedFormData(request); - } catch (_a) { + } + catch (_a) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "API input error: Please make sure to pass valid url encoded form in the request body", @@ -310,10 +284,12 @@ function setHeaderForExpressLikeResponse(res, key, value, allowDuplicateKey) { if (existingValue === undefined) { if (res.header !== undefined) { res.header(key, value); - } else { + } + else { res.setHeader(key, value); } - } else if (allowDuplicateKey) { + } + else if (allowDuplicateKey) { /** We only want to append if it does not already exist For example if the caller is trying to add front token to the access control exposed headers property @@ -322,27 +298,29 @@ function setHeaderForExpressLikeResponse(res, key, value, allowDuplicateKey) { if (typeof existingValue !== "string" || !existingValue.includes(value)) { if (res.header !== undefined) { res.header(key, existingValue + ", " + value); - } else { + } + else { res.setHeader(key, existingValue + ", " + value); } } - } else { + } + else { // we overwrite the current one with the new one if (res.header !== undefined) { res.header(key, value); - } else { + } + else { res.setHeader(key, value); } } - } catch (err) { - throw new Error( - "Error while setting header with key: " + - key + - " and value: " + - value + - "\nError: " + - ((_a = err.message) !== null && _a !== void 0 ? _a : err) - ); + } + catch (err) { + throw new Error("Error while setting header with key: " + + key + + " and value: " + + value + + "\nError: " + + ((_a = err.message) !== null && _a !== void 0 ? _a : err)); } } exports.setHeaderForExpressLikeResponse = setHeaderForExpressLikeResponse; @@ -358,12 +336,7 @@ exports.setHeaderForExpressLikeResponse = setHeaderForExpressLikeResponse; * @param path */ function setCookieForServerResponse(res, key, value, domain, secure, httpOnly, expires, path, sameSite) { - return appendToServerResponse( - res, - constants_1.COOKIE_HEADER, - serializeCookieValue(key, value, domain, secure, httpOnly, expires, path, sameSite), - key - ); + return appendToServerResponse(res, constants_1.COOKIE_HEADER, serializeCookieValue(key, value, domain, secure, httpOnly, expires, path, sameSite), key); } exports.setCookieForServerResponse = setCookieForServerResponse; /** @@ -395,7 +368,8 @@ function getCookieValueToSetInHeader(prev, val, key) { } } prev = removedDuplicate; - } else { + } + else { if (prev.startsWith(key)) { prev = undefined; } diff --git a/lib/build/index.d.ts b/lib/build/index.d.ts index 2bbc7017d..15330fcd6 100644 --- a/lib/build/index.d.ts +++ b/lib/build/index.d.ts @@ -2,7 +2,7 @@ import SuperTokens from "./supertokens"; import SuperTokensError from "./error"; import { UserContext, User as UserType } from "./types"; -import { AccountInfo } from "./recipe/accountlinking/types"; +import { AccountInfoInput } from "./recipe/accountlinking/types"; import RecipeUserId from "./recipeUserId"; import { User } from "./user"; export default class SuperTokensWrapper { @@ -11,11 +11,7 @@ export default class SuperTokensWrapper { static RecipeUserId: typeof RecipeUserId; static User: typeof User; static getAllCORSHeaders(): string[]; - static getUserCount( - includeRecipeIds?: string[], - tenantId?: string, - userContext?: Record - ): Promise; + static getUserCount(includeRecipeIds?: string[], tenantId?: string, userContext?: Record): Promise; static getUsersOldestFirst(input: { tenantId: string; limit?: number; @@ -48,31 +44,25 @@ export default class SuperTokensWrapper { externalUserIdInfo?: string; force?: boolean; userContext?: Record; - }): Promise< - | { - status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"; - } - | { - status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR"; - doesSuperTokensUserIdExist: boolean; - doesExternalUserIdExist: boolean; - } - >; + }): Promise<{ + status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"; + } | { + status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR"; + doesSuperTokensUserIdExist: boolean; + doesExternalUserIdExist: boolean; + }>; static getUserIdMapping(input: { userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"; userContext?: Record; - }): Promise< - | { - status: "OK"; - superTokensUserId: string; - externalUserId: string; - externalUserIdInfo: string | undefined; - } - | { - status: "UNKNOWN_MAPPING_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + superTokensUserId: string; + externalUserId: string; + externalUserIdInfo: string | undefined; + } | { + status: "UNKNOWN_MAPPING_ERROR"; + }>; static deleteUserIdMapping(input: { userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"; @@ -91,23 +81,12 @@ export default class SuperTokensWrapper { status: "OK" | "UNKNOWN_MAPPING_ERROR"; }>; static getUser(userId: string, userContext?: Record): Promise; - static listUsersByAccountInfo( - tenantId: string, - accountInfo: AccountInfo, - doUnionOfAccountInfo?: boolean, - userContext?: Record - ): Promise; - static deleteUser( - userId: string, - removeAllLinkedAccounts?: boolean, - userContext?: Record - ): Promise<{ + static listUsersByAccountInfo(tenantId: string, accountInfo: AccountInfoInput, doUnionOfAccountInfo?: boolean, userContext?: Record): Promise; + static deleteUser(userId: string, removeAllLinkedAccounts?: boolean, userContext?: Record): Promise<{ status: "OK"; }>; static convertToRecipeUserId(recipeUserId: string): RecipeUserId; - static getRequestFromUserContext( - userContext: UserContext | undefined - ): import("./framework").BaseRequest | undefined; + static getRequestFromUserContext(userContext: UserContext | undefined): import("./framework").BaseRequest | undefined; } export declare let init: typeof SuperTokens.init; export declare let getAllCORSHeaders: typeof SuperTokensWrapper.getAllCORSHeaders; diff --git a/lib/build/index.js b/lib/build/index.js index ecdd1b8b6..1f8bfe36f 100644 --- a/lib/build/index.js +++ b/lib/build/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.User = exports.RecipeUserId = exports.Error = exports.getRequestFromUserContext = exports.convertToRecipeUserId = exports.listUsersByAccountInfo = exports.getUser = exports.updateOrDeleteUserIdMappingInfo = exports.deleteUserIdMapping = exports.getUserIdMapping = exports.createUserIdMapping = exports.deleteUser = exports.getUsersNewestFirst = exports.getUsersOldestFirst = exports.getUserCount = exports.getAllCORSHeaders = exports.init = void 0; const supertokens_1 = __importDefault(require("./supertokens")); @@ -32,51 +30,25 @@ class SuperTokensWrapper { return supertokens_1.default.getInstanceOrThrowError().getAllCORSHeaders(); } static getUserCount(includeRecipeIds, tenantId, userContext) { - return supertokens_1.default - .getInstanceOrThrowError() - .getUserCount(includeRecipeIds, tenantId, utils_1.getUserContext(userContext)); + return supertokens_1.default.getInstanceOrThrowError().getUserCount(includeRecipeIds, tenantId, utils_1.getUserContext(userContext)); } static getUsersOldestFirst(input) { - return recipe_1.default.getInstance().recipeInterfaceImpl.getUsers( - Object.assign(Object.assign({ timeJoinedOrder: "ASC" }, input), { - userContext: utils_1.getUserContext(input.userContext), - }) - ); + return recipe_1.default.getInstance().recipeInterfaceImpl.getUsers(Object.assign(Object.assign({ timeJoinedOrder: "ASC" }, input), { userContext: utils_1.getUserContext(input.userContext) })); } static getUsersNewestFirst(input) { - return recipe_1.default.getInstance().recipeInterfaceImpl.getUsers( - Object.assign(Object.assign({ timeJoinedOrder: "DESC" }, input), { - userContext: utils_1.getUserContext(input.userContext), - }) - ); + return recipe_1.default.getInstance().recipeInterfaceImpl.getUsers(Object.assign(Object.assign({ timeJoinedOrder: "DESC" }, input), { userContext: utils_1.getUserContext(input.userContext) })); } static createUserIdMapping(input) { - return supertokens_1.default - .getInstanceOrThrowError() - .createUserIdMapping( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return supertokens_1.default.getInstanceOrThrowError().createUserIdMapping(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static getUserIdMapping(input) { - return supertokens_1.default - .getInstanceOrThrowError() - .getUserIdMapping( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return supertokens_1.default.getInstanceOrThrowError().getUserIdMapping(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static deleteUserIdMapping(input) { - return supertokens_1.default - .getInstanceOrThrowError() - .deleteUserIdMapping( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return supertokens_1.default.getInstanceOrThrowError().deleteUserIdMapping(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static updateOrDeleteUserIdMappingInfo(input) { - return supertokens_1.default - .getInstanceOrThrowError() - .updateOrDeleteUserIdMappingInfo( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return supertokens_1.default.getInstanceOrThrowError().updateOrDeleteUserIdMappingInfo(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static async getUser(userId, userContext) { return await recipe_1.default.getInstance().recipeInterfaceImpl.getUser({ @@ -127,16 +99,6 @@ exports.convertToRecipeUserId = SuperTokensWrapper.convertToRecipeUserId; exports.getRequestFromUserContext = SuperTokensWrapper.getRequestFromUserContext; exports.Error = SuperTokensWrapper.Error; var recipeUserId_2 = require("./recipeUserId"); -Object.defineProperty(exports, "RecipeUserId", { - enumerable: true, - get: function () { - return __importDefault(recipeUserId_2).default; - }, -}); +Object.defineProperty(exports, "RecipeUserId", { enumerable: true, get: function () { return __importDefault(recipeUserId_2).default; } }); var user_2 = require("./user"); -Object.defineProperty(exports, "User", { - enumerable: true, - get: function () { - return user_2.User; - }, -}); +Object.defineProperty(exports, "User", { enumerable: true, get: function () { return user_2.User; } }); diff --git a/lib/build/ingredients/emaildelivery/index.js b/lib/build/ingredients/emaildelivery/index.js index 958cd35a2..e728e628a 100644 --- a/lib/build/ingredients/emaildelivery/index.js +++ b/lib/build/ingredients/emaildelivery/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); class EmailDelivery { diff --git a/lib/build/ingredients/emaildelivery/services/smtp.d.ts b/lib/build/ingredients/emaildelivery/services/smtp.d.ts index a74be5624..a87b5fecb 100644 --- a/lib/build/ingredients/emaildelivery/services/smtp.d.ts +++ b/lib/build/ingredients/emaildelivery/services/smtp.d.ts @@ -23,11 +23,9 @@ export declare type TypeInputSendRawEmail = GetContentResult & { }; export declare type ServiceInterface = { sendRawEmail: (input: TypeInputSendRawEmail) => Promise; - getContent: ( - input: T & { - userContext: UserContext; - } - ) => Promise; + getContent: (input: T & { + userContext: UserContext; + }) => Promise; }; export declare type TypeInput = { smtpSettings: SMTPServiceConfig; diff --git a/lib/build/ingredients/emaildelivery/types.d.ts b/lib/build/ingredients/emaildelivery/types.d.ts index 2986807f5..bccfdf888 100644 --- a/lib/build/ingredients/emaildelivery/types.d.ts +++ b/lib/build/ingredients/emaildelivery/types.d.ts @@ -2,27 +2,19 @@ import OverrideableBuilder from "supertokens-js-override"; import { UserContext } from "../../types"; export declare type EmailDeliveryInterface = { - sendEmail: ( - input: T & { - tenantId: string; - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: T & { + tenantId: string; + userContext: UserContext; + }) => Promise; }; /** * config class parameter when parent Recipe create a new EmailDeliveryIngredient object via constructor */ export interface TypeInput { service?: EmailDeliveryInterface; - override?: ( - originalImplementation: EmailDeliveryInterface, - builder: OverrideableBuilder> - ) => EmailDeliveryInterface; + override?: (originalImplementation: EmailDeliveryInterface, builder: OverrideableBuilder>) => EmailDeliveryInterface; } export interface TypeInputWithService { service: EmailDeliveryInterface; - override?: ( - originalImplementation: EmailDeliveryInterface, - builder: OverrideableBuilder> - ) => EmailDeliveryInterface; + override?: (originalImplementation: EmailDeliveryInterface, builder: OverrideableBuilder>) => EmailDeliveryInterface; } diff --git a/lib/build/ingredients/smsdelivery/index.js b/lib/build/ingredients/smsdelivery/index.js index a52608dc0..6a0c5d4dc 100644 --- a/lib/build/ingredients/smsdelivery/index.js +++ b/lib/build/ingredients/smsdelivery/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); class SmsDelivery { diff --git a/lib/build/ingredients/smsdelivery/services/twilio.d.ts b/lib/build/ingredients/smsdelivery/services/twilio.d.ts index 03e22fb22..a2c850f48 100644 --- a/lib/build/ingredients/smsdelivery/services/twilio.d.ts +++ b/lib/build/ingredients/smsdelivery/services/twilio.d.ts @@ -10,40 +10,33 @@ import { UserContext } from "../../../types"; * if none of "from" and "messagingServiceSid" is passed, error * should be thrown. */ -export declare type TwilioServiceConfig = - | { - accountSid: string; - authToken: string; - from: string; - opts?: ClientOpts; - } - | { - accountSid: string; - authToken: string; - messagingServiceSid: string; - opts?: ClientOpts; - }; +export declare type TwilioServiceConfig = { + accountSid: string; + authToken: string; + from: string; + opts?: ClientOpts; +} | { + accountSid: string; + authToken: string; + messagingServiceSid: string; + opts?: ClientOpts; +}; export interface GetContentResult { body: string; toPhoneNumber: string; } export declare type TypeInputSendRawSms = GetContentResult & { userContext: UserContext; -} & ( - | { - from: string; - } - | { - messagingServiceSid: string; - } - ); +} & ({ + from: string; +} | { + messagingServiceSid: string; +}); export declare type ServiceInterface = { sendRawSms: (input: TypeInputSendRawSms) => Promise; - getContent: ( - input: T & { - userContext: UserContext; - } - ) => Promise; + getContent: (input: T & { + userContext: UserContext; + }) => Promise; }; export declare type TypeInput = { twilioSettings: TwilioServiceConfig; diff --git a/lib/build/ingredients/smsdelivery/services/twilio.js b/lib/build/ingredients/smsdelivery/services/twilio.js index 73876eb37..05b97a999 100644 --- a/lib/build/ingredients/smsdelivery/services/twilio.js +++ b/lib/build/ingredients/smsdelivery/services/twilio.js @@ -3,12 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.normaliseUserInputConfig = void 0; function normaliseUserInputConfig(input) { let from = "from" in input.twilioSettings ? input.twilioSettings.from : undefined; - let messagingServiceSid = - "messagingServiceSid" in input.twilioSettings ? input.twilioSettings.messagingServiceSid : undefined; - if ( - (from === undefined && messagingServiceSid === undefined) || - (from !== undefined && messagingServiceSid !== undefined) - ) { + let messagingServiceSid = "messagingServiceSid" in input.twilioSettings ? input.twilioSettings.messagingServiceSid : undefined; + if ((from === undefined && messagingServiceSid === undefined) || + (from !== undefined && messagingServiceSid !== undefined)) { throw Error(`Please pass exactly one of "from" and "messagingServiceSid" config for twilioSettings.`); } return input; diff --git a/lib/build/ingredients/smsdelivery/types.d.ts b/lib/build/ingredients/smsdelivery/types.d.ts index 781d1bfdf..9a4daf908 100644 --- a/lib/build/ingredients/smsdelivery/types.d.ts +++ b/lib/build/ingredients/smsdelivery/types.d.ts @@ -2,27 +2,19 @@ import OverrideableBuilder from "supertokens-js-override"; import { UserContext } from "../../types"; export declare type SmsDeliveryInterface = { - sendSms: ( - input: T & { - tenantId: string; - userContext: UserContext; - } - ) => Promise; + sendSms: (input: T & { + tenantId: string; + userContext: UserContext; + }) => Promise; }; /** * config class parameter when parent Recipe create a new SmsDeliveryIngredient object via constructor */ export interface TypeInput { service?: SmsDeliveryInterface; - override?: ( - originalImplementation: SmsDeliveryInterface, - builder: OverrideableBuilder> - ) => SmsDeliveryInterface; + override?: (originalImplementation: SmsDeliveryInterface, builder: OverrideableBuilder>) => SmsDeliveryInterface; } export interface TypeInputWithService { service: SmsDeliveryInterface; - override?: ( - originalImplementation: SmsDeliveryInterface, - builder: OverrideableBuilder> - ) => SmsDeliveryInterface; + override?: (originalImplementation: SmsDeliveryInterface, builder: OverrideableBuilder>) => SmsDeliveryInterface; } diff --git a/lib/build/logger.js b/lib/build/logger.js index 9b15f6d42..088fa6b12 100644 --- a/lib/build/logger.js +++ b/lib/build/logger.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.enableDebugLogs = exports.logDebugMessage = void 0; const debug_1 = __importDefault(require("debug")); @@ -29,11 +27,7 @@ const SUPERTOKENS_DEBUG_NAMESPACE = "com.supertokens"; */ function logDebugMessage(message) { if (debug_1.default.enabled(SUPERTOKENS_DEBUG_NAMESPACE)) { - debug_1.default(SUPERTOKENS_DEBUG_NAMESPACE)( - `{t: "${new Date().toISOString()}", message: \"${message}\", file: \"${getFileLocation()}\" sdkVer: "${ - version_1.version - }"}` - ); + debug_1.default(SUPERTOKENS_DEBUG_NAMESPACE)(`{t: "${new Date().toISOString()}", message: \"${message}\", file: \"${getFileLocation()}\" sdkVer: "${version_1.version}"}`); console.log(); } } diff --git a/lib/build/nextjs.d.ts b/lib/build/nextjs.d.ts index 18adb76ac..5d02daf1c 100644 --- a/lib/build/nextjs.d.ts +++ b/lib/build/nextjs.d.ts @@ -16,32 +16,18 @@ declare type PartialNextRequest = { }; }; export default class NextJS { - static superTokensNextWrapper( - middleware: (next: (middlewareError?: any) => void) => Promise, - request: any, - response: any - ): Promise; + static superTokensNextWrapper(middleware: (next: (middlewareError?: any) => void) => Promise, request: any, response: any): Promise; static getAppDirRequestHandler(): (req: Request) => Promise; - static getSSRSession( - cookies: Array<{ - name: string; - value: string; - }> - ): Promise<{ + static getSSRSession(cookies: Array<{ + name: string; + value: string; + }>): Promise<{ accessTokenPayload: JWTPayload | undefined; hasToken: boolean; error: Error | undefined; }>; - static withSession( - req: NextRequest, - handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, - options?: VerifySessionOptions, - userContext?: Record - ): Promise; - static withPreParsedRequestResponse( - req: NextRequest, - handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise - ): Promise; + static withSession(req: NextRequest, handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, options?: VerifySessionOptions, userContext?: Record): Promise; + static withPreParsedRequestResponse(req: NextRequest, handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise): Promise; } export declare let superTokensNextWrapper: typeof NextJS.superTokensNextWrapper; export declare let getAppDirRequestHandler: typeof NextJS.getAppDirRequestHandler; diff --git a/lib/build/nextjs.js b/lib/build/nextjs.js index 6ced5eff5..e3b5adfd1 100644 --- a/lib/build/nextjs.js +++ b/lib/build/nextjs.js @@ -42,7 +42,8 @@ class NextJS { if (!callbackCalled && !response.finished && !response.headersSent) { return resolve(result); } - } catch (err) { + } + catch (err) { await express_1.errorHandler()(err, request, response, (errorHandlerError) => { if (errorHandlerError !== undefined) { return reject(errorHandlerError); @@ -57,10 +58,7 @@ class NextJS { } static async getSSRSession(cookies) { var _a; - let accessToken = - (_a = cookies.find((cookie) => cookie.name === "sAccessToken")) === null || _a === void 0 - ? void 0 - : _a.value; + let accessToken = (_a = cookies.find((cookie) => cookie.name === "sAccessToken")) === null || _a === void 0 ? void 0 : _a.value; return await customFramework_1.getSessionForSSRUsingAccessToken(accessToken); } static async withSession(req, handler, options, userContext) { diff --git a/lib/build/normalisedURLDomain.js b/lib/build/normalisedURLDomain.js index 850678885..9562eda41 100644 --- a/lib/build/normalisedURLDomain.js +++ b/lib/build/normalisedURLDomain.js @@ -34,14 +34,17 @@ function normaliseURLDomainOrThrowError(input, ignoreProtocol = false) { if (ignoreProtocol) { if (urlObj.hostname.startsWith("localhost") || utils_1.isAnIpAddress(urlObj.hostname)) { input = "http://" + urlObj.host; - } else { + } + else { input = "https://" + urlObj.host; } - } else { + } + else { input = urlObj.protocol + "//" + urlObj.host; } return input; - } catch (err) {} + } + catch (err) { } // not a valid URL if (input.startsWith("/")) { throw Error("Please provide a valid domain name"); @@ -51,17 +54,16 @@ function normaliseURLDomainOrThrowError(input, ignoreProtocol = false) { } // If the input contains a . it means they have given a domain name. // So we try assuming that they have given a domain name - if ( - (input.indexOf(".") !== -1 || input.startsWith("localhost")) && + if ((input.indexOf(".") !== -1 || input.startsWith("localhost")) && !input.startsWith("http://") && - !input.startsWith("https://") - ) { + !input.startsWith("https://")) { input = "https://" + input; // at this point, it should be a valid URL. So we test that before doing a recursive call try { new URL(input); return normaliseURLDomainOrThrowError(input, true); - } catch (err) {} + } + catch (err) { } } throw Error("Please provide a valid domain name"); } diff --git a/lib/build/normalisedURLPath.js b/lib/build/normalisedURLPath.js index 64d1a7944..db02d9d5b 100644 --- a/lib/build/normalisedURLPath.js +++ b/lib/build/normalisedURLPath.js @@ -48,15 +48,14 @@ function normaliseURLPathOrThrowError(input) { return input.substr(0, input.length - 1); } return input; - } catch (err) {} + } + catch (err) { } // not a valid URL // If the input contains a . it means they have given a domain name. // So we try assuming that they have given a domain name + path - if ( - (domainGiven(input) || input.startsWith("localhost")) && + if ((domainGiven(input) || input.startsWith("localhost")) && !input.startsWith("http://") && - !input.startsWith("https://") - ) { + !input.startsWith("https://")) { input = "http://" + input; return normaliseURLPathOrThrowError(input); } @@ -68,7 +67,8 @@ function normaliseURLPathOrThrowError(input) { // test that we can convert this to prevent an infinite loop new URL("http://example.com" + input); return normaliseURLPathOrThrowError("http://example.com" + input); - } catch (err) { + } + catch (err) { throw Error("Please provide a valid URL path"); } } @@ -80,10 +80,12 @@ function domainGiven(input) { try { let url = new URL(input); return url.hostname.indexOf(".") !== -1; - } catch (ignored) {} + } + catch (ignored) { } try { let url = new URL("http://" + input); return url.hostname.indexOf(".") !== -1; - } catch (ignored) {} + } + catch (ignored) { } return false; } diff --git a/lib/build/processState.d.ts b/lib/build/processState.d.ts index 138e253bf..078848436 100644 --- a/lib/build/processState.d.ts +++ b/lib/build/processState.d.ts @@ -8,7 +8,7 @@ export declare enum PROCESS_STATE { IS_SIGN_UP_ALLOWED_CALLED = 5, IS_SIGN_IN_ALLOWED_CALLED = 6, IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED = 7, - ADDING_NO_CACHE_HEADER_IN_FETCH = 8, + ADDING_NO_CACHE_HEADER_IN_FETCH = 8 } export declare class ProcessState { history: PROCESS_STATE[]; diff --git a/lib/build/processState.js b/lib/build/processState.js index 9dd483422..414a8d74f 100644 --- a/lib/build/processState.js +++ b/lib/build/processState.js @@ -18,17 +18,16 @@ exports.ProcessState = exports.PROCESS_STATE = void 0; const utils_1 = require("./utils"); var PROCESS_STATE; (function (PROCESS_STATE) { - PROCESS_STATE[(PROCESS_STATE["CALLING_SERVICE_IN_VERIFY"] = 0)] = "CALLING_SERVICE_IN_VERIFY"; - PROCESS_STATE[(PROCESS_STATE["CALLING_SERVICE_IN_GET_API_VERSION"] = 1)] = "CALLING_SERVICE_IN_GET_API_VERSION"; - PROCESS_STATE[(PROCESS_STATE["CALLING_SERVICE_IN_REQUEST_HELPER"] = 2)] = "CALLING_SERVICE_IN_REQUEST_HELPER"; - PROCESS_STATE[(PROCESS_STATE["MULTI_JWKS_VALIDATION"] = 3)] = "MULTI_JWKS_VALIDATION"; - PROCESS_STATE[(PROCESS_STATE["IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS"] = 4)] = - "IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS"; - PROCESS_STATE[(PROCESS_STATE["IS_SIGN_UP_ALLOWED_CALLED"] = 5)] = "IS_SIGN_UP_ALLOWED_CALLED"; - PROCESS_STATE[(PROCESS_STATE["IS_SIGN_IN_ALLOWED_CALLED"] = 6)] = "IS_SIGN_IN_ALLOWED_CALLED"; - PROCESS_STATE[(PROCESS_STATE["IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED"] = 7)] = "IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED"; - PROCESS_STATE[(PROCESS_STATE["ADDING_NO_CACHE_HEADER_IN_FETCH"] = 8)] = "ADDING_NO_CACHE_HEADER_IN_FETCH"; -})((PROCESS_STATE = exports.PROCESS_STATE || (exports.PROCESS_STATE = {}))); + PROCESS_STATE[PROCESS_STATE["CALLING_SERVICE_IN_VERIFY"] = 0] = "CALLING_SERVICE_IN_VERIFY"; + PROCESS_STATE[PROCESS_STATE["CALLING_SERVICE_IN_GET_API_VERSION"] = 1] = "CALLING_SERVICE_IN_GET_API_VERSION"; + PROCESS_STATE[PROCESS_STATE["CALLING_SERVICE_IN_REQUEST_HELPER"] = 2] = "CALLING_SERVICE_IN_REQUEST_HELPER"; + PROCESS_STATE[PROCESS_STATE["MULTI_JWKS_VALIDATION"] = 3] = "MULTI_JWKS_VALIDATION"; + PROCESS_STATE[PROCESS_STATE["IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS"] = 4] = "IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS"; + PROCESS_STATE[PROCESS_STATE["IS_SIGN_UP_ALLOWED_CALLED"] = 5] = "IS_SIGN_UP_ALLOWED_CALLED"; + PROCESS_STATE[PROCESS_STATE["IS_SIGN_IN_ALLOWED_CALLED"] = 6] = "IS_SIGN_IN_ALLOWED_CALLED"; + PROCESS_STATE[PROCESS_STATE["IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED"] = 7] = "IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED"; + PROCESS_STATE[PROCESS_STATE["ADDING_NO_CACHE_HEADER_IN_FETCH"] = 8] = "ADDING_NO_CACHE_HEADER_IN_FETCH"; +})(PROCESS_STATE = exports.PROCESS_STATE || (exports.PROCESS_STATE = {})); class ProcessState { constructor() { this.history = []; @@ -57,10 +56,12 @@ class ProcessState { if (result === undefined) { if (Date.now() - startTime > timeInMS) { resolve(undefined); - } else { + } + else { setTimeout(tryAndGet, 1000); } - } else { + } + else { resolve(result); } } diff --git a/lib/build/querier.d.ts b/lib/build/querier.d.ts index 17d12763b..4b7f18d1c 100644 --- a/lib/build/querier.d.ts +++ b/lib/build/querier.d.ts @@ -20,42 +20,18 @@ export declare class Querier { static reset(): void; getHostsAliveForTesting: () => Set; static getNewInstanceOrThrowError(rIdToCore?: string): Querier; - static init( - hosts?: { - domain: NormalisedURLDomain; - basePath: NormalisedURLPath; - }[], - apiKey?: string, - networkInterceptor?: NetworkInterceptor, - disableCache?: boolean - ): void; + static init(hosts?: { + domain: NormalisedURLDomain; + basePath: NormalisedURLPath; + }[], apiKey?: string, networkInterceptor?: NetworkInterceptor, disableCache?: boolean): void; sendPostRequest: (path: NormalisedURLPath, body: any, userContext: UserContext) => Promise; - sendDeleteRequest: ( - path: NormalisedURLPath, - body: any, - params: any | undefined, - userContext: UserContext - ) => Promise; - sendGetRequest: ( - path: NormalisedURLPath, - params: Record, - userContext: UserContext - ) => Promise; - sendGetRequestWithResponseHeaders: ( - path: NormalisedURLPath, - params: Record, - inpHeaders: Record | undefined, - userContext: UserContext - ) => Promise<{ + sendDeleteRequest: (path: NormalisedURLPath, body: any, params: any | undefined, userContext: UserContext) => Promise; + sendGetRequest: (path: NormalisedURLPath, params: Record, userContext: UserContext) => Promise; + sendGetRequestWithResponseHeaders: (path: NormalisedURLPath, params: Record, inpHeaders: Record | undefined, userContext: UserContext) => Promise<{ body: any; headers: Headers; }>; - sendPutRequest: ( - path: NormalisedURLPath, - body: any, - params: Record, - userContext: UserContext - ) => Promise; + sendPutRequest: (path: NormalisedURLPath, body: any, params: Record, userContext: UserContext) => Promise; sendPatchRequest: (path: NormalisedURLPath, body: any, userContext: UserContext) => Promise; invalidateCoreCallCache: (userContext: UserContext, updGlobalCacheTagIfNecessary?: boolean) => void; getAllCoreUrlsForPath(path: string): string[]; diff --git a/lib/build/querier.js b/lib/build/querier.js index 9ee1f364f..7b59adc6f 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.Querier = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -36,9 +34,7 @@ class Querier { if (Querier.apiVersion !== undefined) { return Querier.apiVersion; } - processState_1.ProcessState.getInstance().addState( - processState_1.PROCESS_STATE.CALLING_SERVICE_IN_GET_API_VERSION - ); + processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.CALLING_SERVICE_IN_GET_API_VERSION); const st = supertokens_1.default.getInstanceOrThrowError(); const appInfo = st.appInfo; const request = st.getRequestFromUserContext(userContext); @@ -47,46 +43,33 @@ class Querier { websiteDomain: appInfo.getOrigin({ request, userContext }).getAsStringDangerous(), }; const queryParams = new URLSearchParams(queryParamsObj).toString(); - let { body: response } = await this.sendRequestHelper( - new normalisedURLPath_1.default("/apiversion"), - "GET", - async (url) => { - let headers = {}; - if (Querier.apiKey !== undefined) { - headers = { - "api-key": Querier.apiKey, - }; - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor( - { - url: url, - method: "get", - headers: headers, - params: queryParamsObj, - }, - userContext - ); - url = request.url; - headers = request.headers; - } - let response = await utils_1.doFetch(url + `?${queryParams}`, { - method: "GET", - headers, - }); - return response; - }, - ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 - ); + let { body: response } = await this.sendRequestHelper(new normalisedURLPath_1.default("/apiversion"), "GET", async (url) => { + let headers = {}; + if (Querier.apiKey !== undefined) { + headers = { + "api-key": Querier.apiKey, + }; + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor({ + url: url, + method: "get", + headers: headers, + params: queryParamsObj, + }, userContext); + url = request.url; + headers = request.headers; + } + let response = await utils_1.doFetch(url + `?${queryParams}`, { + method: "GET", + headers, + }); + return response; + }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); let cdiSupportedByServer = response.versions; - let supportedVersion = utils_1.getLargestVersionFromIntersection( - cdiSupportedByServer, - version_1.cdiSupported - ); + let supportedVersion = utils_1.getLargestVersionFromIntersection(cdiSupportedByServer, version_1.cdiSupported); if (supportedVersion === undefined) { - throw Error( - "The running SuperTokens core version is not compatible with this NodeJS SDK. Please visit https://supertokens.io/docs/community/compatibility to find the right versions" - ); + throw Error("The running SuperTokens core version is not compatible with this NodeJS SDK. Please visit https://supertokens.io/docs/community/compatibility to find the right versions"); } Querier.apiVersion = supportedVersion; return Querier.apiVersion; @@ -101,335 +84,258 @@ class Querier { this.sendPostRequest = async (path, body, userContext) => { var _a; this.invalidateCoreCallCache(userContext); - const { body: respBody } = await this.sendRequestHelper( - path, - "POST", - async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = { - "cdi-version": apiVersion, - }; - headers["content-type"] = "application/json; charset=utf-8"; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor( - { - url: url, - method: "post", - headers: headers, - body: body, - }, - userContext - ); - url = request.url; - headers = request.headers; - if (request.body !== undefined) { - body = request.body; - } + const { body: respBody } = await this.sendRequestHelper(path, "POST", async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = { + "cdi-version": apiVersion, + }; + headers["content-type"] = "application/json; charset=utf-8"; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor({ + url: url, + method: "post", + headers: headers, + body: body, + }, userContext); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; } - return utils_1.doFetch(url, { - method: "POST", - body: body !== undefined ? JSON.stringify(body) : undefined, - headers, - }); - }, - ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 - ); + } + return utils_1.doFetch(url, { + method: "POST", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); return respBody; }; // path should start with "/" this.sendDeleteRequest = async (path, body, params, userContext) => { var _a; this.invalidateCoreCallCache(userContext); - const { body: respBody } = await this.sendRequestHelper( - path, - "DELETE", - async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + const { body: respBody } = await this.sendRequestHelper(path, "DELETE", async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor({ + url: url, + method: "delete", + headers: headers, + params: params, + body: body, + }, userContext); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor( - { - url: url, - method: "delete", - headers: headers, - params: params, - body: body, - }, - userContext - ); - url = request.url; - headers = request.headers; - if (request.body !== undefined) { - body = request.body; - } - if (request.params !== undefined) { - params = request.params; - } + if (request.params !== undefined) { + params = request.params; } - const finalURL = new URL(url); - const searchParams = new URLSearchParams(params); - finalURL.search = searchParams.toString(); - return utils_1.doFetch(finalURL.toString(), { - method: "DELETE", - body: body !== undefined ? JSON.stringify(body) : undefined, - headers, - }); - }, - ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 - ); + } + const finalURL = new URL(url); + const searchParams = new URLSearchParams(params); + finalURL.search = searchParams.toString(); + return utils_1.doFetch(finalURL.toString(), { + method: "DELETE", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); return respBody; }; // path should start with "/" this.sendGetRequest = async (path, params, userContext) => { var _a; - const { body: respBody } = await this.sendRequestHelper( - path, - "GET", - async (url) => { - var _a, _b, _c, _d; - let apiVersion = await this.getAPIVersion(userContext); - let headers = { "cdi-version": apiVersion }; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - /* CACHE CHECK BEGIN */ - const sortedKeys = Object.keys(params).sort(); - const sortedHeaderKeys = Object.keys(headers).sort(); - let uniqueKey = path.getAsStringDangerous(); - for (const key of sortedKeys) { - const value = params[key]; - uniqueKey += `;${key}=${value}`; - } - uniqueKey += ";hdrs"; - for (const key of sortedHeaderKeys) { - const value = headers[key]; - uniqueKey += `;${key}=${value}`; - } - // If globalCacheTag doesn't match the current one (or if it's not defined), we invalidate the cache, because that means - // that there was a non-GET call that didn't have a proper userContext passed to it. - // However, we do not want to invalidate all global caches for a GET call even if it was made without a proper user context. - if ( - ((_a = userContext._default) === null || _a === void 0 ? void 0 : _a.globalCacheTag) !== - Querier.globalCacheTag - ) { - this.invalidateCoreCallCache(userContext, false); - } - if ( - !Querier.disableCache && - uniqueKey in - ((_c = - (_b = userContext._default) === null || _b === void 0 ? void 0 : _b.coreCallCache) !== - null && _c !== void 0 - ? _c - : {}) - ) { - return userContext._default.coreCallCache[uniqueKey]; - } - /* CACHE CHECK END */ - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor( - { - url: url, - method: "get", - headers: headers, - params: params, - }, - userContext - ); - url = request.url; - headers = request.headers; - if (request.params !== undefined) { - params = request.params; - } - } - const finalURL = new URL(url); - const searchParams = new URLSearchParams( - Object.entries(params).filter(([_, value]) => value !== undefined) - ); - finalURL.search = searchParams.toString(); - // Update cache and return - let response = await utils_1.doFetch(finalURL.toString(), { - method: "GET", - headers, - }); - if (response.status === 302) { - return response; - } - if (response.status === 200 && !Querier.disableCache) { - // If the request was successful, we save the result into the cache - // plus we update the cache tag - userContext._default = Object.assign(Object.assign({}, userContext._default), { - coreCallCache: Object.assign( - Object.assign( - {}, - (_d = userContext._default) === null || _d === void 0 ? void 0 : _d.coreCallCache - ), - { [uniqueKey]: response } - ), - globalCacheTag: Querier.globalCacheTag, - }); + const { body: respBody } = await this.sendRequestHelper(path, "GET", async (url) => { + var _a, _b, _c, _d; + let apiVersion = await this.getAPIVersion(userContext); + let headers = { "cdi-version": apiVersion }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + /* CACHE CHECK BEGIN */ + const sortedKeys = Object.keys(params).sort(); + const sortedHeaderKeys = Object.keys(headers).sort(); + let uniqueKey = path.getAsStringDangerous(); + for (const key of sortedKeys) { + const value = params[key]; + uniqueKey += `;${key}=${value}`; + } + uniqueKey += ";hdrs"; + for (const key of sortedHeaderKeys) { + const value = headers[key]; + uniqueKey += `;${key}=${value}`; + } + // If globalCacheTag doesn't match the current one (or if it's not defined), we invalidate the cache, because that means + // that there was a non-GET call that didn't have a proper userContext passed to it. + // However, we do not want to invalidate all global caches for a GET call even if it was made without a proper user context. + if (((_a = userContext._default) === null || _a === void 0 ? void 0 : _a.globalCacheTag) !== Querier.globalCacheTag) { + this.invalidateCoreCallCache(userContext, false); + } + if (!Querier.disableCache && uniqueKey in ((_c = (_b = userContext._default) === null || _b === void 0 ? void 0 : _b.coreCallCache) !== null && _c !== void 0 ? _c : {})) { + return userContext._default.coreCallCache[uniqueKey]; + } + /* CACHE CHECK END */ + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor({ + url: url, + method: "get", + headers: headers, + params: params, + }, userContext); + url = request.url; + headers = request.headers; + if (request.params !== undefined) { + params = request.params; } + } + const finalURL = new URL(url); + const searchParams = new URLSearchParams(Object.entries(params).filter(([_, value]) => value !== undefined)); + finalURL.search = searchParams.toString(); + // Update cache and return + let response = await utils_1.doFetch(finalURL.toString(), { + method: "GET", + headers, + }); + if (response.status === 302) { return response; - }, - ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 - ); + } + if (response.status === 200 && !Querier.disableCache) { + // If the request was successful, we save the result into the cache + // plus we update the cache tag + userContext._default = Object.assign(Object.assign({}, userContext._default), { coreCallCache: Object.assign(Object.assign({}, (_d = userContext._default) === null || _d === void 0 ? void 0 : _d.coreCallCache), { [uniqueKey]: response }), globalCacheTag: Querier.globalCacheTag }); + } + return response; + }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); return respBody; }; this.sendGetRequestWithResponseHeaders = async (path, params, inpHeaders, userContext) => { var _a; - return await this.sendRequestHelper( - path, - "GET", - async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = inpHeaders !== null && inpHeaders !== void 0 ? inpHeaders : {}; - headers["cdi-version"] = apiVersion; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor( - { - url: url, - method: "get", - headers: headers, - params: params, - }, - userContext - ); - url = request.url; - headers = request.headers; - if (request.params !== undefined) { - params = request.params; - } + return await this.sendRequestHelper(path, "GET", async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = inpHeaders !== null && inpHeaders !== void 0 ? inpHeaders : {}; + headers["cdi-version"] = apiVersion; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor({ + url: url, + method: "get", + headers: headers, + params: params, + }, userContext); + url = request.url; + headers = request.headers; + if (request.params !== undefined) { + params = request.params; } - const finalURL = new URL(url); - const searchParams = new URLSearchParams( - Object.entries(params).filter(([_, value]) => value !== undefined) - ); - finalURL.search = searchParams.toString(); - return utils_1.doFetch(finalURL.toString(), { - method: "GET", - headers, - }); - }, - ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 - ); + } + const finalURL = new URL(url); + const searchParams = new URLSearchParams(Object.entries(params).filter(([_, value]) => value !== undefined)); + finalURL.search = searchParams.toString(); + return utils_1.doFetch(finalURL.toString(), { + method: "GET", + headers, + }); + }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); }; // path should start with "/" this.sendPutRequest = async (path, body, params, userContext) => { var _a; this.invalidateCoreCallCache(userContext); - const { body: respBody } = await this.sendRequestHelper( - path, - "PUT", - async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor( - { - url: url, - method: "put", - headers: headers, - body: body, - params: params, - }, - userContext - ); - url = request.url; - headers = request.headers; - if (request.body !== undefined) { - body = request.body; - } + const { body: respBody } = await this.sendRequestHelper(path, "PUT", async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor({ + url: url, + method: "put", + headers: headers, + body: body, + params: params, + }, userContext); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; } - const finalURL = new URL(url); - const searchParams = new URLSearchParams( - Object.entries(params).filter(([_, value]) => value !== undefined) - ); - finalURL.search = searchParams.toString(); - return utils_1.doFetch(finalURL.toString(), { - method: "PUT", - body: body !== undefined ? JSON.stringify(body) : undefined, - headers, - }); - }, - ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 - ); + } + const finalURL = new URL(url); + const searchParams = new URLSearchParams(Object.entries(params).filter(([_, value]) => value !== undefined)); + finalURL.search = searchParams.toString(); + return utils_1.doFetch(finalURL.toString(), { + method: "PUT", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); return respBody; }; // path should start with "/" this.sendPatchRequest = async (path, body, userContext) => { var _a; this.invalidateCoreCallCache(userContext); - const { body: respBody } = await this.sendRequestHelper( - path, - "PATCH", - async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor( - { - url: url, - method: "patch", - headers: headers, - body: body, - }, - userContext - ); - url = request.url; - headers = request.headers; - if (request.body !== undefined) { - body = request.body; - } + const { body: respBody } = await this.sendRequestHelper(path, "PATCH", async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor({ + url: url, + method: "patch", + headers: headers, + body: body, + }, userContext); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; } - return utils_1.doFetch(url, { - method: "PATCH", - body: body !== undefined ? JSON.stringify(body) : undefined, - headers, - }); - }, - ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 - ); + } + return utils_1.doFetch(url, { + method: "PATCH", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); return respBody; }; this.invalidateCoreCallCache = (userContext, updGlobalCacheTagIfNecessary = true) => { var _a; - if ( - updGlobalCacheTagIfNecessary && - ((_a = userContext._default) === null || _a === void 0 ? void 0 : _a.keepCacheAlive) !== true - ) { + if (updGlobalCacheTagIfNecessary && ((_a = userContext._default) === null || _a === void 0 ? void 0 : _a.keepCacheAlive) !== true) { Querier.globalCacheTag = Date.now(); } userContext._default = Object.assign(Object.assign({}, userContext._default), { coreCallCache: {} }); @@ -438,9 +344,7 @@ class Querier { this.sendRequestHelper = async (path, method, requestFunc, numberOfTries, retryInfoMap) => { var _a; if (this.__hosts === undefined) { - throw Error( - "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." - ); + throw Error("No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using."); } if (numberOfTries === 0) { throw Error("No SuperTokens core available to query"); @@ -459,9 +363,7 @@ class Querier { Querier.lastTriedIndex++; Querier.lastTriedIndex = Querier.lastTriedIndex % this.__hosts.length; try { - processState_1.ProcessState.getInstance().addState( - processState_1.PROCESS_STATE.CALLING_SERVICE_IN_REQUEST_HELPER - ); + processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.CALLING_SERVICE_IN_REQUEST_HELPER); logger_1.logDebugMessage(`core-call: ${method} ${url}`); let response = await requestFunc(url); if (utils_1.isTestEnv()) { @@ -470,22 +372,17 @@ class Querier { if (response.status !== 200) { throw response; } - if ( - (_a = response.headers.get("content-type")) === null || _a === void 0 - ? void 0 - : _a.startsWith("text") - ) { + if ((_a = response.headers.get("content-type")) === null || _a === void 0 ? void 0 : _a.startsWith("text")) { return { body: await response.clone().text(), headers: response.headers }; } return { body: await response.clone().json(), headers: response.headers }; - } catch (err) { - if ( - err.message !== undefined && + } + catch (err) { + if (err.message !== undefined && (err.message.includes("Failed to fetch") || err.message.includes("fetch failed") || err.message.includes("ECONNREFUSED") || - err.code === "ECONNREFUSED") - ) { + err.code === "ECONNREFUSED")) { return this.sendRequestHelper(path, method, requestFunc, numberOfTries - 1, retryInfoMap); } if ("status" in err && "text" in err) { @@ -499,16 +396,14 @@ class Querier { return this.sendRequestHelper(path, method, requestFunc, numberOfTries, retryInfoMap); } } - throw new Error( - "SuperTokens core threw an error for a " + - method + - " request to path: '" + - path.getAsStringDangerous() + - "' with status code: " + - err.status + - " and message: " + - (await err.text()) - ); + throw new Error("SuperTokens core threw an error for a " + + method + + " request to path: '" + + path.getAsStringDangerous() + + "' with status code: " + + err.status + + " and message: " + + (await err.text())); } throw err; } diff --git a/lib/build/recipe/accountlinking/index.d.ts b/lib/build/recipe/accountlinking/index.d.ts index 6c8b57705..602f24222 100644 --- a/lib/build/recipe/accountlinking/index.d.ts +++ b/lib/build/recipe/accountlinking/index.d.ts @@ -14,12 +14,7 @@ export default class Wrapper { * same as the input recipeUserId if it was made into a primary user, or if there was * no linking that happened. */ - static createPrimaryUserIdOrLinkAccounts( - tenantId: string, - recipeUserId: RecipeUserId, - session?: SessionContainerInterface, - userContext?: Record - ): Promise; + static createPrimaryUserIdOrLinkAccounts(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainerInterface, userContext?: Record): Promise; /** * This function returns the primary user that the input recipe ID can be * linked to. It can be used to determine which primary account the linking @@ -29,121 +24,64 @@ export default class Wrapper { * that the input recipe ID can be linked to, and therefore it can be made * into a primary user itself. */ - static getPrimaryUserThatCanBeLinkedToRecipeUserId( - tenantId: string, - recipeUserId: RecipeUserId, - userContext?: Record - ): Promise; - static canCreatePrimaryUser( - recipeUserId: RecipeUserId, - userContext?: Record - ): Promise< - | { - status: "OK"; - wasAlreadyAPrimaryUser: boolean; - } - | { - status: - | "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" - | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } - >; - static createPrimaryUser( - recipeUserId: RecipeUserId, - userContext?: Record - ): Promise< - | { - status: "OK"; - user: import("../../types").User; - wasAlreadyAPrimaryUser: boolean; - } - | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - } - | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } - >; - static canLinkAccounts( - recipeUserId: RecipeUserId, - primaryUserId: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - accountsAlreadyLinked: boolean; - } - | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - description: string; - primaryUserId: string; - } - | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } - | { - status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; - } - >; - static linkAccounts( - recipeUserId: RecipeUserId, - primaryUserId: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - accountsAlreadyLinked: boolean; - user: import("../../types").User; - } - | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - user: import("../../types").User; - } - | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } - | { - status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; - } - >; - static unlinkAccount( - recipeUserId: RecipeUserId, - userContext?: Record - ): Promise<{ + static getPrimaryUserThatCanBeLinkedToRecipeUserId(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record): Promise; + static canCreatePrimaryUser(recipeUserId: RecipeUserId, userContext?: Record): Promise<{ + status: "OK"; + wasAlreadyAPrimaryUser: boolean; + } | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + }>; + static createPrimaryUser(recipeUserId: RecipeUserId, userContext?: Record): Promise<{ + status: "OK"; + user: import("../../types").User; + wasAlreadyAPrimaryUser: boolean; + } | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + } | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + }>; + static canLinkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record): Promise<{ + status: "OK"; + accountsAlreadyLinked: boolean; + } | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + description: string; + primaryUserId: string; + } | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } | { + status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; + }>; + static linkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record): Promise<{ + status: "OK"; + accountsAlreadyLinked: boolean; + user: import("../../types").User; + } | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + user: import("../../types").User; + } | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } | { + status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; + }>; + static unlinkAccount(recipeUserId: RecipeUserId, userContext?: Record): Promise<{ status: "OK"; wasRecipeUserDeleted: boolean; wasLinked: boolean; }>; - static isSignUpAllowed( - tenantId: string, - newUser: AccountInfoWithRecipeId, - isVerified: boolean, - session?: SessionContainerInterface, - userContext?: Record - ): Promise; - static isSignInAllowed( - tenantId: string, - recipeUserId: RecipeUserId, - session?: SessionContainerInterface, - userContext?: Record - ): Promise; - static isEmailChangeAllowed( - recipeUserId: RecipeUserId, - newEmail: string, - isVerified: boolean, - session?: SessionContainerInterface, - userContext?: Record - ): Promise; + static isSignUpAllowed(tenantId: string, newUser: AccountInfoWithRecipeId, isVerified: boolean, session?: SessionContainerInterface, userContext?: Record): Promise; + static isSignInAllowed(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainerInterface, userContext?: Record): Promise; + static isEmailChangeAllowed(recipeUserId: RecipeUserId, newEmail: string, isVerified: boolean, session?: SessionContainerInterface, userContext?: Record): Promise; } export declare const init: typeof Recipe.init; export declare const canCreatePrimaryUser: typeof Wrapper.canCreatePrimaryUser; diff --git a/lib/build/recipe/accountlinking/index.js b/lib/build/recipe/accountlinking/index.js index f65eeeaee..2f35585be 100644 --- a/lib/build/recipe/accountlinking/index.js +++ b/lib/build/recipe/accountlinking/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.isEmailChangeAllowed = exports.isSignInAllowed = exports.isSignUpAllowed = exports.getPrimaryUserThatCanBeLinkedToRecipeUserId = exports.createPrimaryUserIdOrLinkAccounts = exports.unlinkAccount = exports.linkAccounts = exports.canLinkAccounts = exports.createPrimaryUser = exports.canCreatePrimaryUser = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); diff --git a/lib/build/recipe/accountlinking/recipe.d.ts b/lib/build/recipe/accountlinking/recipe.d.ts index 261a2faa8..b2c56256d 100644 --- a/lib/build/recipe/accountlinking/recipe.d.ts +++ b/lib/build/recipe/accountlinking/recipe.d.ts @@ -13,54 +13,26 @@ export default class Recipe extends RecipeModule { static RECIPE_ID: string; config: TypeNormalisedInput; recipeInterfaceImpl: RecipeInterface; - constructor( - recipeId: string, - appInfo: NormalisedAppinfo, - config: TypeInput | undefined, - _recipes: {}, - _ingredients: {} - ); + constructor(recipeId: string, appInfo: NormalisedAppinfo, config: TypeInput | undefined, _recipes: {}, _ingredients: {}); static init(config?: TypeInput): RecipeListFunction; static getInstance(): Recipe; getAPIsHandled(): APIHandled[]; - handleAPIRequest( - _id: string, - _tenantId: string, - _req: BaseRequest, - _response: BaseResponse, - _path: normalisedURLPath, - _method: HTTPMethod - ): Promise; + handleAPIRequest(_id: string, _tenantId: string, _req: BaseRequest, _response: BaseResponse, _path: normalisedURLPath, _method: HTTPMethod): Promise; handleError(error: error, _request: BaseRequest, _response: BaseResponse): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; static reset(): void; - getPrimaryUserThatCanBeLinkedToRecipeUserId: ({ - tenantId, - user, - userContext, - }: { + getPrimaryUserThatCanBeLinkedToRecipeUserId: ({ tenantId, user, userContext, }: { tenantId: string; user: User; userContext: UserContext; }) => Promise; - getOldestUserThatCanBeLinkedToRecipeUser: ({ - tenantId, - user, - userContext, - }: { + getOldestUserThatCanBeLinkedToRecipeUser: ({ tenantId, user, userContext, }: { tenantId: string; user: User; userContext: UserContext; }) => Promise; - isSignInAllowed: ({ - user, - accountInfo, - tenantId, - session, - signInVerifiesLoginMethod, - userContext, - }: { + isSignInAllowed: ({ user, accountInfo, tenantId, session, signInVerifiesLoginMethod, userContext, }: { user: User; accountInfo: AccountInfoWithRecipeId | LoginMethod; session: SessionContainerInterface | undefined; @@ -68,28 +40,14 @@ export default class Recipe extends RecipeModule { tenantId: string; userContext: UserContext; }) => Promise; - isSignUpAllowed: ({ - newUser, - isVerified, - session, - tenantId, - userContext, - }: { + isSignUpAllowed: ({ newUser, isVerified, session, tenantId, userContext, }: { newUser: AccountInfoWithRecipeId; isVerified: boolean; session: SessionContainerInterface | undefined; tenantId: string; userContext: UserContext; }) => Promise; - isSignInUpAllowedHelper: ({ - accountInfo, - isVerified, - session, - tenantId, - isSignIn, - user, - userContext, - }: { + isSignInUpAllowedHelper: ({ accountInfo, isVerified, session, tenantId, isSignIn, user, userContext, }: { accountInfo: AccountInfoWithRecipeId | LoginMethod; isVerified: boolean; session: SessionContainerInterface | undefined; @@ -104,43 +62,27 @@ export default class Recipe extends RecipeModule { isVerified: boolean; session: SessionContainerInterface | undefined; userContext: UserContext; - }) => Promise< - | { - allowed: true; - } - | { - allowed: false; - reason: "PRIMARY_USER_CONFLICT" | "ACCOUNT_TAKEOVER_RISK"; - } - >; + }) => Promise<{ + allowed: true; + } | { + allowed: false; + reason: "PRIMARY_USER_CONFLICT" | "ACCOUNT_TAKEOVER_RISK"; + }>; verifyEmailForRecipeUserIfLinkedAccountsAreVerified: (input: { user: User; recipeUserId: RecipeUserId; userContext: UserContext; }) => Promise; - shouldBecomePrimaryUser( - user: User, - tenantId: string, - session: SessionContainerInterface | undefined, - userContext: UserContext - ): Promise; - tryLinkingByAccountInfoOrCreatePrimaryUser({ - inputUser, - session, - tenantId, - userContext, - }: { + shouldBecomePrimaryUser(user: User, tenantId: string, session: SessionContainerInterface | undefined, userContext: UserContext): Promise; + tryLinkingByAccountInfoOrCreatePrimaryUser({ inputUser, session, tenantId, userContext, }: { tenantId: string; inputUser: User; session: SessionContainerInterface | undefined; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - } - | { - status: "NO_LINK"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + } | { + status: "NO_LINK"; + }>; } diff --git a/lib/build/recipe/accountlinking/recipe.js b/lib/build/recipe/accountlinking/recipe.js index a5faf204f..141f665eb 100644 --- a/lib/build/recipe/accountlinking/recipe.js +++ b/lib/build/recipe/accountlinking/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const utils_1 = require("./utils"); @@ -33,25 +31,23 @@ const utils_2 = require("../../utils"); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, config, _recipes, _ingredients) { super(recipeId, appInfo); - this.getPrimaryUserThatCanBeLinkedToRecipeUserId = async ({ tenantId, user, userContext }) => { + this.getPrimaryUserThatCanBeLinkedToRecipeUserId = async ({ tenantId, user, userContext, }) => { // first we check if this user itself is a primary user or not. If it is, we return that. if (user.isPrimaryUser) { return user; } - // then, we try and find a primary user based on the email / phone number / third party ID. + // then, we try and find a primary user based on the email / phone number / third party ID / credentialId. let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo: user.loginMethods[0], + accountInfo: Object.assign(Object.assign({}, user.loginMethods[0]), { + // omit webauthn so we have the correct type + webauthn: undefined }), doUnionOfAccountInfo: true, userContext, }); - logger_1.logDebugMessage( - `getPrimaryUserThatCanBeLinkedToRecipeUserId found ${users.length} matching users` - ); + logger_1.logDebugMessage(`getPrimaryUserThatCanBeLinkedToRecipeUserId found ${users.length} matching users`); let pUsers = users.filter((u) => u.isPrimaryUser); - logger_1.logDebugMessage( - `getPrimaryUserThatCanBeLinkedToRecipeUserId found ${pUsers.length} matching primary users` - ); + logger_1.logDebugMessage(`getPrimaryUserThatCanBeLinkedToRecipeUserId found ${pUsers.length} matching primary users`); if (pUsers.length > 1) { // this means that the new user has account info such that it's // spread across multiple primary user IDs. In this case, even @@ -79,7 +75,7 @@ class Recipe extends recipeModule_1.default { } return pUsers.length === 0 ? undefined : pUsers[0]; }; - this.getOldestUserThatCanBeLinkedToRecipeUser = async ({ tenantId, user, userContext }) => { + this.getOldestUserThatCanBeLinkedToRecipeUser = async ({ tenantId, user, userContext, }) => { // first we check if this user itself is a primary user or not. If it is, we return that since it cannot be linked to anything else if (user.isPrimaryUser) { return user; @@ -87,26 +83,20 @@ class Recipe extends recipeModule_1.default { // then, we try and find matching users based on the email / phone number / third party ID. let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo: user.loginMethods[0], + accountInfo: Object.assign(Object.assign({}, user.loginMethods[0]), { + // omit webauthn so we have the correct type + webauthn: undefined }), doUnionOfAccountInfo: true, userContext, }); logger_1.logDebugMessage(`getOldestUserThatCanBeLinkedToRecipeUser found ${users.length} matching users`); // finally select the oldest one - const oldestUser = - users.length === 0 - ? undefined - : users.reduce((min, curr) => (min.timeJoined < curr.timeJoined ? min : curr)); + const oldestUser = users.length === 0 + ? undefined + : users.reduce((min, curr) => (min.timeJoined < curr.timeJoined ? min : curr)); return oldestUser; }; - this.isSignInAllowed = async ({ - user, - accountInfo, - tenantId, - session, - signInVerifiesLoginMethod, - userContext, - }) => { + this.isSignInAllowed = async ({ user, accountInfo, tenantId, session, signInVerifiesLoginMethod, userContext, }) => { processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.IS_SIGN_IN_ALLOWED_CALLED); if (user.isPrimaryUser || user.loginMethods[0].verified || signInVerifiesLoginMethod) { return true; @@ -121,7 +111,7 @@ class Recipe extends recipeModule_1.default { userContext, }); }; - this.isSignUpAllowed = async ({ newUser, isVerified, session, tenantId, userContext }) => { + this.isSignUpAllowed = async ({ newUser, isVerified, session, tenantId, userContext, }) => { processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.IS_SIGN_UP_ALLOWED_CALLED); if (newUser.email !== undefined && newUser.phoneNumber !== undefined) { // we do this check cause below when we call listUsersByAccountInfo, @@ -138,18 +128,8 @@ class Recipe extends recipeModule_1.default { isSignIn: false, }); }; - this.isSignInUpAllowedHelper = async ({ - accountInfo, - isVerified, - session, - tenantId, - isSignIn, - user, - userContext, - }) => { - processState_1.ProcessState.getInstance().addState( - processState_1.PROCESS_STATE.IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED - ); + this.isSignInUpAllowedHelper = async ({ accountInfo, isVerified, session, tenantId, isSignIn, user, userContext, }) => { + processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED); // since this is a recipe level user, we have to do the following checks // before allowing sign in. We do these checks cause sign in also attempts // account linking: @@ -170,28 +150,25 @@ class Recipe extends recipeModule_1.default { // we do not pass in third party info, or both email or phone // cause we want to guarantee that the output array contains just one // primary user. + // map according to the correct types here so we don't have to change the types upstream let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo, + accountInfo: Object.assign(Object.assign({}, accountInfo), { + // omit webauthn so we have the correct type + webauthn: undefined }), doUnionOfAccountInfo: true, userContext, }); if (users.length === 0) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning true because no user with given account info" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because no user with given account info"); // this is a brand new email / phone number, so we allow sign up. return true; } if (isSignIn && user === undefined) { - throw new Error( - "This should never happen: isSignInUpAllowedHelper called with isSignIn: true, user: undefined" - ); + throw new Error("This should never happen: isSignInUpAllowedHelper called with isSignIn: true, user: undefined"); } if (users.length === 1 && isSignIn && user !== undefined && users[0].id === user.id) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning true because this is sign in and there is only a single user with the given account info" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because this is sign in and there is only a single user with the given account info"); return true; } // now we check if there exists some primary user with the same email / phone number @@ -212,23 +189,13 @@ class Recipe extends recipeModule_1.default { // account which is unverified sends an email verification email, the legit user might // click on the link and that account (which was unverified and could have been controlled // by an attacker), will end up getting linked to this account. - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - accountInfo, - undefined, - session, - tenantId, - userContext - ); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(accountInfo, undefined, session, tenantId, userContext); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning true because account linking is disabled" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because account linking is disabled"); return true; } if (!shouldDoAccountLinking.shouldRequireVerification) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning true because dec does not require email verification" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because dec does not require email verification"); // the dev says they do not care about verification before account linking // so we are OK with the risk mentioned above. return true; @@ -244,24 +211,16 @@ class Recipe extends recipeModule_1.default { } let thisIterationIsVerified = false; if (accountInfo.email !== undefined) { - if ( - currUser.loginMethods[0].hasSameEmailAs(accountInfo.email) && - currUser.loginMethods[0].verified - ) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper found same email for another user and verified" - ); + if (currUser.loginMethods[0].hasSameEmailAs(accountInfo.email) && + currUser.loginMethods[0].verified) { + logger_1.logDebugMessage("isSignInUpAllowedHelper found same email for another user and verified"); thisIterationIsVerified = true; } } if (accountInfo.phoneNumber !== undefined) { - if ( - currUser.loginMethods[0].hasSamePhoneNumberAs(accountInfo.phoneNumber) && - currUser.loginMethods[0].verified - ) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper found same phone number for another user and verified" - ); + if (currUser.loginMethods[0].hasSamePhoneNumberAs(accountInfo.phoneNumber) && + currUser.loginMethods[0].verified) { + logger_1.logDebugMessage("isSignInUpAllowedHelper found same phone number for another user and verified"); thisIterationIsVerified = true; } } @@ -272,52 +231,35 @@ class Recipe extends recipeModule_1.default { // users will just see an email already exists error and then will try another // login method. They can also still just go through the password reset flow // and then gain access to their email password account (which can then be verified). - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning false cause one of the other recipe level users is not verified" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning false cause one of the other recipe level users is not verified"); shouldAllow = false; break; } } - processState_1.ProcessState.getInstance().addState( - processState_1.PROCESS_STATE.IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS - ); + processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS); logger_1.logDebugMessage("isSignInUpAllowedHelper returning " + shouldAllow); return shouldAllow; - } else { + } + else { if (primaryUsers.length > 1) { - throw new Error( - "You have found a bug. Please report to https://github.com/supertokens/supertokens-node/issues" - ); + throw new Error("You have found a bug. Please report to https://github.com/supertokens/supertokens-node/issues"); } let primaryUser = primaryUsers[0]; logger_1.logDebugMessage("isSignInUpAllowedHelper primary user found"); - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - accountInfo, - primaryUser, - session, - tenantId, - userContext - ); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(accountInfo, primaryUser, session, tenantId, userContext); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning true because account linking is disabled" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because account linking is disabled"); return true; } if (!shouldDoAccountLinking.shouldRequireVerification) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning true because dec does not require email verification" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because dec does not require email verification"); // the dev says they do not care about verification before account linking // so we can link this new user to the primary user post recipe user creation // even if that user's email / phone number is not verified. return true; } if (!isVerified) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning false because new user's email is not verified, and primary user with the same email was found." - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning false because new user's email is not verified, and primary user with the same email was found."); // this will exist early with a false here cause it means that // if we come here, the newUser will be linked to the primary user post email // verification. Whilst this seems OK, there is a risk that the actual user might @@ -348,24 +290,19 @@ class Recipe extends recipeModule_1.default { let lM = primaryUser.loginMethods[i]; if (lM.email !== undefined) { if (lM.hasSameEmailAs(accountInfo.email) && lM.verified) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning true cause found same email for primary user and verified" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning true cause found same email for primary user and verified"); return true; } } if (lM.phoneNumber !== undefined) { if (lM.hasSamePhoneNumberAs(accountInfo.phoneNumber) && lM.verified) { - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning true cause found same phone number for primary user and verified" - ); + logger_1.logDebugMessage("isSignInUpAllowedHelper returning true cause found same phone number for primary user and verified"); return true; } } } - logger_1.logDebugMessage( - "isSignInUpAllowedHelper returning false cause primary user does not have the same email or phone number that is verified" - //"isSignInUpAllowedHelper returning false because new user's email is not verified, and primary user with the same email was found." + logger_1.logDebugMessage("isSignInUpAllowedHelper returning false cause primary user does not have the same email or phone number that is verified" + //"isSignInUpAllowedHelper returning false because new user's email is not verified, and primary user with the same email was found." ); return false; } @@ -403,105 +340,67 @@ class Recipe extends recipeModule_1.default { if (inputUser.isPrimaryUser) { // this is condition one from the above comment. if (otherPrimaryUserForNewEmail.length !== 0) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: returning false cause email change will lead to two primary users having same email on ${tenantId}` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: returning false cause email change will lead to two primary users having same email on ${tenantId}`); return { allowed: false, reason: "PRIMARY_USER_CONFLICT" }; } if (input.isVerified) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified and doesn't belong to any other primary user` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified and doesn't belong to any other primary user`); continue; } if (inputUser.loginMethods.some((lm) => lm.hasSameEmailAs(input.newEmail) && lm.verified)) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified in another login method and doesn't belong to any other primary user` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified in another login method and doesn't belong to any other primary user`); continue; } if (otherUsersWithNewEmail.length === 0) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary and the new email doesn't belong to any other user (primary or non-primary)` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is primary and the new email doesn't belong to any other user (primary or non-primary)`); continue; } - const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - otherUsersWithNewEmail[0].loginMethods[0], - inputUser, - input.session, - tenantId, - input.userContext - ); + const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(otherUsersWithNewEmail[0].loginMethods[0], inputUser, input.session, tenantId, input.userContext); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause linking is disabled` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause linking is disabled`); continue; } if (!shouldDoAccountLinking.shouldRequireVerification) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause linking is doesn't require email verification` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause linking is doesn't require email verification`); continue; } - logger_1.logDebugMessage( - `isEmailChangeAllowed: returning false because the user hasn't verified the new email address and there exists another user with it on ${tenantId} and linking requires verification` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: returning false because the user hasn't verified the new email address and there exists another user with it on ${tenantId} and linking requires verification`); return { allowed: false, reason: "ACCOUNT_TAKEOVER_RISK" }; - } else { + } + else { if (input.isVerified) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is verified` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is verified`); continue; } if (inputUser.loginMethods[0].hasSameEmailAs(input.newEmail)) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is same as the older one` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is same as the older one`); continue; } if (otherPrimaryUserForNewEmail.length === 1) { - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - inputUser.loginMethods[0], - otherPrimaryUserForNewEmail[0], - input.session, - tenantId, - input.userContext - ); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(inputUser.loginMethods[0], otherPrimaryUserForNewEmail[0], input.session, tenantId, input.userContext); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary there exists a primary user exists with the new email, but the dev does not have account linking enabled.` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary there exists a primary user exists with the new email, but the dev does not have account linking enabled.`); continue; } if (!shouldDoAccountLinking.shouldRequireVerification) { - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary there exists a primary user exists with the new email, but the dev does not require email verification.` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary there exists a primary user exists with the new email, but the dev does not require email verification.`); continue; } - logger_1.logDebugMessage( - "isEmailChangeAllowed: returning false cause input user is not a primary there exists a primary user exists with the new email." - ); + logger_1.logDebugMessage("isEmailChangeAllowed: returning false cause input user is not a primary there exists a primary user exists with the new email."); return { allowed: false, reason: "ACCOUNT_TAKEOVER_RISK" }; } - logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary no primary user exists with the new email` - ); + logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary no primary user exists with the new email`); continue; } } - logger_1.logDebugMessage( - "isEmailChangeAllowed: returning true cause email change can happen on all tenants the user is part of" - ); + logger_1.logDebugMessage("isEmailChangeAllowed: returning true cause email change can happen on all tenants the user is part of"); return { allowed: true }; }; this.verifyEmailForRecipeUserIfLinkedAccountsAreVerified = async (input) => { try { recipe_1.default.getInstanceOrThrowError(); - } catch (ignored) { + } + catch (ignored) { // if email verification recipe is not initialized, we do a no-op return; } @@ -534,17 +433,15 @@ class Recipe extends recipeModule_1.default { } }); if (shouldVerifyEmail) { - let resp = await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.createEmailVerificationToken({ - // While the token we create here is tenant specific, the verification status is not - // So we can use any tenantId the user is associated with here as long as we use the - // same in the verifyEmailUsingToken call - tenantId: input.user.tenantIds[0], - recipeUserId: input.recipeUserId, - email: recipeUserEmail, - userContext: input.userContext, - }); + let resp = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createEmailVerificationToken({ + // While the token we create here is tenant specific, the verification status is not + // So we can use any tenantId the user is associated with here as long as we use the + // same in the verifyEmailUsingToken call + tenantId: input.user.tenantIds[0], + recipeUserId: input.recipeUserId, + email: recipeUserEmail, + userContext: input.userContext, + }); if (resp.status === "OK") { // we purposely pass in false below cause we don't want account // linking to happen @@ -562,30 +459,19 @@ class Recipe extends recipeModule_1.default { }; this.config = utils_1.validateAndNormaliseUserInput(appInfo, config); { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default( - querier_1.Querier.getNewInstanceOrThrowError(recipeId), - this.config, - this - ) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, this)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } } static init(config) { return (appInfo) => { if (Recipe.instance === undefined) { - Recipe.instance = new Recipe( - Recipe.RECIPE_ID, - appInfo, - config, - {}, - { - emailDelivery: undefined, - } - ); + Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, config, {}, { + emailDelivery: undefined, + }); return Recipe.instance; - } else { + } + else { throw new Error("AccountLinking recipe has already been initialised. Please check your code for bugs."); } }; @@ -597,10 +483,7 @@ class Recipe extends recipeModule_1.default { // is not in the recipeList). static getInstance() { if (Recipe.instance === undefined) { - Recipe.init()( - supertokens_1.default.getInstanceOrThrowError().appInfo, - supertokens_1.default.getInstanceOrThrowError().isInServerlessEnv - ); + Recipe.init()(supertokens_1.default.getInstanceOrThrowError().appInfo, supertokens_1.default.getInstanceOrThrowError().isInServerlessEnv); } return Recipe.instance; } @@ -630,29 +513,19 @@ class Recipe extends recipeModule_1.default { Recipe.instance = undefined; } async shouldBecomePrimaryUser(user, tenantId, session, userContext) { - const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - user.loginMethods[0], - undefined, - session, - tenantId, - userContext - ); + const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(user.loginMethods[0], undefined, session, tenantId, userContext); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - "shouldBecomePrimaryUser returning false because shouldAutomaticallyLink is false" - ); + logger_1.logDebugMessage("shouldBecomePrimaryUser returning false because shouldAutomaticallyLink is false"); return false; } if (shouldDoAccountLinking.shouldRequireVerification && !user.loginMethods[0].verified) { - logger_1.logDebugMessage( - "shouldBecomePrimaryUser returning false because shouldRequireVerification is true but the login method is not verified" - ); + logger_1.logDebugMessage("shouldBecomePrimaryUser returning false because shouldRequireVerification is true but the login method is not verified"); return false; } logger_1.logDebugMessage("shouldBecomePrimaryUser returning true"); return true; } - async tryLinkingByAccountInfoOrCreatePrimaryUser({ inputUser, session, tenantId, userContext }) { + async tryLinkingByAccountInfoOrCreatePrimaryUser({ inputUser, session, tenantId, userContext, }) { let tries = 0; while (tries++ < 100) { const primaryUserThatCanBeLinkedToTheInputUser = await this.getPrimaryUserThatCanBeLinkedToRecipeUserId({ @@ -661,47 +534,26 @@ class Recipe extends recipeModule_1.default { userContext, }); if (primaryUserThatCanBeLinkedToTheInputUser !== undefined) { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: got primary user we can try linking" - ); + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: got primary user we can try linking"); // we check if the inputUser and primaryUserThatCanBeLinkedToTheInputUser are linked based on recipeIds because the inputUser obj could be outdated - if ( - !primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some( - (lm) => lm.recipeUserId.getAsString() === inputUser.loginMethods[0].recipeUserId.getAsString() - ) - ) { + if (!primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some((lm) => lm.recipeUserId.getAsString() === inputUser.loginMethods[0].recipeUserId.getAsString())) { // If we got a primary user that can be linked to the input user and they are is not linked, we try to link them. // The input user in this case cannot be linked to anything else, otherwise multiple primary users would have the same email // we can use the 0 index cause targetUser is not a primary user. - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - inputUser.loginMethods[0], - primaryUserThatCanBeLinkedToTheInputUser, - session, - tenantId, - userContext - ); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(inputUser.loginMethods[0], primaryUserThatCanBeLinkedToTheInputUser, session, tenantId, userContext); // We already checked if factor setup is allowed by this point, but maybe we should check again? if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldAutomaticallyLink is false" - ); + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldAutomaticallyLink is false"); return { status: "NO_LINK" }; } - const accountInfoVerifiedInPrimUser = primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some( - (lm) => - (inputUser.loginMethods[0].email !== undefined && - lm.hasSameEmailAs(inputUser.loginMethods[0].email)) || - (inputUser.loginMethods[0].phoneNumber !== undefined && - lm.hasSamePhoneNumberAs(inputUser.loginMethods[0].phoneNumber) && - lm.verified) - ); - if ( - shouldDoAccountLinking.shouldRequireVerification && - (!inputUser.loginMethods[0].verified || !accountInfoVerifiedInPrimUser) - ) { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified in the new or the primary user" - ); + const accountInfoVerifiedInPrimUser = primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some((lm) => (inputUser.loginMethods[0].email !== undefined && + lm.hasSameEmailAs(inputUser.loginMethods[0].email)) || + (inputUser.loginMethods[0].phoneNumber !== undefined && + lm.hasSamePhoneNumberAs(inputUser.loginMethods[0].phoneNumber) && + lm.verified)); + if (shouldDoAccountLinking.shouldRequireVerification && + (!inputUser.loginMethods[0].verified || !accountInfoVerifiedInPrimUser)) { + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified in the new or the primary user"); return { status: "NO_LINK" }; } logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking"); @@ -713,34 +565,29 @@ class Recipe extends recipeModule_1.default { if (linkAccountsResult.status === "OK") { logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser successfully linked"); return { status: "OK", user: linkAccountsResult.user }; - } else if ( - linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" - ) { + } + else if (linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { // this can happen cause of a race condition wherein the recipe user ID get's linked to // some other primary user whilst this function is running. // We can return this directly, because: // 1. a retry would result in the same // 2. the tryLinkingByAccountInfoOrCreatePrimaryUser doesn't specify where it should be linked // and we can't linked it to anything here after it became primary/linked to another primary user - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser already linked to another user" - ); + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser already linked to another user"); return { status: "OK", user: linkAccountsResult.user, }; - } else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition" - ); + } + else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition"); // this can be possible during a race condition wherein the primary user // that we fetched somehow is no more a primary user. This can happen if // the unlink function was called in parallel on that user. So we can just retry continue; - } else { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition" - ); + } + else { + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition"); // status is "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" // it can come here if the recipe user ID can't be linked to the primary user ID cause // the email / phone number is associated with some other primary user ID. @@ -761,20 +608,11 @@ class Recipe extends recipeModule_1.default { tenantId, userContext, }); - if ( - oldestUserThatCanBeLinkedToTheInputUser !== undefined && - oldestUserThatCanBeLinkedToTheInputUser.id !== inputUser.id - ) { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: got an older user we can try linking" - ); + if (oldestUserThatCanBeLinkedToTheInputUser !== undefined && + oldestUserThatCanBeLinkedToTheInputUser.id !== inputUser.id) { + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: got an older user we can try linking"); // We know that the older user isn't primary, otherwise we'd hit the branch above. - let shouldMakeOlderUserPrimary = await this.shouldBecomePrimaryUser( - oldestUserThatCanBeLinkedToTheInputUser, - tenantId, - session, - userContext - ); + let shouldMakeOlderUserPrimary = await this.shouldBecomePrimaryUser(oldestUserThatCanBeLinkedToTheInputUser, tenantId, session, userContext); // if the app doesn't want to make the older user primary, we can't link to it // so we fall back to trying the newer user primary (and not linking) if (shouldMakeOlderUserPrimary) { @@ -782,38 +620,24 @@ class Recipe extends recipeModule_1.default { recipeUserId: oldestUserThatCanBeLinkedToTheInputUser.loginMethods[0].recipeUserId, userContext, }); - if ( - createPrimaryUserResult.status === - "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" || - createPrimaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" - ) { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: retrying because createPrimaryUser returned " + - createPrimaryUserResult.status - ); + if (createPrimaryUserResult.status === + "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" || + createPrimaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR") { + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: retrying because createPrimaryUser returned " + + createPrimaryUserResult.status); continue; } // If we got a primary user that can be linked to the input user and they are is not linked, we try to link them. // The input user in this case cannot be linked to anything else, otherwise multiple primary users would have the same email // we can use the 0 index cause targetUser is not a primary user. - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - inputUser.loginMethods[0], - createPrimaryUserResult.user, - session, - tenantId, - userContext - ); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(inputUser.loginMethods[0], createPrimaryUserResult.user, session, tenantId, userContext); // We already checked if factor setup is allowed by this point, but maybe we should check again? if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldAutomaticallyLink is false" - ); + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldAutomaticallyLink is false"); return { status: "NO_LINK" }; } if (shouldDoAccountLinking.shouldRequireVerification && !inputUser.loginMethods[0].verified) { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified" - ); + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified"); return { status: "NO_LINK" }; } logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking"); @@ -825,34 +649,29 @@ class Recipe extends recipeModule_1.default { if (linkAccountsResult.status === "OK") { logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser successfully linked"); return { status: "OK", user: linkAccountsResult.user }; - } else if ( - linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" - ) { + } + else if (linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { // this can happen cause of a race condition wherein the recipe user ID get's linked to // some other primary user whilst this function is running. // We can return this directly, because: // 1. a retry would result in the same // 2. the tryLinkingByAccountInfoOrCreatePrimaryUser doesn't specify where it should be linked // and we can't linked it to anything here after it became primary/linked to another primary user - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser already linked to another user" - ); + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser already linked to another user"); return { status: "OK", user: linkAccountsResult.user, }; - } else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition" - ); + } + else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition"); // this can be possible during a race condition wherein the primary user // that we fetched somehow is no more a primary user. This can happen if // the unlink function was called in parallel on that user. So we can just retry continue; - } else { - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition" - ); + } + else { + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition"); // status is "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" // it can come here if the recipe user ID can't be linked to the primary user ID cause // the email / phone number is associated with some other primary user ID. @@ -864,9 +683,7 @@ class Recipe extends recipeModule_1.default { } } } - logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: trying to make the current user primary" - ); + logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: trying to make the current user primary"); // In this case we have no other account we can link to, so we check if the current user should become a primary user if (await this.shouldBecomePrimaryUser(inputUser, tenantId, session, userContext)) { let createPrimaryUserResult = await this.recipeInterfaceImpl.createPrimaryUser({ @@ -878,15 +695,14 @@ class Recipe extends recipeModule_1.default { // meaning that the recipe user ID is already linked to another primary user id (race condition), or that some other // primary user ID exists with the same email / phone number (again, race condition). // In this case we call a retry in createPrimaryUserIdOrLinkByAccountInfo - if ( - createPrimaryUserResult.status === - "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" || - createPrimaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" - ) { + if (createPrimaryUserResult.status === + "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" || + createPrimaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR") { continue; } return createPrimaryUserResult; - } else { + } + else { // If not we return it unchanged return { status: "OK", user: inputUser }; } diff --git a/lib/build/recipe/accountlinking/recipeImplementation.d.ts b/lib/build/recipe/accountlinking/recipeImplementation.d.ts index 76484c9cd..ca08e95b6 100644 --- a/lib/build/recipe/accountlinking/recipeImplementation.d.ts +++ b/lib/build/recipe/accountlinking/recipeImplementation.d.ts @@ -2,8 +2,4 @@ import { RecipeInterface, TypeNormalisedInput } from "./types"; import { Querier } from "../../querier"; import type AccountLinkingRecipe from "./recipe"; -export default function getRecipeImplementation( - querier: Querier, - config: TypeNormalisedInput, - recipeInstance: AccountLinkingRecipe -): RecipeInterface; +export default function getRecipeImplementation(querier: Querier, config: TypeNormalisedInput, recipeInstance: AccountLinkingRecipe): RecipeInterface; diff --git a/lib/build/recipe/accountlinking/recipeImplementation.js b/lib/build/recipe/accountlinking/recipeImplementation.js index 55774660f..3cef18ff6 100644 --- a/lib/build/recipe/accountlinking/recipeImplementation.js +++ b/lib/build/recipe/accountlinking/recipeImplementation.js @@ -13,96 +13,52 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const user_1 = require("../../user"); function getRecipeImplementation(querier, config, recipeInstance) { return { - getUsers: async function ({ - tenantId, - timeJoinedOrder, - limit, - paginationToken, - includeRecipeIds, - query, - userContext, - }) { + getUsers: async function ({ tenantId, timeJoinedOrder, limit, paginationToken, includeRecipeIds, query, userContext, }) { let includeRecipeIdsStr = undefined; if (includeRecipeIds !== undefined) { includeRecipeIdsStr = includeRecipeIds.join(","); } - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default( - `${tenantId !== null && tenantId !== void 0 ? tenantId : "public"}/users` - ), - Object.assign( - { - includeRecipeIds: includeRecipeIdsStr, - timeJoinedOrder: timeJoinedOrder, - limit: limit, - paginationToken: paginationToken, - }, - query - ), - userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`${tenantId !== null && tenantId !== void 0 ? tenantId : "public"}/users`), Object.assign({ includeRecipeIds: includeRecipeIdsStr, timeJoinedOrder: timeJoinedOrder, limit: limit, paginationToken: paginationToken }, query), userContext); return { users: response.users.map((u) => new user_1.User(u)), nextPaginationToken: response.nextPaginationToken, }; }, - canCreatePrimaryUser: async function ({ recipeUserId, userContext }) { - return await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/accountlinking/user/primary/check"), - { - recipeUserId: recipeUserId.getAsString(), - }, - userContext - ); + canCreatePrimaryUser: async function ({ recipeUserId, userContext, }) { + return await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/primary/check"), { + recipeUserId: recipeUserId.getAsString(), + }, userContext); }, - createPrimaryUser: async function ({ recipeUserId, userContext }) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/accountlinking/user/primary"), - { - recipeUserId: recipeUserId.getAsString(), - }, - userContext - ); + createPrimaryUser: async function ({ recipeUserId, userContext, }) { + let response = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/primary"), { + recipeUserId: recipeUserId.getAsString(), + }, userContext); if (response.status === "OK") { response.user = new user_1.User(response.user); } return response; }, - canLinkAccounts: async function ({ recipeUserId, primaryUserId, userContext }) { - let result = await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/accountlinking/user/link/check"), - { - recipeUserId: recipeUserId.getAsString(), - primaryUserId, - }, - userContext - ); + canLinkAccounts: async function ({ recipeUserId, primaryUserId, userContext, }) { + let result = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/link/check"), { + recipeUserId: recipeUserId.getAsString(), + primaryUserId, + }, userContext); return result; }, - linkAccounts: async function ({ recipeUserId, primaryUserId, userContext }) { - const accountsLinkingResult = await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/accountlinking/user/link"), - { - recipeUserId: recipeUserId.getAsString(), - primaryUserId, - }, - userContext - ); - if ( - ["OK", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"].includes( - accountsLinkingResult.status - ) - ) { + linkAccounts: async function ({ recipeUserId, primaryUserId, userContext, }) { + const accountsLinkingResult = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/link"), { + recipeUserId: recipeUserId.getAsString(), + primaryUserId, + }, userContext); + if (["OK", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"].includes(accountsLinkingResult.status)) { accountsLinkingResult.user = new user_1.User(accountsLinkingResult.user); } if (accountsLinkingResult.status === "OK") { @@ -121,9 +77,7 @@ function getRecipeImplementation(querier, config, recipeInstance) { throw Error("this error should never be thrown"); } user = updatedUser; - let loginMethodInfo = user.loginMethods.find( - (u) => u.recipeUserId.getAsString() === recipeUserId.getAsString() - ); + let loginMethodInfo = user.loginMethods.find((u) => u.recipeUserId.getAsString() === recipeUserId.getAsString()); if (loginMethodInfo === undefined) { throw Error("this error should never be thrown"); } @@ -133,55 +87,37 @@ function getRecipeImplementation(querier, config, recipeInstance) { } return accountsLinkingResult; }, - unlinkAccount: async function ({ recipeUserId, userContext }) { - let accountsUnlinkingResult = await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/accountlinking/user/unlink"), - { - recipeUserId: recipeUserId.getAsString(), - }, - userContext - ); + unlinkAccount: async function ({ recipeUserId, userContext, }) { + let accountsUnlinkingResult = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/unlink"), { + recipeUserId: recipeUserId.getAsString(), + }, userContext); return accountsUnlinkingResult; }, getUser: async function ({ userId, userContext }) { - let result = await querier.sendGetRequest( - new normalisedURLPath_1.default("/user/id"), - { - userId, - }, - userContext - ); + let result = await querier.sendGetRequest(new normalisedURLPath_1.default("/user/id"), { + userId, + }, userContext); if (result.status === "OK") { return new user_1.User(result.user); } return undefined; }, - listUsersByAccountInfo: async function ({ tenantId, accountInfo, doUnionOfAccountInfo, userContext }) { + listUsersByAccountInfo: async function ({ tenantId, accountInfo, doUnionOfAccountInfo, userContext, }) { var _a, _b; - let result = await querier.sendGetRequest( - new normalisedURLPath_1.default( - `${tenantId !== null && tenantId !== void 0 ? tenantId : "public"}/users/by-accountinfo` - ), - { - email: accountInfo.email, - phoneNumber: accountInfo.phoneNumber, - thirdPartyId: (_a = accountInfo.thirdParty) === null || _a === void 0 ? void 0 : _a.id, - thirdPartyUserId: (_b = accountInfo.thirdParty) === null || _b === void 0 ? void 0 : _b.userId, - doUnionOfAccountInfo, - }, - userContext - ); + let result = await querier.sendGetRequest(new normalisedURLPath_1.default(`${tenantId !== null && tenantId !== void 0 ? tenantId : "public"}/users/by-accountinfo`), { + email: accountInfo.email, + phoneNumber: accountInfo.phoneNumber, + thirdPartyId: (_a = accountInfo.thirdParty) === null || _a === void 0 ? void 0 : _a.id, + thirdPartyUserId: (_b = accountInfo.thirdParty) === null || _b === void 0 ? void 0 : _b.userId, + doUnionOfAccountInfo, + }, userContext); return result.users.map((u) => new user_1.User(u)); }, - deleteUser: async function ({ userId, removeAllLinkedAccounts, userContext }) { - return await querier.sendPostRequest( - new normalisedURLPath_1.default("/user/remove"), - { - userId, - removeAllLinkedAccounts, - }, - userContext - ); + deleteUser: async function ({ userId, removeAllLinkedAccounts, userContext, }) { + return await querier.sendPostRequest(new normalisedURLPath_1.default("/user/remove"), { + userId, + removeAllLinkedAccounts, + }, userContext); }, }; } diff --git a/lib/build/recipe/accountlinking/types.d.ts b/lib/build/recipe/accountlinking/types.d.ts index 845780080..386a87c19 100644 --- a/lib/build/recipe/accountlinking/types.d.ts +++ b/lib/build/recipe/accountlinking/types.d.ts @@ -5,54 +5,30 @@ import RecipeUserId from "../../recipeUserId"; import { SessionContainerInterface } from "../session/types"; export declare type TypeInput = { onAccountLinked?: (user: User, newAccountInfo: RecipeLevelUser, userContext: UserContext) => Promise; - shouldDoAutomaticAccountLinking?: ( - newAccountInfo: AccountInfoWithRecipeId & { - recipeUserId?: RecipeUserId; - }, - user: User | undefined, - session: SessionContainerInterface | undefined, - tenantId: string, - userContext: UserContext - ) => Promise< - | { - shouldAutomaticallyLink: false; - } - | { - shouldAutomaticallyLink: true; - shouldRequireVerification: boolean; - } - >; + shouldDoAutomaticAccountLinking?: (newAccountInfo: AccountInfoWithRecipeId & { + recipeUserId?: RecipeUserId; + }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: UserContext) => Promise<{ + shouldAutomaticallyLink: false; + } | { + shouldAutomaticallyLink: true; + shouldRequireVerification: boolean; + }>; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; }; }; export declare type TypeNormalisedInput = { onAccountLinked: (user: User, newAccountInfo: RecipeLevelUser, userContext: UserContext) => Promise; - shouldDoAutomaticAccountLinking: ( - newAccountInfo: AccountInfoWithRecipeId & { - recipeUserId?: RecipeUserId; - }, - user: User | undefined, - session: SessionContainerInterface | undefined, - tenantId: string, - userContext: UserContext - ) => Promise< - | { - shouldAutomaticallyLink: false; - } - | { - shouldAutomaticallyLink: true; - shouldRequireVerification: boolean; - } - >; + shouldDoAutomaticAccountLinking: (newAccountInfo: AccountInfoWithRecipeId & { + recipeUserId?: RecipeUserId; + }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: UserContext) => Promise<{ + shouldAutomaticallyLink: false; + } | { + shouldAutomaticallyLink: true; + shouldRequireVerification: boolean; + }>; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; }; }; export declare type RecipeInterface = { @@ -73,85 +49,66 @@ export declare type RecipeInterface = { canCreatePrimaryUser: (input: { recipeUserId: RecipeUserId; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - wasAlreadyAPrimaryUser: boolean; - } - | { - status: - | "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" - | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } - >; + }) => Promise<{ + status: "OK"; + wasAlreadyAPrimaryUser: boolean; + } | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + }>; createPrimaryUser: (input: { recipeUserId: RecipeUserId; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - user: User; - wasAlreadyAPrimaryUser: boolean; - } - | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - } - | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } - >; + }) => Promise<{ + status: "OK"; + user: User; + wasAlreadyAPrimaryUser: boolean; + } | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + } | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + }>; canLinkAccounts: (input: { recipeUserId: RecipeUserId; primaryUserId: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - accountsAlreadyLinked: boolean; - } - | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - description: string; - primaryUserId: string; - } - | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } - | { - status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; - } - >; + }) => Promise<{ + status: "OK"; + accountsAlreadyLinked: boolean; + } | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + description: string; + primaryUserId: string; + } | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } | { + status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; + }>; linkAccounts: (input: { recipeUserId: RecipeUserId; primaryUserId: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - accountsAlreadyLinked: boolean; - user: User; - } - | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - user: User; - } - | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } - | { - status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; - } - >; + }) => Promise<{ + status: "OK"; + accountsAlreadyLinked: boolean; + user: User; + } | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + user: User; + } | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } | { + status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; + }>; unlinkAccount: (input: { recipeUserId: RecipeUserId; userContext: UserContext; @@ -160,10 +117,13 @@ export declare type RecipeInterface = { wasRecipeUserDeleted: boolean; wasLinked: boolean; }>; - getUser: (input: { userId: string; userContext: UserContext }) => Promise; + getUser: (input: { + userId: string; + userContext: UserContext; + }) => Promise; listUsersByAccountInfo: (input: { tenantId: string; - accountInfo: AccountInfo; + accountInfo: AccountInfoInput; doUnionOfAccountInfo: boolean; userContext: UserContext; }) => Promise; @@ -186,6 +146,11 @@ export declare type AccountInfo = { credentialIds: string[]; }; }; +export declare type AccountInfoInput = Omit & { + webauthn?: { + credentialId: string; + }; +}; export declare type AccountInfoWithRecipeId = { recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; } & AccountInfo; diff --git a/lib/build/recipe/accountlinking/utils.js b/lib/build/recipe/accountlinking/utils.js index 13cd42e61..105b1b3c9 100644 --- a/lib/build/recipe/accountlinking/utils.js +++ b/lib/build/recipe/accountlinking/utils.js @@ -15,7 +15,7 @@ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = exports.recipeInitDefinedShouldDoAutomaticAccountLinking = void 0; -async function defaultOnAccountLinked() {} +async function defaultOnAccountLinked() { } async function defaultShouldDoAutomaticAccountLinking() { return { shouldAutomaticallyLink: false, @@ -26,15 +26,9 @@ function recipeInitDefinedShouldDoAutomaticAccountLinking(config) { } exports.recipeInitDefinedShouldDoAutomaticAccountLinking = recipeInitDefinedShouldDoAutomaticAccountLinking; function validateAndNormaliseUserInput(_, config) { - let onAccountLinked = - (config === null || config === void 0 ? void 0 : config.onAccountLinked) || defaultOnAccountLinked; - let shouldDoAutomaticAccountLinking = - (config === null || config === void 0 ? void 0 : config.shouldDoAutomaticAccountLinking) || - defaultShouldDoAutomaticAccountLinking; - let override = Object.assign( - { functions: (originalImplementation) => originalImplementation }, - config === null || config === void 0 ? void 0 : config.override - ); + let onAccountLinked = (config === null || config === void 0 ? void 0 : config.onAccountLinked) || defaultOnAccountLinked; + let shouldDoAutomaticAccountLinking = (config === null || config === void 0 ? void 0 : config.shouldDoAutomaticAccountLinking) || defaultShouldDoAutomaticAccountLinking; + let override = Object.assign({ functions: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { override, onAccountLinked, diff --git a/lib/build/recipe/dashboard/api/analytics.d.ts b/lib/build/recipe/dashboard/api/analytics.d.ts index 1b0c81d49..6e549751c 100644 --- a/lib/build/recipe/dashboard/api/analytics.d.ts +++ b/lib/build/recipe/dashboard/api/analytics.d.ts @@ -4,9 +4,4 @@ import { UserContext } from "../../../types"; export declare type Response = { status: "OK"; }; -export default function analyticsPost( - _: APIInterface, - ___: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function analyticsPost(_: APIInterface, ___: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/analytics.js b/lib/build/recipe/dashboard/api/analytics.js index 18762e156..d897d50e7 100644 --- a/lib/build/recipe/dashboard/api/analytics.js +++ b/lib/build/recipe/dashboard/api/analytics.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_1 = __importDefault(require("../../../supertokens")); const querier_1 = require("../../../querier"); @@ -53,10 +51,9 @@ async function analyticsPost(_, ___, options, userContext) { if (response.exists) { telemetryId = response.telemetryId; } - numberOfUsers = await supertokens_1.default - .getInstanceOrThrowError() - .getUserCount(undefined, undefined, userContext); - } catch (_) { + numberOfUsers = await supertokens_1.default.getInstanceOrThrowError().getUserCount(undefined, undefined, userContext); + } + catch (_) { // If either telemetry id API or user count fetch fails, no event should be sent return { status: "OK", @@ -86,7 +83,8 @@ async function analyticsPost(_, ___, options, userContext) { "content-type": "application/json; charset=utf-8", }, }); - } catch (e) { + } + catch (e) { // Ignored } return { diff --git a/lib/build/recipe/dashboard/api/apiKeyProtector.d.ts b/lib/build/recipe/dashboard/api/apiKeyProtector.d.ts index eb8ad8d0e..d8c92a94d 100644 --- a/lib/build/recipe/dashboard/api/apiKeyProtector.d.ts +++ b/lib/build/recipe/dashboard/api/apiKeyProtector.d.ts @@ -1,10 +1,4 @@ // @ts-nocheck import { UserContext } from "../../../types"; import { APIFunction, APIInterface, APIOptions } from "../types"; -export default function apiKeyProtector( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - apiFunction: APIFunction, - userContext: UserContext -): Promise; +export default function apiKeyProtector(apiImplementation: APIInterface, tenantId: string, options: APIOptions, apiFunction: APIFunction, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/apiKeyProtector.js b/lib/build/recipe/dashboard/api/apiKeyProtector.js index 50a9d7031..a8ed7440b 100644 --- a/lib/build/recipe/dashboard/api/apiKeyProtector.js +++ b/lib/build/recipe/dashboard/api/apiKeyProtector.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../error")); const utils_1 = require("../utils"); @@ -15,7 +13,8 @@ async function apiKeyProtector(apiImplementation, tenantId, options, apiFunction config: options.config, userContext, }); - } catch (e) { + } + catch (e) { if (error_1.default.isErrorFromSuperTokens(e) && e.type === error_1.default.OPERATION_NOT_ALLOWED) { options.res.setStatusCode(403); options.res.sendJSONResponse({ diff --git a/lib/build/recipe/dashboard/api/dashboard.d.ts b/lib/build/recipe/dashboard/api/dashboard.d.ts index 252a4acf3..560d91749 100644 --- a/lib/build/recipe/dashboard/api/dashboard.d.ts +++ b/lib/build/recipe/dashboard/api/dashboard.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { UserContext } from "../../../types"; import { APIInterface, APIOptions } from "../types"; -export default function dashboard( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function dashboard(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/implementation.js b/lib/build/recipe/dashboard/api/implementation.js index 5423f37b4..a92b4b930 100644 --- a/lib/build/recipe/dashboard/api/implementation.js +++ b/lib/build/recipe/dashboard/api/implementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLDomain_1 = __importDefault(require("../../../normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); @@ -31,25 +29,18 @@ function getAPIImplementation() { const bundleBasePathString = await input.options.recipeImplementation.getDashboardBundleLocation({ userContext: input.userContext, }); - const bundleDomain = - new normalisedURLDomain_1.default(bundleBasePathString).getAsStringDangerous() + + const bundleDomain = new normalisedURLDomain_1.default(bundleBasePathString).getAsStringDangerous() + new normalisedURLPath_1.default(bundleBasePathString).getAsStringDangerous(); let connectionURI = ""; const superTokensInstance = supertokens_1.default.getInstanceOrThrowError(); const authMode = input.options.config.authMode; if (superTokensInstance.supertokens !== undefined) { connectionURI = - new normalisedURLDomain_1.default( - superTokensInstance.supertokens.connectionURI.split(";")[0] - ).getAsStringDangerous() + - new normalisedURLPath_1.default( - superTokensInstance.supertokens.connectionURI.split(";")[0] - ).getAsStringDangerous(); + new normalisedURLDomain_1.default(superTokensInstance.supertokens.connectionURI.split(";")[0]).getAsStringDangerous() + + new normalisedURLPath_1.default(superTokensInstance.supertokens.connectionURI.split(";")[0]).getAsStringDangerous(); } let isSearchEnabled = false; - const cdiVersion = await querier_1.Querier.getNewInstanceOrThrowError(input.options.recipeId).getAPIVersion( - input.userContext - ); + const cdiVersion = await querier_1.Querier.getNewInstanceOrThrowError(input.options.recipeId).getAPIVersion(input.userContext); if (utils_1.maxVersion("2.20", cdiVersion) === cdiVersion) { // Only enable search if CDI version is 2.20 or above isSearchEnabled = true; @@ -75,8 +66,8 @@ function getAPIImplementation() { }); window.staticBasePath = "${bundleDomain}/static" window.dashboardAppPath = "${input.options.appInfo.apiBasePath - .appendPath(new normalisedURLPath_1.default(constants_1.DASHBOARD_API)) - .getAsStringDangerous()}" + .appendPath(new normalisedURLPath_1.default(constants_1.DASHBOARD_API)) + .getAsStringDangerous()}" window.connectionURI = "${connectionURI}" window.authMode = "${authMode}" window.isSearchEnabled = "${isSearchEnabled}" diff --git a/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.d.ts b/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.d.ts index 97bbf0654..b2477273e 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.d.ts @@ -1,21 +1,13 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - createdNew: boolean; - } - | { - status: "UNKNOWN_TENANT_ERROR"; - } - | { - status: "BOXY_ERROR"; - message: string; - }; -export default function createOrUpdateThirdPartyConfig( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; + createdNew: boolean; +} | { + status: "UNKNOWN_TENANT_ERROR"; +} | { + status: "BOXY_ERROR"; + message: string; +}; +export default function createOrUpdateThirdPartyConfig(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.js b/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.js index e4f151e0d..a6bf1e185 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.js +++ b/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -25,23 +23,11 @@ async function createOrUpdateThirdPartyConfig(_, tenantId, options, userContext) if (tenantRes.thirdParty.providers.length === 0) { // This means that the tenant was using the static list of providers, we need to add them all before adding the new one const mtRecipe = recipe_1.default.getInstance(); - const staticProviders = - (_a = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && - _a !== void 0 - ? _a - : []; - for (const provider of staticProviders.filter( - (provider) => - provider.includeInNonPublicTenantsByDefault === true || tenantId === constants_1.DEFAULT_TENANT_ID - )) { - await multitenancy_1.default.createOrUpdateThirdPartyConfig( - tenantId, - { - thirdPartyId: provider.config.thirdPartyId, - }, - undefined, - userContext - ); + const staticProviders = (_a = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && _a !== void 0 ? _a : []; + for (const provider of staticProviders.filter((provider) => provider.includeInNonPublicTenantsByDefault === true || tenantId === constants_1.DEFAULT_TENANT_ID)) { + await multitenancy_1.default.createOrUpdateThirdPartyConfig(tenantId, { + thirdPartyId: provider.config.thirdPartyId, + }, undefined, userContext); // delay after each provider to avoid rate limiting await new Promise((r) => setTimeout(r, 500)); // 500ms } @@ -50,17 +36,14 @@ async function createOrUpdateThirdPartyConfig(_, tenantId, options, userContext) const boxyURL = providerConfig.clients[0].additionalConfig.boxyURL; const boxyAPIKey = providerConfig.clients[0].additionalConfig.boxyAPIKey; providerConfig.clients[0].additionalConfig.boxyAPIKey = undefined; - if ( - boxyAPIKey && + if (boxyAPIKey && (providerConfig.clients[0].additionalConfig.samlInputType === "xml" || - providerConfig.clients[0].additionalConfig.samlInputType === "url") - ) { + providerConfig.clients[0].additionalConfig.samlInputType === "url")) { const requestBody = { name: "", label: "", description: "", - tenant: - providerConfig.clients[0].additionalConfig.boxyTenant || + tenant: providerConfig.clients[0].additionalConfig.boxyTenant || `${tenantId}-${providerConfig.thirdPartyId}`, product: providerConfig.clients[0].additionalConfig.boxyProduct || "supertokens", defaultRedirectUrl: providerConfig.clients[0].additionalConfig.redirectURLs[0], @@ -74,14 +57,10 @@ async function createOrUpdateThirdPartyConfig(_, tenantId, options, userContext) const normalisedDomain = new normalisedURLDomain_1.default(boxyURL); const normalisedBasePath = new normalisedURLPath_1.default(boxyURL); const connectionsPath = new normalisedURLPath_1.default("/api/v1/saml/config"); - const resp = await thirdpartyUtils_1.doPostRequest( - normalisedDomain.getAsStringDangerous() + - normalisedBasePath.appendPath(connectionsPath).getAsStringDangerous(), - requestBody, - { - Authorization: `Api-Key ${boxyAPIKey}`, - } - ); + const resp = await thirdpartyUtils_1.doPostRequest(normalisedDomain.getAsStringDangerous() + + normalisedBasePath.appendPath(connectionsPath).getAsStringDangerous(), requestBody, { + Authorization: `Api-Key ${boxyAPIKey}`, + }); if (resp.status !== 200) { if (resp.status === 401) { return { @@ -101,12 +80,7 @@ async function createOrUpdateThirdPartyConfig(_, tenantId, options, userContext) providerConfig.clients[0].clientSecret = resp.jsonResponse.clientSecret; } } - const thirdPartyRes = await multitenancy_1.default.createOrUpdateThirdPartyConfig( - tenantId, - providerConfig, - undefined, - userContext - ); + const thirdPartyRes = await multitenancy_1.default.createOrUpdateThirdPartyConfig(tenantId, providerConfig, undefined, userContext); return thirdPartyRes; } exports.default = createOrUpdateThirdPartyConfig; diff --git a/lib/build/recipe/dashboard/api/multitenancy/createTenant.d.ts b/lib/build/recipe/dashboard/api/multitenancy/createTenant.d.ts index 70b59720b..2cc3b257c 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/createTenant.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/createTenant.d.ts @@ -1,21 +1,13 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - createdNew: boolean; - } - | { - status: "MULTITENANCY_NOT_ENABLED_IN_CORE_ERROR" | "TENANT_ID_ALREADY_EXISTS_ERROR"; - } - | { - status: "INVALID_TENANT_ID_ERROR"; - message: string; - }; -export default function createTenant( - _: APIInterface, - __: string, - options: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; + createdNew: boolean; +} | { + status: "MULTITENANCY_NOT_ENABLED_IN_CORE_ERROR" | "TENANT_ID_ALREADY_EXISTS_ERROR"; +} | { + status: "INVALID_TENANT_ID_ERROR"; + message: string; +}; +export default function createTenant(_: APIInterface, __: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/createTenant.js b/lib/build/recipe/dashboard/api/multitenancy/createTenant.js index 4db6c70d4..84f89d289 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/createTenant.js +++ b/lib/build/recipe/dashboard/api/multitenancy/createTenant.js @@ -1,27 +1,24 @@ "use strict"; -var __rest = - (this && this.__rest) || - function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; - } - return t; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const error_1 = __importDefault(require("../../../../error")); async function createTenant(_, __, options, userContext) { const requestBody = await options.req.getJSONBody(); - const { tenantId } = requestBody, - config = __rest(requestBody, ["tenantId"]); + const { tenantId } = requestBody, config = __rest(requestBody, ["tenantId"]); if (typeof tenantId !== "string" || tenantId === "") { throw new error_1.default({ message: "Missing required parameter 'tenantId'", @@ -31,7 +28,8 @@ async function createTenant(_, __, options, userContext) { let tenantRes; try { tenantRes = await multitenancy_1.default.createOrUpdateTenant(tenantId, config, userContext); - } catch (err) { + } + catch (err) { const errMsg = err.message; if (errMsg.includes("SuperTokens core threw an error for a ")) { if (errMsg.includes("with status code: 402")) { diff --git a/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.d.ts b/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.d.ts index 60d1a433e..bf8c3e27c 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.d.ts @@ -1,17 +1,10 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - didExist: boolean; - } - | { - status: "CANNOT_DELETE_PUBLIC_TENANT_ERROR"; - }; -export default function deleteTenant( - _: APIInterface, - tenantId: string, - __: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; + didExist: boolean; +} | { + status: "CANNOT_DELETE_PUBLIC_TENANT_ERROR"; +}; +export default function deleteTenant(_: APIInterface, tenantId: string, __: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.js b/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.js index 7a7d7f863..d21feb322 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.js +++ b/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.js @@ -1,16 +1,15 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); async function deleteTenant(_, tenantId, __, userContext) { try { const deleteTenantRes = await multitenancy_1.default.deleteTenant(tenantId, userContext); return deleteTenantRes; - } catch (err) { + } + catch (err) { const errMsg = err.message; if (errMsg.includes("SuperTokens core threw an error for a ") && errMsg.includes("with status code: 403")) { return { diff --git a/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.d.ts b/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.d.ts index e2cdddbd8..46ac0d97e 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.d.ts @@ -1,17 +1,10 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - didConfigExist: boolean; - } - | { - status: "UNKNOWN_TENANT_ERROR"; - }; -export default function deleteThirdPartyConfig( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; + didConfigExist: boolean; +} | { + status: "UNKNOWN_TENANT_ERROR"; +}; +export default function deleteThirdPartyConfig(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.js b/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.js index 6bd16f2f9..82bd4f5e5 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.js +++ b/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -29,28 +27,17 @@ async function deleteThirdPartyConfig(_, tenantId, options, userContext) { if (thirdPartyIdsFromCore.length === 0) { // this means that the tenant was using the static list of providers, we need to add them all before deleting one const mtRecipe = recipe_1.default.getInstance(); - const staticProviders = - (_a = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && - _a !== void 0 - ? _a - : []; - for (const provider of staticProviders.filter( - (provider) => - provider.includeInNonPublicTenantsByDefault === true || tenantId === constants_1.DEFAULT_TENANT_ID - )) { + const staticProviders = (_a = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && _a !== void 0 ? _a : []; + for (const provider of staticProviders.filter((provider) => provider.includeInNonPublicTenantsByDefault === true || tenantId === constants_1.DEFAULT_TENANT_ID)) { const providerId = provider.config.thirdPartyId; - await multitenancy_1.default.createOrUpdateThirdPartyConfig( - tenantId, - { - thirdPartyId: providerId, - }, - undefined, - userContext - ); + await multitenancy_1.default.createOrUpdateThirdPartyConfig(tenantId, { + thirdPartyId: providerId, + }, undefined, userContext); // delay after each provider to avoid rate limiting await new Promise((r) => setTimeout(r, 500)); // 500ms } - } else if (thirdPartyIdsFromCore.length === 1 && thirdPartyIdsFromCore[0] === thirdPartyId) { + } + else if (thirdPartyIdsFromCore.length === 1 && thirdPartyIdsFromCore[0] === thirdPartyId) { if (tenantRes.firstFactors === undefined) { // add all static first factors except thirdparty await multitenancy_1.default.createOrUpdateTenant(tenantId, { @@ -62,7 +49,8 @@ async function deleteThirdPartyConfig(_, tenantId, options, userContext) { multifactorauth_1.FactorIds.LINK_EMAIL, ], }); - } else if (tenantRes.firstFactors.includes("thirdparty")) { + } + else if (tenantRes.firstFactors.includes("thirdparty")) { // add all static first factors except thirdparty const newFirstFactors = tenantRes.firstFactors.filter((factor) => factor !== "thirdparty"); await multitenancy_1.default.createOrUpdateTenant(tenantId, { diff --git a/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.d.ts b/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.d.ts index 2db41aae6..f2abcd1a6 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.d.ts @@ -1,29 +1,22 @@ // @ts-nocheck import { APIInterface, APIOptions, CoreConfigFieldInfo } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - tenant: { - tenantId: string; - thirdParty: { - providers: { - thirdPartyId: string; - name: string; - }[]; - }; - firstFactors: string[]; - requiredSecondaryFactors?: string[] | null; - coreConfig: CoreConfigFieldInfo[]; - userCount: number; - }; - } - | { - status: "UNKNOWN_TENANT_ERROR"; - }; -export default function getTenantInfo( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; + tenant: { + tenantId: string; + thirdParty: { + providers: { + thirdPartyId: string; + name: string; + }[]; + }; + firstFactors: string[]; + requiredSecondaryFactors?: string[] | null; + coreConfig: CoreConfigFieldInfo[]; + userCount: number; + }; +} | { + status: "UNKNOWN_TENANT_ERROR"; +}; +export default function getTenantInfo(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.js b/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.js index b7f39cc18..3d5a90245 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.js +++ b/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.js @@ -1,20 +1,18 @@ "use strict"; -var __rest = - (this && this.__rest) || - function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; - } - return t; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -32,65 +30,35 @@ async function getTenantInfo(_, tenantId, options, userContext) { status: "UNKNOWN_TENANT_ERROR", }; } - let { status } = tenantRes, - tenantConfig = __rest(tenantRes, ["status"]); + let { status } = tenantRes, tenantConfig = __rest(tenantRes, ["status"]); let firstFactors = utils_1.getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantConfig); if (tenantRes === undefined) { return { status: "UNKNOWN_TENANT_ERROR", }; } - const userCount = await supertokens_1.default - .getInstanceOrThrowError() - .getUserCount(undefined, tenantId, userContext); - const providersFromCore = - (_a = tenantRes === null || tenantRes === void 0 ? void 0 : tenantRes.thirdParty) === null || _a === void 0 - ? void 0 - : _a.providers; + const userCount = await supertokens_1.default.getInstanceOrThrowError().getUserCount(undefined, tenantId, userContext); + const providersFromCore = (_a = tenantRes === null || tenantRes === void 0 ? void 0 : tenantRes.thirdParty) === null || _a === void 0 ? void 0 : _a.providers; const mtRecipe = recipe_1.default.getInstance(); - const staticProviders = - (_b = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && - _b !== void 0 - ? _b - : []; - const mergedProvidersFromCoreAndStatic = configUtils_1.mergeProvidersFromCoreAndStatic( - providersFromCore, - staticProviders, - tenantId === constants_1.DEFAULT_TENANT_ID - ); + const staticProviders = (_b = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && _b !== void 0 ? _b : []; + const mergedProvidersFromCoreAndStatic = configUtils_1.mergeProvidersFromCoreAndStatic(providersFromCore, staticProviders, tenantId === constants_1.DEFAULT_TENANT_ID); let querier = querier_1.Querier.getNewInstanceOrThrowError(options.recipeId); - let coreConfig = await querier.sendGetRequest( - new normalisedURLPath_1.default(`/${tenantId}/recipe/dashboard/tenant/core-config`), - {}, - userContext - ); + let coreConfig = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/dashboard/tenant/core-config`), {}, userContext); const tenant = { tenantId, thirdParty: { - providers: await Promise.all( - mergedProvidersFromCoreAndStatic.map(async (provider) => { - try { - const providerInstance = await configUtils_1.findAndCreateProviderInstance( - mergedProvidersFromCoreAndStatic, - provider.config.thirdPartyId, - provider.config.clients[0].clientType, - userContext - ); - return { - thirdPartyId: provider.config.thirdPartyId, - name: - providerInstance === null || providerInstance === void 0 - ? void 0 - : providerInstance.config.name, - }; - } catch (_) { - return { - thirdPartyId: provider.config.thirdPartyId, - name: provider.config.thirdPartyId, - }; - } - }) - ), + providers: await Promise.all(mergedProvidersFromCoreAndStatic.map(async (provider) => { + try { + const providerInstance = await configUtils_1.findAndCreateProviderInstance(mergedProvidersFromCoreAndStatic, provider.config.thirdPartyId, provider.config.clients[0].clientType, userContext); + return { thirdPartyId: provider.config.thirdPartyId, name: providerInstance === null || providerInstance === void 0 ? void 0 : providerInstance.config.name }; + } + catch (_) { + return { + thirdPartyId: provider.config.thirdPartyId, + name: provider.config.thirdPartyId, + }; + } + })), }, firstFactors: firstFactors, requiredSecondaryFactors: tenantRes.requiredSecondaryFactors, diff --git a/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.d.ts b/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.d.ts index 9666f1919..bdb8977a3 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.d.ts @@ -2,21 +2,14 @@ import { APIInterface, APIOptions } from "../../types"; import { ProviderConfig } from "../../../thirdparty/types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - providerConfig: ProviderConfig & { - isGetAuthorisationRedirectUrlOverridden: boolean; - isExchangeAuthCodeForOAuthTokensOverridden: boolean; - isGetUserInfoOverridden: boolean; - }; - } - | { - status: "UNKNOWN_TENANT_ERROR"; - }; -export default function getThirdPartyConfig( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; + providerConfig: ProviderConfig & { + isGetAuthorisationRedirectUrlOverridden: boolean; + isExchangeAuthCodeForOAuthTokensOverridden: boolean; + isGetUserInfoOverridden: boolean; + }; +} | { + status: "UNKNOWN_TENANT_ERROR"; +}; +export default function getThirdPartyConfig(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.js b/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.js index 91278ce5f..f73f73471 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.js +++ b/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.js @@ -1,20 +1,18 @@ "use strict"; -var __rest = - (this && this.__rest) || - function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; - } - return t; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -34,13 +32,10 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { if (thirdPartyId === undefined) { throw new Error("Please provide thirdPartyId"); } - let providersFromCore = - (_a = tenantRes === null || tenantRes === void 0 ? void 0 : tenantRes.thirdParty) === null || _a === void 0 - ? void 0 - : _a.providers; + let providersFromCore = (_a = tenantRes === null || tenantRes === void 0 ? void 0 : tenantRes.thirdParty) === null || _a === void 0 ? void 0 : _a.providers; const mtRecipe = recipe_1.default.getInstance(); let staticProviders = (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) - ? mtRecipe.staticThirdPartyProviders.map((provider) => Object.assign({}, provider)) + ? mtRecipe.staticThirdPartyProviders.map((provider) => (Object.assign({}, provider))) : []; let additionalConfig = undefined; // filter out providers that is not matching thirdPartyId @@ -59,12 +54,14 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { if (oktaDomain !== undefined) { additionalConfig = { oktaDomain }; } - } else if (thirdPartyId === "active-directory") { + } + else if (thirdPartyId === "active-directory") { const directoryId = options.req.getKeyValueFromQuery("directoryId"); if (directoryId !== undefined) { additionalConfig = { directoryId }; } - } else if (thirdPartyId === "boxy-saml") { + } + else if (thirdPartyId === "boxy-saml") { let boxyURL = options.req.getKeyValueFromQuery("boxyUrl"); let boxyAPIKey = options.req.getKeyValueFromQuery("boxyAPIKey"); if (boxyURL !== undefined) { @@ -73,7 +70,8 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { additionalConfig = Object.assign(Object.assign({}, additionalConfig), { boxyAPIKey }); } } - } else if (thirdPartyId === "google-workspaces") { + } + else if (thirdPartyId === "google-workspaces") { const hd = options.req.getKeyValueFromQuery("hd"); if (hd !== undefined) { additionalConfig = { hd }; @@ -84,14 +82,7 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { providersFromCore[0].authorizationEndpoint = undefined; providersFromCore[0].tokenEndpoint = undefined; providersFromCore[0].userInfoEndpoint = undefined; - providersFromCore[0].clients = ((_b = providersFromCore[0].clients) !== null && _b !== void 0 - ? _b - : [] - ).map((client) => - Object.assign(Object.assign({}, client), { - additionalConfig: Object.assign(Object.assign({}, client.additionalConfig), additionalConfig), - }) - ); + providersFromCore[0].clients = ((_b = providersFromCore[0].clients) !== null && _b !== void 0 ? _b : []).map((client) => (Object.assign(Object.assign({}, client), { additionalConfig: Object.assign(Object.assign({}, client.additionalConfig), additionalConfig) }))); } } // filter out other providers from static @@ -110,38 +101,17 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { additionalConfig = { teamId: "", keyId: "", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", }; } if (staticProviders.length === 1) { // modify additional config if query param is passed if (additionalConfig !== undefined) { // we set these to undefined so that these can be computed using the query param that was provided - staticProviders[0] = Object.assign(Object.assign({}, staticProviders[0]), { - config: Object.assign(Object.assign({}, staticProviders[0].config), { - oidcDiscoveryEndpoint: undefined, - authorizationEndpoint: undefined, - tokenEndpoint: undefined, - userInfoEndpoint: undefined, - clients: ((_c = staticProviders[0].config.clients) !== null && _c !== void 0 ? _c : []).map( - (client) => - Object.assign(Object.assign({}, client), { - additionalConfig: Object.assign( - Object.assign({}, client.additionalConfig), - additionalConfig - ), - }) - ), - }), - }); + staticProviders[0] = Object.assign(Object.assign({}, staticProviders[0]), { config: Object.assign(Object.assign({}, staticProviders[0].config), { oidcDiscoveryEndpoint: undefined, authorizationEndpoint: undefined, tokenEndpoint: undefined, userInfoEndpoint: undefined, clients: ((_c = staticProviders[0].config.clients) !== null && _c !== void 0 ? _c : []).map((client) => (Object.assign(Object.assign({}, client), { additionalConfig: Object.assign(Object.assign({}, client.additionalConfig), additionalConfig) }))) }) }); } } - let mergedProvidersFromCoreAndStatic = configUtils_1.mergeProvidersFromCoreAndStatic( - providersFromCore, - staticProviders, - true - ); + let mergedProvidersFromCoreAndStatic = configUtils_1.mergeProvidersFromCoreAndStatic(providersFromCore, staticProviders, true); if (mergedProvidersFromCoreAndStatic.length !== 1) { throw new Error("should never come here!"); } @@ -149,10 +119,7 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { if (mergedProvider.config.thirdPartyId === thirdPartyId) { if (mergedProvider.config.clients === undefined || mergedProvider.config.clients.length === 0) { mergedProvider.config.clients = [ - Object.assign( - { clientId: "nonguessable-temporary-client-id" }, - additionalConfig !== undefined ? { additionalConfig } : {} - ), + Object.assign({ clientId: "nonguessable-temporary-client-id" }, (additionalConfig !== undefined ? { additionalConfig } : {})), ]; } } @@ -169,22 +136,8 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { let foundCorrectConfig = false; for (const client of (_d = provider.config.clients) !== null && _d !== void 0 ? _d : []) { try { - const providerInstance = await configUtils_1.findAndCreateProviderInstance( - mergedProvidersFromCoreAndStatic, - thirdPartyId, - client.clientType, - userContext - ); - const _f = providerInstance.config, - { clientId, clientSecret, clientType, scope, additionalConfig, forcePKCE } = _f, - commonConfig = __rest(_f, [ - "clientId", - "clientSecret", - "clientType", - "scope", - "additionalConfig", - "forcePKCE", - ]); + const providerInstance = await configUtils_1.findAndCreateProviderInstance(mergedProvidersFromCoreAndStatic, thirdPartyId, client.clientType, userContext); + const _f = providerInstance.config, { clientId, clientSecret, clientType, scope, additionalConfig, forcePKCE } = _f, commonConfig = __rest(_f, ["clientId", "clientSecret", "clientType", "scope", "additionalConfig", "forcePKCE"]); clients.push({ clientId, clientSecret, @@ -200,10 +153,8 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { if (beforeOverride.getAuthorisationRedirectURL !== afterOverride.getAuthorisationRedirectURL) { isGetAuthorisationRedirectUrlOverridden = true; } - if ( - beforeOverride.exchangeAuthCodeForOAuthTokens !== - afterOverride.exchangeAuthCodeForOAuthTokens - ) { + if (beforeOverride.exchangeAuthCodeForOAuthTokens !== + afterOverride.exchangeAuthCodeForOAuthTokens) { isExchangeAuthCodeForOAuthTokensOverridden = true; } if (beforeOverride.getUserInfo !== afterOverride.getUserInfo) { @@ -211,7 +162,8 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { } } foundCorrectConfig = true; - } catch (err) { + } + catch (err) { // ignore the error clients.push(client); } @@ -222,20 +174,13 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { break; } } - if ( - (additionalConfig === null || additionalConfig === void 0 ? void 0 : additionalConfig.privateKey) !== undefined - ) { + if ((additionalConfig === null || additionalConfig === void 0 ? void 0 : additionalConfig.privateKey) !== undefined) { additionalConfig.privateKey = ""; } const tempClients = clients.filter((client) => client.clientId === "nonguessable-temporary-client-id"); const finalClients = clients.filter((client) => client.clientId !== "nonguessable-temporary-client-id"); if (finalClients.length === 0) { - finalClients.push( - Object.assign( - Object.assign(Object.assign({}, tempClients[0]), { clientId: "", clientSecret: "" }), - additionalConfig !== undefined ? { additionalConfig } : {} - ) - ); + finalClients.push(Object.assign(Object.assign(Object.assign({}, tempClients[0]), { clientId: "", clientSecret: "" }), (additionalConfig !== undefined ? { additionalConfig } : {}))); } // fill in boxy info from boxy instance if (thirdPartyId.startsWith("boxy-saml")) { @@ -246,41 +191,27 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { const normalisedDomain = new normalisedURLDomain_1.default(boxyURL); const normalisedBasePath = new normalisedURLPath_1.default(boxyURL); const connectionsPath = new normalisedURLPath_1.default("/api/v1/saml/config"); - const resp = await thirdpartyUtils_1.doGetRequest( - normalisedDomain.getAsStringDangerous() + - normalisedBasePath.appendPath(connectionsPath).getAsStringDangerous(), - { - clientID: finalClients[0].clientId, - }, - { - Authorization: `Api-Key ${boxyAPIKey}`, - } - ); + const resp = await thirdpartyUtils_1.doGetRequest(normalisedDomain.getAsStringDangerous() + + normalisedBasePath.appendPath(connectionsPath).getAsStringDangerous(), { + clientID: finalClients[0].clientId, + }, { + Authorization: `Api-Key ${boxyAPIKey}`, + }); if (resp.status === 200) { // we don't care about non 200 status codes since we are just trying to populate whatever possible if (resp.jsonResponse === undefined) { throw new Error("should never happen"); } - finalClients[0].additionalConfig = Object.assign( - Object.assign({}, finalClients[0].additionalConfig), - { - redirectURLs: resp.jsonResponse.redirectUrl, - boxyTenant: resp.jsonResponse.tenant, - boxyProduct: resp.jsonResponse.product, - } - ); + finalClients[0].additionalConfig = Object.assign(Object.assign({}, finalClients[0].additionalConfig), { redirectURLs: resp.jsonResponse.redirectUrl, boxyTenant: resp.jsonResponse.tenant, boxyProduct: resp.jsonResponse.product }); } } } } return { status: "OK", - providerConfig: Object.assign(Object.assign({}, commonProviderConfig), { - clients: finalClients, - isGetAuthorisationRedirectUrlOverridden, + providerConfig: Object.assign(Object.assign({}, commonProviderConfig), { clients: finalClients, isGetAuthorisationRedirectUrlOverridden, isExchangeAuthCodeForOAuthTokensOverridden, - isGetUserInfoOverridden, - }), + isGetUserInfoOverridden }), }; } exports.default = getThirdPartyConfig; diff --git a/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.d.ts b/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.d.ts index cbbb9de24..ef658826e 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.d.ts @@ -9,10 +9,5 @@ export declare type Response = { status: "OK"; tenants: TenantWithLoginMethods[]; }; -export default function listAllTenantsWithLoginMethods( - _: APIInterface, - __: string, - ___: APIOptions, - userContext: UserContext -): Promise; +export default function listAllTenantsWithLoginMethods(_: APIInterface, __: string, ___: APIOptions, userContext: UserContext): Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.js b/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.js index 27fa9c61a..fb3397bf0 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.js +++ b/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); const utils_1 = require("./utils"); diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.d.ts b/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.d.ts index ee2738561..2bc2bcfa6 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.d.ts @@ -1,20 +1,12 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - } - | { - status: "UNKNOWN_TENANT_ERROR"; - } - | { - status: "INVALID_CONFIG_ERROR"; - message: string; - }; -export default function updateTenantCoreConfig( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; +} | { + status: "UNKNOWN_TENANT_ERROR"; +} | { + status: "INVALID_CONFIG_ERROR"; + message: string; +}; +export default function updateTenantCoreConfig(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.js b/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.js index 98d4b45dc..f66aee851 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.js +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); async function updateTenantCoreConfig(_, tenantId, options, userContext) { @@ -26,7 +24,8 @@ async function updateTenantCoreConfig(_, tenantId, options, userContext) { }, userContext, }); - } catch (err) { + } + catch (err) { const errMsg = err.message; if (errMsg.includes("SuperTokens core threw an error for a ") && errMsg.includes("with status code: 400")) { return { diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.d.ts b/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.d.ts index 2c27af7ae..b7c034bc2 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.d.ts @@ -1,20 +1,12 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - } - | { - status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; - message: string; - } - | { - status: "UNKNOWN_TENANT_ERROR"; - }; -export default function updateTenantFirstFactor( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; +} | { + status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; + message: string; +} | { + status: "UNKNOWN_TENANT_ERROR"; +}; +export default function updateTenantFirstFactor(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.js b/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.js index 72964fd49..149cfc090 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.js +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); const utils_1 = require("./utils"); @@ -12,18 +10,14 @@ async function updateTenantFirstFactor(_, tenantId, options, userContext) { const { factorId, enable } = requestBody; const mtRecipe = recipe_1.default.getInstance(); if (enable === true) { - if ( - !(mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.allAvailableFirstFactors.includes(factorId)) - ) { + if (!(mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.allAvailableFirstFactors.includes(factorId))) { return { status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR", message: utils_1.getFactorNotAvailableMessage(factorId, mtRecipe.allAvailableFirstFactors), }; } } - const tenantRes = await (mtRecipe === null || mtRecipe === void 0 - ? void 0 - : mtRecipe.recipeInterfaceImpl.getTenant({ tenantId, userContext })); + const tenantRes = await (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.recipeInterfaceImpl.getTenant({ tenantId, userContext })); if (tenantRes === undefined) { return { status: "UNKNOWN_TENANT_ERROR", @@ -34,18 +28,17 @@ async function updateTenantFirstFactor(_, tenantId, options, userContext) { if (!firstFactors.includes(factorId)) { firstFactors.push(factorId); } - } else { + } + else { firstFactors = firstFactors.filter((f) => f !== factorId); } - await (mtRecipe === null || mtRecipe === void 0 - ? void 0 - : mtRecipe.recipeInterfaceImpl.createOrUpdateTenant({ - tenantId, - config: { - firstFactors, - }, - userContext, - })); + await (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.recipeInterfaceImpl.createOrUpdateTenant({ + tenantId, + config: { + firstFactors, + }, + userContext, + })); return { status: "OK", }; diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.d.ts b/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.d.ts index 74e206124..5c355687a 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.d.ts @@ -1,24 +1,15 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = - | { - status: "OK"; - isMFARequirementsForAuthOverridden: boolean; - } - | { - status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; - message: string; - } - | { - status: "MFA_NOT_INITIALIZED_ERROR"; - } - | { - status: "UNKNOWN_TENANT_ERROR"; - }; -export default function updateTenantSecondaryFactor( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export declare type Response = { + status: "OK"; + isMFARequirementsForAuthOverridden: boolean; +} | { + status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; + message: string; +} | { + status: "MFA_NOT_INITIALIZED_ERROR"; +} | { + status: "UNKNOWN_TENANT_ERROR"; +}; +export default function updateTenantSecondaryFactor(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.js b/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.js index 8729bcb42..f85fceada 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.js +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); const recipe_2 = __importDefault(require("../../../multifactorauth/recipe")); @@ -18,9 +16,7 @@ async function updateTenantSecondaryFactor(_, tenantId, options, userContext) { status: "MFA_NOT_INITIALIZED_ERROR", }; } - const tenantRes = await (mtRecipe === null || mtRecipe === void 0 - ? void 0 - : mtRecipe.recipeInterfaceImpl.getTenant({ tenantId, userContext })); + const tenantRes = await (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.recipeInterfaceImpl.getTenant({ tenantId, userContext })); if (tenantRes === undefined) { return { status: "UNKNOWN_TENANT_ERROR", @@ -35,25 +31,22 @@ async function updateTenantSecondaryFactor(_, tenantId, options, userContext) { }; } } - let secondaryFactors = utils_1.getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit( - tenantRes - ); + let secondaryFactors = utils_1.getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantRes); if (enable === true) { if (!secondaryFactors.includes(factorId)) { secondaryFactors.push(factorId); } - } else { + } + else { secondaryFactors = secondaryFactors.filter((f) => f !== factorId); } - await (mtRecipe === null || mtRecipe === void 0 - ? void 0 - : mtRecipe.recipeInterfaceImpl.createOrUpdateTenant({ - tenantId, - config: { - requiredSecondaryFactors: secondaryFactors.length > 0 ? secondaryFactors : null, - }, - userContext, - })); + await (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.recipeInterfaceImpl.createOrUpdateTenant({ + tenantId, + config: { + requiredSecondaryFactors: secondaryFactors.length > 0 ? secondaryFactors : null, + }, + userContext, + })); return { status: "OK", isMFARequirementsForAuthOverridden: mfaInstance.isGetMfaRequirementsForAuthOverridden, diff --git a/lib/build/recipe/dashboard/api/multitenancy/utils.d.ts b/lib/build/recipe/dashboard/api/multitenancy/utils.d.ts index 76df5b8c0..53cd451e2 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/utils.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/utils.d.ts @@ -1,10 +1,6 @@ // @ts-nocheck import { TenantConfig } from "../../../multitenancy/types"; -export declare function getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit( - tenantDetailsFromCore: TenantConfig -): string[]; -export declare function getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit( - tenantDetailsFromCore: TenantConfig -): string[]; +export declare function getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantDetailsFromCore: TenantConfig): string[]; +export declare function getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantDetailsFromCore: TenantConfig): string[]; export declare function factorIdToRecipe(factorId: string): string; export declare function getFactorNotAvailableMessage(factorId: string, availableFactors: string[]): string; diff --git a/lib/build/recipe/dashboard/api/multitenancy/utils.js b/lib/build/recipe/dashboard/api/multitenancy/utils.js index 2dcb998ed..95da4fc2f 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/utils.js +++ b/lib/build/recipe/dashboard/api/multitenancy/utils.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getFactorNotAvailableMessage = exports.factorIdToRecipe = exports.getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit = exports.getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit = void 0; const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -15,9 +13,11 @@ function getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantDe let mtInstance = recipe_1.default.getInstanceOrThrowError(); if (tenantDetailsFromCore.firstFactors !== undefined) { firstFactors = tenantDetailsFromCore.firstFactors; // highest priority, config from core - } else if (mtInstance.staticFirstFactors !== undefined) { + } + else if (mtInstance.staticFirstFactors !== undefined) { firstFactors = mtInstance.staticFirstFactors; // next priority, static config - } else { + } + else { // Fallback to all available factors (de-duplicated) firstFactors = Array.from(new Set(mtInstance.allAvailableFirstFactors)); } @@ -28,14 +28,12 @@ function getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantDe // enabled recipes in all cases irrespective of whether they are using MFA or not let validFirstFactors = []; for (const factorId of firstFactors) { - if ( - utils_1.isFactorConfiguredForTenant({ - tenantConfig: tenantDetailsFromCore, - allAvailableFirstFactors: mtInstance.allAvailableFirstFactors, - firstFactors: firstFactors, - factorId, - }) - ) { + if (utils_1.isFactorConfiguredForTenant({ + tenantConfig: tenantDetailsFromCore, + allAvailableFirstFactors: mtInstance.allAvailableFirstFactors, + firstFactors: firstFactors, + factorId, + })) { validFirstFactors.push(factorId); } } @@ -48,12 +46,7 @@ function getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKI return []; } let secondaryFactors = mfaInstance.getAllAvailableSecondaryFactorIds(tenantDetailsFromCore); - secondaryFactors = secondaryFactors.filter((factorId) => { - var _a; - return ((_a = tenantDetailsFromCore.requiredSecondaryFactors) !== null && _a !== void 0 ? _a : []).includes( - factorId - ); - }); + secondaryFactors = secondaryFactors.filter((factorId) => { var _a; return ((_a = tenantDetailsFromCore.requiredSecondaryFactors) !== null && _a !== void 0 ? _a : []).includes(factorId); }); return secondaryFactors; } exports.getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit = getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit; @@ -75,19 +68,12 @@ function getFactorNotAvailableMessage(factorId, availableFactors) { if (recipeName !== "Passwordless") { return `Please initialise ${recipeName} recipe to be able to use this login method`; } - const passwordlessFactors = [ - multifactorauth_1.FactorIds.LINK_EMAIL, - multifactorauth_1.FactorIds.LINK_PHONE, - multifactorauth_1.FactorIds.OTP_EMAIL, - multifactorauth_1.FactorIds.OTP_PHONE, - ]; + const passwordlessFactors = [multifactorauth_1.FactorIds.LINK_EMAIL, multifactorauth_1.FactorIds.LINK_PHONE, multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.OTP_PHONE]; const passwordlessFactorsNotAvailable = passwordlessFactors.filter((f) => !availableFactors.includes(f)); if (passwordlessFactorsNotAvailable.length === 4) { return `Please initialise Passwordless recipe to be able to use this login method`; } const [flowType, contactMethod] = factorId.split("-"); - return `Please ensure that Passwordless recipe is initialised with contactMethod: ${contactMethod.toUpperCase()} and flowType: ${ - flowType === "otp" ? "USER_INPUT_CODE" : "MAGIC_LINK" - }`; + return `Please ensure that Passwordless recipe is initialised with contactMethod: ${contactMethod.toUpperCase()} and flowType: ${flowType === "otp" ? "USER_INPUT_CODE" : "MAGIC_LINK"}`; } exports.getFactorNotAvailableMessage = getFactorNotAvailableMessage; diff --git a/lib/build/recipe/dashboard/api/search/tagsGet.d.ts b/lib/build/recipe/dashboard/api/search/tagsGet.d.ts index 68ed9b3fe..8824e6f5c 100644 --- a/lib/build/recipe/dashboard/api/search/tagsGet.d.ts +++ b/lib/build/recipe/dashboard/api/search/tagsGet.d.ts @@ -5,10 +5,5 @@ declare type TagsResponse = { status: "OK"; tags: string[]; }; -export declare const getSearchTags: ( - _: APIInterface, - ___: string, - options: APIOptions, - userContext: UserContext -) => Promise; +export declare const getSearchTags: (_: APIInterface, ___: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/search/tagsGet.js b/lib/build/recipe/dashboard/api/search/tagsGet.js index 06c87156f..4075a32d1 100644 --- a/lib/build/recipe/dashboard/api/search/tagsGet.js +++ b/lib/build/recipe/dashboard/api/search/tagsGet.js @@ -13,22 +13,16 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSearchTags = void 0; const querier_1 = require("../../../../querier"); const normalisedURLPath_1 = __importDefault(require("../../../../normalisedURLPath")); const getSearchTags = async (_, ___, options, userContext) => { let querier = querier_1.Querier.getNewInstanceOrThrowError(options.recipeId); - let tagsResponse = await querier.sendGetRequest( - new normalisedURLPath_1.default("/user/search/tags"), - {}, - userContext - ); + let tagsResponse = await querier.sendGetRequest(new normalisedURLPath_1.default("/user/search/tags"), {}, userContext); return tagsResponse; }; exports.getSearchTags = getSearchTags; diff --git a/lib/build/recipe/dashboard/api/signIn.js b/lib/build/recipe/dashboard/api/signIn.js index 5f52060a2..27c82654e 100644 --- a/lib/build/recipe/dashboard/api/signIn.js +++ b/lib/build/recipe/dashboard/api/signIn.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../../../error")); @@ -38,14 +36,10 @@ async function signIn(_, options, userContext) { }); } let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); - const signInResponse = await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/dashboard/signin"), - { - email, - password, - }, - userContext - ); + const signInResponse = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/dashboard/signin"), { + email, + password, + }, userContext); utils_1.send200Response(options.res, signInResponse); return true; } diff --git a/lib/build/recipe/dashboard/api/signOut.d.ts b/lib/build/recipe/dashboard/api/signOut.d.ts index b10c8509a..46f4609ac 100644 --- a/lib/build/recipe/dashboard/api/signOut.d.ts +++ b/lib/build/recipe/dashboard/api/signOut.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../types"; import { UserContext } from "../../../types"; -export default function signOut( - _: APIInterface, - ___: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signOut(_: APIInterface, ___: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/signOut.js b/lib/build/recipe/dashboard/api/signOut.js index 48e0d22fa..66864f736 100644 --- a/lib/build/recipe/dashboard/api/signOut.js +++ b/lib/build/recipe/dashboard/api/signOut.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const querier_1 = require("../../../querier"); @@ -26,16 +24,11 @@ async function signOut(_, ___, options, userContext) { var _a; if (options.config.authMode === "api-key") { utils_1.send200Response(options.res, { status: "OK" }); - } else { - const sessionIdFormAuthHeader = - (_a = options.req.getHeaderValue("authorization")) === null || _a === void 0 ? void 0 : _a.split(" ")[1]; + } + else { + const sessionIdFormAuthHeader = (_a = options.req.getHeaderValue("authorization")) === null || _a === void 0 ? void 0 : _a.split(" ")[1]; let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); - const sessionDeleteResponse = await querier.sendDeleteRequest( - new normalisedURLPath_1.default("/recipe/dashboard/session"), - {}, - { sessionId: sessionIdFormAuthHeader }, - userContext - ); + const sessionDeleteResponse = await querier.sendDeleteRequest(new normalisedURLPath_1.default("/recipe/dashboard/session"), {}, { sessionId: sessionIdFormAuthHeader }, userContext); utils_1.send200Response(options.res, sessionDeleteResponse); } return true; diff --git a/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.d.ts b/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.d.ts index d35ed6a22..ac7d16c42 100644 --- a/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.d.ts +++ b/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.d.ts @@ -2,27 +2,18 @@ import { APIInterface, APIOptions } from "../../../types"; import { User, UserContext } from "../../../../../types"; import RecipeUserId from "../../../../../recipeUserId"; -declare type Response = - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; - } - | { - status: "EMAIL_VALIDATION_ERROR"; - message: string; - } - | { - status: "PASSWORD_VALIDATION_ERROR"; - message: string; - }; -export declare const createEmailPasswordUser: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -) => Promise; +declare type Response = { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; +} | { + status: "EMAIL_ALREADY_EXISTS_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; +} | { + status: "EMAIL_VALIDATION_ERROR"; + message: string; +} | { + status: "PASSWORD_VALIDATION_ERROR"; + message: string; +}; +export declare const createEmailPasswordUser: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.js b/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.js index cf84423bc..0d082488d 100644 --- a/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.js +++ b/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.createEmailPasswordUser = void 0; const error_1 = __importDefault(require("../../../../../error")); @@ -27,7 +25,8 @@ const createEmailPasswordUser = async (_, tenantId, options, userContext) => { let emailPassword = undefined; try { emailPassword = recipe_1.default.getInstanceOrThrowError(); - } catch (error) { + } + catch (error) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; @@ -67,10 +66,9 @@ const createEmailPasswordUser = async (_, tenantId, options, userContext) => { // For some reason TS complains if I check the other status codes then throw... if (response.status === "OK" || response.status === "EMAIL_ALREADY_EXISTS_ERROR") { return response; - } else { - throw new Error( - "This should never happen: EmailPassword.signUp threw a session user related error without passing a session" - ); + } + else { + throw new Error("This should never happen: EmailPassword.signUp threw a session user related error without passing a session"); } }; exports.createEmailPasswordUser = createEmailPasswordUser; diff --git a/lib/build/recipe/dashboard/api/user/create/passwordlessUser.d.ts b/lib/build/recipe/dashboard/api/user/create/passwordlessUser.d.ts index 2518b7a8c..937c286d7 100644 --- a/lib/build/recipe/dashboard/api/user/create/passwordlessUser.d.ts +++ b/lib/build/recipe/dashboard/api/user/create/passwordlessUser.d.ts @@ -2,28 +2,19 @@ import { APIInterface, APIOptions } from "../../../types"; import { User } from "../../../../../types"; import RecipeUserId from "../../../../../recipeUserId"; -declare type Response = - | { - status: string; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "FEATURE_NOT_ENABLED_ERROR"; - } - | { - status: "EMAIL_VALIDATION_ERROR"; - message: string; - } - | { - status: "PHONE_VALIDATION_ERROR"; - message: string; - }; -export declare const createPasswordlessUser: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - __: any -) => Promise; +declare type Response = { + status: string; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; +} | { + status: "FEATURE_NOT_ENABLED_ERROR"; +} | { + status: "EMAIL_VALIDATION_ERROR"; + message: string; +} | { + status: "PHONE_VALIDATION_ERROR"; + message: string; +}; +export declare const createPasswordlessUser: (_: APIInterface, tenantId: string, options: APIOptions, __: any) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/user/create/passwordlessUser.js b/lib/build/recipe/dashboard/api/user/create/passwordlessUser.js index 05e44821e..afe2777d4 100644 --- a/lib/build/recipe/dashboard/api/user/create/passwordlessUser.js +++ b/lib/build/recipe/dashboard/api/user/create/passwordlessUser.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.createPasswordlessUser = void 0; const error_1 = __importDefault(require("../../../../../error")); @@ -28,7 +26,8 @@ const createPasswordlessUser = async (_, tenantId, options, __) => { let passwordlessRecipe = undefined; try { passwordlessRecipe = recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; @@ -42,11 +41,9 @@ const createPasswordlessUser = async (_, tenantId, options, __) => { message: "Please provide exactly one of email or phoneNumber", }); } - if ( - email !== undefined && + if (email !== undefined && (passwordlessRecipe.config.contactMethod === "EMAIL" || - passwordlessRecipe.config.contactMethod === "EMAIL_OR_PHONE") - ) { + passwordlessRecipe.config.contactMethod === "EMAIL_OR_PHONE")) { email = email.trim(); let validationError = undefined; validationError = await passwordlessRecipe.config.validateEmailAddress(email, tenantId); @@ -57,11 +54,9 @@ const createPasswordlessUser = async (_, tenantId, options, __) => { }; } } - if ( - phoneNumber !== undefined && + if (phoneNumber !== undefined && (passwordlessRecipe.config.contactMethod === "PHONE" || - passwordlessRecipe.config.contactMethod === "EMAIL_OR_PHONE") - ) { + passwordlessRecipe.config.contactMethod === "EMAIL_OR_PHONE")) { let validationError = undefined; validationError = await passwordlessRecipe.config.validatePhoneNumber(phoneNumber, tenantId); if (validationError !== undefined) { @@ -75,12 +70,11 @@ const createPasswordlessUser = async (_, tenantId, options, __) => { // this can come here if the user has provided their own impl of validatePhoneNumber and // the phone number is valid according to their impl, but not according to the libphonenumber-js lib. phoneNumber = phoneNumber.trim(); - } else { + } + else { phoneNumber = parsedPhoneNumber.format("E.164"); } } - return await passwordless_1.default.signInUp( - email !== undefined ? { email, tenantId } : { phoneNumber: phoneNumber, tenantId } - ); + return await passwordless_1.default.signInUp(email !== undefined ? { email, tenantId } : { phoneNumber: phoneNumber, tenantId }); }; exports.createPasswordlessUser = createPasswordlessUser; diff --git a/lib/build/recipe/dashboard/api/userdetails/userDelete.js b/lib/build/recipe/dashboard/api/userdetails/userDelete.js index 7840c2141..7fb9e72ef 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userDelete.js +++ b/lib/build/recipe/dashboard/api/userdetails/userDelete.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userDelete = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -14,8 +12,7 @@ const userDelete = async (_, ___, options, __) => { if (removeAllLinkedAccountsQueryValue !== undefined) { removeAllLinkedAccountsQueryValue = removeAllLinkedAccountsQueryValue.trim().toLowerCase(); } - const removeAllLinkedAccounts = - removeAllLinkedAccountsQueryValue === undefined ? undefined : removeAllLinkedAccountsQueryValue === "true"; + const removeAllLinkedAccounts = removeAllLinkedAccountsQueryValue === undefined ? undefined : removeAllLinkedAccountsQueryValue === "true"; if (userId === undefined || userId === "") { throw new error_1.default({ message: "Missing required parameter 'userId'", diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyGet.js b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyGet.js index affe7a2ae..675e92f01 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyGet.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userEmailVerifyGet = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -21,16 +19,13 @@ const userEmailVerifyGet = async (_, ___, options, userContext) => { } try { recipe_1.default.getInstanceOrThrowError(); - } catch (e) { + } + catch (e) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; } - const response = await emailverification_1.default.isEmailVerified( - new recipeUserId_1.default(recipeUserId), - undefined, - userContext - ); + const response = await emailverification_1.default.isEmailVerified(new recipeUserId_1.default(recipeUserId), undefined, userContext); return { status: "OK", isVerified: response, diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.d.ts index 9b92ba163..579bc5ceb 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.d.ts @@ -4,10 +4,5 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK"; }; -export declare const userEmailVerifyPut: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -) => Promise; +export declare const userEmailVerifyPut: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.js b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.js index 604be3c27..fed7bba1e 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userEmailVerifyPut = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -26,33 +24,20 @@ const userEmailVerifyPut = async (_, tenantId, options, userContext) => { }); } if (verified) { - const tokenResponse = await emailverification_1.default.createEmailVerificationToken( - tenantId, - new recipeUserId_1.default(recipeUserId), - undefined, - userContext - ); + const tokenResponse = await emailverification_1.default.createEmailVerificationToken(tenantId, new recipeUserId_1.default(recipeUserId), undefined, userContext); if (tokenResponse.status === "EMAIL_ALREADY_VERIFIED_ERROR") { return { status: "OK", }; } - const verifyResponse = await emailverification_1.default.verifyEmailUsingToken( - tenantId, - tokenResponse.token, - undefined, - userContext - ); + const verifyResponse = await emailverification_1.default.verifyEmailUsingToken(tenantId, tokenResponse.token, undefined, userContext); if (verifyResponse.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { // This should never happen because we consume the token immediately after creating it throw new Error("Should not come here"); } - } else { - await emailverification_1.default.unverifyEmail( - new recipeUserId_1.default(recipeUserId), - undefined, - userContext - ); + } + else { + await emailverification_1.default.unverifyEmail(new recipeUserId_1.default(recipeUserId), undefined, userContext); } return { status: "OK", diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.d.ts b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.d.ts index 16b68a15c..22eba52b8 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.d.ts @@ -4,10 +4,5 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK" | "EMAIL_ALREADY_VERIFIED_ERROR"; }; -export declare const userEmailVerifyTokenPost: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -) => Promise; +export declare const userEmailVerifyTokenPost: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.js b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.js index 6eed7be0d..23b94b39c 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.js +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userEmailVerifyTokenPost = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -25,12 +23,6 @@ const userEmailVerifyTokenPost = async (_, tenantId, options, userContext) => { type: error_1.default.BAD_INPUT_ERROR, }); } - return await emailverification_1.default.sendEmailVerificationEmail( - tenantId, - user.id, - __1.convertToRecipeUserId(recipeUserId), - undefined, - userContext - ); + return await emailverification_1.default.sendEmailVerificationEmail(tenantId, user.id, __1.convertToRecipeUserId(recipeUserId), undefined, userContext); }; exports.userEmailVerifyTokenPost = userEmailVerifyTokenPost; diff --git a/lib/build/recipe/dashboard/api/userdetails/userGet.js b/lib/build/recipe/dashboard/api/userdetails/userGet.js index 9affeabb7..791cf025d 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userGet.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userGet = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -26,23 +24,18 @@ const userGet = async (_, ___, options, userContext) => { } try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "OK", - user: Object.assign(Object.assign({}, user.toJson()), { - firstName: "FEATURE_NOT_ENABLED", - lastName: "FEATURE_NOT_ENABLED", - }), + user: Object.assign(Object.assign({}, user.toJson()), { firstName: "FEATURE_NOT_ENABLED", lastName: "FEATURE_NOT_ENABLED" }), }; } const userMetaData = await usermetadata_1.default.getUserMetadata(userId, userContext); const { first_name, last_name } = userMetaData.metadata; return { status: "OK", - user: Object.assign(Object.assign({}, user.toJson()), { - firstName: first_name === undefined ? "" : first_name, - lastName: last_name === undefined ? "" : last_name, - }), + user: Object.assign(Object.assign({}, user.toJson()), { firstName: first_name === undefined ? "" : first_name, lastName: last_name === undefined ? "" : last_name }), }; }; exports.userGet = userGet; diff --git a/lib/build/recipe/dashboard/api/userdetails/userMetadataGet.js b/lib/build/recipe/dashboard/api/userdetails/userMetadataGet.js index b2e71cdce..5e8a80c5b 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userMetadataGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userMetadataGet.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userMetaDataGet = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -19,7 +17,8 @@ const userMetaDataGet = async (_, ___, options, userContext) => { } try { recipe_1.default.getInstanceOrThrowError(); - } catch (e) { + } + catch (e) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.d.ts index f6099dc09..5fafc9caa 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.d.ts @@ -4,10 +4,5 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK"; }; -export declare const userMetadataPut: ( - _: APIInterface, - ___: string, - options: APIOptions, - userContext: UserContext -) => Promise; +export declare const userMetadataPut: (_: APIInterface, ___: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.js b/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.js index 0b77de89f..38a4df027 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userMetadataPut = void 0; const recipe_1 = __importDefault(require("../../../usermetadata/recipe")); @@ -39,7 +37,8 @@ const userMetadataPut = async (_, ___, options, userContext) => { if (parsedData === null) { throw new Error(); } - } catch (e) { + } + catch (e) { throw new error_1.default({ message: "'data' must be a valid JSON body", type: error_1.default.BAD_INPUT_ERROR, diff --git a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts index eb0b5a928..ce43b0f15 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts @@ -1,18 +1,11 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -declare type Response = - | { - status: "OK"; - } - | { - status: "INVALID_PASSWORD_ERROR"; - error: string; - }; -export declare const userPasswordPut: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -) => Promise; +declare type Response = { + status: "OK"; +} | { + status: "INVALID_PASSWORD_ERROR"; + error: string; +}; +export declare const userPasswordPut: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js index 9045520c5..dfc2230b1 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userPasswordPut = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -31,14 +29,13 @@ const userPasswordPut = async (_, tenantId, options, userContext) => { tenantIdForPasswordPolicy: tenantId, userContext, }); - if ( - updateResponse.status === "UNKNOWN_USER_ID_ERROR" || + if (updateResponse.status === "UNKNOWN_USER_ID_ERROR" || updateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR" || - updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" - ) { + updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { // Techincally it can but its an edge case so we assume that it wont throw new Error("Should never come here"); - } else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { + } + else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { return { status: "INVALID_PASSWORD_ERROR", error: updateResponse.failureReason, diff --git a/lib/build/recipe/dashboard/api/userdetails/userPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userPut.d.ts index b4156bb5c..f4b1b5471 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userPut.d.ts @@ -1,36 +1,24 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -declare type Response = - | { - status: "OK"; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - | { - status: "INVALID_EMAIL_ERROR"; - error: string; - } - | { - status: "PHONE_ALREADY_EXISTS_ERROR"; - } - | { - status: "INVALID_PHONE_ERROR"; - error: string; - } - | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - error: string; - } - | { - status: "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; - error: string; - }; -export declare const userPut: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -) => Promise; +declare type Response = { + status: "OK"; +} | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; +} | { + status: "INVALID_EMAIL_ERROR"; + error: string; +} | { + status: "PHONE_ALREADY_EXISTS_ERROR"; +} | { + status: "INVALID_PHONE_ERROR"; + error: string; +} | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + error: string; +} | { + status: "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; + error: string; +}; +export declare const userPut: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userPut.js b/lib/build/recipe/dashboard/api/userdetails/userPut.js index ce83eb5be..0563fe54c 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userPut.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userPut = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -19,9 +17,7 @@ const utils_2 = require("../../../passwordless/utils"); const recipeUserId_1 = __importDefault(require("../../../../recipeUserId")); const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, userContext) => { if (recipeId === "emailpassword") { - let emailFormFields = recipe_1.default - .getInstanceOrThrowError() - .config.signUpFeature.formFields.filter((field) => field.id === constants_1.FORM_FIELD_EMAIL_ID); + let emailFormFields = recipe_1.default.getInstanceOrThrowError().config.signUpFeature.formFields.filter((field) => field.id === constants_1.FORM_FIELD_EMAIL_ID); let validationError = await emailFormFields[0].validate(email, tenantId, userContext); if (validationError !== undefined) { return { @@ -38,12 +34,14 @@ const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, u return { status: "EMAIL_ALREADY_EXISTS_ERROR", }; - } else if (emailUpdateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { + } + else if (emailUpdateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { return { status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", reason: emailUpdateResponse.reason, }; - } else if (emailUpdateResponse.status === "UNKNOWN_USER_ID_ERROR") { + } + else if (emailUpdateResponse.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); } return { @@ -60,7 +58,8 @@ const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, u isValidEmail = false; validationError = validationResult; } - } else { + } + else { const validationResult = await passwordlessConfig.validateEmailAddress(email, tenantId); if (validationResult !== undefined) { isValidEmail = false; @@ -86,10 +85,8 @@ const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, u status: "EMAIL_ALREADY_EXISTS_ERROR", }; } - if ( - updateResult.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" || - updateResult.status === "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" - ) { + if (updateResult.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" || + updateResult.status === "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR") { return { status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", reason: updateResult.reason, @@ -114,7 +111,8 @@ const updatePhoneForRecipeId = async (recipeUserId, phone, tenantId, userContext isValidPhone = false; validationError = validationResult; } - } else { + } + else { const validationResult = await passwordlessConfig.validatePhoneNumber(phone, tenantId); if (validationResult !== undefined) { isValidPhone = false; @@ -200,11 +198,7 @@ const userPut = async (_, tenantId, options, userContext) => { type: error_1.default.BAD_INPUT_ERROR, }); } - let userResponse = await utils_1.getUserForRecipeId( - new recipeUserId_1.default(recipeUserId), - recipeId, - userContext - ); + let userResponse = await utils_1.getUserForRecipeId(new recipeUserId_1.default(recipeUserId), recipeId, userContext); if (userResponse.user === undefined || userResponse.recipe === undefined) { throw new Error("Should never come here"); } @@ -213,7 +207,8 @@ const userPut = async (_, tenantId, options, userContext) => { try { recipe_3.default.getInstanceOrThrowError(); isRecipeInitialised = true; - } catch (_) { + } + catch (_) { // no op } if (isRecipeInitialised) { @@ -228,13 +223,7 @@ const userPut = async (_, tenantId, options, userContext) => { } } if (email.trim() !== "") { - const emailUpdateResponse = await updateEmailForRecipeId( - userResponse.recipe, - new recipeUserId_1.default(recipeUserId), - email.trim(), - tenantId, - userContext - ); + const emailUpdateResponse = await updateEmailForRecipeId(userResponse.recipe, new recipeUserId_1.default(recipeUserId), email.trim(), tenantId, userContext); if (emailUpdateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { return { error: emailUpdateResponse.reason, @@ -246,12 +235,7 @@ const userPut = async (_, tenantId, options, userContext) => { } } if (phone.trim() !== "") { - const phoneUpdateResponse = await updatePhoneForRecipeId( - new recipeUserId_1.default(recipeUserId), - phone.trim(), - tenantId, - userContext - ); + const phoneUpdateResponse = await updatePhoneForRecipeId(new recipeUserId_1.default(recipeUserId), phone.trim(), tenantId, userContext); if (phoneUpdateResponse.status === "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR") { return { error: phoneUpdateResponse.reason, diff --git a/lib/build/recipe/dashboard/api/userdetails/userSessionsGet.js b/lib/build/recipe/dashboard/api/userdetails/userSessionsGet.js index 2528cf4ff..c9ac1cab9 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userSessionsGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userSessionsGet.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userSessionsGet = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -20,21 +18,20 @@ const userSessionsGet = async (_, ___, options, userContext) => { let sessions = []; let sessionInfoPromises = []; for (let i = 0; i < response.length; i++) { - sessionInfoPromises.push( - new Promise(async (res, rej) => { - try { - const sessionResponse = await session_1.default.getSessionInformation(response[i], userContext); - if (sessionResponse !== undefined) { - const accessTokenPayload = sessionResponse.customClaimsInAccessTokenPayload; - delete sessionResponse.customClaimsInAccessTokenPayload; - sessions[i] = Object.assign(Object.assign({}, sessionResponse), { accessTokenPayload }); - } - res(); - } catch (e) { - rej(e); + sessionInfoPromises.push(new Promise(async (res, rej) => { + try { + const sessionResponse = await session_1.default.getSessionInformation(response[i], userContext); + if (sessionResponse !== undefined) { + const accessTokenPayload = sessionResponse.customClaimsInAccessTokenPayload; + delete sessionResponse.customClaimsInAccessTokenPayload; + sessions[i] = Object.assign(Object.assign({}, sessionResponse), { accessTokenPayload }); } - }) - ); + res(); + } + catch (e) { + rej(e); + } + })); } await Promise.all(sessionInfoPromises); return { diff --git a/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.d.ts b/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.d.ts index 1e9a3d118..8de8f408c 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.d.ts @@ -4,10 +4,5 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK"; }; -export declare const userSessionsPost: ( - _: APIInterface, - ___: string, - options: APIOptions, - userContext: UserContext -) => Promise; +export declare const userSessionsPost: (_: APIInterface, ___: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.js b/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.js index 51ab7f94a..f744843f6 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.js +++ b/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userSessionsPost = void 0; const error_1 = __importDefault(require("../../../../error")); diff --git a/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.d.ts b/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.d.ts index 15b661f25..4524da11c 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.d.ts @@ -4,10 +4,5 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK"; }; -export declare const userUnlink: ( - _: APIInterface, - ___: string, - options: APIOptions, - userContext: UserContext -) => Promise; +export declare const userUnlink: (_: APIInterface, ___: string, options: APIOptions, userContext: UserContext) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.js b/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.js index 11283fce1..7a4934507 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.userUnlink = void 0; const error_1 = __importDefault(require("../../../../error")); diff --git a/lib/build/recipe/dashboard/api/userroles/addRoleToUser.d.ts b/lib/build/recipe/dashboard/api/userroles/addRoleToUser.d.ts index 8485ed162..64a486f8d 100644 --- a/lib/build/recipe/dashboard/api/userroles/addRoleToUser.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/addRoleToUser.d.ts @@ -1,17 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; -declare const addRoleToUser: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - __: any -) => Promise< - | { - status: "OK"; - didUserAlreadyHaveRole: boolean; - } - | { - status: "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; - } ->; +declare const addRoleToUser: (_: APIInterface, tenantId: string, options: APIOptions, __: any) => Promise<{ + status: "OK"; + didUserAlreadyHaveRole: boolean; +} | { + status: "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; +}>; export default addRoleToUser; diff --git a/lib/build/recipe/dashboard/api/userroles/addRoleToUser.js b/lib/build/recipe/dashboard/api/userroles/addRoleToUser.js index c7b1e0397..4f2319581 100644 --- a/lib/build/recipe/dashboard/api/userroles/addRoleToUser.js +++ b/lib/build/recipe/dashboard/api/userroles/addRoleToUser.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../userroles")); @@ -11,7 +9,8 @@ const error_1 = __importDefault(require("../../../../error")); const addRoleToUser = async (_, tenantId, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/getRolesForUser.d.ts b/lib/build/recipe/dashboard/api/userroles/getRolesForUser.d.ts index 0de8b9c3e..1dcc29877 100644 --- a/lib/build/recipe/dashboard/api/userroles/getRolesForUser.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/getRolesForUser.d.ts @@ -1,17 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; -declare const getRolesForUser: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - __: any -) => Promise< - | { - status: "OK"; - roles: string[]; - } - | { - status: "FEATURE_NOT_ENABLED_ERROR"; - } ->; +declare const getRolesForUser: (_: APIInterface, tenantId: string, options: APIOptions, __: any) => Promise<{ + status: "OK"; + roles: string[]; +} | { + status: "FEATURE_NOT_ENABLED_ERROR"; +}>; export default getRolesForUser; diff --git a/lib/build/recipe/dashboard/api/userroles/getRolesForUser.js b/lib/build/recipe/dashboard/api/userroles/getRolesForUser.js index be76ed593..a9b079815 100644 --- a/lib/build/recipe/dashboard/api/userroles/getRolesForUser.js +++ b/lib/build/recipe/dashboard/api/userroles/getRolesForUser.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const userroles_1 = __importDefault(require("../../../userroles")); const recipe_1 = __importDefault(require("../../../userroles/recipe")); @@ -12,7 +10,8 @@ const getRolesForUser = async (_, tenantId, options, __) => { const userId = options.req.getKeyValueFromQuery("userId"); try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.d.ts b/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.d.ts index 206ddc1af..2c4b06094 100644 --- a/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.d.ts @@ -1,17 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../../types"; -declare const getPermissionsForRole: ( - _: APIInterface, - ___: string, - options: APIOptions, - __: any -) => Promise< - | { - status: "OK"; - permissions: string[]; - } - | { - status: "FEATURE_NOT_ENABLED_ERROR" | "UNKNOWN_ROLE_ERROR"; - } ->; +declare const getPermissionsForRole: (_: APIInterface, ___: string, options: APIOptions, __: any) => Promise<{ + status: "OK"; + permissions: string[]; +} | { + status: "FEATURE_NOT_ENABLED_ERROR" | "UNKNOWN_ROLE_ERROR"; +}>; export default getPermissionsForRole; diff --git a/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.js b/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.js index fac738710..329519039 100644 --- a/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.js +++ b/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../../userroles")); @@ -11,7 +9,8 @@ const error_1 = __importDefault(require("../../../../../error")); const getPermissionsForRole = async (_, ___, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.d.ts b/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.d.ts index e4904b004..ec4a827ad 100644 --- a/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.d.ts @@ -1,11 +1,6 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../../types"; -declare const removePermissionsFromRole: ( - _: APIInterface, - ___: string, - options: APIOptions, - __: any -) => Promise<{ +declare const removePermissionsFromRole: (_: APIInterface, ___: string, options: APIOptions, __: any) => Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; }>; export default removePermissionsFromRole; diff --git a/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.js b/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.js index cab2e0a91..73209a3f4 100644 --- a/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.js +++ b/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../../userroles")); @@ -11,7 +9,8 @@ const error_1 = __importDefault(require("../../../../../error")); const removePermissionsFromRole = async (_, ___, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/removeUserRole.d.ts b/lib/build/recipe/dashboard/api/userroles/removeUserRole.d.ts index bdf782e49..f771637d1 100644 --- a/lib/build/recipe/dashboard/api/userroles/removeUserRole.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/removeUserRole.d.ts @@ -1,17 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; -declare const removeUserRole: ( - _: APIInterface, - tenantId: string, - options: APIOptions, - __: any -) => Promise< - | { - status: "OK"; - didUserHaveRole: boolean; - } - | { - status: "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; - } ->; +declare const removeUserRole: (_: APIInterface, tenantId: string, options: APIOptions, __: any) => Promise<{ + status: "OK"; + didUserHaveRole: boolean; +} | { + status: "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; +}>; export default removeUserRole; diff --git a/lib/build/recipe/dashboard/api/userroles/removeUserRole.js b/lib/build/recipe/dashboard/api/userroles/removeUserRole.js index 025c93fcd..750534243 100644 --- a/lib/build/recipe/dashboard/api/userroles/removeUserRole.js +++ b/lib/build/recipe/dashboard/api/userroles/removeUserRole.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../userroles")); @@ -11,7 +9,8 @@ const error_1 = __importDefault(require("../../../../error")); const removeUserRole = async (_, tenantId, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.d.ts b/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.d.ts index 75663bcb0..7193f19c2 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.d.ts @@ -1,17 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../../types"; -declare const createRoleOrAddPermissions: ( - _: APIInterface, - __: string, - options: APIOptions, - ___: any -) => Promise< - | { - status: "OK"; - createdNewRole: boolean; - } - | { - status: "FEATURE_NOT_ENABLED_ERROR"; - } ->; +declare const createRoleOrAddPermissions: (_: APIInterface, __: string, options: APIOptions, ___: any) => Promise<{ + status: "OK"; + createdNewRole: boolean; +} | { + status: "FEATURE_NOT_ENABLED_ERROR"; +}>; export default createRoleOrAddPermissions; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.js b/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.js index 1540fe51d..f2f3f2310 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.js +++ b/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../../userroles")); @@ -11,7 +9,8 @@ const error_1 = __importDefault(require("../../../../../error")); const createRoleOrAddPermissions = async (_, __, options, ___) => { try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.d.ts b/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.d.ts index 14e5cf5b7..4dd5dc9af 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.d.ts @@ -1,17 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../../types"; -declare const deleteRole: ( - _: APIInterface, - ___: string, - options: APIOptions, - __: any -) => Promise< - | { - status: "OK"; - didRoleExist: boolean; - } - | { - status: "FEATURE_NOT_ENABLED_ERROR"; - } ->; +declare const deleteRole: (_: APIInterface, ___: string, options: APIOptions, __: any) => Promise<{ + status: "OK"; + didRoleExist: boolean; +} | { + status: "FEATURE_NOT_ENABLED_ERROR"; +}>; export default deleteRole; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.js b/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.js index 3814268fb..ac282b88c 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.js +++ b/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../../userroles")); @@ -11,7 +9,8 @@ const error_1 = __importDefault(require("../../../../../error")); const deleteRole = async (_, ___, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/getAllRoles.js b/lib/build/recipe/dashboard/api/userroles/roles/getAllRoles.js index bce2b636d..c85556ed7 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/getAllRoles.js +++ b/lib/build/recipe/dashboard/api/userroles/roles/getAllRoles.js @@ -1,16 +1,15 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const userroles_1 = __importDefault(require("../../../../userroles")); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const getAllRoles = async (_, __, ____) => { try { recipe_1.default.getInstanceOrThrowError(); - } catch (_) { + } + catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/usersCountGet.d.ts b/lib/build/recipe/dashboard/api/usersCountGet.d.ts index 2f90538df..da09c8db8 100644 --- a/lib/build/recipe/dashboard/api/usersCountGet.d.ts +++ b/lib/build/recipe/dashboard/api/usersCountGet.d.ts @@ -5,9 +5,4 @@ export declare type Response = { status: "OK"; count: number; }; -export default function usersCountGet( - _: APIInterface, - tenantId: string, - __: APIOptions, - userContext: UserContext -): Promise; +export default function usersCountGet(_: APIInterface, tenantId: string, __: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/dashboard/api/usersCountGet.js b/lib/build/recipe/dashboard/api/usersCountGet.js index 55757ab2d..d85a1e462 100644 --- a/lib/build/recipe/dashboard/api/usersCountGet.js +++ b/lib/build/recipe/dashboard/api/usersCountGet.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_1 = __importDefault(require("../../../supertokens")); async function usersCountGet(_, tenantId, __, userContext) { diff --git a/lib/build/recipe/dashboard/api/usersGet.d.ts b/lib/build/recipe/dashboard/api/usersGet.d.ts index 00834f077..5bfb9dd24 100644 --- a/lib/build/recipe/dashboard/api/usersGet.d.ts +++ b/lib/build/recipe/dashboard/api/usersGet.d.ts @@ -6,8 +6,6 @@ export declare type Response = { users: UserWithFirstAndLastName[]; }; export default function usersGet(_: APIInterface, tenantId: string, options: APIOptions): Promise; -export declare function getSearchParamsFromURL( - path: string -): { +export declare function getSearchParamsFromURL(path: string): { [key: string]: string; }; diff --git a/lib/build/recipe/dashboard/api/usersGet.js b/lib/build/recipe/dashboard/api/usersGet.js index d843dabdd..00d8c6ff4 100644 --- a/lib/build/recipe/dashboard/api/usersGet.js +++ b/lib/build/recipe/dashboard/api/usersGet.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSearchParamsFromURL = void 0; const error_1 = __importDefault(require("../../../error")); @@ -31,24 +29,24 @@ async function usersGet(_, tenantId, options) { } let paginationToken = options.req.getKeyValueFromQuery("paginationToken"); const query = getSearchParamsFromURL(options.req.getOriginalURL()); - let usersResponse = - timeJoinedOrder === "DESC" - ? await __1.getUsersNewestFirst({ - tenantId, - query, - limit: parseInt(limit), - paginationToken, - }) - : await __1.getUsersOldestFirst({ - tenantId, - query, - limit: parseInt(limit), - paginationToken, - }); + let usersResponse = timeJoinedOrder === "DESC" + ? await __1.getUsersNewestFirst({ + tenantId, + query, + limit: parseInt(limit), + paginationToken, + }) + : await __1.getUsersOldestFirst({ + tenantId, + query, + limit: parseInt(limit), + paginationToken, + }); // If the UserMetaData recipe has been initialised, fetch first and last name try { recipe_1.default.getInstanceOrThrowError(); - } catch (e) { + } + catch (e) { // Recipe has not been initialised, return without first name and last name return { status: "OK", @@ -60,23 +58,18 @@ async function usersGet(_, tenantId, options) { let metaDataFetchPromises = []; for (let i = 0; i < usersResponse.users.length; i++) { const userObj = usersResponse.users[i].toJson(); - metaDataFetchPromises.push( - () => - new Promise(async (resolve, reject) => { - try { - const userMetaDataResponse = await usermetadata_1.default.getUserMetadata(userObj.id); - const { first_name, last_name } = userMetaDataResponse.metadata; - updatedUsersArray[i] = Object.assign(Object.assign({}, userObj), { - firstName: first_name, - lastName: last_name, - }); - resolve(true); - } catch (e) { - // Something went wrong when fetching user meta data - reject(e); - } - }) - ); + metaDataFetchPromises.push(() => new Promise(async (resolve, reject) => { + try { + const userMetaDataResponse = await usermetadata_1.default.getUserMetadata(userObj.id); + const { first_name, last_name } = userMetaDataResponse.metadata; + updatedUsersArray[i] = Object.assign(Object.assign({}, userObj), { firstName: first_name, lastName: last_name }); + resolve(true); + } + catch (e) { + // Something went wrong when fetching user meta data + reject(e); + } + })); } let promiseArrayStartPosition = 0; let batchSize = 5; diff --git a/lib/build/recipe/dashboard/api/validateKey.js b/lib/build/recipe/dashboard/api/validateKey.js index 4e1df7946..b8655fc0d 100644 --- a/lib/build/recipe/dashboard/api/validateKey.js +++ b/lib/build/recipe/dashboard/api/validateKey.js @@ -21,7 +21,8 @@ async function validateKey(_, options, userContext) { options.res.sendJSONResponse({ status: "OK", }); - } else { + } + else { utils_1.sendUnauthorisedAccess(options.res); } return true; diff --git a/lib/build/recipe/dashboard/error.js b/lib/build/recipe/dashboard/error.js index 469d8e656..cd2f594ea 100644 --- a/lib/build/recipe/dashboard/error.js +++ b/lib/build/recipe/dashboard/error.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2022, VRAI Labs and/or its affiliates. All rights reserved. * diff --git a/lib/build/recipe/dashboard/index.js b/lib/build/recipe/dashboard/index.js index 7ad60228f..772d2760c 100644 --- a/lib/build/recipe/dashboard/index.js +++ b/lib/build/recipe/dashboard/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.init = void 0; /* Copyright (c) 2022, VRAI Labs and/or its affiliates. All rights reserved. @@ -21,7 +19,8 @@ exports.init = void 0; * under the License. */ const recipe_1 = __importDefault(require("./recipe")); -class Wrapper {} +class Wrapper { +} exports.default = Wrapper; Wrapper.init = recipe_1.default.init; exports.init = Wrapper.init; diff --git a/lib/build/recipe/dashboard/recipe.d.ts b/lib/build/recipe/dashboard/recipe.d.ts index 8fbe6583e..fec1cb7b1 100644 --- a/lib/build/recipe/dashboard/recipe.d.ts +++ b/lib/build/recipe/dashboard/recipe.d.ts @@ -17,15 +17,7 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - __: NormalisedURLPath, - ___: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, __: NormalisedURLPath, ___: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: error, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is error; diff --git a/lib/build/recipe/dashboard/recipe.js b/lib/build/recipe/dashboard/recipe.js index 5f68a6add..647b8c63b 100644 --- a/lib/build/recipe/dashboard/recipe.js +++ b/lib/build/recipe/dashboard/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const recipeModule_1 = __importDefault(require("../../recipeModule")); @@ -83,345 +81,259 @@ class Recipe extends recipeModule_1.default { }, { id: constants_1.DASHBOARD_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase("/roles") - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase("/roles")), disabled: false, method: "get", }, { id: constants_1.DASHBOARD_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase("/tenants") - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase("/tenants")), disabled: false, method: "get", }, { id: constants_1.SIGN_IN_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.SIGN_IN_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.SIGN_IN_API)), disabled: false, method: "post", }, { id: constants_1.VALIDATE_KEY_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.VALIDATE_KEY_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.VALIDATE_KEY_API)), disabled: false, method: "post", }, { id: constants_1.SIGN_OUT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.SIGN_OUT_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.SIGN_OUT_API)), disabled: false, method: "post", }, { id: constants_1.USERS_LIST_GET_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERS_LIST_GET_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERS_LIST_GET_API)), disabled: false, method: "get", }, { id: constants_1.USERS_COUNT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERS_COUNT_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERS_COUNT_API)), disabled: false, method: "get", }, { id: constants_1.USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_API)), disabled: false, method: "get", }, { id: constants_1.USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_API)), disabled: false, method: "post", }, { id: constants_1.USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_API)), disabled: false, method: "put", }, { id: constants_1.USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_API)), disabled: false, method: "delete", }, { id: constants_1.USER_EMAIL_VERIFY_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_API)), disabled: false, method: "get", }, { id: constants_1.USER_EMAIL_VERIFY_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_API)), disabled: false, method: "put", }, { id: constants_1.USER_METADATA_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_METADATA_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_METADATA_API)), disabled: false, method: "get", }, { id: constants_1.USER_METADATA_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_METADATA_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_METADATA_API)), disabled: false, method: "put", }, { id: constants_1.USER_SESSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_SESSIONS_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_SESSIONS_API)), disabled: false, method: "get", }, { id: constants_1.USER_SESSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_SESSIONS_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_SESSIONS_API)), disabled: false, method: "post", }, { id: constants_1.USER_PASSWORD_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_PASSWORD_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_PASSWORD_API)), disabled: false, method: "put", }, { id: constants_1.USER_EMAIL_VERIFY_TOKEN_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_TOKEN_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_TOKEN_API)), disabled: false, method: "post", }, { id: constants_1.SEARCH_TAGS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.SEARCH_TAGS_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.SEARCH_TAGS_API)), disabled: false, method: "get", }, { id: constants_1.DASHBOARD_ANALYTICS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.DASHBOARD_ANALYTICS_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.DASHBOARD_ANALYTICS_API)), disabled: false, method: "post", }, { id: constants_1.UNLINK_USER, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.UNLINK_USER) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.UNLINK_USER)), disabled: false, method: "get", }, { id: constants_1.USERROLES_LIST_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_LIST_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_LIST_API)), disabled: false, method: "get", }, { id: constants_1.USERROLES_ROLE_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_ROLE_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_ROLE_API)), disabled: false, method: "put", }, { id: constants_1.USERROLES_ROLE_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_ROLE_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_ROLE_API)), disabled: false, method: "delete", }, { id: constants_1.USERROLES_PERMISSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_PERMISSIONS_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_PERMISSIONS_API)), disabled: false, method: "get", }, { id: constants_1.USERROLES_PERMISSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_PERMISSIONS_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_PERMISSIONS_API)), disabled: false, method: "put", }, { id: constants_1.USERROLES_REMOVE_PERMISSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_REMOVE_PERMISSIONS_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_REMOVE_PERMISSIONS_API)), disabled: false, method: "put", }, { id: constants_1.USERROLES_USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API)), disabled: false, method: "put", }, { id: constants_1.USERROLES_USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API)), disabled: false, method: "get", }, { id: constants_1.USERROLES_USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API)), disabled: false, method: "delete", }, { id: constants_1.CREATE_EMAIL_PASSWORD_USER, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.CREATE_EMAIL_PASSWORD_USER) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.CREATE_EMAIL_PASSWORD_USER)), disabled: false, method: "post", }, { id: constants_1.CREATE_PASSWORDLESS_USER, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.CREATE_PASSWORDLESS_USER) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.CREATE_PASSWORDLESS_USER)), disabled: false, method: "post", }, { id: constants_1.LIST_TENANTS_WITH_LOGIN_METHODS, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.LIST_TENANTS_WITH_LOGIN_METHODS) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.LIST_TENANTS_WITH_LOGIN_METHODS)), disabled: false, method: "get", }, { id: constants_1.TENANT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API)), disabled: false, method: "get", }, { id: constants_1.TENANT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API)), disabled: false, method: "delete", }, { id: constants_1.TENANT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API)), disabled: false, method: "post", }, { id: constants_1.UPDATE_TENANT_FIRST_FACTOR_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_FIRST_FACTOR_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_FIRST_FACTOR_API)), disabled: false, method: "put", }, { id: constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API)), disabled: false, method: "put", }, { id: constants_1.UPDATE_TENANT_CORE_CONFIG_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_CORE_CONFIG_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_CORE_CONFIG_API)), disabled: false, method: "put", }, { id: constants_1.TENANT_THIRD_PARTY_CONFIG_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API)), disabled: false, method: "get", }, { id: constants_1.TENANT_THIRD_PARTY_CONFIG_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API)), disabled: false, method: "put", }, { id: constants_1.TENANT_THIRD_PARTY_CONFIG_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default( - utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API) - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API)), disabled: false, method: "delete", }, @@ -451,9 +363,11 @@ class Recipe extends recipeModule_1.default { let apiFunction; if (id === constants_1.USERS_LIST_GET_API) { apiFunction = usersGet_1.default; - } else if (id === constants_1.USERS_COUNT_API) { + } + else if (id === constants_1.USERS_COUNT_API) { apiFunction = usersCountGet_1.default; - } else if (id === constants_1.USER_API) { + } + else if (id === constants_1.USER_API) { if (req.getMethod() === "get") { apiFunction = userGet_1.userGet; } @@ -463,55 +377,69 @@ class Recipe extends recipeModule_1.default { if (req.getMethod() === "put") { apiFunction = userPut_1.userPut; } - } else if (id === constants_1.USER_EMAIL_VERIFY_API) { + } + else if (id === constants_1.USER_EMAIL_VERIFY_API) { if (req.getMethod() === "get") { apiFunction = userEmailVerifyGet_1.userEmailVerifyGet; } if (req.getMethod() === "put") { apiFunction = userEmailVerifyPut_1.userEmailVerifyPut; } - } else if (id === constants_1.USER_METADATA_API) { + } + else if (id === constants_1.USER_METADATA_API) { if (req.getMethod() === "get") { apiFunction = userMetadataGet_1.userMetaDataGet; } if (req.getMethod() === "put") { apiFunction = userMetadataPut_1.userMetadataPut; } - } else if (id === constants_1.USER_SESSIONS_API) { + } + else if (id === constants_1.USER_SESSIONS_API) { if (req.getMethod() === "get") { apiFunction = userSessionsGet_1.userSessionsGet; } if (req.getMethod() === "post") { apiFunction = userSessionsPost_1.userSessionsPost; } - } else if (id === constants_1.USER_PASSWORD_API) { + } + else if (id === constants_1.USER_PASSWORD_API) { apiFunction = userPasswordPut_1.userPasswordPut; - } else if (id === constants_1.USER_EMAIL_VERIFY_TOKEN_API) { + } + else if (id === constants_1.USER_EMAIL_VERIFY_TOKEN_API) { apiFunction = userEmailVerifyTokenPost_1.userEmailVerifyTokenPost; - } else if (id === constants_1.SEARCH_TAGS_API) { + } + else if (id === constants_1.SEARCH_TAGS_API) { apiFunction = tagsGet_1.getSearchTags; - } else if (id === constants_1.SIGN_OUT_API) { + } + else if (id === constants_1.SIGN_OUT_API) { apiFunction = signOut_1.default; - } else if (id === constants_1.DASHBOARD_ANALYTICS_API && req.getMethod() === "post") { + } + else if (id === constants_1.DASHBOARD_ANALYTICS_API && req.getMethod() === "post") { apiFunction = analytics_1.default; - } else if (id === constants_1.UNLINK_USER) { + } + else if (id === constants_1.UNLINK_USER) { apiFunction = userUnlinkGet_1.userUnlink; - } else if (id === constants_1.USERROLES_LIST_API) { + } + else if (id === constants_1.USERROLES_LIST_API) { apiFunction = getAllRoles_1.default; - } else if (id === constants_1.USERROLES_ROLE_API) { + } + else if (id === constants_1.USERROLES_ROLE_API) { if (req.getMethod() === "put") { apiFunction = createRoleOrAddPermissions_1.default; } if (req.getMethod() === "delete") { apiFunction = deleteRole_1.default; } - } else if (id === constants_1.USERROLES_PERMISSIONS_API) { + } + else if (id === constants_1.USERROLES_PERMISSIONS_API) { if (req.getMethod() === "get") { apiFunction = getPermissionsForRole_1.default; } - } else if (id === constants_1.USERROLES_REMOVE_PERMISSIONS_API) { + } + else if (id === constants_1.USERROLES_REMOVE_PERMISSIONS_API) { apiFunction = removePermissions_1.default; - } else if (id === constants_1.USERROLES_USER_API) { + } + else if (id === constants_1.USERROLES_USER_API) { if (req.getMethod() === "put") { apiFunction = addRoleToUser_1.default; } @@ -521,19 +449,23 @@ class Recipe extends recipeModule_1.default { if (req.getMethod() === "delete") { apiFunction = removeUserRole_1.default; } - } else if (id === constants_1.CREATE_EMAIL_PASSWORD_USER) { + } + else if (id === constants_1.CREATE_EMAIL_PASSWORD_USER) { if (req.getMethod() === "post") { apiFunction = emailpasswordUser_1.createEmailPasswordUser; } - } else if (id === constants_1.CREATE_PASSWORDLESS_USER) { + } + else if (id === constants_1.CREATE_PASSWORDLESS_USER) { if (req.getMethod() === "post") { apiFunction = passwordlessUser_1.createPasswordlessUser; } - } else if (id === constants_1.LIST_TENANTS_WITH_LOGIN_METHODS) { + } + else if (id === constants_1.LIST_TENANTS_WITH_LOGIN_METHODS) { if (req.getMethod() === "get") { apiFunction = listAllTenantsWithLoginMethods_1.default; } - } else if (id === constants_1.TENANT_API) { + } + else if (id === constants_1.TENANT_API) { if (req.getMethod() === "post") { apiFunction = createTenant_1.default; } @@ -543,13 +475,17 @@ class Recipe extends recipeModule_1.default { if (req.getMethod() === "delete") { apiFunction = deleteTenant_1.default; } - } else if (id === constants_1.UPDATE_TENANT_FIRST_FACTOR_API) { + } + else if (id === constants_1.UPDATE_TENANT_FIRST_FACTOR_API) { apiFunction = updateTenantFirstFactor_1.default; - } else if (id === constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API) { + } + else if (id === constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API) { apiFunction = updateTenantSecondaryFactor_1.default; - } else if (id === constants_1.UPDATE_TENANT_CORE_CONFIG_API) { + } + else if (id === constants_1.UPDATE_TENANT_CORE_CONFIG_API) { apiFunction = updateTenantCoreConfig_1.default; - } else if (id === constants_1.TENANT_THIRD_PARTY_CONFIG_API) { + } + else if (id === constants_1.TENANT_THIRD_PARTY_CONFIG_API) { if (req.getMethod() === "get") { apiFunction = getThirdPartyConfig_1.default; } @@ -597,7 +533,8 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } else { + } + else { throw new Error("Dashboard recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/dashboard/recipeImplementation.js b/lib/build/recipe/dashboard/recipeImplementation.js index 9ad3fa57c..4168d351b 100644 --- a/lib/build/recipe/dashboard/recipeImplementation.js +++ b/lib/build/recipe/dashboard/recipeImplementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("./error")); const logger_1 = require("../../logger"); @@ -38,17 +36,10 @@ function getRecipeImplementation() { if (!input.config.apiKey) { // make the check for the API endpoint here with querier let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); - const authHeaderValue = - (_a = input.req.getHeaderValue("authorization")) === null || _a === void 0 - ? void 0 - : _a.split(" ")[1]; - const sessionVerificationResponse = await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/dashboard/session/verify"), - { - sessionId: authHeaderValue, - }, - input.userContext - ); + const authHeaderValue = (_a = input.req.getHeaderValue("authorization")) === null || _a === void 0 ? void 0 : _a.split(" ")[1]; + const sessionVerificationResponse = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/dashboard/session/verify"), { + sessionId: authHeaderValue, + }, input.userContext); if (sessionVerificationResponse.status !== "OK") { return false; } @@ -67,22 +58,16 @@ function getRecipeImplementation() { return true; } if (admins.length === 0) { - logger_1.logDebugMessage( - "User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin" - ); + logger_1.logDebugMessage("User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin"); throw new error_1.default(); } const userEmail = sessionVerificationResponse.email; if (userEmail === undefined || typeof userEmail !== "string") { - logger_1.logDebugMessage( - "User Dashboard: Returning Unauthorised because no email was returned from the core. Should never come here" - ); + logger_1.logDebugMessage("User Dashboard: Returning Unauthorised because no email was returned from the core. Should never come here"); return false; } if (!admins.includes(userEmail)) { - logger_1.logDebugMessage( - "User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin" - ); + logger_1.logDebugMessage("User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin"); throw new error_1.default(); } } diff --git a/lib/build/recipe/dashboard/types.d.ts b/lib/build/recipe/dashboard/types.d.ts index 9f8721a34..792778876 100644 --- a/lib/build/recipe/dashboard/types.d.ts +++ b/lib/build/recipe/dashboard/types.d.ts @@ -6,10 +6,7 @@ export declare type TypeInput = { apiKey?: string; admins?: string[]; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -18,15 +15,14 @@ export declare type TypeNormalisedInput = { admins?: string[]; authMode: AuthMode; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type RecipeInterface = { - getDashboardBundleLocation(input: { userContext: UserContext }): Promise; + getDashboardBundleLocation(input: { + userContext: UserContext; + }): Promise; shouldAllowAccess(input: { req: BaseRequest; config: TypeNormalisedInput; @@ -43,14 +39,12 @@ export declare type APIOptions = { appInfo: NormalisedAppinfo; }; export declare type APIInterface = { - dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise); + dashboardGET: undefined | ((input: { + options: APIOptions; + userContext: UserContext; + }) => Promise); }; -export declare type APIFunction = ( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -) => Promise; +export declare type APIFunction = (apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; export declare type RecipeIdForUser = "emailpassword" | "thirdparty" | "passwordless"; export declare type AuthMode = "api-key" | "email-password"; export declare type UserWithFirstAndLastName = User & { diff --git a/lib/build/recipe/dashboard/utils.d.ts b/lib/build/recipe/dashboard/utils.d.ts index f742b1cd6..3302c7ae6 100644 --- a/lib/build/recipe/dashboard/utils.d.ts +++ b/lib/build/recipe/dashboard/utils.d.ts @@ -6,11 +6,7 @@ import { UserContext } from "../../types"; export declare function validateAndNormaliseUserInput(config?: TypeInput): TypeNormalisedInput; export declare function sendUnauthorisedAccess(res: BaseResponse): void; export declare function isValidRecipeId(recipeId: string): recipeId is RecipeIdForUser; -export declare function getUserForRecipeId( - recipeUserId: RecipeUserId, - recipeId: string, - userContext: UserContext -): Promise<{ +export declare function getUserForRecipeId(recipeUserId: RecipeUserId, recipeId: string, userContext: UserContext): Promise<{ user: UserWithFirstAndLastName | undefined; recipe: "emailpassword" | "thirdparty" | "passwordless" | undefined; }>; diff --git a/lib/build/recipe/dashboard/utils.js b/lib/build/recipe/dashboard/utils.js index 9960c120c..e20575b64 100644 --- a/lib/build/recipe/dashboard/utils.js +++ b/lib/build/recipe/dashboard/utils.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getApiPathWithDashboardBase = exports.validateApiKey = exports.getUserForRecipeId = exports.isValidRecipeId = exports.sendUnauthorisedAccess = exports.validateAndNormaliseUserInput = void 0; const utils_1 = require("../../utils"); @@ -28,28 +26,15 @@ const recipe_3 = __importDefault(require("../thirdparty/recipe")); const recipe_4 = __importDefault(require("../passwordless/recipe")); const logger_1 = require("../../logger"); function validateAndNormaliseUserInput(config) { - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === undefined ? {} : config.override - ); - if ( - (config === null || config === void 0 ? void 0 : config.apiKey) !== undefined && - (config === null || config === void 0 ? void 0 : config.admins) !== undefined - ) { + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, (config === undefined ? {} : config.override)); + if ((config === null || config === void 0 ? void 0 : config.apiKey) !== undefined && (config === null || config === void 0 ? void 0 : config.admins) !== undefined) { logger_1.logDebugMessage("User Dashboard: Providing 'admins' has no effect when using an apiKey."); } let admins; if ((config === null || config === void 0 ? void 0 : config.admins) !== undefined) { admins = config.admins.map((email) => utils_1.normaliseEmail(email)); } - return Object.assign(Object.assign({}, config), { - override, - authMode: config !== undefined && config.apiKey ? "api-key" : "email-password", - admins, - }); + return Object.assign(Object.assign({}, config), { override, authMode: config !== undefined && config.apiKey ? "api-key" : "email-password", admins }); } exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; function sendUnauthorisedAccess(res) { @@ -84,9 +69,7 @@ async function _getUserForRecipeId(recipeUserId, recipeId, userContext) { recipe: undefined, }; } - const loginMethod = user.loginMethods.find( - (m) => m.recipeId === recipeId && m.recipeUserId.getAsString() === recipeUserId.getAsString() - ); + const loginMethod = user.loginMethods.find((m) => m.recipeId === recipeId && m.recipeUserId.getAsString() === recipeUserId.getAsString()); if (loginMethod === undefined) { return { user: undefined, @@ -98,21 +81,26 @@ async function _getUserForRecipeId(recipeUserId, recipeId, userContext) { // we detect if this recipe has been init or not.. recipe_2.default.getInstanceOrThrowError(); recipe = "emailpassword"; - } catch (e) { + } + catch (e) { // No - op } - } else if (recipeId === recipe_3.default.RECIPE_ID) { + } + else if (recipeId === recipe_3.default.RECIPE_ID) { try { recipe_3.default.getInstanceOrThrowError(); recipe = "thirdparty"; - } catch (e) { + } + catch (e) { // No - op } - } else if (recipeId === recipe_4.default.RECIPE_ID) { + } + else if (recipeId === recipe_4.default.RECIPE_ID) { try { recipe_4.default.getInstanceOrThrowError(); recipe = "passwordless"; - } catch (e) { + } + catch (e) { // No - op } } @@ -124,8 +112,7 @@ async function _getUserForRecipeId(recipeUserId, recipeId, userContext) { async function validateApiKey(input) { let apiKeyHeaderValue = input.req.getHeaderValue("authorization"); // We receieve the api key as `Bearer API_KEY`, this retrieves just the key - apiKeyHeaderValue = - apiKeyHeaderValue === null || apiKeyHeaderValue === void 0 ? void 0 : apiKeyHeaderValue.split(" ")[1]; + apiKeyHeaderValue = apiKeyHeaderValue === null || apiKeyHeaderValue === void 0 ? void 0 : apiKeyHeaderValue.split(" ")[1]; if (apiKeyHeaderValue === undefined) { return false; } diff --git a/lib/build/recipe/emailpassword/api/emailExists.d.ts b/lib/build/recipe/emailpassword/api/emailExists.d.ts index 2f55b6d3b..478175dec 100644 --- a/lib/build/recipe/emailpassword/api/emailExists.d.ts +++ b/lib/build/recipe/emailpassword/api/emailExists.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function emailExists( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function emailExists(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/emailpassword/api/emailExists.js b/lib/build/recipe/emailpassword/api/emailExists.js index b76ac08c7..b79014fa4 100644 --- a/lib/build/recipe/emailpassword/api/emailExists.js +++ b/lib/build/recipe/emailpassword/api/emailExists.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/emailpassword/api/generatePasswordResetToken.d.ts b/lib/build/recipe/emailpassword/api/generatePasswordResetToken.d.ts index 146d43586..9ce0acc4b 100644 --- a/lib/build/recipe/emailpassword/api/generatePasswordResetToken.d.ts +++ b/lib/build/recipe/emailpassword/api/generatePasswordResetToken.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function generatePasswordResetToken( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function generatePasswordResetToken(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/emailpassword/api/generatePasswordResetToken.js b/lib/build/recipe/emailpassword/api/generatePasswordResetToken.js index f4e469a9d..20766cadb 100644 --- a/lib/build/recipe/emailpassword/api/generatePasswordResetToken.js +++ b/lib/build/recipe/emailpassword/api/generatePasswordResetToken.js @@ -23,12 +23,7 @@ async function generatePasswordResetToken(apiImplementation, tenantId, options, } const requestBody = await options.req.getJSONBody(); // step 1 - let formFields = await utils_2.validateFormFieldsOrThrowError( - options.config.resetPasswordUsingTokenFeature.formFieldsForGenerateTokenForm, - requestBody.formFields, - tenantId, - userContext - ); + let formFields = await utils_2.validateFormFieldsOrThrowError(options.config.resetPasswordUsingTokenFeature.formFieldsForGenerateTokenForm, requestBody.formFields, tenantId, userContext); let result = await apiImplementation.generatePasswordResetTokenPOST({ formFields, tenantId, diff --git a/lib/build/recipe/emailpassword/api/implementation.js b/lib/build/recipe/emailpassword/api/implementation.js index 501fe1f30..9e877002d 100644 --- a/lib/build/recipe/emailpassword/api/implementation.js +++ b/lib/build/recipe/emailpassword/api/implementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("../../../logger"); const __1 = require("../../../"); @@ -15,7 +13,7 @@ const authUtils_1 = require("../../../authUtils"); const utils_2 = require("../../thirdparty/utils"); function getAPIImplementation() { return { - emailExistsGET: async function ({ email, tenantId, userContext }) { + emailExistsGET: async function ({ email, tenantId, userContext, }) { // even if the above returns true, we still need to check if there // exists an email password user with the same email cause the function // above does not check for that. @@ -27,27 +25,22 @@ function getAPIImplementation() { doUnionOfAccountInfo: false, userContext, }); - let emailPasswordUserExists = - users.find((u) => { - return ( - u.loginMethods.find((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)) !== - undefined - ); - }) !== undefined; + let emailPasswordUserExists = users.find((u) => { + return (u.loginMethods.find((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)) !== + undefined); + }) !== undefined; return { status: "OK", exists: emailPasswordUserExists, }; }, - generatePasswordResetTokenPOST: async function ({ formFields, tenantId, options, userContext }) { + generatePasswordResetTokenPOST: async function ({ formFields, tenantId, options, userContext, }) { // NOTE: Check for email being a non-string value. This check will likely // never evaluate to `true` as there is an upper-level check for the type // in validation but kept here to be safe. const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; if (typeof emailAsUnknown !== "string") - throw new Error( - "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" - ); + throw new Error("Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"); const email = emailAsUnknown; // this function will be reused in different parts of the flow below.. async function generateAndSendPasswordResetToken(primaryUserId, recipeUserId) { @@ -59,11 +52,7 @@ function getAPIImplementation() { userContext, }); if (response.status === "UNKNOWN_USER_ID_ERROR") { - logger_1.logDebugMessage( - `Password reset email not sent, unknown user id: ${ - recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() - }` - ); + logger_1.logDebugMessage(`Password reset email not sent, unknown user id: ${recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString()}`); return { status: "OK", }; @@ -106,9 +95,7 @@ function getAPIImplementation() { // for later use. let emailPasswordAccount = undefined; for (let i = 0; i < users.length; i++) { - let emailPasswordAccountTmp = users[i].loginMethods.find( - (l) => l.recipeId === "emailpassword" && l.hasSameEmailAs(email) - ); + let emailPasswordAccountTmp = users[i].loginMethods.find((l) => l.recipeId === "emailpassword" && l.hasSameEmailAs(email)); if (emailPasswordAccountTmp !== undefined) { emailPasswordAccount = emailPasswordAccountTmp; break; @@ -116,43 +103,23 @@ function getAPIImplementation() { } // we find the primary user ID from the user's list for later use. let linkingCandidate = users.find((u) => u.isPrimaryUser); - logger_1.logDebugMessage( - "generatePasswordResetTokenPOST: primary linking candidate: " + - (linkingCandidate === null || linkingCandidate === void 0 ? void 0 : linkingCandidate.id) - ); + logger_1.logDebugMessage("generatePasswordResetTokenPOST: primary linking candidate: " + (linkingCandidate === null || linkingCandidate === void 0 ? void 0 : linkingCandidate.id)); logger_1.logDebugMessage("generatePasswordResetTokenPOST: linking candidate count " + users.length); // If there is no existing primary user and there is a single option to link // we see if that user can become primary (and a candidate for linking) if (linkingCandidate === undefined && users.length > 0) { // If the only user that exists with this email is a non-primary emailpassword user, then we can just let them reset their password, because: // we are not going to link anything and there is no risk of account takeover. - if ( - users.length === 1 && + if (users.length === 1 && users[0].loginMethods[0].recipeUserId.getAsString() === - (emailPasswordAccount === null || emailPasswordAccount === void 0 - ? void 0 - : emailPasswordAccount.recipeUserId.getAsString()) - ) { - return await generateAndSendPasswordResetToken( - emailPasswordAccount.recipeUserId.getAsString(), - emailPasswordAccount.recipeUserId - ); + (emailPasswordAccount === null || emailPasswordAccount === void 0 ? void 0 : emailPasswordAccount.recipeUserId.getAsString())) { + return await generateAndSendPasswordResetToken(emailPasswordAccount.recipeUserId.getAsString(), emailPasswordAccount.recipeUserId); } const oldestUser = users.sort((a, b) => a.timeJoined - b.timeJoined)[0]; - logger_1.logDebugMessage( - `generatePasswordResetTokenPOST: oldest recipe level-linking candidate: ${oldestUser.id} (w/ ${ - oldestUser.loginMethods[0].verified ? "verified" : "unverified" - } email)` - ); + logger_1.logDebugMessage(`generatePasswordResetTokenPOST: oldest recipe level-linking candidate: ${oldestUser.id} (w/ ${oldestUser.loginMethods[0].verified ? "verified" : "unverified"} email)`); // Otherwise, we check if the user can become primary. - const shouldBecomePrimaryUser = await recipe_1.default - .getInstance() - .shouldBecomePrimaryUser(oldestUser, tenantId, undefined, userContext); - logger_1.logDebugMessage( - `generatePasswordResetTokenPOST: recipe level-linking candidate ${ - shouldBecomePrimaryUser ? "can" : "can not" - } become primary` - ); + const shouldBecomePrimaryUser = await recipe_1.default.getInstance().shouldBecomePrimaryUser(oldestUser, tenantId, undefined, userContext); + logger_1.logDebugMessage(`generatePasswordResetTokenPOST: recipe level-linking candidate ${shouldBecomePrimaryUser ? "can" : "can not"} become primary`); if (shouldBecomePrimaryUser) { linkingCandidate = oldestUser; } @@ -166,10 +133,7 @@ function getAPIImplementation() { status: "OK", }; } - return await generateAndSendPasswordResetToken( - emailPasswordAccount.recipeUserId.getAsString(), - emailPasswordAccount.recipeUserId - ); + return await generateAndSendPasswordResetToken(emailPasswordAccount.recipeUserId.getAsString(), emailPasswordAccount.recipeUserId); } /* This security measure helps prevent the following attack: @@ -193,63 +157,48 @@ function getAPIImplementation() { // First we check if there is any login method in which the input email is verified. // If that is the case, then it's proven that the user owns the email and we can // trust linking of the email password account. - let emailVerified = - linkingCandidate.loginMethods.find((lm) => { - return lm.hasSameEmailAs(email) && lm.verified; - }) !== undefined; + let emailVerified = linkingCandidate.loginMethods.find((lm) => { + return lm.hasSameEmailAs(email) && lm.verified; + }) !== undefined; // then, we check if the primary user has any other email / phone number // associated with this account - and if it does, then it means that // there is a risk of account takeover, so we do not allow the token to be generated - let hasOtherEmailOrPhone = - linkingCandidate.loginMethods.find((lm) => { - // we do the extra undefined check below cause - // hasSameEmailAs returns false if the lm.email is undefined, and - // we want to check that the email is different as opposed to email - // not existing in lm. - return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; - }) !== undefined; + let hasOtherEmailOrPhone = linkingCandidate.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + }) !== undefined; // If we allow this to pass, then: // 1. the if (!emailVerified && hasOtherEmailOrPhone) { return { status: "PASSWORD_RESET_NOT_ALLOWED", - reason: - "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + reason: "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", }; } if (linkingCandidate.isPrimaryUser && emailPasswordAccount !== undefined) { // If a primary user has the input email as verified or has no other emails then it is always allowed to reset their own password: // - there is no risk of account takeover, because they have verified this email or haven't linked it to anything else (checked above this block) // - there will be no linking as a result of this action, so we do not need to check for linking (checked here by seeing that the two accounts are already linked) - let areTheTwoAccountsLinked = - linkingCandidate.loginMethods.find((lm) => { - return lm.recipeUserId.getAsString() === emailPasswordAccount.recipeUserId.getAsString(); - }) !== undefined; + let areTheTwoAccountsLinked = linkingCandidate.loginMethods.find((lm) => { + return lm.recipeUserId.getAsString() === emailPasswordAccount.recipeUserId.getAsString(); + }) !== undefined; if (areTheTwoAccountsLinked) { - return await generateAndSendPasswordResetToken( - linkingCandidate.id, - emailPasswordAccount.recipeUserId - ); + return await generateAndSendPasswordResetToken(linkingCandidate.id, emailPasswordAccount.recipeUserId); } } // Here we know that the two accounts are NOT linked. We now need to check for an // extra security measure here to make sure that the input email in the primary user // is verified, and if not, we need to make sure that there is no other email / phone number // associated with the primary user account. If there is, then we do not proceed. - let shouldDoAccountLinkingResponse = await recipe_1.default - .getInstance() - .config.shouldDoAutomaticAccountLinking( - emailPasswordAccount !== undefined - ? emailPasswordAccount - : { - recipeId: "emailpassword", - email, - }, - linkingCandidate, - undefined, - tenantId, - userContext - ); + let shouldDoAccountLinkingResponse = await recipe_1.default.getInstance().config.shouldDoAutomaticAccountLinking(emailPasswordAccount !== undefined + ? emailPasswordAccount + : { + recipeId: "emailpassword", + email, + }, linkingCandidate, undefined, tenantId, userContext); // Now we need to check that if there exists any email password user at all // for the input email. If not, then it implies that when the token is consumed, // then we will create a new user - so we should only generate the token if @@ -263,9 +212,7 @@ function getAPIImplementation() { // code consume cannot be linked to the primary user - therefore, we should // not generate a password reset token if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - `Password reset email not sent, since email password user didn't exist, and account linking not enabled` - ); + logger_1.logDebugMessage(`Password reset email not sent, since email password user didn't exist, and account linking not enabled`); return { status: "OK", }; @@ -285,10 +232,9 @@ function getAPIImplementation() { // we will be creating a new email password account when the token // is consumed and linking it to this primary user. return await generateAndSendPasswordResetToken(linkingCandidate.id, undefined); - } else { - logger_1.logDebugMessage( - `Password reset email not sent, isSignUpAllowed returned false for email: ${email}` - ); + } + else { + logger_1.logDebugMessage(`Password reset email not sent, isSignUpAllowed returned false for email: ${email}`); return { status: "OK", }; @@ -299,26 +245,21 @@ function getAPIImplementation() { // here we will go ahead with the token generation cause // even when the token is consumed, we will not be linking the accounts // so no need to check for anything - return await generateAndSendPasswordResetToken( - emailPasswordAccount.recipeUserId.getAsString(), - emailPasswordAccount.recipeUserId - ); + return await generateAndSendPasswordResetToken(emailPasswordAccount.recipeUserId.getAsString(), emailPasswordAccount.recipeUserId); } // Here we accounted for both `shouldRequireVerification` by the above checks (where we return ERR_CODE_001) return await generateAndSendPasswordResetToken(linkingCandidate.id, emailPasswordAccount.recipeUserId); }, - passwordResetPOST: async function ({ formFields, token, tenantId, options, userContext }) { + passwordResetPOST: async function ({ formFields, token, tenantId, options, userContext, }) { async function markEmailAsVerified(recipeUserId, email) { const emailVerificationInstance = recipe_2.default.getInstance(); if (emailVerificationInstance) { - const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( - { - tenantId, - recipeUserId, - email, - userContext, - } - ); + const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken({ + tenantId, + recipeUserId, + email, + userContext, + }); if (tokenResponse.status === "OK") { await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ tenantId, @@ -340,23 +281,24 @@ function getAPIImplementation() { password: newPassword, userContext, }); - if ( - updateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR" || - updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" - ) { + if (updateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR" || + updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { throw new Error("This should never come here because we are not updating the email"); - } else if (updateResponse.status === "UNKNOWN_USER_ID_ERROR") { + } + else if (updateResponse.status === "UNKNOWN_USER_ID_ERROR") { // This should happen only cause of a race condition where the user // might be deleted before token creation and consumption. return { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", }; - } else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { + } + else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { return { status: "PASSWORD_POLICY_VIOLATED_ERROR", failureReason: updateResponse.failureReason, }; - } else { + } + else { // status: "OK" // If the update was successful, we try to mark the email as verified. // We do this because we assume that the password reset token was delivered by email (and to the appropriate email address) @@ -368,10 +310,7 @@ function getAPIImplementation() { // If we verified (and linked) the existing user with the original password, User M would get access to the current user and any linked users. await markEmailAsVerified(recipeUserId, emailForWhomTokenWasGenerated); // We refresh the user information here, because the verification status may be updated, which is used during linking. - const updatedUserAfterEmailVerification = await __1.getUser( - recipeUserId.getAsString(), - userContext - ); + const updatedUserAfterEmailVerification = await __1.getUser(recipeUserId.getAsString(), userContext); if (updatedUserAfterEmailVerification === undefined) { throw new Error("Should never happen - user deleted after during password reset"); } @@ -394,8 +333,7 @@ function getAPIImplementation() { session: undefined, userContext, }); - const userAfterWeTriedLinking = - linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; + const userAfterWeTriedLinking = linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; return { status: "OK", email: emailForWhomTokenWasGenerated, @@ -408,9 +346,7 @@ function getAPIImplementation() { // in validation but kept here to be safe. const newPasswordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; if (typeof newPasswordAsUnknown !== "string") - throw new Error( - "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" - ); + throw new Error("Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError"); let newPassword = newPasswordAsUnknown; let tokenConsumptionResponse = await options.recipeImplementation.consumePasswordResetToken({ token, @@ -442,40 +378,32 @@ function getAPIImplementation() { // primary user id (userIdForWhomTokenWasGenerated), in this case, // we still don't allow password update, cause the user should try again // and the token should be regenerated for the right recipe user. - return ( - lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && lm.recipeId === "emailpassword" - ); + return (lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && lm.recipeId === "emailpassword"); }); if (tokenGeneratedForEmailPasswordUser) { if (!existingUser.isPrimaryUser) { // If this is a recipe level emailpassword user, we can always allow them to reset their password. - return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( - new recipeUserId_1.default(userIdForWhomTokenWasGenerated) - ); + return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary(new recipeUserId_1.default(userIdForWhomTokenWasGenerated)); } // If the user is a primary user resetting the password of an emailpassword user linked to it // we need to check for account takeover risk (similar to what we do when generating the token) // We check if there is any login method in which the input email is verified. // If that is the case, then it's proven that the user owns the email and we can // trust linking of the email password account. - let emailVerified = - existingUser.loginMethods.find((lm) => { - return lm.hasSameEmailAs(emailForWhomTokenWasGenerated) && lm.verified; - }) !== undefined; + let emailVerified = existingUser.loginMethods.find((lm) => { + return lm.hasSameEmailAs(emailForWhomTokenWasGenerated) && lm.verified; + }) !== undefined; // finally, we check if the primary user has any other email / phone number // associated with this account - and if it does, then it means that // there is a risk of account takeover, so we do not allow the token to be generated - let hasOtherEmailOrPhone = - existingUser.loginMethods.find((lm) => { - // we do the extra undefined check below cause - // hasSameEmailAs returns false if the lm.email is undefined, and - // we want to check that the email is different as opposed to email - // not existing in lm. - return ( - (lm.email !== undefined && !lm.hasSameEmailAs(emailForWhomTokenWasGenerated)) || - lm.phoneNumber !== undefined - ); - }) !== undefined; + let hasOtherEmailOrPhone = existingUser.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return ((lm.email !== undefined && !lm.hasSameEmailAs(emailForWhomTokenWasGenerated)) || + lm.phoneNumber !== undefined); + }) !== undefined; if (!emailVerified && hasOtherEmailOrPhone) { // We can return an invalid token error, because in this case the token should not have been created // whenever they try to re-create it they'll see the appropriate error message @@ -484,9 +412,7 @@ function getAPIImplementation() { }; } // since this doesn't result in linking and there is no risk of account takeover, we can allow the password reset to proceed - return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( - new recipeUserId_1.default(userIdForWhomTokenWasGenerated) - ); + return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary(new recipeUserId_1.default(userIdForWhomTokenWasGenerated)); } // this means that the existingUser is primary but does not have an emailpassword user associated // with it. It could now mean that no emailpassword user exists, or it could mean that @@ -514,15 +440,13 @@ function getAPIImplementation() { return { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", }; - } else { + } + else { // we mark the email as verified because password reset also requires // access to the email to work.. This has a good side effect that // any other login method with the same email in existingAccount will also get marked // as verified. - await markEmailAsVerified( - createUserResponse.user.loginMethods[0].recipeUserId, - tokenConsumptionResponse.email - ); + await markEmailAsVerified(createUserResponse.user.loginMethods[0].recipeUserId, tokenConsumptionResponse.email); const updatedUser = await __1.getUser(createUserResponse.user.id, userContext); if (updatedUser === undefined) { throw new Error("Should never happen - user deleted after during password reset"); @@ -554,26 +478,14 @@ function getAPIImplementation() { }; } }, - signInPOST: async function ({ - formFields, - tenantId, - session, - shouldTryLinkingWithSessionUser, - options, - userContext, - }) { + signInPOST: async function ({ formFields, tenantId, session, shouldTryLinkingWithSessionUser, options, userContext, }) { const errorCodeMap = { - SIGN_IN_NOT_ALLOWED: - "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", + SIGN_IN_NOT_ALLOWED: "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", LINKING_TO_SESSION_USER_FAILED: { - EMAIL_VERIFICATION_REQUIRED: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", - 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_012)", + EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", + 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_012)", }, }; const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; @@ -582,21 +494,15 @@ function getAPIImplementation() { // check for type is done in a parent function but they are kept // here to be on the safe side. if (typeof emailAsUnknown !== "string") - throw new Error( - "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" - ); + throw new Error("Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"); if (typeof passwordAsUnknown !== "string") - throw new Error( - "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" - ); + throw new Error("Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError"); let email = emailAsUnknown; let password = passwordAsUnknown; const recipeId = "emailpassword"; const checkCredentialsOnTenant = async (tenantId) => { - return ( - (await options.recipeImplementation.verifyCredentials({ email, password, tenantId, userContext })) - .status === "OK" - ); + return ((await options.recipeImplementation.verifyCredentials({ email, password, tenantId, userContext })) + .status === "OK"); }; if (utils_2.isFakeEmail(email) && session === undefined) { // Fake emails cannot be used as a first factor @@ -604,16 +510,14 @@ function getAPIImplementation() { status: "WRONG_CREDENTIALS_ERROR", }; } - const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( - { - accountInfo: { email }, - userContext, - recipeId, - session, - tenantId, - checkCredentialsOnTenant, - } - ); + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ + accountInfo: { email }, + userContext, + recipeId, + session, + tenantId, + checkCredentialsOnTenant, + }); const isVerified = authenticatingUser !== undefined && authenticatingUser.loginMethod.verified; // We check this before preAuthChecks, because that function assumes that if isSignUp is false, // then authenticatingUser is defined. While it wouldn't technically cause any problems with @@ -631,8 +535,7 @@ function getAPIImplementation() { }, factorIds: ["emailpassword"], isSignUp: false, - authenticatingUser: - authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, isVerified, signInVerifiesLoginMethod: false, skipSessionUserUpdateInCore: false, @@ -645,11 +548,7 @@ function getAPIImplementation() { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); } if (preAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - preAuthChecks, - errorCodeMap, - "SIGN_IN_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); } if (utils_2.isFakeEmail(email) && preAuthChecks.isFirstFactor) { // Fake emails cannot be used as a first factor @@ -669,11 +568,7 @@ function getAPIImplementation() { return signInResponse; } if (signInResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - signInResponse, - errorCodeMap, - "SIGN_IN_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(signInResponse, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); } const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ authenticatedUser: signInResponse.user, @@ -687,11 +582,7 @@ function getAPIImplementation() { userContext, }); if (postAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - postAuthChecks, - errorCodeMap, - "SIGN_IN_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); } return { status: "OK", @@ -699,26 +590,14 @@ function getAPIImplementation() { user: postAuthChecks.user, }; }, - signUpPOST: async function ({ - formFields, - tenantId, - session, - shouldTryLinkingWithSessionUser, - options, - userContext, - }) { + signUpPOST: async function ({ formFields, tenantId, session, shouldTryLinkingWithSessionUser, options, userContext, }) { const errorCodeMap = { - SIGN_UP_NOT_ALLOWED: - "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", LINKING_TO_SESSION_USER_FAILED: { - EMAIL_VERIFICATION_REQUIRED: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", - 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_016)", + EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", + 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_016)", }, }; const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; @@ -727,13 +606,9 @@ function getAPIImplementation() { // check for type is done in a parent function but they are kept // here to be on the safe side. if (typeof emailAsUnknown !== "string") - throw new Error( - "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" - ); + throw new Error("Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"); if (typeof passwordAsUnknown !== "string") - throw new Error( - "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" - ); + throw new Error("Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError"); let email = emailAsUnknown; let password = passwordAsUnknown; const preAuthCheckRes = await authUtils_1.AuthUtils.preAuthChecks({ @@ -753,32 +628,22 @@ function getAPIImplementation() { shouldTryLinkingWithSessionUser, }); if (preAuthCheckRes.status === "SIGN_UP_NOT_ALLOWED") { - const conflictingUsers = await recipe_1.default - .getInstance() - .recipeInterfaceImpl.listUsersByAccountInfo({ - tenantId, - accountInfo: { - email, - }, - doUnionOfAccountInfo: false, - userContext, - }); - if ( - conflictingUsers.some((u) => - u.loginMethods.some((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)) - ) - ) { + const conflictingUsers = await recipe_1.default.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + if (conflictingUsers.some((u) => u.loginMethods.some((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)))) { return { status: "EMAIL_ALREADY_EXISTS_ERROR", }; } } if (preAuthCheckRes.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - preAuthCheckRes, - errorCodeMap, - "SIGN_UP_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthCheckRes, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); } if (utils_2.isFakeEmail(email) && preAuthCheckRes.isFirstFactor) { // Fake emails cannot be used as a first factor @@ -798,11 +663,7 @@ function getAPIImplementation() { return signUpResponse; } if (signUpResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - signUpResponse, - errorCodeMap, - "SIGN_UP_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(signUpResponse, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); } const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ authenticatedUser: signUpResponse.user, @@ -819,11 +680,7 @@ function getAPIImplementation() { // It should never actually come here, but we do it cause of consistency. // If it does come here (in case there is a bug), it would make this func throw // anyway, cause there is no SIGN_IN_NOT_ALLOWED in the errorCodeMap. - authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - postAuthChecks, - errorCodeMap, - "SIGN_UP_NOT_ALLOWED" - ); + authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); throw new Error("This should never happen"); } return { diff --git a/lib/build/recipe/emailpassword/api/passwordReset.d.ts b/lib/build/recipe/emailpassword/api/passwordReset.d.ts index 08aef7f99..3a2304943 100644 --- a/lib/build/recipe/emailpassword/api/passwordReset.d.ts +++ b/lib/build/recipe/emailpassword/api/passwordReset.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function passwordReset( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function passwordReset(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/emailpassword/api/passwordReset.js b/lib/build/recipe/emailpassword/api/passwordReset.js index 0565d8dcd..f9275da56 100644 --- a/lib/build/recipe/emailpassword/api/passwordReset.js +++ b/lib/build/recipe/emailpassword/api/passwordReset.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -32,12 +30,7 @@ async function passwordReset(apiImplementation, tenantId, options, userContext) // - we want to throw this error before consuming the token, so that the user can try again // - there is a case in the api impl where we create a new user, and we want to assign // a password that meets the password policy. - let formFields = await utils_2.validateFormFieldsOrThrowError( - options.config.resetPasswordUsingTokenFeature.formFieldsForPasswordResetForm, - requestBody.formFields, - tenantId, - userContext - ); + let formFields = await utils_2.validateFormFieldsOrThrowError(options.config.resetPasswordUsingTokenFeature.formFieldsForPasswordResetForm, requestBody.formFields, tenantId, userContext); let token = requestBody.token; if (token === undefined) { throw new error_1.default({ @@ -72,14 +65,11 @@ async function passwordReset(apiImplementation, tenantId, options, userContext) message: "Error in input formFields", }); } - utils_1.send200Response( - options.res, - result.status === "OK" - ? { - status: "OK", - } - : result - ); + utils_1.send200Response(options.res, result.status === "OK" + ? { + status: "OK", + } + : result); return true; } exports.default = passwordReset; diff --git a/lib/build/recipe/emailpassword/api/signin.d.ts b/lib/build/recipe/emailpassword/api/signin.d.ts index 4a712efbe..178b76b67 100644 --- a/lib/build/recipe/emailpassword/api/signin.d.ts +++ b/lib/build/recipe/emailpassword/api/signin.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function signInAPI( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signInAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/emailpassword/api/signin.js b/lib/build/recipe/emailpassword/api/signin.js index 42a7f16b3..2f9685bef 100644 --- a/lib/build/recipe/emailpassword/api/signin.js +++ b/lib/build/recipe/emailpassword/api/signin.js @@ -24,19 +24,9 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { } const body = await options.req.getJSONBody(); // step 1 - let formFields = await utils_2.validateFormFieldsOrThrowError( - options.config.signInFeature.formFields, - body.formFields, - tenantId, - userContext - ); + let formFields = await utils_2.validateFormFieldsOrThrowError(options.config.signInFeature.formFields, body.formFields, tenantId, userContext); const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( - options.req, - options.res, - shouldTryLinkingWithSessionUser, - userContext - ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -49,11 +39,9 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { userContext, }); if (result.status === "OK") { - utils_1.send200Response( - options.res, - Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) - ); - } else { + utils_1.send200Response(options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); + } + else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/emailpassword/api/signup.d.ts b/lib/build/recipe/emailpassword/api/signup.d.ts index 1487513c1..7306003af 100644 --- a/lib/build/recipe/emailpassword/api/signup.d.ts +++ b/lib/build/recipe/emailpassword/api/signup.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function signUpAPI( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signUpAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/emailpassword/api/signup.js b/lib/build/recipe/emailpassword/api/signup.js index 6a793ec2c..5acf52ba8 100644 --- a/lib/build/recipe/emailpassword/api/signup.js +++ b/lib/build/recipe/emailpassword/api/signup.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -30,22 +28,9 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { } const requestBody = await options.req.getJSONBody(); // step 1 - let formFields = await utils_2.validateFormFieldsOrThrowError( - options.config.signUpFeature.formFields, - requestBody.formFields, - tenantId, - userContext - ); - const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( - options.req, - requestBody - ); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( - options.req, - options.res, - shouldTryLinkingWithSessionUser, - userContext - ); + let formFields = await utils_2.validateFormFieldsOrThrowError(options.config.signUpFeature.formFields, requestBody.formFields, tenantId, userContext); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -58,13 +43,12 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { userContext: userContext, }); if (result.status === "OK") { - utils_1.send200Response( - options.res, - Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) - ); - } else if (result.status === "GENERAL_ERROR") { + utils_1.send200Response(options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); + } + else if (result.status === "GENERAL_ERROR") { utils_1.send200Response(options.res, result); - } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { + } + else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { throw new error_1.default({ type: error_1.default.FIELD_ERROR, payload: [ @@ -75,7 +59,8 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { ], message: "Error in input formFields", }); - } else { + } + else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/emailpassword/api/utils.d.ts b/lib/build/recipe/emailpassword/api/utils.d.ts index 0f67df755..bfba7a7e6 100644 --- a/lib/build/recipe/emailpassword/api/utils.d.ts +++ b/lib/build/recipe/emailpassword/api/utils.d.ts @@ -1,14 +1,7 @@ // @ts-nocheck import { NormalisedFormField } from "../types"; import { UserContext } from "../../../types"; -export declare function validateFormFieldsOrThrowError( - configFormFields: NormalisedFormField[], - formFieldsRaw: any, - tenantId: string, - userContext: UserContext -): Promise< - { - id: string; - value: unknown; - }[] ->; +export declare function validateFormFieldsOrThrowError(configFormFields: NormalisedFormField[], formFieldsRaw: any, tenantId: string, userContext: UserContext): Promise<{ + id: string; + value: unknown; +}[]>; diff --git a/lib/build/recipe/emailpassword/api/utils.js b/lib/build/recipe/emailpassword/api/utils.js index 69da81aba..96610fa0d 100644 --- a/lib/build/recipe/emailpassword/api/utils.js +++ b/lib/build/recipe/emailpassword/api/utils.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateFormFieldsOrThrowError = void 0; const error_1 = __importDefault(require("../error")); @@ -35,9 +33,7 @@ async function validateFormFieldsOrThrowError(configFormFields, formFieldsRaw, t // we trim the email: https://github.com/supertokens/supertokens-core/issues/99 formFields = formFields.map((field) => { if (field.id === constants_1.FORM_FIELD_EMAIL_ID) { - return Object.assign(Object.assign({}, field), { - value: typeof field.value === "string" ? field.value.trim() : field.value, - }); + return Object.assign(Object.assign({}, field), { value: typeof field.value === "string" ? field.value.trim() : field.value }); } return field; }); @@ -67,8 +63,7 @@ async function validateFormOrThrowError(inputs, configFormFields, tenantId, user const input = inputs.find((input) => input.id === formField.id); // Add the not optional error if input is not passed // and the field is not optional. - const isValidInput = - !!input && + const isValidInput = !!input && ((typeof input.value === "string" ? input.value.length > 0 : input.value !== null && typeof input.value !== "undefined") || diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.d.ts index 7184fcbbd..30d5ecabf 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.d.ts @@ -2,14 +2,11 @@ import { TypeEmailPasswordEmailDeliveryInput } from "../../../types"; import { NormalisedAppinfo, UserContext } from "../../../../../types"; import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; -export default class BackwardCompatibilityService - implements EmailDeliveryInterface { +export default class BackwardCompatibilityService implements EmailDeliveryInterface { private isInServerlessEnv; private appInfo; constructor(appInfo: NormalisedAppinfo, isInServerlessEnv: boolean); - sendEmail: ( - input: TypeEmailPasswordEmailDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: TypeEmailPasswordEmailDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js index 8d799d719..ea19c0863 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js @@ -9,18 +9,14 @@ class BackwardCompatibilityService { // will get reset by the getUserById call above. try { if (!this.isInServerlessEnv) { - passwordResetFunctions_1 - .createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.passwordResetLink) - .catch((_) => {}); - } else { + passwordResetFunctions_1.createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.passwordResetLink).catch((_) => { }); + } + else { // see https://github.com/supertokens/supertokens-node/pull/135 - await passwordResetFunctions_1.createAndSendEmailUsingSupertokensService( - this.appInfo, - input.user, - input.passwordResetLink - ); + await passwordResetFunctions_1.createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.passwordResetLink); } - } catch (_) {} + } + catch (_) { } }; this.isInServerlessEnv = isInServerlessEnv; this.appInfo = appInfo; diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/index.js index 91700aeaf..d648973c6 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMTPService = void 0; const smtp_1 = __importDefault(require("./smtp")); diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.d.ts index 357c2f852..ea7f79b14 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.d.ts +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.d.ts @@ -6,9 +6,7 @@ import { UserContext } from "../../../../../types"; export default class SMTPService implements EmailDeliveryInterface { serviceImpl: ServiceInterface; constructor(config: TypeInput); - sendEmail: ( - input: TypeEmailPasswordEmailDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: TypeEmailPasswordEmailDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.js index dedcbe33f..da1ee9c8a 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const nodemailer_1 = require("nodemailer"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); @@ -12,9 +10,7 @@ class SMTPService { constructor(config) { this.sendEmail = async (input) => { let content = await this.serviceImpl.getContent(input); - await this.serviceImpl.sendRawEmail( - Object.assign(Object.assign({}, content), { userContext: input.userContext }) - ); + await this.serviceImpl.sendRawEmail(Object.assign(Object.assign({}, content), { userContext: input.userContext })); }; const transporter = nodemailer_1.createTransport({ host: config.smtpSettings.host, @@ -25,9 +21,7 @@ class SMTPService { }, secure: config.smtpSettings.secure, }); - let builder = new supertokens_js_override_1.default( - serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) - ); + let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from)); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.d.ts b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.d.ts index 34240509a..f54789fa9 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.d.ts +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.d.ts @@ -1,7 +1,5 @@ // @ts-nocheck import { TypeEmailPasswordPasswordResetEmailDeliveryInput } from "../../../types"; import { GetContentResult } from "../../../../../ingredients/emaildelivery/services/smtp"; -export default function getPasswordResetEmailContent( - input: TypeEmailPasswordPasswordResetEmailDeliveryInput -): GetContentResult; +export default function getPasswordResetEmailContent(input: TypeEmailPasswordPasswordResetEmailDeliveryInput): GetContentResult; export declare function getPasswordResetEmailHTML(appName: string, email: string, resetLink: string): string; diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.js b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.js index 62ec6c5a9..1649bca3b 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPasswordResetEmailHTML = void 0; const supertokens_1 = __importDefault(require("../../../../../supertokens")); diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.d.ts b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.d.ts index 95fdbda32..917f30d7d 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.d.ts +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.d.ts @@ -2,10 +2,7 @@ import { TypeEmailPasswordEmailDeliveryInput } from "../../../../types"; import { Transporter } from "nodemailer"; import { ServiceInterface } from "../../../../../../ingredients/emaildelivery/services/smtp"; -export declare function getServiceImplementation( - transporter: Transporter, - from: { - name: string; - email: string; - } -): ServiceInterface; +export declare function getServiceImplementation(transporter: Transporter, from: { + name: string; + email: string; +}): ServiceInterface; diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.js index bc0d7525c..efaed1687 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const passwordReset_1 = __importDefault(require("../passwordReset")); @@ -31,7 +29,8 @@ function getServiceImplementation(transporter, from) { subject: input.subject, html: input.body, }); - } else { + } + else { await transporter.sendMail({ from: `${from.name} <${from.email}>`, to: input.toEmail, diff --git a/lib/build/recipe/emailpassword/error.d.ts b/lib/build/recipe/emailpassword/error.d.ts index d4dc2cf9b..e72600d1d 100644 --- a/lib/build/recipe/emailpassword/error.d.ts +++ b/lib/build/recipe/emailpassword/error.d.ts @@ -2,19 +2,15 @@ import STError from "../../error"; export default class SessionError extends STError { static FIELD_ERROR: "FIELD_ERROR"; - constructor( - options: - | { - type: "FIELD_ERROR"; - payload: { - id: string; - error: string; - }[]; - message: string; - } - | { - type: "BAD_INPUT_ERROR"; - message: string; - } - ); + constructor(options: { + type: "FIELD_ERROR"; + payload: { + id: string; + error: string; + }[]; + message: string; + } | { + type: "BAD_INPUT_ERROR"; + message: string; + }); } diff --git a/lib/build/recipe/emailpassword/error.js b/lib/build/recipe/emailpassword/error.js index ce0b25647..8a9e8ef0e 100644 --- a/lib/build/recipe/emailpassword/error.js +++ b/lib/build/recipe/emailpassword/error.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/emailpassword/index.d.ts b/lib/build/recipe/emailpassword/index.d.ts index 42b4a07db..05910e54f 100644 --- a/lib/build/recipe/emailpassword/index.d.ts +++ b/lib/build/recipe/emailpassword/index.d.ts @@ -8,92 +8,41 @@ import { User } from "../../types"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static signUp( - tenantId: string, - email: string, - password: string, - session?: undefined, - userContext?: Record - ): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - >; - static signUp( - tenantId: string, - email: string, - password: string, - session: SessionContainerInterface, - userContext?: Record - ): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - | { - 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"; - } - >; - static signIn( - tenantId: string, - email: string, - password: string, - session?: undefined, - userContext?: Record - ): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "WRONG_CREDENTIALS_ERROR"; - } - >; - static signIn( - tenantId: string, - email: string, - password: string, - session: SessionContainerInterface, - userContext?: Record - ): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "WRONG_CREDENTIALS_ERROR"; - } - | { - 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"; - } - >; - static verifyCredentials( - tenantId: string, - email: string, - password: string, - userContext?: Record - ): Promise<{ + static signUp(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + }>; + static signUp(tenantId: string, email: string, password: string, session: SessionContainerInterface, userContext?: Record): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } | { + 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"; + }>; + static signIn(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "WRONG_CREDENTIALS_ERROR"; + }>; + static signIn(tenantId: string, email: string, password: string, session: SessionContainerInterface, userContext?: Record): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "WRONG_CREDENTIALS_ERROR"; + } | { + 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"; + }>; + static verifyCredentials(tenantId: string, email: string, password: string, userContext?: Record): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR"; }>; /** @@ -107,48 +56,25 @@ export default class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static createResetPasswordToken( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - token: string; - } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } - >; - static resetPasswordUsingToken( - tenantId: string, - token: string, - newPassword: string, - userContext?: Record - ): Promise< - | { - status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - } - | { - status: "PASSWORD_POLICY_VIOLATED_ERROR"; - failureReason: string; - } - >; - static consumePasswordResetToken( - tenantId: string, - token: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - email: string; - userId: string; - } - | { - status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - } - >; + static createResetPasswordToken(tenantId: string, userId: string, email: string, userContext?: Record): Promise<{ + status: "OK"; + token: string; + } | { + status: "UNKNOWN_USER_ID_ERROR"; + }>; + static resetPasswordUsingToken(tenantId: string, token: string, newPassword: string, userContext?: Record): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + } | { + status: "PASSWORD_POLICY_VIOLATED_ERROR"; + failureReason: string; + }>; + static consumePasswordResetToken(tenantId: string, token: string, userContext?: Record): Promise<{ + status: "OK"; + email: string; + userId: string; + } | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + }>; static updateEmailOrPassword(input: { recipeUserId: RecipeUserId; email?: string; @@ -156,46 +82,27 @@ export default class Wrapper { userContext?: Record; applyPasswordPolicy?: boolean; tenantIdForPasswordPolicy?: string; - }): Promise< - | { - status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; - } - | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } - | { - status: "PASSWORD_POLICY_VIOLATED_ERROR"; - failureReason: string; - } - >; - static createResetPasswordLink( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - link: string; - } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } - >; - static sendResetPasswordEmail( - tenantId: string, - userId: string, - email: string, - userContext?: Record - ): Promise<{ + }): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; + } | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } | { + status: "PASSWORD_POLICY_VIOLATED_ERROR"; + failureReason: string; + }>; + static createResetPasswordLink(tenantId: string, userId: string, email: string, userContext?: Record): Promise<{ + status: "OK"; + link: string; + } | { + status: "UNKNOWN_USER_ID_ERROR"; + }>; + static sendResetPasswordEmail(tenantId: string, userId: string, email: string, userContext?: Record): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR"; }>; - static sendEmail( - input: TypeEmailPasswordEmailDeliveryInput & { - userContext?: Record; - } - ): Promise; + static sendEmail(input: TypeEmailPasswordEmailDeliveryInput & { + userContext?: Record; + }): Promise; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/emailpassword/index.js b/lib/build/recipe/emailpassword/index.js index 2c77224c0..17e9aab7d 100644 --- a/lib/build/recipe/emailpassword/index.js +++ b/lib/build/recipe/emailpassword/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.sendEmail = exports.sendResetPasswordEmail = exports.createResetPasswordLink = exports.updateEmailOrPassword = exports.consumePasswordResetToken = exports.resetPasswordUsingToken = exports.createResetPasswordToken = exports.verifyCredentials = exports.signIn = exports.signUp = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -112,15 +110,7 @@ class Wrapper { }); } static updateEmailOrPassword(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.updateEmailOrPassword( - Object.assign(Object.assign({}, input), { - userContext: utils_2.getUserContext(input.userContext), - tenantIdForPasswordPolicy: - input.tenantIdForPasswordPolicy === undefined - ? constants_1.DEFAULT_TENANT_ID - : input.tenantIdForPasswordPolicy, - }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.updateEmailOrPassword(Object.assign(Object.assign({}, input), { userContext: utils_2.getUserContext(input.userContext), tenantIdForPasswordPolicy: input.tenantIdForPasswordPolicy === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantIdForPasswordPolicy })); } static async createResetPasswordLink(tenantId, userId, email, userContext) { const ctx = utils_2.getUserContext(userContext); @@ -170,12 +160,7 @@ class Wrapper { } static async sendEmail(input) { let recipeInstance = recipe_1.default.getInstanceOrThrowError(); - return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail( - Object.assign(Object.assign({}, input), { - tenantId: input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId, - userContext: utils_2.getUserContext(input.userContext), - }) - ); + return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail(Object.assign(Object.assign({}, input), { tenantId: input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId, userContext: utils_2.getUserContext(input.userContext) })); } } exports.default = Wrapper; diff --git a/lib/build/recipe/emailpassword/passwordResetFunctions.d.ts b/lib/build/recipe/emailpassword/passwordResetFunctions.d.ts index e7ed30fe1..5638c375e 100644 --- a/lib/build/recipe/emailpassword/passwordResetFunctions.d.ts +++ b/lib/build/recipe/emailpassword/passwordResetFunctions.d.ts @@ -1,10 +1,6 @@ // @ts-nocheck import { NormalisedAppinfo } from "../../types"; -export declare function createAndSendEmailUsingSupertokensService( - appInfo: NormalisedAppinfo, - user: { - id: string; - email: string; - }, - passwordResetURLWithToken: string -): Promise; +export declare function createAndSendEmailUsingSupertokensService(appInfo: NormalisedAppinfo, user: { + id: string; + email: string; +}, passwordResetURLWithToken: string): Promise; diff --git a/lib/build/recipe/emailpassword/passwordResetFunctions.js b/lib/build/recipe/emailpassword/passwordResetFunctions.js index 23ec9186f..f3a8bdab6 100644 --- a/lib/build/recipe/emailpassword/passwordResetFunctions.js +++ b/lib/build/recipe/emailpassword/passwordResetFunctions.js @@ -21,21 +21,16 @@ async function createAndSendEmailUsingSupertokensService(appInfo, user, password if (utils_1.isTestEnv()) { return; } - await utils_1.postWithFetch( - "https://api.supertokens.io/0/st/auth/password/reset", - { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, - { - email: user.email, - appName: appInfo.appName, - passwordResetURL: passwordResetURLWithToken, - }, - { - successLog: `Password reset email sent to ${user.email}`, - errorLogHeader: "Error sending password reset email", - } - ); + await utils_1.postWithFetch("https://api.supertokens.io/0/st/auth/password/reset", { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, { + email: user.email, + appName: appInfo.appName, + passwordResetURL: passwordResetURLWithToken, + }, { + successLog: `Password reset email sent to ${user.email}`, + errorLogHeader: "Error sending password reset email", + }); } exports.createAndSendEmailUsingSupertokensService = createAndSendEmailUsingSupertokensService; diff --git a/lib/build/recipe/emailpassword/recipe.d.ts b/lib/build/recipe/emailpassword/recipe.d.ts index 9ceb82533..811ff0ea1 100644 --- a/lib/build/recipe/emailpassword/recipe.d.ts +++ b/lib/build/recipe/emailpassword/recipe.d.ts @@ -15,28 +15,14 @@ export default class Recipe extends RecipeModule { apiImpl: APIInterface; isInServerlessEnv: boolean; emailDelivery: EmailDeliveryIngredient; - constructor( - recipeId: string, - appInfo: NormalisedAppinfo, - isInServerlessEnv: boolean, - config: TypeInput | undefined, - ingredients: { - emailDelivery: EmailDeliveryIngredient | undefined; - } - ); + constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput | undefined, ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + }); static getInstanceOrThrowError(): Recipe; static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - _path: NormalisedURLPath, - _method: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _request: BaseRequest, response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/emailpassword/recipe.js b/lib/build/recipe/emailpassword/recipe.js index dc968c536..aaec67688 100644 --- a/lib/build/recipe/emailpassword/recipe.js +++ b/lib/build/recipe/emailpassword/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -60,9 +58,7 @@ class Recipe extends recipeModule_1.default { }, { method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default( - constants_1.GENERATE_PASSWORD_RESET_TOKEN_API - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.GENERATE_PASSWORD_RESET_TOKEN_API), id: constants_1.GENERATE_PASSWORD_RESET_TOKEN_API, disabled: this.apiImpl.generatePasswordResetTokenPOST === undefined, }, @@ -99,13 +95,17 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.SIGN_UP_API) { return await signup_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.SIGN_IN_API) { + } + else if (id === constants_1.SIGN_IN_API) { return await signin_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.GENERATE_PASSWORD_RESET_TOKEN_API) { + } + else if (id === constants_1.GENERATE_PASSWORD_RESET_TOKEN_API) { return await generatePasswordResetToken_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.PASSWORD_RESET_API) { + } + else if (id === constants_1.PASSWORD_RESET_API) { return await passwordReset_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API || id === constants_1.SIGNUP_EMAIL_EXISTS_API_OLD) { + } + else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API || id === constants_1.SIGNUP_EMAIL_EXISTS_API_OLD) { return await emailExists_1.default(this.apiImpl, tenantId, options, userContext); } return false; @@ -117,10 +117,12 @@ class Recipe extends recipeModule_1.default { status: "FIELD_ERROR", formFields: err.payload, }); - } else { + } + else { throw err; } - } else { + } + else { throw err; } }; @@ -134,12 +136,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); { const getEmailPasswordConfig = () => this.config; - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default( - querier_1.Querier.getNewInstanceOrThrowError(recipeId), - getEmailPasswordConfig - ) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), getEmailPasswordConfig)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -191,9 +188,7 @@ class Recipe extends recipeModule_1.default { return a.timeJoined - b.timeJoined; }); // Then we take the ones that belong to this recipe - const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter( - (lm) => lm.recipeId === Recipe.RECIPE_ID - ); + const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter((lm) => lm.recipeId === Recipe.RECIPE_ID); let result; if (recipeLoginMethodsOrderedByTimeJoinedOldestFirst.length !== 0) { // If there are login methods belonging to this recipe, the factor is set up @@ -220,18 +215,16 @@ class Recipe extends recipeModule_1.default { .map((lm) => lm.email), ]; // We handle moving the session email to the top of the list later - } else { + } + else { // This factor hasn't been set up, we list all emails belonging to the user - if ( - orderedLoginMethodsByTimeJoinedOldestFirst.some( - (lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email) - ) - ) { + if (orderedLoginMethodsByTimeJoinedOldestFirst.some((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email))) { // If there is at least one real email address linked to the user, we only suggest real addresses result = orderedLoginMethodsByTimeJoinedOldestFirst .filter((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email)) .map((lm) => lm.email); - } else { + } + else { // Else we use the fake ones result = orderedLoginMethodsByTimeJoinedOldestFirst .filter((lm) => lm.email !== undefined && utils_3.isFakeEmail(lm.email)) @@ -285,7 +278,8 @@ class Recipe extends recipeModule_1.default { emailDelivery: undefined, }); return Recipe.instance; - } else { + } + else { throw new Error("Emailpassword recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/emailpassword/recipeImplementation.d.ts b/lib/build/recipe/emailpassword/recipeImplementation.d.ts index cfb8e6ad0..3d70a735b 100644 --- a/lib/build/recipe/emailpassword/recipeImplementation.d.ts +++ b/lib/build/recipe/emailpassword/recipeImplementation.d.ts @@ -1,7 +1,4 @@ // @ts-nocheck import { RecipeInterface, TypeNormalisedInput } from "./types"; import { Querier } from "../../querier"; -export default function getRecipeInterface( - querier: Querier, - getEmailPasswordConfig: () => TypeNormalisedInput -): RecipeInterface; +export default function getRecipeInterface(querier: Querier, getEmailPasswordConfig: () => TypeNormalisedInput): RecipeInterface; diff --git a/lib/build/recipe/emailpassword/recipeImplementation.js b/lib/build/recipe/emailpassword/recipeImplementation.js index acbbb1c7f..3f214d583 100644 --- a/lib/build/recipe/emailpassword/recipeImplementation.js +++ b/lib/build/recipe/emailpassword/recipeImplementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../accountlinking/recipe")); const recipe_2 = __importDefault(require("../emailverification/recipe")); @@ -27,16 +25,14 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { return response; } let updatedUser = response.user; - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( - { - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - } - ); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }); if (linkResult.status != "OK") { return linkResult; } @@ -48,16 +44,10 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { }; }, createNewRecipeUser: async function (input) { - const resp = await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${input.tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : input.tenantId}/recipe/signup` - ), - { - email: input.email, - password: input.password, - }, - input.userContext - ); + const resp = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : input.tenantId}/recipe/signup`), { + email: input.email, + password: input.password, + }, input.userContext); if (resp.status === "OK") { return { status: "OK", @@ -72,9 +62,7 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { 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() - ); + const loginMethod = response.user.loginMethods.find((lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString()); if (!loginMethod.verified) { await recipe_1.default.getInstance().verifyEmailForRecipeUserIfLinkedAccountsAreVerified({ user: response.user, @@ -92,18 +80,16 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { // 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 __1.getUser(response.recipeUserId.getAsString(), userContext); + response.user = (await __1.getUser(response.recipeUserId.getAsString(), userContext)); } - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( - { - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - } - ); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }); if (linkResult.status === "LINKING_TO_SESSION_USER_FAILED") { return linkResult; } @@ -111,17 +97,11 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { } return response; }, - verifyCredentials: async function ({ email, password, tenantId, userContext }) { - const response = await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/recipe/signin` - ), - { - email, - password, - }, - userContext - ); + verifyCredentials: async function ({ email, password, tenantId, userContext, }) { + const response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/recipe/signin`), { + email, + password, + }, userContext); if (response.status === "OK") { return { status: "OK", @@ -133,34 +113,18 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { status: "WRONG_CREDENTIALS_ERROR", }; }, - createResetPasswordToken: async function ({ userId, email, tenantId, userContext }) { + createResetPasswordToken: async function ({ userId, email, tenantId, userContext, }) { // the input user ID can be a recipe or a primary user ID. - return await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId - }/recipe/user/password/reset/token` - ), - { - userId, - email, - }, - userContext - ); + return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/recipe/user/password/reset/token`), { + userId, + email, + }, userContext); }, - consumePasswordResetToken: async function ({ token, tenantId, userContext }) { - return await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId - }/recipe/user/password/reset/token/consume` - ), - { - method: "token", - token, - }, - userContext - ); + consumePasswordResetToken: async function ({ token, tenantId, userContext, }) { + return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/recipe/user/password/reset/token/consume`), { + method: "token", + token, + }, userContext); }, updateEmailOrPassword: async function (input) { const accountLinking = recipe_1.default.getInstance(); @@ -188,10 +152,9 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { 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.", + 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.", }; } } @@ -199,11 +162,7 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { 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 - ); + const error = await passwordField.validate(input.password, input.tenantIdForPasswordPolicy, input.userContext); if (error !== undefined) { return { status: "PASSWORD_POLICY_VIOLATED_ERROR", @@ -218,16 +177,11 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { // 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( - new normalisedURLPath_1.default(`/recipe/user`), - { - recipeUserId: input.recipeUserId.getAsString(), - email: input.email, - password: input.password, - }, - {}, - input.userContext - ); + let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/user`), { + recipeUserId: input.recipeUserId.getAsString(), + email: input.email, + password: input.password, + }, {}, input.userContext); if (response.status === "OK") { const user = await __1.getUser(input.recipeUserId.getAsString(), input.userContext); if (user === undefined) { diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index fa3eb1408..1e370bf30 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -2,25 +2,17 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; import { SessionContainerInterface } from "../session/types"; -import { - TypeInput as EmailDeliveryTypeInput, - TypeInputWithService as EmailDeliveryTypeInputWithService, -} from "../../ingredients/emaildelivery/types"; +import { TypeInput as EmailDeliveryTypeInput, TypeInputWithService as EmailDeliveryTypeInputWithService } from "../../ingredients/emaildelivery/types"; import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; export declare type TypeNormalisedInput = { signUpFeature: TypeNormalisedInputSignUp; signInFeature: TypeNormalisedInputSignIn; - getEmailDeliveryConfig: ( - isInServerlessEnv: boolean - ) => EmailDeliveryTypeInputWithService; + getEmailDeliveryConfig: (isInServerlessEnv: boolean) => EmailDeliveryTypeInputWithService; resetPasswordUsingTokenFeature: TypeNormalisedInputResetPasswordUsingTokenFeature; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -55,10 +47,7 @@ export declare type TypeInput = { signUpFeature?: TypeInputSignUp; emailDelivery?: EmailDeliveryTypeInput; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -70,39 +59,28 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - | { - 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"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } | { + 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"; + }>; createNewRecipeUser(input: { email: string; password: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + }>; signIn(input: { email: string; password: string; @@ -110,39 +88,28 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "WRONG_CREDENTIALS_ERROR"; - } - | { - 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"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "WRONG_CREDENTIALS_ERROR"; + } | { + 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"; + }>; verifyCredentials(input: { email: string; password: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "WRONG_CREDENTIALS_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "WRONG_CREDENTIALS_ERROR"; + }>; /** * We pass in the email as well to this function cause the input userId * may not be associated with an emailpassword account. In this case, we @@ -153,29 +120,23 @@ export declare type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - token: string; - } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + token: string; + } | { + status: "UNKNOWN_USER_ID_ERROR"; + }>; consumePasswordResetToken(input: { token: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - email: string; - userId: string; - } - | { - status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + email: string; + userId: string; + } | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + }>; updateEmailOrPassword(input: { recipeUserId: RecipeUserId; email?: string; @@ -183,19 +144,15 @@ export declare type RecipeInterface = { userContext: UserContext; applyPasswordPolicy?: boolean; tenantIdForPasswordPolicy: string; - }): Promise< - | { - status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; - } - | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } - | { - status: "PASSWORD_POLICY_VIOLATED_ERROR"; - failureReason: string; - } - >; + }): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; + } | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } | { + status: "PASSWORD_POLICY_VIOLATED_ERROR"; + failureReason: string; + }>; }; export declare type APIOptions = { recipeImplementation: RecipeInterface; @@ -208,120 +165,88 @@ export declare type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; export declare type APIInterface = { - emailExistsGET: - | undefined - | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - exists: boolean; - } - | GeneralErrorResponse - >); - generatePasswordResetTokenPOST: - | undefined - | ((input: { - formFields: { - id: string; - value: unknown; - }[]; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - } - | { - status: "PASSWORD_RESET_NOT_ALLOWED"; - reason: string; - } - | GeneralErrorResponse - >); - passwordResetPOST: - | undefined - | ((input: { - formFields: { - id: string; - value: unknown; - }[]; - token: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - email: string; - user: User; - } - | { - status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - } - | { - status: "PASSWORD_POLICY_VIOLATED_ERROR"; - failureReason: string; - } - | GeneralErrorResponse - >); - signInPOST: - | undefined - | ((input: { - formFields: { - id: string; - value: unknown; - }[]; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - user: User; - session: SessionContainerInterface; - } - | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } - | { - status: "WRONG_CREDENTIALS_ERROR"; - } - | GeneralErrorResponse - >); - signUpPOST: - | undefined - | ((input: { - formFields: { - id: string; - value: unknown; - }[]; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - user: User; - session: SessionContainerInterface; - } - | { - status: "SIGN_UP_NOT_ALLOWED"; - reason: string; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - | GeneralErrorResponse - >); + emailExistsGET: undefined | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + exists: boolean; + } | GeneralErrorResponse>); + generatePasswordResetTokenPOST: undefined | ((input: { + formFields: { + id: string; + value: unknown; + }[]; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + } | { + status: "PASSWORD_RESET_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse>); + passwordResetPOST: undefined | ((input: { + formFields: { + id: string; + value: unknown; + }[]; + token: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + email: string; + user: User; + } | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + } | { + status: "PASSWORD_POLICY_VIOLATED_ERROR"; + failureReason: string; + } | GeneralErrorResponse>); + signInPOST: undefined | ((input: { + formFields: { + id: string; + value: unknown; + }[]; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + user: User; + session: SessionContainerInterface; + } | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } | { + status: "WRONG_CREDENTIALS_ERROR"; + } | GeneralErrorResponse>); + signUpPOST: undefined | ((input: { + formFields: { + id: string; + value: unknown; + }[]; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + user: User; + session: SessionContainerInterface; + } | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } | GeneralErrorResponse>); }; export declare type TypeEmailPasswordPasswordResetEmailDeliveryInput = { type: "PASSWORD_RESET"; diff --git a/lib/build/recipe/emailpassword/utils.d.ts b/lib/build/recipe/emailpassword/utils.d.ts index a62eb7fe7..b9aa6f378 100644 --- a/lib/build/recipe/emailpassword/utils.d.ts +++ b/lib/build/recipe/emailpassword/utils.d.ts @@ -3,25 +3,10 @@ import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput, NormalisedFormField, TypeInputFormField } from "./types"; import { NormalisedAppinfo, UserContext } from "../../types"; import { BaseRequest } from "../../framework"; -export declare function validateAndNormaliseUserInput( - recipeInstance: Recipe, - appInfo: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(recipeInstance: Recipe, appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; export declare function normaliseSignUpFormFields(formFields?: TypeInputFormField[]): NormalisedFormField[]; -export declare function defaultPasswordValidator( - value: any -): Promise< - | "Development bug: Please make sure the password field yields a string" - | "Password must contain at least 8 characters, including a number" - | "Password's length must be lesser than 100 characters" - | "Password must contain at least one alphabet" - | "Password must contain at least one number" - | undefined ->; -export declare function defaultEmailValidator( - value: any -): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; +export declare function defaultPasswordValidator(value: any): Promise<"Development bug: Please make sure the password field yields a string" | "Password must contain at least 8 characters, including a number" | "Password's length must be lesser than 100 characters" | "Password must contain at least one alphabet" | "Password must contain at least one number" | undefined>; +export declare function defaultEmailValidator(value: any): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; export declare function getPasswordResetLink(input: { appInfo: NormalisedAppinfo; token: string; diff --git a/lib/build/recipe/emailpassword/utils.js b/lib/build/recipe/emailpassword/utils.js index 18014af69..afc4c7a4d 100644 --- a/lib/build/recipe/emailpassword/utils.js +++ b/lib/build/recipe/emailpassword/utils.js @@ -13,36 +13,21 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPasswordResetLink = exports.defaultEmailValidator = exports.defaultPasswordValidator = exports.normaliseSignUpFormFields = exports.validateAndNormaliseUserInput = void 0; const constants_1 = require("./constants"); const backwardCompatibility_1 = __importDefault(require("./emaildelivery/services/backwardCompatibility")); function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { - let signUpFeature = validateAndNormaliseSignupConfig( - recipeInstance, - appInfo, - config === undefined ? undefined : config.signUpFeature - ); + let signUpFeature = validateAndNormaliseSignupConfig(recipeInstance, appInfo, config === undefined ? undefined : config.signUpFeature); let signInFeature = validateAndNormaliseSignInConfig(recipeInstance, appInfo, signUpFeature); let resetPasswordUsingTokenFeature = validateAndNormaliseResetPasswordUsingTokenConfig(signUpFeature); - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); function getEmailDeliveryConfig(isInServerlessEnv) { var _a; - let emailService = - (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 - ? void 0 - : _a.service; + let emailService = (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; /** * If the user has not passed even that config, we use the default * createAndSendCustomEmail implementation which calls our supertokens API @@ -50,7 +35,7 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { if (emailService === undefined) { emailService = new backwardCompatibility_1.default(appInfo, isInServerlessEnv); } - return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { + return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { /** * if we do * let emailDelivery = { @@ -62,8 +47,7 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { * it it again get set to undefined, so we * set service at the end */ - service: emailService, - }); + service: emailService }); } return { signUpFeature, @@ -78,21 +62,21 @@ function validateAndNormaliseResetPasswordUsingTokenConfig(signUpConfig) { let formFieldsForPasswordResetForm = signUpConfig.formFields .filter((filter) => filter.id === constants_1.FORM_FIELD_PASSWORD_ID) .map((field) => { - return { - id: field.id, - validate: field.validate, - optional: false, - }; - }); + return { + id: field.id, + validate: field.validate, + optional: false, + }; + }); let formFieldsForGenerateTokenForm = signUpConfig.formFields .filter((filter) => filter.id === constants_1.FORM_FIELD_EMAIL_ID) .map((field) => { - return { - id: field.id, - validate: field.validate, - optional: false, - }; - }); + return { + id: field.id, + validate: field.validate, + optional: false, + }; + }); return { formFieldsForPasswordResetForm, formFieldsForGenerateTokenForm, @@ -100,18 +84,15 @@ function validateAndNormaliseResetPasswordUsingTokenConfig(signUpConfig) { } function normaliseSignInFormFields(formFields) { return formFields - .filter( - (filter) => - filter.id === constants_1.FORM_FIELD_EMAIL_ID || filter.id === constants_1.FORM_FIELD_PASSWORD_ID - ) + .filter((filter) => filter.id === constants_1.FORM_FIELD_EMAIL_ID || filter.id === constants_1.FORM_FIELD_PASSWORD_ID) .map((field) => { - return { - id: field.id, - // see issue: https://github.com/supertokens/supertokens-node/issues/36 - validate: field.id === constants_1.FORM_FIELD_EMAIL_ID ? field.validate : defaultValidator, - optional: false, - }; - }); + return { + id: field.id, + // see issue: https://github.com/supertokens/supertokens-node/issues/36 + validate: field.id === constants_1.FORM_FIELD_EMAIL_ID ? field.validate : defaultValidator, + optional: false, + }; + }); } function validateAndNormaliseSignInConfig(_, __, signUpConfig) { let formFields = normaliseSignInFormFields(signUpConfig.formFields); @@ -129,13 +110,15 @@ function normaliseSignUpFormFields(formFields) { validate: field.validate === undefined ? defaultPasswordValidator : field.validate, optional: false, }); - } else if (field.id === constants_1.FORM_FIELD_EMAIL_ID) { + } + else if (field.id === constants_1.FORM_FIELD_EMAIL_ID) { normalisedFormFields.push({ id: field.id, validate: field.validate === undefined ? defaultEmailValidator : field.validate, optional: false, }); - } else { + } + else { normalisedFormFields.push({ id: field.id, validate: field.validate === undefined ? defaultValidator : field.validate, @@ -201,29 +184,23 @@ async function defaultEmailValidator(value) { if (typeof value !== "string") { return "Development bug: Please make sure the email field yields a string"; } - if ( - value.match( - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - ) === null - ) { + if (value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) === null) { return "Email is invalid"; } return undefined; } exports.defaultEmailValidator = defaultEmailValidator; function getPasswordResetLink(input) { - return ( - input.appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() + + return (input.appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + input.appInfo.websiteBasePath.getAsStringDangerous() + "/reset-password?token=" + input.token + "&tenantId=" + - input.tenantId - ); + input.tenantId); } exports.getPasswordResetLink = getPasswordResetLink; diff --git a/lib/build/recipe/emailverification/api/emailVerify.d.ts b/lib/build/recipe/emailverification/api/emailVerify.d.ts index 1f7b416c8..7025fa039 100644 --- a/lib/build/recipe/emailverification/api/emailVerify.d.ts +++ b/lib/build/recipe/emailverification/api/emailVerify.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function emailVerify( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function emailVerify(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/emailverification/api/emailVerify.js b/lib/build/recipe/emailverification/api/emailVerify.js index 96a693995..3a8d5675b 100644 --- a/lib/build/recipe/emailverification/api/emailVerify.js +++ b/lib/build/recipe/emailverification/api/emailVerify.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -43,12 +41,7 @@ async function emailVerify(apiImplementation, tenantId, options, userContext) { message: "The email verification token must be a string", }); } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: false }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: false }, userContext); let response = await apiImplementation.verifyEmailPOST({ token, tenantId, @@ -61,19 +54,16 @@ async function emailVerify(apiImplementation, tenantId, options, userContext) { // automatically added to the response by the createNewSession function call // inside the verifyEmailPOST function. result = { status: "OK" }; - } else { + } + else { result = response; } - } else { + } + else { if (apiImplementation.isEmailVerifiedGET === undefined) { return false; } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [] }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [] }, userContext); result = await apiImplementation.isEmailVerifiedGET({ options, session, diff --git a/lib/build/recipe/emailverification/api/generateEmailVerifyToken.d.ts b/lib/build/recipe/emailverification/api/generateEmailVerifyToken.d.ts index 487897d0e..8ec37da34 100644 --- a/lib/build/recipe/emailverification/api/generateEmailVerifyToken.d.ts +++ b/lib/build/recipe/emailverification/api/generateEmailVerifyToken.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function generateEmailVerifyToken( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function generateEmailVerifyToken(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/emailverification/api/generateEmailVerifyToken.js b/lib/build/recipe/emailverification/api/generateEmailVerifyToken.js index 32ed210ee..203987e05 100644 --- a/lib/build/recipe/emailverification/api/generateEmailVerifyToken.js +++ b/lib/build/recipe/emailverification/api/generateEmailVerifyToken.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -26,12 +24,7 @@ async function generateEmailVerifyToken(apiImplementation, options, userContext) if (apiImplementation.generateEmailVerifyTokenPOST === undefined) { return false; } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [] }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [] }, userContext); const result = await apiImplementation.generateEmailVerifyTokenPOST({ options, session: session, diff --git a/lib/build/recipe/emailverification/api/implementation.js b/lib/build/recipe/emailverification/api/implementation.js index b41d7d7b1..d414da90c 100644 --- a/lib/build/recipe/emailverification/api/implementation.js +++ b/lib/build/recipe/emailverification/api/implementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("../../../logger"); const recipe_1 = __importDefault(require("../recipe")); @@ -23,15 +21,13 @@ function getAPIInterface() { return verifyTokenResponse; } // status: "OK" - let newSession = await recipe_1.default - .getInstanceOrThrowError() - .updateSessionIfRequiredPostEmailVerification({ - req: options.req, - res: options.res, - session, - recipeUserIdWhoseEmailGotVerified: verifyTokenResponse.user.recipeUserId, - userContext, - }); + let newSession = await recipe_1.default.getInstanceOrThrowError().updateSessionIfRequiredPostEmailVerification({ + req: options.req, + res: options.res, + session, + recipeUserIdWhoseEmailGotVerified: verifyTokenResponse.user.recipeUserId, + userContext, + }); return { status: "OK", user: verifyTokenResponse.user, @@ -40,9 +36,7 @@ function getAPIInterface() { }, isEmailVerifiedGET: async function ({ userContext, session, options }) { // In this API, we will check if the session's recipe user id's email is verified or not. - const emailInfo = await recipe_1.default - .getInstanceOrThrowError() - .getEmailForRecipeUserId(undefined, session.getRecipeUserId(userContext), userContext); + const emailInfo = await recipe_1.default.getInstanceOrThrowError().getEmailForRecipeUserId(undefined, session.getRecipeUserId(userContext), userContext); if (emailInfo.status === "OK") { const isVerified = await options.recipeImplementation.isEmailVerified({ recipeUserId: session.getRecipeUserId(userContext), @@ -55,34 +49,35 @@ function getAPIInterface() { // whilst the first browser is polling this API - in this case, // we want to have the same effect to the session as if the // email was opened on the original browser itself. - let newSession = await recipe_1.default - .getInstanceOrThrowError() - .updateSessionIfRequiredPostEmailVerification({ - req: options.req, - res: options.res, - session, - recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), - userContext, - }); + let newSession = await recipe_1.default.getInstanceOrThrowError().updateSessionIfRequiredPostEmailVerification({ + req: options.req, + res: options.res, + session, + recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), + userContext, + }); return { status: "OK", isVerified: true, newSession, }; - } else { + } + else { await session.setClaimValue(emailVerificationClaim_1.EmailVerificationClaim, false, userContext); return { status: "OK", isVerified: false, }; } - } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } + else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { // We consider people without email addresses as validated return { status: "OK", isVerified: true, }; - } else { + } + else { // this means that the user ID is not known to supertokens. This could // happen if the current session's user ID is not an auth user, // or if it belong to a recipe user ID that got deleted. Either way, @@ -96,31 +91,26 @@ function getAPIInterface() { generateEmailVerifyTokenPOST: async function ({ options, userContext, session }) { // In this API, we generate the email verification token for session's recipe user ID. const tenantId = session.getTenantId(); - const emailInfo = await recipe_1.default - .getInstanceOrThrowError() - .getEmailForRecipeUserId(undefined, session.getRecipeUserId(userContext), userContext); + const emailInfo = await recipe_1.default.getInstanceOrThrowError().getEmailForRecipeUserId(undefined, session.getRecipeUserId(userContext), userContext); if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { - logger_1.logDebugMessage( - `Email verification email not sent to user ${session - .getRecipeUserId(userContext) - .getAsString()} because it doesn't have an email address.` - ); + logger_1.logDebugMessage(`Email verification email not sent to user ${session + .getRecipeUserId(userContext) + .getAsString()} because it doesn't have an email address.`); // this can happen if the user ID was found, but it has no email. In this // case, we treat it as a success case. - let newSession = await recipe_1.default - .getInstanceOrThrowError() - .updateSessionIfRequiredPostEmailVerification({ - req: options.req, - res: options.res, - session, - recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), - userContext, - }); + let newSession = await recipe_1.default.getInstanceOrThrowError().updateSessionIfRequiredPostEmailVerification({ + req: options.req, + res: options.res, + session, + recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), + userContext, + }); return { status: "EMAIL_ALREADY_VERIFIED_ERROR", newSession, }; - } else if (emailInfo.status === "OK") { + } + else if (emailInfo.status === "OK") { let response = await options.recipeImplementation.createEmailVerificationToken({ recipeUserId: session.getRecipeUserId(userContext), email: emailInfo.email, @@ -130,20 +120,16 @@ function getAPIInterface() { // In case the email is already verified, we do the same thing // as what happens in the verifyEmailPOST API post email verification (cause maybe the session is outdated). if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - logger_1.logDebugMessage( - `Email verification email not sent to user ${session - .getRecipeUserId(userContext) - .getAsString()} because it is already verified.` - ); - let newSession = await recipe_1.default - .getInstanceOrThrowError() - .updateSessionIfRequiredPostEmailVerification({ - req: options.req, - res: options.res, - session, - recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), - userContext, - }); + logger_1.logDebugMessage(`Email verification email not sent to user ${session + .getRecipeUserId(userContext) + .getAsString()} because it is already verified.`); + let newSession = await recipe_1.default.getInstanceOrThrowError().updateSessionIfRequiredPostEmailVerification({ + req: options.req, + res: options.res, + session, + recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), + userContext, + }); return { status: "EMAIL_ALREADY_VERIFIED_ERROR", newSession, @@ -177,14 +163,13 @@ function getAPIInterface() { return { status: "OK", }; - } else { + } + else { // this means that the user ID is not known to supertokens. This could // happen if the current session's user ID is not an auth user, // or if it belong to a recipe user ID that got deleted. Either way, // we logout the user. - logger_1.logDebugMessage( - "generateEmailVerifyTokenPOST: Returning UNAUTHORISED because the user id provided is unknown" - ); + logger_1.logDebugMessage("generateEmailVerifyTokenPOST: Returning UNAUTHORISED because the user id provided is unknown"); throw new error_1.default({ type: error_1.default.UNAUTHORISED, message: "Unknown User ID provided" }); } }, diff --git a/lib/build/recipe/emailverification/emailVerificationClaim.js b/lib/build/recipe/emailverification/emailVerificationClaim.js index 092eddd10..af2b89ddd 100644 --- a/lib/build/recipe/emailverification/emailVerificationClaim.js +++ b/lib/build/recipe/emailverification/emailVerificationClaim.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.EmailVerificationClaim = exports.EmailVerificationClaimClass = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -24,38 +22,35 @@ class EmailVerificationClaimClass extends claims_1.BooleanClaim { email: emailInfo.email, userContext, }); - } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } + else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { // We consider people without email addresses as validated return true; - } else { + } + else { throw new Error("UNKNOWN_USER_ID"); } }, }); - this.validators = Object.assign(Object.assign({}, this.validators), { - isVerified: (refetchTimeOnFalseInSeconds = 10, maxAgeInSeconds) => - Object.assign(Object.assign({}, this.validators.hasValue(true, maxAgeInSeconds)), { - shouldRefetch: (payload, userContext) => { - const value = this.getValueFromPayload(payload, userContext); - if (value === undefined) { + this.validators = Object.assign(Object.assign({}, this.validators), { isVerified: (refetchTimeOnFalseInSeconds = 10, maxAgeInSeconds) => (Object.assign(Object.assign({}, this.validators.hasValue(true, maxAgeInSeconds)), { shouldRefetch: (payload, userContext) => { + const value = this.getValueFromPayload(payload, userContext); + if (value === undefined) { + return true; + } + const currentTime = Date.now(); + const lastRefetchTime = this.getLastRefetchTime(payload, userContext); + if (maxAgeInSeconds !== undefined) { + if (lastRefetchTime < currentTime - maxAgeInSeconds * 1000) { return true; } - const currentTime = Date.now(); - const lastRefetchTime = this.getLastRefetchTime(payload, userContext); - if (maxAgeInSeconds !== undefined) { - if (lastRefetchTime < currentTime - maxAgeInSeconds * 1000) { - return true; - } - } - if (value === false) { - if (lastRefetchTime < currentTime - refetchTimeOnFalseInSeconds * 1000) { - return true; - } + } + if (value === false) { + if (lastRefetchTime < currentTime - refetchTimeOnFalseInSeconds * 1000) { + return true; } - return false; - }, - }), - }); + } + return false; + } })) }); } } exports.EmailVerificationClaimClass = EmailVerificationClaimClass; diff --git a/lib/build/recipe/emailverification/emailVerificationFunctions.d.ts b/lib/build/recipe/emailverification/emailVerificationFunctions.d.ts index 2eb1c0427..8128246cb 100644 --- a/lib/build/recipe/emailverification/emailVerificationFunctions.d.ts +++ b/lib/build/recipe/emailverification/emailVerificationFunctions.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { UserEmailInfo } from "./types"; import { NormalisedAppinfo } from "../../types"; -export declare function createAndSendEmailUsingSupertokensService( - appInfo: NormalisedAppinfo, - user: UserEmailInfo, - emailVerifyURLWithToken: string -): Promise; +export declare function createAndSendEmailUsingSupertokensService(appInfo: NormalisedAppinfo, user: UserEmailInfo, emailVerifyURLWithToken: string): Promise; diff --git a/lib/build/recipe/emailverification/emailVerificationFunctions.js b/lib/build/recipe/emailverification/emailVerificationFunctions.js index fb6b03bfc..d22a0dd6a 100644 --- a/lib/build/recipe/emailverification/emailVerificationFunctions.js +++ b/lib/build/recipe/emailverification/emailVerificationFunctions.js @@ -20,21 +20,16 @@ async function createAndSendEmailUsingSupertokensService(appInfo, user, emailVer if (utils_1.isTestEnv()) { return; } - await utils_1.postWithFetch( - "https://api.supertokens.io/0/st/auth/email/verify", - { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, - { - email: user.email, - appName: appInfo.appName, - emailVerifyURL: emailVerifyURLWithToken, - }, - { - successLog: `Email sent to ${user.email}`, - errorLogHeader: "Error sending verification email", - } - ); + await utils_1.postWithFetch("https://api.supertokens.io/0/st/auth/email/verify", { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, { + email: user.email, + appName: appInfo.appName, + emailVerifyURL: emailVerifyURLWithToken, + }, { + successLog: `Email sent to ${user.email}`, + errorLogHeader: "Error sending verification email", + }); } exports.createAndSendEmailUsingSupertokensService = createAndSendEmailUsingSupertokensService; diff --git a/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.d.ts index d5c7479ac..f95580eb6 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.d.ts @@ -2,14 +2,11 @@ import { TypeEmailVerificationEmailDeliveryInput } from "../../../types"; import { NormalisedAppinfo, UserContext } from "../../../../../types"; import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; -export default class BackwardCompatibilityService - implements EmailDeliveryInterface { +export default class BackwardCompatibilityService implements EmailDeliveryInterface { private appInfo; private isInServerlessEnv; constructor(appInfo: NormalisedAppinfo, isInServerlessEnv: boolean); - sendEmail: ( - input: TypeEmailVerificationEmailDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: TypeEmailVerificationEmailDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.js index 7fc3cc6f6..fccc4aecc 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.js @@ -6,18 +6,14 @@ class BackwardCompatibilityService { this.sendEmail = async (input) => { try { if (!this.isInServerlessEnv) { - emailVerificationFunctions_1 - .createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.emailVerifyLink) - .catch((_) => {}); - } else { + emailVerificationFunctions_1.createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.emailVerifyLink).catch((_) => { }); + } + else { // see https://github.com/supertokens/supertokens-node/pull/135 - await emailVerificationFunctions_1.createAndSendEmailUsingSupertokensService( - this.appInfo, - input.user, - input.emailVerifyLink - ); + await emailVerificationFunctions_1.createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.emailVerifyLink); } - } catch (_) {} + } + catch (_) { } }; this.appInfo = appInfo; this.isInServerlessEnv = isInServerlessEnv; diff --git a/lib/build/recipe/emailverification/emaildelivery/services/index.js b/lib/build/recipe/emailverification/emaildelivery/services/index.js index 91700aeaf..d648973c6 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/index.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMTPService = void 0; const smtp_1 = __importDefault(require("./smtp")); diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/emailVerify.js b/lib/build/recipe/emailverification/emaildelivery/services/smtp/emailVerify.js index 5c7406a82..000d552f8 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/emailVerify.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/emailVerify.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEmailVerifyEmailHTML = void 0; const supertokens_1 = __importDefault(require("../../../../../supertokens")); diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.d.ts index cca4977ab..7693cfff6 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.d.ts +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.d.ts @@ -6,9 +6,7 @@ import { UserContext } from "../../../../../types"; export default class SMTPService implements EmailDeliveryInterface { serviceImpl: ServiceInterface; constructor(config: TypeInput); - sendEmail: ( - input: TypeEmailVerificationEmailDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: TypeEmailVerificationEmailDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.js b/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.js index dedcbe33f..da1ee9c8a 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const nodemailer_1 = require("nodemailer"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); @@ -12,9 +10,7 @@ class SMTPService { constructor(config) { this.sendEmail = async (input) => { let content = await this.serviceImpl.getContent(input); - await this.serviceImpl.sendRawEmail( - Object.assign(Object.assign({}, content), { userContext: input.userContext }) - ); + await this.serviceImpl.sendRawEmail(Object.assign(Object.assign({}, content), { userContext: input.userContext })); }; const transporter = nodemailer_1.createTransport({ host: config.smtpSettings.host, @@ -25,9 +21,7 @@ class SMTPService { }, secure: config.smtpSettings.secure, }); - let builder = new supertokens_js_override_1.default( - serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) - ); + let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from)); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.d.ts b/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.d.ts index 3f668725f..d072890a9 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.d.ts +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.d.ts @@ -2,10 +2,7 @@ import { TypeEmailVerificationEmailDeliveryInput } from "../../../types"; import { Transporter } from "nodemailer"; import { ServiceInterface } from "../../../../../ingredients/emaildelivery/services/smtp"; -export declare function getServiceImplementation( - transporter: Transporter, - from: { - name: string; - email: string; - } -): ServiceInterface; +export declare function getServiceImplementation(transporter: Transporter, from: { + name: string; + email: string; +}): ServiceInterface; diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.js b/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.js index 3160ed2a7..05fc06879 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const emailVerify_1 = __importDefault(require("./emailVerify")); @@ -31,7 +29,8 @@ function getServiceImplementation(transporter, from) { subject: input.subject, html: input.body, }); - } else { + } + else { await transporter.sendMail({ from: `${from.name} <${from.email}>`, to: input.toEmail, diff --git a/lib/build/recipe/emailverification/error.d.ts b/lib/build/recipe/emailverification/error.d.ts index 486758b61..d6412505c 100644 --- a/lib/build/recipe/emailverification/error.d.ts +++ b/lib/build/recipe/emailverification/error.d.ts @@ -1,5 +1,8 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); + constructor(options: { + type: "BAD_INPUT_ERROR"; + message: string; + }); } diff --git a/lib/build/recipe/emailverification/error.js b/lib/build/recipe/emailverification/error.js index b0baf2c94..4f3bedcb7 100644 --- a/lib/build/recipe/emailverification/error.js +++ b/lib/build/recipe/emailverification/error.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/emailverification/index.d.ts b/lib/build/recipe/emailverification/index.d.ts index d502537b3..8f90db852 100644 --- a/lib/build/recipe/emailverification/index.d.ts +++ b/lib/build/recipe/emailverification/index.d.ts @@ -1,99 +1,45 @@ // @ts-nocheck import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { - RecipeInterface, - APIOptions, - APIInterface, - UserEmailInfo, - TypeEmailVerificationEmailDeliveryInput, -} from "./types"; +import { RecipeInterface, APIOptions, APIInterface, UserEmailInfo, TypeEmailVerificationEmailDeliveryInput } from "./types"; import RecipeUserId from "../../recipeUserId"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; static EmailVerificationClaim: import("./emailVerificationClaim").EmailVerificationClaimClass; - static createEmailVerificationToken( - tenantId: string, - recipeUserId: RecipeUserId, - email?: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - token: string; - } - | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - } - >; - static createEmailVerificationLink( - tenantId: string, - recipeUserId: RecipeUserId, - email?: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - link: string; - } - | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - } - >; - static sendEmailVerificationEmail( - tenantId: string, - userId: string, - recipeUserId: RecipeUserId, - email?: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - } - | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - } - >; - static verifyEmailUsingToken( - tenantId: string, - token: string, - attemptAccountLinking?: boolean, - userContext?: Record - ): Promise< - | { - status: "OK"; - user: UserEmailInfo; - } - | { - status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; - } - >; - static isEmailVerified( - recipeUserId: RecipeUserId, - email?: string, - userContext?: Record - ): Promise; - static revokeEmailVerificationTokens( - tenantId: string, - recipeUserId: RecipeUserId, - email?: string, - userContext?: Record - ): Promise<{ + static createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ + status: "OK"; + token: string; + } | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + }>; + static createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ + status: "OK"; + link: string; + } | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + }>; + static sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ + status: "OK"; + } | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + }>; + static verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record): Promise<{ + status: "OK"; + user: UserEmailInfo; + } | { + status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; + }>; + static isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise; + static revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ status: string; }>; - static unverifyEmail( - recipeUserId: RecipeUserId, - email?: string, - userContext?: Record - ): Promise<{ + static unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ status: string; }>; - static sendEmail( - input: TypeEmailVerificationEmailDeliveryInput & { - userContext?: Record; - } - ): Promise; + static sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { + userContext?: Record; + }): Promise; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/emailverification/index.js b/lib/build/recipe/emailverification/index.js index af145da81..12e01e62c 100644 --- a/lib/build/recipe/emailverification/index.js +++ b/lib/build/recipe/emailverification/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.EmailVerificationClaim = exports.sendEmail = exports.unverifyEmail = exports.revokeEmailVerificationTokens = exports.isEmailVerified = exports.verifyEmailUsingToken = exports.sendEmailVerificationEmail = exports.createEmailVerificationLink = exports.createEmailVerificationToken = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -34,11 +32,13 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } + else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { return { status: "EMAIL_ALREADY_VERIFIED_ERROR", }; - } else { + } + else { throw new global.Error("Unknown User ID provided without email"); } } @@ -77,11 +77,13 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } + else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { return { status: "EMAIL_ALREADY_VERIFIED_ERROR", }; - } else { + } + else { throw new global.Error("Unknown User ID provided without email"); } } @@ -121,9 +123,11 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } + else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { return true; - } else { + } + else { throw new global.Error("Unknown User ID provided without email"); } } @@ -143,14 +147,16 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } + else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { // This only happens for phone based passwordless users (or if the user added a custom getEmailForUserId) // We can return OK here, since there is no way to create an email verification token // if getEmailForUserId returns EMAIL_DOES_NOT_EXIST_ERROR. return { status: "OK", }; - } else { + } + else { throw new global.Error("Unknown User ID provided without email"); } } @@ -168,12 +174,14 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } + else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { // Here we are returning OK since that's how it used to work, but a later call to isVerified will still return true return { status: "OK", }; - } else { + } + else { throw new global.Error("Unknown User ID provided without email"); } } @@ -185,9 +193,7 @@ class Wrapper { } static async sendEmail(input) { let recipeInstance = recipe_1.default.getInstanceOrThrowError(); - return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail( - Object.assign(Object.assign({}, input), { userContext: utils_2.getUserContext(input.userContext) }) - ); + return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail(Object.assign(Object.assign({}, input), { userContext: utils_2.getUserContext(input.userContext) })); } } exports.default = Wrapper; @@ -205,9 +211,4 @@ exports.revokeEmailVerificationTokens = Wrapper.revokeEmailVerificationTokens; exports.unverifyEmail = Wrapper.unverifyEmail; exports.sendEmail = Wrapper.sendEmail; var emailVerificationClaim_2 = require("./emailVerificationClaim"); -Object.defineProperty(exports, "EmailVerificationClaim", { - enumerable: true, - get: function () { - return emailVerificationClaim_2.EmailVerificationClaim; - }, -}); +Object.defineProperty(exports, "EmailVerificationClaim", { enumerable: true, get: function () { return emailVerificationClaim_2.EmailVerificationClaim; } }); diff --git a/lib/build/recipe/emailverification/recipe.d.ts b/lib/build/recipe/emailverification/recipe.d.ts index fb0b8c778..1244c211e 100644 --- a/lib/build/recipe/emailverification/recipe.d.ts +++ b/lib/build/recipe/emailverification/recipe.d.ts @@ -17,29 +17,15 @@ export default class Recipe extends RecipeModule { apiImpl: APIInterface; isInServerlessEnv: boolean; emailDelivery: EmailDeliveryIngredient; - constructor( - recipeId: string, - appInfo: NormalisedAppinfo, - isInServerlessEnv: boolean, - config: TypeInput, - ingredients: { - emailDelivery: EmailDeliveryIngredient | undefined; - } - ); + constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput, ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + }); static getInstanceOrThrowError(): Recipe; static getInstance(): Recipe | undefined; static init(config: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - _: NormalisedURLPath, - __: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/emailverification/recipe.js b/lib/build/recipe/emailverification/recipe.js index 3aefa5818..45eb92844 100644 --- a/lib/build/recipe/emailverification/recipe.js +++ b/lib/build/recipe/emailverification/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -47,9 +45,7 @@ class Recipe extends recipeModule_1.default { return [ { method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default( - constants_1.GENERATE_EMAIL_VERIFY_TOKEN_API - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.GENERATE_EMAIL_VERIFY_TOKEN_API), id: constants_1.GENERATE_EMAIL_VERIFY_TOKEN_API, disabled: this.apiImpl.generateEmailVerifyTokenPOST === undefined, }, @@ -80,7 +76,8 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.GENERATE_EMAIL_VERIFY_TOKEN_API) { return await generateEmailVerifyToken_1.default(this.apiImpl, options, userContext); - } else { + } + else { return await emailVerify_1.default(this.apiImpl, tenantId, options, userContext); } }; @@ -116,7 +113,8 @@ class Recipe extends recipeModule_1.default { email: currLM.email, status: "OK", }; - } else { + } + else { return { status: "EMAIL_DOES_NOT_EXIST_ERROR", }; @@ -149,10 +147,7 @@ class Recipe extends recipeModule_1.default { return primaryUser === null || primaryUser === void 0 ? void 0 : primaryUser.id; }; this.updateSessionIfRequiredPostEmailVerification = async (input) => { - let primaryUserId = await this.getPrimaryUserIdForRecipeUser( - input.recipeUserIdWhoseEmailGotVerified, - input.userContext - ); + let primaryUserId = await this.getPrimaryUserIdForRecipeUser(input.recipeUserIdWhoseEmailGotVerified, input.userContext); // if a session exists in the API, then we can update the session // claim related to email verification if (input.session !== undefined) { @@ -168,20 +163,14 @@ class Recipe extends recipeModule_1.default { // --> (Case 4) This is post login account linking, in which the account that got verified // got linked to ANOTHER primary account (user ID of account has changed to a different user ID != session.getUserId, but // we should ignore this since it will result in the user's session changing.) - if ( - input.session.getRecipeUserId(input.userContext).getAsString() === - input.recipeUserIdWhoseEmailGotVerified.getAsString() - ) { - logger_1.logDebugMessage( - "updateSessionIfRequiredPostEmailVerification the session belongs to the verified user" - ); + if (input.session.getRecipeUserId(input.userContext).getAsString() === + input.recipeUserIdWhoseEmailGotVerified.getAsString()) { + logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification the session belongs to the verified user"); // this means that the session's login method's account is the // one that just got verified and that we are NOT doing post login // account linking. So this is only for (Case 1) and (Case 2) if (input.session.getUserId() === primaryUserId) { - logger_1.logDebugMessage( - "updateSessionIfRequiredPostEmailVerification the session userId matches the primary user id, so we are only refreshing the claim" - ); + logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification the session userId matches the primary user id, so we are only refreshing the claim"); // if the session's primary user ID is equal to the // primary user ID that the account was linked to, then // this means that the new account became a primary user (Case 1) @@ -192,11 +181,9 @@ class Recipe extends recipeModule_1.default { try { // EmailVerificationClaim will be based on the recipeUserId // and not the primary user ID. - await input.session.fetchAndSetClaim( - emailVerificationClaim_1.EmailVerificationClaim, - input.userContext - ); - } catch (err) { + await input.session.fetchAndSetClaim(emailVerificationClaim_1.EmailVerificationClaim, input.userContext); + } + catch (err) { // This should never happen, since we've just set the status above. if (err.message === "UNKNOWN_USER_ID") { throw new error_2.default({ @@ -207,10 +194,9 @@ class Recipe extends recipeModule_1.default { throw err; } return; - } else { - logger_1.logDebugMessage( - "updateSessionIfRequiredPostEmailVerification the session user id doesn't match the primary user id, so we are revoking all sessions and creating a new one" - ); + } + else { + logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification the session user id doesn't match the primary user id, so we are revoking all sessions and creating a new one"); // if the session's primary user ID is NOT equal to the // primary user ID that the account that it was linked to, then // this means that the new account got linked to another primary user (Case 2) @@ -218,27 +204,13 @@ class Recipe extends recipeModule_1.default { // a new session // Revoke all session belonging to session.getRecipeUserId() // We do not really need to do this, but we do it anyway.. no harm. - await session_1.default.revokeAllSessionsForUser( - input.recipeUserIdWhoseEmailGotVerified.getAsString(), - false, - undefined, - input.userContext - ); + await session_1.default.revokeAllSessionsForUser(input.recipeUserIdWhoseEmailGotVerified.getAsString(), false, undefined, input.userContext); // create a new session and return that.. - return await session_1.default.createNewSession( - input.req, - input.res, - input.session.getTenantId(), - input.session.getRecipeUserId(input.userContext), - {}, - {}, - input.userContext - ); + return await session_1.default.createNewSession(input.req, input.res, input.session.getTenantId(), input.session.getRecipeUserId(input.userContext), {}, {}, input.userContext); } - } else { - logger_1.logDebugMessage( - "updateSessionIfRequiredPostEmailVerification the verified user doesn't match the session" - ); + } + else { + logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification the verified user doesn't match the session"); // this means that the session's login method's account was NOT the // one that just got verified and that we ARE doing post login // account linking. So this is only for (Case 3) and (Case 4) @@ -248,7 +220,8 @@ class Recipe extends recipeModule_1.default { // linked user's account). return undefined; } - } else { + } + else { logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification got no session"); // the session is updated when the is email verification GET API is called // so we don't do anything in this API. @@ -258,12 +231,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default( - querier_1.Querier.getNewInstanceOrThrowError(recipeId), - this.getEmailForRecipeUserId - ) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.getEmailForRecipeUserId)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -295,22 +263,15 @@ class Recipe extends recipeModule_1.default { emailDelivery: undefined, }); postSuperTokensInitCallbacks_1.PostSuperTokensInitCallbacks.addPostInitCallback(() => { - recipe_1.default - .getInstanceOrThrowError() - .addClaimFromOtherRecipe(emailVerificationClaim_1.EmailVerificationClaim); + recipe_1.default.getInstanceOrThrowError().addClaimFromOtherRecipe(emailVerificationClaim_1.EmailVerificationClaim); if (config.mode === "REQUIRED") { - recipe_1.default - .getInstanceOrThrowError() - .addClaimValidatorFromOtherRecipe( - emailVerificationClaim_1.EmailVerificationClaim.validators.isVerified() - ); + recipe_1.default.getInstanceOrThrowError().addClaimValidatorFromOtherRecipe(emailVerificationClaim_1.EmailVerificationClaim.validators.isVerified()); } }); return Recipe.instance; - } else { - throw new Error( - "Emailverification recipe has already been initialised. Please check your code for bugs." - ); + } + else { + throw new Error("Emailverification recipe has already been initialised. Please check your code for bugs."); } }; } diff --git a/lib/build/recipe/emailverification/recipeImplementation.d.ts b/lib/build/recipe/emailverification/recipeImplementation.d.ts index 625b1087e..57ed3970a 100644 --- a/lib/build/recipe/emailverification/recipeImplementation.d.ts +++ b/lib/build/recipe/emailverification/recipeImplementation.d.ts @@ -2,7 +2,4 @@ import { RecipeInterface } from "./"; import { Querier } from "../../querier"; import { GetEmailForRecipeUserIdFunc } from "./types"; -export default function getRecipeInterface( - querier: Querier, - getEmailForRecipeUserId: GetEmailForRecipeUserIdFunc -): RecipeInterface; +export default function getRecipeInterface(querier: Querier, getEmailForRecipeUserId: GetEmailForRecipeUserIdFunc): RecipeInterface; diff --git a/lib/build/recipe/emailverification/recipeImplementation.js b/lib/build/recipe/emailverification/recipeImplementation.js index 347bc8f44..a07b06793 100644 --- a/lib/build/recipe/emailverification/recipeImplementation.js +++ b/lib/build/recipe/emailverification/recipeImplementation.js @@ -1,44 +1,35 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const __1 = require("../.."); function getRecipeInterface(querier, getEmailForRecipeUserId) { return { - createEmailVerificationToken: async function ({ recipeUserId, email, tenantId, userContext }) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${tenantId}/recipe/user/email/verify/token`), - { - userId: recipeUserId.getAsString(), - email, - }, - userContext - ); + createEmailVerificationToken: async function ({ recipeUserId, email, tenantId, userContext, }) { + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/user/email/verify/token`), { + userId: recipeUserId.getAsString(), + email, + }, userContext); if (response.status === "OK") { return { status: "OK", token: response.token, }; - } else { + } + else { return { status: "EMAIL_ALREADY_VERIFIED_ERROR", }; } }, - verifyEmailUsingToken: async function ({ token, attemptAccountLinking, tenantId, userContext }) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${tenantId}/recipe/user/email/verify`), - { - method: "token", - token, - }, - userContext - ); + verifyEmailUsingToken: async function ({ token, attemptAccountLinking, tenantId, userContext, }) { + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/user/email/verify`), { + method: "token", + token, + }, userContext); if (response.status === "OK") { const recipeUserId = new recipeUserId_1.default(response.userId); if (attemptAccountLinking) { @@ -70,43 +61,32 @@ function getRecipeInterface(querier, getEmailForRecipeUserId) { email: response.email, }, }; - } else { + } + else { return { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR", }; } }, - isEmailVerified: async function ({ recipeUserId, email, userContext }) { - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/user/email/verify"), - { - userId: recipeUserId.getAsString(), - email, - }, - userContext - ); + isEmailVerified: async function ({ recipeUserId, email, userContext, }) { + let response = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/user/email/verify"), { + userId: recipeUserId.getAsString(), + email, + }, userContext); return response.isVerified; }, revokeEmailVerificationTokens: async function (input) { - await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/user/email/verify/token/remove`), - { - userId: input.recipeUserId.getAsString(), - email: input.email, - }, - input.userContext - ); + await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/user/email/verify/token/remove`), { + userId: input.recipeUserId.getAsString(), + email: input.email, + }, input.userContext); return { status: "OK" }; }, unverifyEmail: async function (input) { - await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/user/email/verify/remove"), - { - userId: input.recipeUserId.getAsString(), - email: input.email, - }, - input.userContext - ); + await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/user/email/verify/remove"), { + userId: input.recipeUserId.getAsString(), + email: input.email, + }, input.userContext); return { status: "OK" }; }, }; diff --git a/lib/build/recipe/emailverification/types.d.ts b/lib/build/recipe/emailverification/types.d.ts index fb0e7c24d..cc8269b5d 100644 --- a/lib/build/recipe/emailverification/types.d.ts +++ b/lib/build/recipe/emailverification/types.d.ts @@ -1,10 +1,7 @@ // @ts-nocheck import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; -import { - TypeInput as EmailDeliveryTypeInput, - TypeInputWithService as EmailDeliveryTypeInputWithService, -} from "../../ingredients/emaildelivery/types"; +import { TypeInput as EmailDeliveryTypeInput, TypeInputWithService as EmailDeliveryTypeInputWithService } from "../../ingredients/emaildelivery/types"; import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; import { GeneralErrorResponse, NormalisedAppinfo, UserContext } from "../../types"; import { SessionContainerInterface } from "../session/types"; @@ -13,48 +10,28 @@ import { User } from "../../types"; export declare type TypeInput = { mode: "REQUIRED" | "OPTIONAL"; emailDelivery?: EmailDeliveryTypeInput; - getEmailForRecipeUserId?: ( - recipeUserId: RecipeUserId, - userContext: UserContext - ) => Promise< - | { - status: "OK"; - email: string; - } - | { - status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; - } - >; + getEmailForRecipeUserId?: (recipeUserId: RecipeUserId, userContext: UserContext) => Promise<{ + status: "OK"; + email: string; + } | { + status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; + }>; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { mode: "REQUIRED" | "OPTIONAL"; - getEmailDeliveryConfig: ( - isInServerlessEnv: boolean - ) => EmailDeliveryTypeInputWithService; - getEmailForRecipeUserId?: ( - recipeUserId: RecipeUserId, - userContext: UserContext - ) => Promise< - | { - status: "OK"; - email: string; - } - | { - status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; - } - >; + getEmailDeliveryConfig: (isInServerlessEnv: boolean) => EmailDeliveryTypeInputWithService; + getEmailForRecipeUserId?: (recipeUserId: RecipeUserId, userContext: UserContext) => Promise<{ + status: "OK"; + email: string; + } | { + status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; + }>; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -68,30 +45,28 @@ export declare type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - token: string; - } - | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + token: string; + } | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + }>; verifyEmailUsingToken(input: { token: string; attemptAccountLinking: boolean; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: UserEmailInfo; - } - | { - status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; - } - >; - isEmailVerified(input: { recipeUserId: RecipeUserId; email: string; userContext: UserContext }): Promise; + }): Promise<{ + status: "OK"; + user: UserEmailInfo; + } | { + status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; + }>; + isEmailVerified(input: { + recipeUserId: RecipeUserId; + email: string; + userContext: UserContext; + }): Promise; revokeEmailVerificationTokens(input: { recipeUserId: RecipeUserId; email: string; @@ -119,55 +94,38 @@ export declare type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; export declare type APIInterface = { - verifyEmailPOST: - | undefined - | ((input: { - token: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - session: SessionContainerInterface | undefined; - }) => Promise< - | { - status: "OK"; - user: UserEmailInfo; - newSession?: SessionContainerInterface; - } - | { - status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; - } - | GeneralErrorResponse - >); - isEmailVerifiedGET: - | undefined - | ((input: { - options: APIOptions; - userContext: UserContext; - session: SessionContainerInterface; - }) => Promise< - | { - status: "OK"; - isVerified: boolean; - newSession?: SessionContainerInterface; - } - | GeneralErrorResponse - >); - generateEmailVerifyTokenPOST: - | undefined - | ((input: { - options: APIOptions; - userContext: UserContext; - session: SessionContainerInterface; - }) => Promise< - | { - status: "OK"; - } - | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - newSession?: SessionContainerInterface; - } - | GeneralErrorResponse - >); + verifyEmailPOST: undefined | ((input: { + token: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + session: SessionContainerInterface | undefined; + }) => Promise<{ + status: "OK"; + user: UserEmailInfo; + newSession?: SessionContainerInterface; + } | { + status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; + } | GeneralErrorResponse>); + isEmailVerifiedGET: undefined | ((input: { + options: APIOptions; + userContext: UserContext; + session: SessionContainerInterface; + }) => Promise<{ + status: "OK"; + isVerified: boolean; + newSession?: SessionContainerInterface; + } | GeneralErrorResponse>); + generateEmailVerifyTokenPOST: undefined | ((input: { + options: APIOptions; + userContext: UserContext; + session: SessionContainerInterface; + }) => Promise<{ + status: "OK"; + } | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + newSession?: SessionContainerInterface; + } | GeneralErrorResponse>); }; export declare type TypeEmailVerificationEmailDeliveryInput = { type: "EMAIL_VERIFICATION"; @@ -179,16 +137,9 @@ export declare type TypeEmailVerificationEmailDeliveryInput = { emailVerifyLink: string; tenantId: string; }; -export declare type GetEmailForRecipeUserIdFunc = ( - user: User | undefined, - recipeUserId: RecipeUserId, - userContext: UserContext -) => Promise< - | { - status: "OK"; - email: string; - } - | { - status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; - } ->; +export declare type GetEmailForRecipeUserIdFunc = (user: User | undefined, recipeUserId: RecipeUserId, userContext: UserContext) => Promise<{ + status: "OK"; + email: string; +} | { + status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; +}>; diff --git a/lib/build/recipe/emailverification/utils.d.ts b/lib/build/recipe/emailverification/utils.d.ts index 60043dc8a..fdfc82551 100644 --- a/lib/build/recipe/emailverification/utils.d.ts +++ b/lib/build/recipe/emailverification/utils.d.ts @@ -3,11 +3,7 @@ import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; import { NormalisedAppinfo, UserContext } from "../../types"; import { BaseRequest } from "../../framework"; -export declare function validateAndNormaliseUserInput( - _: Recipe, - appInfo: NormalisedAppinfo, - config: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(_: Recipe, appInfo: NormalisedAppinfo, config: TypeInput): TypeNormalisedInput; export declare function getEmailVerifyLink(input: { appInfo: NormalisedAppinfo; token: string; diff --git a/lib/build/recipe/emailverification/utils.js b/lib/build/recipe/emailverification/utils.js index 35d938da9..cb1e28ec6 100644 --- a/lib/build/recipe/emailverification/utils.js +++ b/lib/build/recipe/emailverification/utils.js @@ -13,22 +13,14 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEmailVerifyLink = exports.validateAndNormaliseUserInput = void 0; const backwardCompatibility_1 = __importDefault(require("./emaildelivery/services/backwardCompatibility")); function validateAndNormaliseUserInput(_, appInfo, config) { - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config.override); function getEmailDeliveryConfig(isInServerlessEnv) { var _a; let emailService = (_a = config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; @@ -40,7 +32,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { if (emailService === undefined) { emailService = new backwardCompatibility_1.default(appInfo, isInServerlessEnv); } - return Object.assign(Object.assign({}, config.emailDelivery), { + return Object.assign(Object.assign({}, config.emailDelivery), { /** * if we do * let emailDelivery = { @@ -52,8 +44,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { * it it again get set to undefined, so we * set service at the end */ - service: emailService, - }); + service: emailService }); } return { mode: config.mode, @@ -64,19 +55,17 @@ function validateAndNormaliseUserInput(_, appInfo, config) { } exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; function getEmailVerifyLink(input) { - return ( - input.appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() + + return (input.appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + input.appInfo.websiteBasePath.getAsStringDangerous() + "/verify-email" + "?token=" + input.token + "&tenantId=" + - input.tenantId - ); + input.tenantId); } exports.getEmailVerifyLink = getEmailVerifyLink; diff --git a/lib/build/recipe/jwt/api/getJWKS.d.ts b/lib/build/recipe/jwt/api/getJWKS.d.ts index 92997a1a4..b1fd38fd9 100644 --- a/lib/build/recipe/jwt/api/getJWKS.d.ts +++ b/lib/build/recipe/jwt/api/getJWKS.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { UserContext } from "../../../types"; import { APIInterface, APIOptions } from "../types"; -export default function getJWKS( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function getJWKS(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/jwt/api/getJWKS.js b/lib/build/recipe/jwt/api/getJWKS.js index d4d77a9eb..184eae22e 100644 --- a/lib/build/recipe/jwt/api/getJWKS.js +++ b/lib/build/recipe/jwt/api/getJWKS.js @@ -25,7 +25,8 @@ async function getJWKS(apiImplementation, options, userContext) { }); if ("status" in result && result.status === "GENERAL_ERROR") { utils_1.send200Response(options.res, result); - } else { + } + else { options.res.setHeader("Access-Control-Allow-Origin", "*", false); utils_1.send200Response(options.res, result); } diff --git a/lib/build/recipe/jwt/api/implementation.js b/lib/build/recipe/jwt/api/implementation.js index e52174f38..34f279ba5 100644 --- a/lib/build/recipe/jwt/api/implementation.js +++ b/lib/build/recipe/jwt/api/implementation.js @@ -16,7 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); function getAPIImplementation() { return { - getJWKSGET: async function ({ options, userContext }) { + getJWKSGET: async function ({ options, userContext, }) { const resp = await options.recipeImplementation.getJWKS({ userContext }); if (resp.validityInSeconds !== undefined) { options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false); diff --git a/lib/build/recipe/jwt/index.d.ts b/lib/build/recipe/jwt/index.d.ts index ed6ab4c4e..5c41018e9 100644 --- a/lib/build/recipe/jwt/index.d.ts +++ b/lib/build/recipe/jwt/index.d.ts @@ -3,23 +3,13 @@ import Recipe from "./recipe"; import { APIInterface, RecipeInterface, APIOptions, JsonWebKey } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static createJWT( - payload: any, - validitySeconds?: number, - useStaticSigningKey?: boolean, - userContext?: Record - ): Promise< - | { - status: "OK"; - jwt: string; - } - | { - status: "UNSUPPORTED_ALGORITHM_ERROR"; - } - >; - static getJWKS( - userContext?: Record - ): Promise<{ + static createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record): Promise<{ + status: "OK"; + jwt: string; + } | { + status: "UNSUPPORTED_ALGORITHM_ERROR"; + }>; + static getJWKS(userContext?: Record): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number | undefined; }>; diff --git a/lib/build/recipe/jwt/index.js b/lib/build/recipe/jwt/index.js index 8ba933f81..6e496a60b 100644 --- a/lib/build/recipe/jwt/index.js +++ b/lib/build/recipe/jwt/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getJWKS = exports.createJWT = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/jwt/recipe.d.ts b/lib/build/recipe/jwt/recipe.d.ts index 076cc7c64..d9eeb862b 100644 --- a/lib/build/recipe/jwt/recipe.d.ts +++ b/lib/build/recipe/jwt/recipe.d.ts @@ -17,15 +17,7 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled(): APIHandled[]; - handleAPIRequest: ( - _id: string, - _tenantId: string | undefined, - req: BaseRequest, - res: BaseResponse, - _path: normalisedURLPath, - _method: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (_id: string, _tenantId: string | undefined, req: BaseRequest, res: BaseResponse, _path: normalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; diff --git a/lib/build/recipe/jwt/recipe.js b/lib/build/recipe/jwt/recipe.js index bb92d24a8..086c795b4 100644 --- a/lib/build/recipe/jwt/recipe.js +++ b/lib/build/recipe/jwt/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -47,13 +45,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_2.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default( - querier_1.Querier.getNewInstanceOrThrowError(recipeId), - this.config, - appInfo - ) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -73,7 +65,8 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } else { + } + else { throw new Error("JWT recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/jwt/recipeImplementation.d.ts b/lib/build/recipe/jwt/recipeImplementation.d.ts index 5109fbcb1..55dd3d95e 100644 --- a/lib/build/recipe/jwt/recipeImplementation.d.ts +++ b/lib/build/recipe/jwt/recipeImplementation.d.ts @@ -2,8 +2,4 @@ import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; import { RecipeInterface, TypeNormalisedInput } from "./types"; -export default function getRecipeInterface( - querier: Querier, - config: TypeNormalisedInput, - appInfo: NormalisedAppinfo -): RecipeInterface; +export default function getRecipeInterface(querier: Querier, config: TypeNormalisedInput, appInfo: NormalisedAppinfo): RecipeInterface; diff --git a/lib/build/recipe/jwt/recipeImplementation.js b/lib/build/recipe/jwt/recipeImplementation.js index 0ac8ddf6f..93d8ad7f9 100644 --- a/lib/build/recipe/jwt/recipeImplementation.js +++ b/lib/build/recipe/jwt/recipeImplementation.js @@ -13,50 +13,40 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const defaultJWKSMaxAge = 60; // This corresponds to the dynamicSigningKeyOverlapMS in the core function getRecipeInterface(querier, config, appInfo) { return { - createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext }) { + createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext, }) { if (validitySeconds === undefined) { // If the user does not provide a validity to this function and the config validity is also undefined, use 100 years (in seconds) validitySeconds = config.jwtValiditySeconds; } - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/jwt"), - { - payload: payload !== null && payload !== void 0 ? payload : {}, - validity: validitySeconds, - useStaticSigningKey: useStaticSigningKey !== false, - algorithm: "RS256", - jwksDomain: appInfo.apiDomain.getAsStringDangerous(), - }, - userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/jwt"), { + payload: payload !== null && payload !== void 0 ? payload : {}, + validity: validitySeconds, + useStaticSigningKey: useStaticSigningKey !== false, + algorithm: "RS256", + jwksDomain: appInfo.apiDomain.getAsStringDangerous(), + }, userContext); if (response.status === "OK") { return { status: "OK", jwt: response.jwt, }; - } else { + } + else { return { status: "UNSUPPORTED_ALGORITHM_ERROR", }; } }, getJWKS: async function ({ userContext }) { - const { body, headers } = await querier.sendGetRequestWithResponseHeaders( - new normalisedURLPath_1.default("/.well-known/jwks.json"), - {}, - undefined, - userContext - ); + const { body, headers } = await querier.sendGetRequestWithResponseHeaders(new normalisedURLPath_1.default("/.well-known/jwks.json"), {}, undefined, userContext); let validityInSeconds = defaultJWKSMaxAge; const cacheControl = headers.get("Cache-Control"); if (cacheControl) { diff --git a/lib/build/recipe/jwt/types.d.ts b/lib/build/recipe/jwt/types.d.ts index 84c1802ae..2afde80ca 100644 --- a/lib/build/recipe/jwt/types.d.ts +++ b/lib/build/recipe/jwt/types.d.ts @@ -13,20 +13,14 @@ export declare type JsonWebKey = { export declare type TypeInput = { jwtValiditySeconds?: number; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { jwtValiditySeconds: number; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -44,15 +38,12 @@ export declare type RecipeInterface = { validitySeconds?: number; useStaticSigningKey?: boolean; userContext: UserContext; - }): Promise< - | { - status: "OK"; - jwt: string; - } - | { - status: "UNSUPPORTED_ALGORITHM_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + jwt: string; + } | { + status: "UNSUPPORTED_ALGORITHM_ERROR"; + }>; getJWKS(input: { userContext: UserContext; }): Promise<{ @@ -61,15 +52,10 @@ export declare type RecipeInterface = { }>; }; export declare type APIInterface = { - getJWKSGET: - | undefined - | ((input: { - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - keys: JsonWebKey[]; - } - | GeneralErrorResponse - >); + getJWKSGET: undefined | ((input: { + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + keys: JsonWebKey[]; + } | GeneralErrorResponse>); }; diff --git a/lib/build/recipe/jwt/utils.d.ts b/lib/build/recipe/jwt/utils.d.ts index 4025b1b44..133d4840f 100644 --- a/lib/build/recipe/jwt/utils.d.ts +++ b/lib/build/recipe/jwt/utils.d.ts @@ -2,8 +2,4 @@ import { NormalisedAppinfo } from "../../types"; import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput( - _: Recipe, - __: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(_: Recipe, __: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; diff --git a/lib/build/recipe/jwt/utils.js b/lib/build/recipe/jwt/utils.js index 9c38d23ce..f3383e481 100644 --- a/lib/build/recipe/jwt/utils.js +++ b/lib/build/recipe/jwt/utils.js @@ -17,18 +17,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(_, __, config) { var _a; - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { - jwtValiditySeconds: - (_a = config === null || config === void 0 ? void 0 : config.jwtValiditySeconds) !== null && _a !== void 0 - ? _a - : 3153600000, + jwtValiditySeconds: (_a = config === null || config === void 0 ? void 0 : config.jwtValiditySeconds) !== null && _a !== void 0 ? _a : 3153600000, override, }; } diff --git a/lib/build/recipe/multifactorauth/api/implementation.js b/lib/build/recipe/multifactorauth/api/implementation.js index 954ae11bd..97cea6a97 100644 --- a/lib/build/recipe/multifactorauth/api/implementation.js +++ b/lib/build/recipe/multifactorauth/api/implementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const multiFactorAuthClaim_1 = require("../multiFactorAuthClaim"); const error_1 = __importDefault(require("../../session/error")); @@ -65,29 +63,19 @@ function getAPIInterface() { userContext, }); factorsAllowedToSetup.push(id); - } catch (err) { + } + catch (err) { if (!(error_1.default.isErrorFromSuperTokens(err) && err.type === error_1.default.INVALID_CLAIMS)) { throw err; } // ignore claims error and not add to the list of factors allowed to be set up } } - const nextSetOfUnsatisfiedFactors = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors( - mfaInfo.completedFactors, - mfaInfo.mfaRequirementsForAuth - ); - let getEmailsForFactorsResult = options.recipeInstance.getEmailsForFactors( - sessionUser, - session.getRecipeUserId(userContext) - ); - let getPhoneNumbersForFactorsResult = options.recipeInstance.getPhoneNumbersForFactors( - sessionUser, - session.getRecipeUserId(userContext) - ); - if ( - getEmailsForFactorsResult.status === "UNKNOWN_SESSION_RECIPE_USER_ID" || - getPhoneNumbersForFactorsResult.status === "UNKNOWN_SESSION_RECIPE_USER_ID" - ) { + const nextSetOfUnsatisfiedFactors = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors(mfaInfo.completedFactors, mfaInfo.mfaRequirementsForAuth); + let getEmailsForFactorsResult = options.recipeInstance.getEmailsForFactors(sessionUser, session.getRecipeUserId(userContext)); + let getPhoneNumbersForFactorsResult = options.recipeInstance.getPhoneNumbersForFactors(sessionUser, session.getRecipeUserId(userContext)); + if (getEmailsForFactorsResult.status === "UNKNOWN_SESSION_RECIPE_USER_ID" || + getPhoneNumbersForFactorsResult.status === "UNKNOWN_SESSION_RECIPE_USER_ID") { throw new error_1.default({ type: "UNAUTHORISED", message: "User no longer associated with the session", @@ -99,15 +87,9 @@ function getAPIInterface() { // where user has already setup a factor and not completed it, none of the factors will be allowed to // be setup, and that that will result in an empty next array. However, we want to show the factor // that the user has already setup in that case. - const next = nextSetOfUnsatisfiedFactors.factorIds.filter( - (factorId) => factorsAllowedToSetup.includes(factorId) || factorsSetUpForUser.includes(factorId) - ); + const next = nextSetOfUnsatisfiedFactors.factorIds.filter((factorId) => factorsAllowedToSetup.includes(factorId) || factorsSetUpForUser.includes(factorId)); if (next.length === 0 && nextSetOfUnsatisfiedFactors.factorIds.length !== 0) { - throw new Error( - `The user is required to complete secondary factors they are not allowed to (${nextSetOfUnsatisfiedFactors.factorIds.join( - ", " - )}), likely because of configuration issues.` - ); + throw new Error(`The user is required to complete secondary factors they are not allowed to (${nextSetOfUnsatisfiedFactors.factorIds.join(", ")}), likely because of configuration issues.`); } return { status: "OK", diff --git a/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.d.ts b/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.d.ts index 081461387..9e9fa464c 100644 --- a/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.d.ts +++ b/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function resyncSessionAndFetchMFAInfo( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function resyncSessionAndFetchMFAInfo(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.js b/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.js index c9819b6ba..37e9caa14 100644 --- a/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.js +++ b/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -25,12 +23,7 @@ async function resyncSessionAndFetchMFAInfo(apiImplementation, options, userCont if (apiImplementation.resyncSessionAndFetchMFAInfoPUT === undefined) { return false; } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: true }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); let response = await apiImplementation.resyncSessionAndFetchMFAInfoPUT({ options, session, diff --git a/lib/build/recipe/multifactorauth/index.d.ts b/lib/build/recipe/multifactorauth/index.d.ts index b2d06cd51..2cbad735b 100644 --- a/lib/build/recipe/multifactorauth/index.d.ts +++ b/lib/build/recipe/multifactorauth/index.d.ts @@ -17,32 +17,13 @@ export default class Wrapper { THIRDPARTY: string; TOTP: string; }; - static assertAllowedToSetupFactorElseThrowInvalidClaimError( - session: SessionContainerInterface, - factorId: string, - userContext?: Record - ): Promise; - static getMFARequirementsForAuth( - session: SessionContainerInterface, - userContext?: Record - ): Promise; - static markFactorAsCompleteInSession( - session: SessionContainerInterface, - factorId: string, - userContext?: Record - ): Promise; + static assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainerInterface, factorId: string, userContext?: Record): Promise; + static getMFARequirementsForAuth(session: SessionContainerInterface, userContext?: Record): Promise; + static markFactorAsCompleteInSession(session: SessionContainerInterface, factorId: string, userContext?: Record): Promise; static getFactorsSetupForUser(userId: string, userContext?: Record): Promise; static getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record): Promise; - static addToRequiredSecondaryFactorsForUser( - userId: string, - factorId: string, - userContext?: Record - ): Promise; - static removeFromRequiredSecondaryFactorsForUser( - userId: string, - factorId: string, - userContext?: Record - ): Promise; + static addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record): Promise; + static removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record): Promise; } export declare let init: typeof Recipe.init; export declare let assertAllowedToSetupFactorElseThrowInvalidClaimError: typeof Wrapper.assertAllowedToSetupFactorElseThrowInvalidClaimError; diff --git a/lib/build/recipe/multifactorauth/index.js b/lib/build/recipe/multifactorauth/index.js index 25e208c4c..f6a7e5c06 100644 --- a/lib/build/recipe/multifactorauth/index.js +++ b/lib/build/recipe/multifactorauth/index.js @@ -13,31 +13,19 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.FactorIds = exports.MultiFactorAuthClaim = exports.removeFromRequiredSecondaryFactorsForUser = exports.addToRequiredSecondaryFactorsForUser = exports.getMFARequirementsForAuth = exports.getRequiredSecondaryFactorsForUser = exports.getFactorsSetupForUser = exports.markFactorAsCompleteInSession = exports.assertAllowedToSetupFactorElseThrowInvalidClaimError = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); const multiFactorAuthClaim_1 = require("./multiFactorAuthClaim"); -Object.defineProperty(exports, "MultiFactorAuthClaim", { - enumerable: true, - get: function () { - return multiFactorAuthClaim_1.MultiFactorAuthClaim; - }, -}); +Object.defineProperty(exports, "MultiFactorAuthClaim", { enumerable: true, get: function () { return multiFactorAuthClaim_1.MultiFactorAuthClaim; } }); const __1 = require("../.."); const utils_1 = require("../../utils"); const utils_2 = require("./utils"); const types_1 = require("./types"); -Object.defineProperty(exports, "FactorIds", { - enumerable: true, - get: function () { - return types_1.FactorIds; - }, -}); +Object.defineProperty(exports, "FactorIds", { enumerable: true, get: function () { return types_1.FactorIds; } }); class Wrapper { static async assertAllowedToSetupFactorElseThrowInvalidClaimError(session, factorId, userContext) { let ctx = utils_1.getUserContext(userContext); @@ -46,19 +34,17 @@ class Wrapper { userContext: ctx, }); const factorsSetUpForUser = await Wrapper.getFactorsSetupForUser(session.getUserId(), ctx); - await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.assertAllowedToSetupFactorElseThrowInvalidClaimError({ - session, - factorId, - get factorsSetUpForUser() { - return Promise.resolve(factorsSetUpForUser); - }, - get mfaRequirementsForAuth() { - return Promise.resolve(mfaInfo.mfaRequirementsForAuth); - }, - userContext: ctx, - }); + await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.assertAllowedToSetupFactorElseThrowInvalidClaimError({ + session, + factorId, + get factorsSetUpForUser() { + return Promise.resolve(factorsSetUpForUser); + }, + get mfaRequirementsForAuth() { + return Promise.resolve(mfaInfo.mfaRequirementsForAuth); + }, + userContext: ctx, + }); } static async getMFARequirementsForAuth(session, userContext) { let ctx = utils_1.getUserContext(userContext); @@ -112,8 +98,7 @@ Wrapper.init = recipe_1.default.init; Wrapper.MultiFactorAuthClaim = multiFactorAuthClaim_1.MultiFactorAuthClaim; Wrapper.FactorIds = types_1.FactorIds; exports.init = Wrapper.init; -exports.assertAllowedToSetupFactorElseThrowInvalidClaimError = - Wrapper.assertAllowedToSetupFactorElseThrowInvalidClaimError; +exports.assertAllowedToSetupFactorElseThrowInvalidClaimError = Wrapper.assertAllowedToSetupFactorElseThrowInvalidClaimError; exports.markFactorAsCompleteInSession = Wrapper.markFactorAsCompleteInSession; exports.getFactorsSetupForUser = Wrapper.getFactorsSetupForUser; exports.getRequiredSecondaryFactorsForUser = Wrapper.getRequiredSecondaryFactorsForUser; diff --git a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts index 07976ada7..ff964c9f0 100644 --- a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts +++ b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts @@ -14,50 +14,26 @@ export declare class MultiFactorAuthClaimClass extends SessionClaim SessionClaimValidator; hasCompletedRequirementList(requirementList: MFARequirementList, id?: string): SessionClaimValidator; }; - getNextSetOfUnsatisfiedFactors( - completedFactors: MFAClaimValue["c"], - requirementList: MFARequirementList - ): { + getNextSetOfUnsatisfiedFactors(completedFactors: MFAClaimValue["c"], requirementList: MFARequirementList): { factorIds: string[]; type: "string" | "oneOf" | "allOfInAnyOrder"; }; - fetchValue: ( - _userId: string, - recipeUserId: RecipeUserId, - tenantId: string, - currentPayload: JSONObject | undefined, - userContext: UserContext - ) => Promise<{ + fetchValue: (_userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext) => Promise<{ c: Record; v: boolean; }>; - addToPayload_internal: ( - payload: JSONObject, - value: MFAClaimValue - ) => { - [x: string]: - | string - | number - | boolean - | JSONObject - | import("../../types").JSONArray - | { - c: { - [x: string]: number | undefined; - }; - v: boolean; - } - | null - | undefined; + addToPayload_internal: (payload: JSONObject, value: MFAClaimValue) => { + [x: string]: string | number | boolean | JSONObject | import("../../types").JSONArray | { + c: { + [x: string]: number | undefined; + }; + v: boolean; + } | null | undefined; }; - removeFromPayload: ( - payload: JSONObject - ) => { + removeFromPayload: (payload: JSONObject) => { [x: string]: import("../../types").JSONValue; }; - removeFromPayloadByMerge_internal: ( - payload: JSONObject - ) => { + removeFromPayloadByMerge_internal: (payload: JSONObject) => { [x: string]: import("../../types").JSONValue; }; getValueFromPayload: (payload: JSONObject) => MFAClaimValue; diff --git a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js index da46bd6b1..c35e304ff 100644 --- a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js +++ b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js @@ -38,15 +38,10 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { }; this.addToPayload_internal = (payload, value) => { const prevValue = payload[this.key]; - return Object.assign(Object.assign({}, payload), { - [this.key]: { - c: Object.assign( - Object.assign({}, prevValue === null || prevValue === void 0 ? void 0 : prevValue.c), - value.c - ), + return Object.assign(Object.assign({}, payload), { [this.key]: { + c: Object.assign(Object.assign({}, prevValue === null || prevValue === void 0 ? void 0 : prevValue.c), value.c), v: value.v, - }, - }); + } }); }; this.removeFromPayload = (payload) => { const retVal = Object.assign({}, payload); @@ -75,12 +70,11 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { const { v } = claimVal; return { isValid: v, - reason: - v === false - ? { - message: "MFA requirement for auth is not satisfied", - } - : undefined, + reason: v === false + ? { + message: "MFA requirement for auth is not satisfied", + } + : undefined, }; }, }), @@ -102,10 +96,7 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { throw new Error("This should never happen, claim value not present in payload"); } const { c: completedFactors } = claimVal; - const nextSetOfUnsatisfiedFactors = this.getNextSetOfUnsatisfiedFactors( - completedFactors, - requirementList - ); + const nextSetOfUnsatisfiedFactors = this.getNextSetOfUnsatisfiedFactors(completedFactors, requirementList); if (nextSetOfUnsatisfiedFactors.factorIds.length === 0) { // No item in the requirementList is left unsatisfied, hence is Valid return { @@ -116,29 +107,28 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { return { isValid: false, reason: { - message: - "Factor validation failed: " + + message: "Factor validation failed: " + nextSetOfUnsatisfiedFactors.factorIds[0] + " not completed", factorId: nextSetOfUnsatisfiedFactors.factorIds[0], }, }; - } else if (nextSetOfUnsatisfiedFactors.type === "oneOf") { + } + else if (nextSetOfUnsatisfiedFactors.type === "oneOf") { return { isValid: false, reason: { - message: - "None of these factors are complete in the session: " + + message: "None of these factors are complete in the session: " + nextSetOfUnsatisfiedFactors.factorIds.join(", "), oneOf: nextSetOfUnsatisfiedFactors.factorIds, }, }; - } else { + } + else { return { isValid: false, reason: { - message: - "Some of the factors are not complete in the session: " + + message: "Some of the factors are not complete in the session: " + nextSetOfUnsatisfiedFactors.factorIds.join(", "), allOfInAnyOrder: nextSetOfUnsatisfiedFactors.factorIds, }, @@ -163,7 +153,8 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { type = "string"; nextFactors.add(req); } - } else if ("oneOf" in req) { + } + else if ("oneOf" in req) { let satisfied = false; for (const factorId of req.oneOf) { if (completedFactors[factorId] !== undefined) { @@ -176,7 +167,8 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { nextFactors.add(factorId); } } - } else if ("allOfInAnyOrder" in req) { + } + else if ("allOfInAnyOrder" in req) { for (const factorId of req.allOfInAnyOrder) { type = "allOfInAnyOrder"; if (completedFactors[factorId] === undefined) { diff --git a/lib/build/recipe/multifactorauth/recipe.d.ts b/lib/build/recipe/multifactorauth/recipe.d.ts index c7abce064..bdbefd773 100644 --- a/lib/build/recipe/multifactorauth/recipe.d.ts +++ b/lib/build/recipe/multifactorauth/recipe.d.ts @@ -4,16 +4,7 @@ import NormalisedURLPath from "../../normalisedURLPath"; import RecipeModule from "../../recipeModule"; import STError from "../../error"; import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; -import { - APIInterface, - GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc, - GetEmailsForFactorFromOtherRecipesFunc, - GetFactorsSetupForUserFromOtherRecipesFunc, - GetPhoneNumbersForFactorsFromOtherRecipesFunc, - RecipeInterface, - TypeInput, - TypeNormalisedInput, -} from "./types"; +import { APIInterface, GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc, GetEmailsForFactorFromOtherRecipesFunc, GetFactorsSetupForUserFromOtherRecipesFunc, GetPhoneNumbersForFactorsFromOtherRecipesFunc, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; import { User } from "../../user"; import RecipeUserId from "../../recipeUserId"; import { Querier } from "../../querier"; @@ -37,45 +28,25 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - _tenantId: string, - req: BaseRequest, - res: BaseResponse, - _: NormalisedURLPath, - __: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, _tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; - addFuncToGetAllAvailableSecondaryFactorIdsFromOtherRecipes: ( - f: GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc - ) => void; + addFuncToGetAllAvailableSecondaryFactorIdsFromOtherRecipes: (f: GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc) => void; getAllAvailableSecondaryFactorIds: (tenantConfig: TenantConfig) => string[]; addFuncToGetFactorsSetupForUserFromOtherRecipes: (func: GetFactorsSetupForUserFromOtherRecipesFunc) => void; addFuncToGetEmailsForFactorFromOtherRecipes: (func: GetEmailsForFactorFromOtherRecipesFunc) => void; - getEmailsForFactors: ( - user: User, - sessionRecipeUserId: RecipeUserId - ) => - | { - status: "OK"; - factorIdToEmailsMap: Record; - } - | { - status: "UNKNOWN_SESSION_RECIPE_USER_ID"; - }; + getEmailsForFactors: (user: User, sessionRecipeUserId: RecipeUserId) => { + status: "OK"; + factorIdToEmailsMap: Record; + } | { + status: "UNKNOWN_SESSION_RECIPE_USER_ID"; + }; addFuncToGetPhoneNumbersForFactorsFromOtherRecipes: (func: GetPhoneNumbersForFactorsFromOtherRecipesFunc) => void; - getPhoneNumbersForFactors: ( - user: User, - sessionRecipeUserId: RecipeUserId - ) => - | { - status: "OK"; - factorIdToPhoneNumberMap: Record; - } - | { - status: "UNKNOWN_SESSION_RECIPE_USER_ID"; - }; + getPhoneNumbersForFactors: (user: User, sessionRecipeUserId: RecipeUserId) => { + status: "OK"; + factorIdToPhoneNumberMap: Record; + } | { + status: "UNKNOWN_SESSION_RECIPE_USER_ID"; + }; } diff --git a/lib/build/recipe/multifactorauth/recipe.js b/lib/build/recipe/multifactorauth/recipe.js index 31b8241a8..379536a17 100644 --- a/lib/build/recipe/multifactorauth/recipe.js +++ b/lib/build/recipe/multifactorauth/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -48,9 +46,7 @@ class Recipe extends recipeModule_1.default { return [ { method: "put", - pathWithoutApiBasePath: new normalisedURLPath_1.default( - constants_1.RESYNC_SESSION_AND_FETCH_MFA_INFO - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.RESYNC_SESSION_AND_FETCH_MFA_INFO), id: constants_1.RESYNC_SESSION_AND_FETCH_MFA_INFO, disabled: this.apiImpl.resyncSessionAndFetchMFAInfoPUT === undefined, }, @@ -113,10 +109,7 @@ class Recipe extends recipeModule_1.default { status: "UNKNOWN_SESSION_RECIPE_USER_ID", }; } - result.factorIdToEmailsMap = Object.assign( - Object.assign({}, result.factorIdToEmailsMap), - funcResult.factorIdToEmailsMap - ); + result.factorIdToEmailsMap = Object.assign(Object.assign({}, result.factorIdToEmailsMap), funcResult.factorIdToEmailsMap); } return result; }; @@ -135,10 +128,7 @@ class Recipe extends recipeModule_1.default { status: "UNKNOWN_SESSION_RECIPE_USER_ID", }; } - result.factorIdToPhoneNumberMap = Object.assign( - Object.assign({}, result.factorIdToPhoneNumberMap), - funcResult.factorIdToPhoneNumberMap - ); + result.factorIdToPhoneNumberMap = Object.assign(Object.assign({}, result.factorIdToPhoneNumberMap), funcResult.factorIdToPhoneNumberMap); } return result; }; @@ -148,11 +138,7 @@ class Recipe extends recipeModule_1.default { let originalImpl = recipeImplementation_1.default(this); let builder = new supertokens_js_override_1.default(originalImpl); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); - if ( - ((_a = config === null || config === void 0 ? void 0 : config.override) === null || _a === void 0 - ? void 0 - : _a.functions) !== undefined - ) { + if (((_a = config === null || config === void 0 ? void 0 : config.override) === null || _a === void 0 ? void 0 : _a.functions) !== undefined) { this.isGetMfaRequirementsForAuthOverridden = true; // assuming that's what most people will override } } @@ -168,11 +154,7 @@ class Recipe extends recipeModule_1.default { // We don't add MultiFactorAuthClaim as a global claim because the values are populated // on factor setup / completion any way (in the sign in / up APIs). // SessionRecipe.getInstanceOrThrowError().addClaimFromOtherRecipe(MultiFactorAuthClaim); - recipe_1.default - .getInstanceOrThrowError() - .addClaimValidatorFromOtherRecipe( - multiFactorAuthClaim_1.MultiFactorAuthClaim.validators.hasCompletedMFARequirementsForAuth() - ); + recipe_1.default.getInstanceOrThrowError().addClaimValidatorFromOtherRecipe(multiFactorAuthClaim_1.MultiFactorAuthClaim.validators.hasCompletedMFARequirementsForAuth()); }); this.querier = querier_1.Querier.getNewInstanceOrThrowError(recipeId); } @@ -190,10 +172,9 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } else { - throw new Error( - "MultiFactorAuth recipe has already been initialised. Please check your code for bugs." - ); + } + else { + throw new Error("MultiFactorAuth recipe has already been initialised. Please check your code for bugs."); } }; } diff --git a/lib/build/recipe/multifactorauth/recipeImplementation.js b/lib/build/recipe/multifactorauth/recipeImplementation.js index 611743da9..55097e8ac 100644 --- a/lib/build/recipe/multifactorauth/recipeImplementation.js +++ b/lib/build/recipe/multifactorauth/recipeImplementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const usermetadata_1 = __importDefault(require("../usermetadata")); const multiFactorAuthClaim_1 = require("./multiFactorAuthClaim"); @@ -45,10 +43,7 @@ function getRecipeInterface(recipeInstance) { } return factorIds; }, - getMFARequirementsForAuth: async function ({ - requiredSecondaryFactorsForUser, - requiredSecondaryFactorsForTenant, - }) { + getMFARequirementsForAuth: async function ({ requiredSecondaryFactorsForUser, requiredSecondaryFactorsForTenant, }) { // default requirements for Auth is the union of required factors for user and tenant // https://github.com/supertokens/supertokens-core/issues/554#issuecomment-1752852720 const allFactors = new Set(); @@ -83,57 +78,32 @@ function getRecipeInterface(recipeInstance) { throw new Error("should never happen"); } if (claimVal.v) { - logger_1.logDebugMessage( - `assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: true because the session already satisfied auth reqs` - ); + logger_1.logDebugMessage(`assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: true because the session already satisfied auth reqs`); return { isValid: true }; } - const setOfUnsatisfiedFactors = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors( - claimVal.c, - await input.mfaRequirementsForAuth - ); + const setOfUnsatisfiedFactors = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors(claimVal.c, await input.mfaRequirementsForAuth); const factorsSetUpForUserRes = await input.factorsSetUpForUser; if (setOfUnsatisfiedFactors.factorIds.some((id) => factorsSetUpForUserRes.includes(id))) { - logger_1.logDebugMessage( - `assertAllowedToSetupFactorElseThrowInvalidClaimError ${ - input.factorId - }: false because there are items already set up in the next set of unsatisfied factors: ${setOfUnsatisfiedFactors.factorIds.join( - ", " - )}` - ); + logger_1.logDebugMessage(`assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: false because there are items already set up in the next set of unsatisfied factors: ${setOfUnsatisfiedFactors.factorIds.join(", ")}`); return { isValid: false, reason: "Completed factors in the session does not satisfy the MFA requirements for auth", }; } - if ( - setOfUnsatisfiedFactors.factorIds.length > 0 && - !setOfUnsatisfiedFactors.factorIds.includes(input.factorId) - ) { + if (setOfUnsatisfiedFactors.factorIds.length > 0 && + !setOfUnsatisfiedFactors.factorIds.includes(input.factorId)) { // It can be a security issue if we don't do this check // Consider this case: // Requirements: [{oneOf: ["totp", "otp-email"]}, "otp-phone"] (this is what I call the lower sms costs case) // The user has setup otp-phone previously, but no totp or email // During sign-in, they'd be allowed to add a new phone number, then set up TOTP and complete sign-in, completely bypassing the old phone number. - logger_1.logDebugMessage( - `assertAllowedToSetupFactorElseThrowInvalidClaimError ${ - input.factorId - }: false because user is trying to set up factor that is not in the next set of unsatisfied factors: ${setOfUnsatisfiedFactors.factorIds.join( - ", " - )}` - ); + logger_1.logDebugMessage(`assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: false because user is trying to set up factor that is not in the next set of unsatisfied factors: ${setOfUnsatisfiedFactors.factorIds.join(", ")}`); return { isValid: false, reason: "Not allowed to setup factor that is not in the next set of unsatisfied factors", }; } - logger_1.logDebugMessage( - `assertAllowedToSetupFactorElseThrowInvalidClaimError ${ - input.factorId - }: true because the next set of unsatisfied factors is ${ - setOfUnsatisfiedFactors.factorIds.length === 0 ? "empty" : "cannot be completed otherwise" - }` - ); + logger_1.logDebugMessage(`assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: true because the next set of unsatisfied factors is ${setOfUnsatisfiedFactors.factorIds.length === 0 ? "empty" : "cannot be completed otherwise"}`); return { isValid: true }; }, }; @@ -149,55 +119,31 @@ function getRecipeInterface(recipeInstance) { getRequiredSecondaryFactorsForUser: async function ({ userId, userContext }) { var _a, _b; const metadata = await usermetadata_1.default.getUserMetadata(userId, userContext); - return (_b = - (_a = metadata.metadata._supertokens) === null || _a === void 0 - ? void 0 - : _a.requiredSecondaryFactors) !== null && _b !== void 0 - ? _b - : []; + return (_b = (_a = metadata.metadata._supertokens) === null || _a === void 0 ? void 0 : _a.requiredSecondaryFactors) !== null && _b !== void 0 ? _b : []; }, addToRequiredSecondaryFactorsForUser: async function ({ userId, factorId, userContext }) { var _a, _b; const metadata = await usermetadata_1.default.getUserMetadata(userId, userContext); - const factorIds = - (_b = - (_a = metadata.metadata._supertokens) === null || _a === void 0 - ? void 0 - : _a.requiredSecondaryFactors) !== null && _b !== void 0 - ? _b - : []; + const factorIds = (_b = (_a = metadata.metadata._supertokens) === null || _a === void 0 ? void 0 : _a.requiredSecondaryFactors) !== null && _b !== void 0 ? _b : []; if (factorIds.includes(factorId)) { return; } factorIds.push(factorId); - const metadataUpdate = Object.assign(Object.assign({}, metadata.metadata), { - _supertokens: Object.assign(Object.assign({}, metadata.metadata._supertokens), { - requiredSecondaryFactors: factorIds, - }), - }); + const metadataUpdate = Object.assign(Object.assign({}, metadata.metadata), { _supertokens: Object.assign(Object.assign({}, metadata.metadata._supertokens), { requiredSecondaryFactors: factorIds }) }); await usermetadata_1.default.updateUserMetadata(userId, metadataUpdate, userContext); }, removeFromRequiredSecondaryFactorsForUser: async function ({ userId, factorId, userContext }) { var _a, _b; const metadata = await usermetadata_1.default.getUserMetadata(userId, userContext); - if ( - ((_a = metadata.metadata._supertokens) === null || _a === void 0 - ? void 0 - : _a.requiredSecondaryFactors) === undefined - ) { + if (((_a = metadata.metadata._supertokens) === null || _a === void 0 ? void 0 : _a.requiredSecondaryFactors) === undefined) { return; } - let factorIds = - (_b = metadata.metadata._supertokens.requiredSecondaryFactors) !== null && _b !== void 0 ? _b : []; + let factorIds = (_b = metadata.metadata._supertokens.requiredSecondaryFactors) !== null && _b !== void 0 ? _b : []; if (!factorIds.includes(factorId)) { return; } factorIds = factorIds.filter((id) => id !== factorId); - const metadataUpdate = Object.assign(Object.assign({}, metadata.metadata), { - _supertokens: Object.assign(Object.assign({}, metadata.metadata._supertokens), { - requiredSecondaryFactors: factorIds, - }), - }); + const metadataUpdate = Object.assign(Object.assign({}, metadata.metadata), { _supertokens: Object.assign(Object.assign({}, metadata.metadata._supertokens), { requiredSecondaryFactors: factorIds }) }); await usermetadata_1.default.updateUserMetadata(userId, metadataUpdate, userContext); }, }; diff --git a/lib/build/recipe/multifactorauth/types.d.ts b/lib/build/recipe/multifactorauth/types.d.ts index e02cbadb4..79cc8dc7a 100644 --- a/lib/build/recipe/multifactorauth/types.d.ts +++ b/lib/build/recipe/multifactorauth/types.d.ts @@ -8,15 +8,11 @@ import { SessionContainerInterface } from "../session/types"; import Recipe from "./recipe"; import { TenantConfig } from "../multitenancy/types"; import RecipeUserId from "../../recipeUserId"; -export declare type MFARequirementList = ( - | { - oneOf: string[]; - } - | { - allOfInAnyOrder: string[]; - } - | string -)[]; +export declare type MFARequirementList = ({ + oneOf: string[]; +} | { + allOfInAnyOrder: string[]; +} | string)[]; export declare type MFAClaimValue = { c: Record; v: boolean; @@ -24,20 +20,14 @@ export declare type MFAClaimValue = { export declare type TypeInput = { firstFactors?: string[]; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { firstFactors?: string[]; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -64,8 +54,14 @@ export declare type RecipeInterface = { factorId: string; userContext: UserContext; }) => Promise; - getFactorsSetupForUser: (input: { user: User; userContext: UserContext }) => Promise; - getRequiredSecondaryFactorsForUser: (input: { userId: string; userContext: UserContext }) => Promise; + getFactorsSetupForUser: (input: { + user: User; + userContext: UserContext; + }) => Promise; + getRequiredSecondaryFactorsForUser: (input: { + userId: string; + userContext: UserContext; + }) => Promise; addToRequiredSecondaryFactorsForUser: (input: { userId: string; factorId: string; @@ -87,53 +83,35 @@ export declare type APIOptions = { res: BaseResponse; }; export declare type APIInterface = { - resyncSessionAndFetchMFAInfoPUT: - | undefined - | ((input: { - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - factors: { - next: string[]; - alreadySetup: string[]; - allowedToSetup: string[]; - }; - emails: Record; - phoneNumbers: Record; - } - | GeneralErrorResponse - >); + resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + factors: { + next: string[]; + alreadySetup: string[]; + allowedToSetup: string[]; + }; + emails: Record; + phoneNumbers: Record; + } | GeneralErrorResponse>); }; -export declare type GetFactorsSetupForUserFromOtherRecipesFunc = ( - user: User, - userContext: UserContext -) => Promise; +export declare type GetFactorsSetupForUserFromOtherRecipesFunc = (user: User, userContext: UserContext) => Promise; export declare type GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc = (tenantConfig: TenantConfig) => string[]; -export declare type GetEmailsForFactorFromOtherRecipesFunc = ( - user: User, - sessionRecipeUserId: RecipeUserId -) => - | { - status: "OK"; - factorIdToEmailsMap: Record; - } - | { - status: "UNKNOWN_SESSION_RECIPE_USER_ID"; - }; -export declare type GetPhoneNumbersForFactorsFromOtherRecipesFunc = ( - user: User, - sessionRecipeUserId: RecipeUserId -) => - | { - status: "OK"; - factorIdToPhoneNumberMap: Record; - } - | { - status: "UNKNOWN_SESSION_RECIPE_USER_ID"; - }; +export declare type GetEmailsForFactorFromOtherRecipesFunc = (user: User, sessionRecipeUserId: RecipeUserId) => { + status: "OK"; + factorIdToEmailsMap: Record; +} | { + status: "UNKNOWN_SESSION_RECIPE_USER_ID"; +}; +export declare type GetPhoneNumbersForFactorsFromOtherRecipesFunc = (user: User, sessionRecipeUserId: RecipeUserId) => { + status: "OK"; + factorIdToPhoneNumberMap: Record; +} | { + status: "UNKNOWN_SESSION_RECIPE_USER_ID"; +}; export declare const FactorIds: { EMAILPASSWORD: string; WEBAUTHN: string; diff --git a/lib/build/recipe/multifactorauth/utils.d.ts b/lib/build/recipe/multifactorauth/utils.d.ts index 97f09f4e9..62de22b62 100644 --- a/lib/build/recipe/multifactorauth/utils.d.ts +++ b/lib/build/recipe/multifactorauth/utils.d.ts @@ -4,21 +4,16 @@ import { UserContext } from "../../types"; import { SessionContainerInterface } from "../session/types"; import { RecipeUserId } from "../.."; export declare function validateAndNormaliseUserInput(config?: TypeInput): TypeNormalisedInput; -export declare const updateAndGetMFARelatedInfoInSession: ( - input: ( - | { - sessionRecipeUserId: RecipeUserId; - tenantId: string; - accessTokenPayload: any; - } - | { - session: SessionContainerInterface; - } - ) & { - updatedFactorId?: string; - userContext: UserContext; - } -) => Promise<{ +export declare const updateAndGetMFARelatedInfoInSession: (input: ({ + sessionRecipeUserId: RecipeUserId; + tenantId: string; + accessTokenPayload: any; +} | { + session: SessionContainerInterface; +}) & { + updatedFactorId?: string; + userContext: UserContext; +}) => Promise<{ completedFactors: MFAClaimValue["c"]; mfaRequirementsForAuth: MFARequirementList; isMFARequirementsForAuthSatisfied: boolean; diff --git a/lib/build/recipe/multifactorauth/utils.js b/lib/build/recipe/multifactorauth/utils.js index f365b1e5a..e7a1b5b1f 100644 --- a/lib/build/recipe/multifactorauth/utils.js +++ b/lib/build/recipe/multifactorauth/utils.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateAndGetMFARelatedInfoInSession = exports.validateAndNormaliseUserInput = void 0; const multitenancy_1 = __importDefault(require("../multitenancy")); @@ -29,19 +27,10 @@ const error_1 = __importDefault(require("../session/error")); const types_1 = require("./types"); const utils_1 = require("../multitenancy/utils"); function validateAndNormaliseUserInput(config) { - if ( - (config === null || config === void 0 ? void 0 : config.firstFactors) !== undefined && - (config === null || config === void 0 ? void 0 : config.firstFactors.length) === 0 - ) { + if ((config === null || config === void 0 ? void 0 : config.firstFactors) !== undefined && (config === null || config === void 0 ? void 0 : config.firstFactors.length) === 0) { throw new Error("'firstFactors' can be either undefined or a non-empty array"); } - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { firstFactors: config === null || config === void 0 ? void 0 : config.firstFactors, override, @@ -59,7 +48,8 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { tenantId = input.session.getTenantId(input.userContext); accessTokenPayload = input.session.getAccessTokenPayload(input.userContext); sessionHandle = input.session.getHandle(input.userContext); - } else { + } + else { sessionRecipeUserId = input.sessionRecipeUserId; tenantId = input.tenantId; accessTokenPayload = input.accessTokenPayload; @@ -76,7 +66,8 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { }, v: true, // updated later in the function }; - } else { + } + else { updatedClaimVal = true; mfaClaimValue.c[input.updatedFactorId] = Math.floor(Date.now() / 1000); } @@ -104,36 +95,32 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { for (const lM of sessionUser.loginMethods) { if (lM.recipeUserId.getAsString() === sessionRecipeUserId.getAsString()) { if (lM.recipeId === "emailpassword") { - let validRes = await utils_1.isValidFirstFactor( - tenantId, - types_1.FactorIds.EMAILPASSWORD, - input.userContext - ); + let validRes = await utils_1.isValidFirstFactor(tenantId, types_1.FactorIds.EMAILPASSWORD, input.userContext); if (validRes.status === "TENANT_NOT_FOUND_ERROR") { throw new error_1.default({ type: error_1.default.UNAUTHORISED, message: "Tenant not found", }); - } else if (validRes.status === "OK") { + } + else if (validRes.status === "OK") { computedFirstFactorIdForSession = types_1.FactorIds.EMAILPASSWORD; break; } - } else if (lM.recipeId === "thirdparty") { - let validRes = await utils_1.isValidFirstFactor( - tenantId, - types_1.FactorIds.THIRDPARTY, - input.userContext - ); + } + else if (lM.recipeId === "thirdparty") { + let validRes = await utils_1.isValidFirstFactor(tenantId, types_1.FactorIds.THIRDPARTY, input.userContext); if (validRes.status === "TENANT_NOT_FOUND_ERROR") { throw new error_1.default({ type: error_1.default.UNAUTHORISED, message: "Tenant not found", }); - } else if (validRes.status === "OK") { + } + else if (validRes.status === "OK") { computedFirstFactorIdForSession = types_1.FactorIds.THIRDPARTY; break; } - } else { + } + else { let factorsToCheck = []; if (lM.email !== undefined) { factorsToCheck.push(types_1.FactorIds.LINK_EMAIL); @@ -150,7 +137,8 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { type: error_1.default.UNAUTHORISED, message: "Tenant not found", }); - } else if (validRes.status === "OK") { + } + else if (validRes.status === "OK") { computedFirstFactorIdForSession = factorId; break; } @@ -192,68 +180,55 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { }); return userProm; } - const mfaRequirementsForAuth = await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.getMFARequirementsForAuth({ - accessTokenPayload, - tenantId, - get user() { - return userGetter(); - }, - get factorsSetUpForUser() { - return userGetter().then((user) => - recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getFactorsSetupForUser({ - user, - userContext: input.userContext, - }) - ); - }, - get requiredSecondaryFactorsForUser() { - return userGetter().then((sessionUser) => { - if (sessionUser === undefined) { - throw new error_1.default({ - type: error_1.default.UNAUTHORISED, - message: "Session user not found", - }); - } - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.getRequiredSecondaryFactorsForUser({ - userId: sessionUser.id, - userContext: input.userContext, - }); - }); - }, - get requiredSecondaryFactorsForTenant() { - return multitenancy_1.default.getTenant(tenantId, input.userContext).then((tenantInfo) => { - var _a; - if (tenantInfo === undefined) { - throw new error_1.default({ - type: error_1.default.UNAUTHORISED, - message: "Tenant not found", - }); - } - return (_a = tenantInfo.requiredSecondaryFactors) !== null && _a !== void 0 ? _a : []; + const mfaRequirementsForAuth = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getMFARequirementsForAuth({ + accessTokenPayload, + tenantId, + get user() { + return userGetter(); + }, + get factorsSetUpForUser() { + return userGetter().then((user) => recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getFactorsSetupForUser({ + user, + userContext: input.userContext, + })); + }, + get requiredSecondaryFactorsForUser() { + return userGetter().then((sessionUser) => { + if (sessionUser === undefined) { + throw new error_1.default({ + type: error_1.default.UNAUTHORISED, + message: "Session user not found", + }); + } + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getRequiredSecondaryFactorsForUser({ + userId: sessionUser.id, + userContext: input.userContext, }); - }, - completedFactors, - userContext: input.userContext, - }); - const areAuthReqsComplete = - multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors( - completedFactors, - mfaRequirementsForAuth - ).factorIds.length === 0; + }); + }, + get requiredSecondaryFactorsForTenant() { + return multitenancy_1.default.getTenant(tenantId, input.userContext).then((tenantInfo) => { + var _a; + if (tenantInfo === undefined) { + throw new error_1.default({ + type: error_1.default.UNAUTHORISED, + message: "Tenant not found", + }); + } + return (_a = tenantInfo.requiredSecondaryFactors) !== null && _a !== void 0 ? _a : []; + }); + }, + completedFactors, + userContext: input.userContext, + }); + const areAuthReqsComplete = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors(completedFactors, mfaRequirementsForAuth).factorIds + .length === 0; if (mfaClaimValue.v !== areAuthReqsComplete) { updatedClaimVal = true; mfaClaimValue.v = areAuthReqsComplete; } if ("session" in input && updatedClaimVal) { - await input.session.setClaimValue( - multiFactorAuthClaim_1.MultiFactorAuthClaim, - mfaClaimValue, - input.userContext - ); + await input.session.setClaimValue(multiFactorAuthClaim_1.MultiFactorAuthClaim, mfaClaimValue, input.userContext); } return { completedFactors, diff --git a/lib/build/recipe/multitenancy/allowedDomainsClaim.js b/lib/build/recipe/multitenancy/allowedDomainsClaim.js index d679919b5..e3f9f25c1 100644 --- a/lib/build/recipe/multitenancy/allowedDomainsClaim.js +++ b/lib/build/recipe/multitenancy/allowedDomainsClaim.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.AllowedDomainsClaim = exports.AllowedDomainsClaimClass = void 0; const claims_1 = require("../session/claims"); diff --git a/lib/build/recipe/multitenancy/api/implementation.js b/lib/build/recipe/multitenancy/api/implementation.js index a2528571b..ad2104c6f 100644 --- a/lib/build/recipe/multitenancy/api/implementation.js +++ b/lib/build/recipe/multitenancy/api/implementation.js @@ -15,20 +15,11 @@ function getAPIInterface() { } const providerInputsFromStatic = options.staticThirdPartyProviders; const providerConfigsFromCore = tenantConfigRes.thirdParty.providers; - const mergedProviders = configUtils_1.mergeProvidersFromCoreAndStatic( - providerConfigsFromCore, - providerInputsFromStatic, - tenantId === constants_1.DEFAULT_TENANT_ID - ); + const mergedProviders = configUtils_1.mergeProvidersFromCoreAndStatic(providerConfigsFromCore, providerInputsFromStatic, tenantId === constants_1.DEFAULT_TENANT_ID); const finalProviderList = []; for (const providerInput of mergedProviders) { try { - const providerInstance = await configUtils_1.findAndCreateProviderInstance( - mergedProviders, - providerInput.config.thirdPartyId, - clientType, - userContext - ); + const providerInstance = await configUtils_1.findAndCreateProviderInstance(mergedProviders, providerInput.config.thirdPartyId, clientType, userContext); if (providerInstance === undefined) { throw new Error("should never come here"); // because creating instance from the merged provider list itself } @@ -36,7 +27,8 @@ function getAPIInterface() { id: providerInstance.id, name: providerInstance.config.name, }); - } catch (err) { + } + catch (err) { if (err.type === "CLIENT_TYPE_NOT_FOUND_ERROR") { continue; } @@ -46,9 +38,11 @@ function getAPIInterface() { let firstFactors; if (tenantConfigRes.firstFactors !== undefined) { firstFactors = tenantConfigRes.firstFactors; // highest priority, config from core - } else if (options.staticFirstFactors !== undefined) { + } + else if (options.staticFirstFactors !== undefined) { firstFactors = options.staticFirstFactors; // next priority, static config - } else { + } + else { // Fallback to all available factors (de-duplicated) firstFactors = Array.from(new Set(options.allAvailableFirstFactors)); } @@ -77,8 +71,7 @@ function getAPIInterface() { providers: finalProviderList, }, passwordless: { - enabled: - validFirstFactors.includes("otp-email") || + enabled: validFirstFactors.includes("otp-email") || validFirstFactors.includes("otp-phone") || validFirstFactors.includes("link-email") || validFirstFactors.includes("link-phone"), diff --git a/lib/build/recipe/multitenancy/api/loginMethods.d.ts b/lib/build/recipe/multitenancy/api/loginMethods.d.ts index c497a7557..a6f390bb6 100644 --- a/lib/build/recipe/multitenancy/api/loginMethods.d.ts +++ b/lib/build/recipe/multitenancy/api/loginMethods.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function loginMethodsAPI( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function loginMethodsAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/multitenancy/error.d.ts b/lib/build/recipe/multitenancy/error.d.ts index 486758b61..d6412505c 100644 --- a/lib/build/recipe/multitenancy/error.d.ts +++ b/lib/build/recipe/multitenancy/error.d.ts @@ -1,5 +1,8 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); + constructor(options: { + type: "BAD_INPUT_ERROR"; + message: string; + }); } diff --git a/lib/build/recipe/multitenancy/error.js b/lib/build/recipe/multitenancy/error.js index c6c1f6ccc..a0e8daf7f 100644 --- a/lib/build/recipe/multitenancy/error.js +++ b/lib/build/recipe/multitenancy/error.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/multitenancy/index.d.ts b/lib/build/recipe/multitenancy/index.d.ts index 6aef31375..543dd643c 100644 --- a/lib/build/recipe/multitenancy/index.d.ts +++ b/lib/build/recipe/multitenancy/index.d.ts @@ -6,87 +6,47 @@ import { AllowedDomainsClaim } from "./allowedDomainsClaim"; import RecipeUserId from "../../recipeUserId"; export default class Wrapper { static init: typeof Recipe.init; - static createOrUpdateTenant( - tenantId: string, - config?: { - firstFactors?: string[] | null; - requiredSecondaryFactors?: string[] | null; - coreConfig?: { - [key: string]: any; - }; - }, - userContext?: Record - ): Promise<{ + static createOrUpdateTenant(tenantId: string, config?: { + firstFactors?: string[] | null; + requiredSecondaryFactors?: string[] | null; + coreConfig?: { + [key: string]: any; + }; + }, userContext?: Record): Promise<{ status: "OK"; createdNew: boolean; }>; - static deleteTenant( - tenantId: string, - userContext?: Record - ): Promise<{ + static deleteTenant(tenantId: string, userContext?: Record): Promise<{ status: "OK"; didExist: boolean; }>; - static getTenant( - tenantId: string, - userContext?: Record - ): Promise< - | ({ - status: "OK"; - } & TenantConfig) - | undefined - >; - static listAllTenants( - userContext?: Record - ): Promise<{ + static getTenant(tenantId: string, userContext?: Record): Promise<({ + status: "OK"; + } & TenantConfig) | undefined>; + static listAllTenants(userContext?: Record): Promise<{ status: "OK"; tenants: ({ tenantId: string; } & TenantConfig)[]; }>; - static createOrUpdateThirdPartyConfig( - tenantId: string, - config: ProviderConfig, - skipValidation?: boolean, - userContext?: Record - ): Promise<{ + static createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record): Promise<{ status: "OK"; createdNew: boolean; }>; - static deleteThirdPartyConfig( - tenantId: string, - thirdPartyId: string, - userContext?: Record - ): Promise<{ + static deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record): Promise<{ status: "OK"; didConfigExist: boolean; }>; - static associateUserToTenant( - tenantId: string, - recipeUserId: RecipeUserId, - userContext?: Record - ): Promise< - | { - status: "OK"; - wasAlreadyAssociated: boolean; - } - | { - status: - | "UNKNOWN_USER_ID_ERROR" - | "EMAIL_ALREADY_EXISTS_ERROR" - | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" - | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"; - } - | { - status: "ASSOCIATION_NOT_ALLOWED_ERROR"; - reason: string; - } - >; - static disassociateUserFromTenant( - tenantId: string, - recipeUserId: RecipeUserId, - userContext?: Record - ): Promise<{ + static associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record): Promise<{ + status: "OK"; + wasAlreadyAssociated: boolean; + } | { + status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"; + } | { + status: "ASSOCIATION_NOT_ALLOWED_ERROR"; + reason: string; + }>; + static disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record): Promise<{ status: "OK"; wasAssociated: boolean; }>; diff --git a/lib/build/recipe/multitenancy/index.js b/lib/build/recipe/multitenancy/index.js index bb6446939..d2708f438 100644 --- a/lib/build/recipe/multitenancy/index.js +++ b/lib/build/recipe/multitenancy/index.js @@ -13,21 +13,14 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.AllowedDomainsClaim = exports.disassociateUserFromTenant = exports.associateUserToTenant = exports.deleteThirdPartyConfig = exports.createOrUpdateThirdPartyConfig = exports.listAllTenants = exports.getTenant = exports.deleteTenant = exports.createOrUpdateTenant = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); const allowedDomainsClaim_1 = require("./allowedDomainsClaim"); -Object.defineProperty(exports, "AllowedDomainsClaim", { - enumerable: true, - get: function () { - return allowedDomainsClaim_1.AllowedDomainsClaim; - }, -}); +Object.defineProperty(exports, "AllowedDomainsClaim", { enumerable: true, get: function () { return allowedDomainsClaim_1.AllowedDomainsClaim; } }); const utils_1 = require("../../utils"); class Wrapper { static async createOrUpdateTenant(tenantId, config, userContext) { diff --git a/lib/build/recipe/multitenancy/recipe.d.ts b/lib/build/recipe/multitenancy/recipe.d.ts index e649d0782..8e81aa641 100644 --- a/lib/build/recipe/multitenancy/recipe.d.ts +++ b/lib/build/recipe/multitenancy/recipe.d.ts @@ -23,15 +23,7 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - _: NormalisedURLPath, - __: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/multitenancy/recipe.js b/lib/build/recipe/multitenancy/recipe.js index 20f9c22be..d2073745c 100644 --- a/lib/build/recipe/multitenancy/recipe.js +++ b/lib/build/recipe/multitenancy/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -79,9 +77,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId)) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId))); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -106,16 +102,16 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance.getAllowedDomainsForTenantId !== undefined) { postSuperTokensInitCallbacks_1.PostSuperTokensInitCallbacks.addPostInitCallback(() => { try { - recipe_1.default - .getInstanceOrThrowError() - .addClaimFromOtherRecipe(allowedDomainsClaim_1.AllowedDomainsClaim); - } catch (_a) { + recipe_1.default.getInstanceOrThrowError().addClaimFromOtherRecipe(allowedDomainsClaim_1.AllowedDomainsClaim); + } + catch (_a) { // Skip adding claims if session recipe is not initialised } }); } return Recipe.instance; - } else { + } + else { throw new Error("Multitenancy recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/multitenancy/recipeImplementation.js b/lib/build/recipe/multitenancy/recipeImplementation.js index 044c7e319..eec929ffb 100644 --- a/lib/build/recipe/multitenancy/recipeImplementation.js +++ b/lib/build/recipe/multitenancy/recipeImplementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const constants_1 = require("./constants"); @@ -13,103 +11,49 @@ function getRecipeInterface(querier) { return tenantIdFromFrontend; }, createOrUpdateTenant: async function ({ tenantId, config, userContext }) { - let response = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/v2`), - Object.assign({ tenantId }, config), - {}, - userContext - ); + let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/v2`), Object.assign({ tenantId }, config), {}, userContext); return response; }, deleteTenant: async function ({ tenantId, userContext }) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/remove`), - { - tenantId, - }, - userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/remove`), { + tenantId, + }, userContext); return response; }, getTenant: async function ({ tenantId, userContext }) { - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/multitenancy/tenant/v2` - ), - {}, - userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/tenant/v2`), {}, userContext); if (response.status === "TENANT_NOT_FOUND_ERROR") { return undefined; } return response; }, listAllTenants: async function ({ userContext }) { - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/list/v2`), - {}, - userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/list/v2`), {}, userContext); return response; }, createOrUpdateThirdPartyConfig: async function ({ tenantId, config, skipValidation, userContext }) { - let response = await querier.sendPutRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/multitenancy/config/thirdparty` - ), - { - config, - skipValidation, - }, - {}, - userContext - ); + let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/config/thirdparty`), { + config, + skipValidation, + }, {}, userContext); return response; }, deleteThirdPartyConfig: async function ({ tenantId, thirdPartyId, userContext }) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/multitenancy/config/thirdparty/remove` - ), - { - thirdPartyId, - }, - userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/config/thirdparty/remove`), { + thirdPartyId, + }, userContext); return response; }, associateUserToTenant: async function ({ tenantId, recipeUserId, userContext }) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/multitenancy/tenant/user` - ), - { - recipeUserId: recipeUserId.getAsString(), - }, - userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/tenant/user`), { + recipeUserId: recipeUserId.getAsString(), + }, userContext); return response; }, disassociateUserFromTenant: async function ({ tenantId, recipeUserId, userContext }) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/multitenancy/tenant/user/remove` - ), - { - recipeUserId: recipeUserId.getAsString(), - }, - userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/tenant/user/remove`), { + recipeUserId: recipeUserId.getAsString(), + }, userContext); return response; }, }; diff --git a/lib/build/recipe/multitenancy/types.d.ts b/lib/build/recipe/multitenancy/types.d.ts index e3d0008a6..0a2df0ec8 100644 --- a/lib/build/recipe/multitenancy/types.d.ts +++ b/lib/build/recipe/multitenancy/types.d.ts @@ -7,20 +7,14 @@ import RecipeUserId from "../../recipeUserId"; export declare type TypeInput = { getAllowedDomainsForTenantId?: (tenantId: string, userContext: UserContext) => Promise; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { getAllowedDomainsForTenantId?: (tenantId: string, userContext: UserContext) => Promise; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -35,7 +29,10 @@ export declare type TenantConfig = { }; }; export declare type RecipeInterface = { - getTenantId: (input: { tenantIdFromFrontend: string; userContext: UserContext }) => Promise; + getTenantId: (input: { + tenantIdFromFrontend: string; + userContext: UserContext; + }) => Promise; createOrUpdateTenant: (input: { tenantId: string; config?: { @@ -60,12 +57,9 @@ export declare type RecipeInterface = { getTenant: (input: { tenantId: string; userContext: UserContext; - }) => Promise< - | ({ - status: "OK"; - } & TenantConfig) - | undefined - >; + }) => Promise<({ + status: "OK"; + } & TenantConfig) | undefined>; listAllTenants: (input: { userContext: UserContext; }) => Promise<{ @@ -95,23 +89,15 @@ export declare type RecipeInterface = { tenantId: string; recipeUserId: RecipeUserId; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - wasAlreadyAssociated: boolean; - } - | { - status: - | "UNKNOWN_USER_ID_ERROR" - | "EMAIL_ALREADY_EXISTS_ERROR" - | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" - | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"; - } - | { - status: "ASSOCIATION_NOT_ALLOWED_ERROR"; - reason: string; - } - >; + }) => Promise<{ + status: "OK"; + wasAlreadyAssociated: boolean; + } | { + status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"; + } | { + status: "ASSOCIATION_NOT_ALLOWED_ERROR"; + reason: string; + }>; disassociateUserFromTenant: (input: { tenantId: string; recipeUserId: RecipeUserId; @@ -138,24 +124,21 @@ export declare type APIInterface = { clientType?: string; options: APIOptions; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - emailPassword: { - enabled: boolean; - }; - thirdParty: { - enabled: boolean; - providers: { - id: string; - name?: string; - }[]; - }; - passwordless: { - enabled: boolean; - }; - firstFactors: string[]; - } - | GeneralErrorResponse - >; + }) => Promise<{ + status: "OK"; + emailPassword: { + enabled: boolean; + }; + thirdParty: { + enabled: boolean; + providers: { + id: string; + name?: string; + }[]; + }; + passwordless: { + enabled: boolean; + }; + firstFactors: string[]; + } | GeneralErrorResponse>; }; diff --git a/lib/build/recipe/multitenancy/utils.d.ts b/lib/build/recipe/multitenancy/utils.d.ts index 3bc6e2abc..7190e47b9 100644 --- a/lib/build/recipe/multitenancy/utils.d.ts +++ b/lib/build/recipe/multitenancy/utils.d.ts @@ -2,26 +2,14 @@ import { TypeInput, TypeNormalisedInput, TenantConfig } from "./types"; import { UserContext } from "../../types"; export declare function validateAndNormaliseUserInput(config?: TypeInput): TypeNormalisedInput; -export declare const isValidFirstFactor: ( - tenantId: string, - factorId: string, - userContext: UserContext -) => Promise< - | { - status: "OK"; - } - | { - status: "INVALID_FIRST_FACTOR_ERROR"; - } - | { - status: "TENANT_NOT_FOUND_ERROR"; - } ->; -export declare function isFactorConfiguredForTenant({ - allAvailableFirstFactors, - firstFactors, - factorId, -}: { +export declare const isValidFirstFactor: (tenantId: string, factorId: string, userContext: UserContext) => Promise<{ + status: "OK"; +} | { + status: "INVALID_FIRST_FACTOR_ERROR"; +} | { + status: "TENANT_NOT_FOUND_ERROR"; +}>; +export declare function isFactorConfiguredForTenant({ allAvailableFirstFactors, firstFactors, factorId, }: { tenantConfig: TenantConfig; allAvailableFirstFactors: string[]; firstFactors: string[]; diff --git a/lib/build/recipe/multitenancy/utils.js b/lib/build/recipe/multitenancy/utils.js index 3883f88f5..835493628 100644 --- a/lib/build/recipe/multitenancy/utils.js +++ b/lib/build/recipe/multitenancy/utils.js @@ -13,38 +13,29 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __rest = - (this && this.__rest) || - function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; - } - return t; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.isFactorConfiguredForTenant = exports.isValidFirstFactor = exports.validateAndNormaliseUserInput = void 0; const recipe_1 = __importDefault(require("./recipe")); const logger_1 = require("../../logger"); const types_1 = require("../multifactorauth/types"); function validateAndNormaliseUserInput(config) { - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { - getAllowedDomainsForTenantId: - config === null || config === void 0 ? void 0 : config.getAllowedDomainsForTenantId, + getAllowedDomainsForTenantId: config === null || config === void 0 ? void 0 : config.getAllowedDomainsForTenantId, override, }; } @@ -61,29 +52,21 @@ const isValidFirstFactor = async function (tenantId, factorId, userContext) { status: "TENANT_NOT_FOUND_ERROR", }; } - const { status: _ } = tenantInfo, - tenantConfig = __rest(tenantInfo, ["status"]); + const { status: _ } = tenantInfo, tenantConfig = __rest(tenantInfo, ["status"]); const firstFactorsFromMFA = mtRecipe.staticFirstFactors; - logger_1.logDebugMessage( - `isValidFirstFactor got ${ - (_a = tenantConfig.firstFactors) === null || _a === void 0 ? void 0 : _a.join(", ") - } from tenant config` - ); + logger_1.logDebugMessage(`isValidFirstFactor got ${(_a = tenantConfig.firstFactors) === null || _a === void 0 ? void 0 : _a.join(", ")} from tenant config`); logger_1.logDebugMessage(`isValidFirstFactor got ${firstFactorsFromMFA} from MFA`); // first factors configured in core is prioritised over the ones configured statically - let configuredFirstFactors = - tenantConfig.firstFactors !== undefined ? tenantConfig.firstFactors : firstFactorsFromMFA; + let configuredFirstFactors = tenantConfig.firstFactors !== undefined ? tenantConfig.firstFactors : firstFactorsFromMFA; if (configuredFirstFactors === undefined) { configuredFirstFactors = mtRecipe.allAvailableFirstFactors; } - if ( - isFactorConfiguredForTenant({ - tenantConfig, - allAvailableFirstFactors: mtRecipe.allAvailableFirstFactors, - firstFactors: configuredFirstFactors, - factorId, - }) - ) { + if (isFactorConfiguredForTenant({ + tenantConfig, + allAvailableFirstFactors: mtRecipe.allAvailableFirstFactors, + firstFactors: configuredFirstFactors, + factorId, + })) { return { status: "OK", }; @@ -93,14 +76,11 @@ const isValidFirstFactor = async function (tenantId, factorId, userContext) { }; }; exports.isValidFirstFactor = isValidFirstFactor; -function isFactorConfiguredForTenant({ allAvailableFirstFactors, firstFactors, factorId }) { +function isFactorConfiguredForTenant({ allAvailableFirstFactors, firstFactors, factorId, }) { // Here we filter the array so that we only have: // 1. Factors that other recipes have marked as available // 2. Custom factors (not in the built-in FactorIds list) - let configuredFirstFactors = firstFactors.filter( - (factorId) => - allAvailableFirstFactors.includes(factorId) || !Object.values(types_1.FactorIds).includes(factorId) - ); + let configuredFirstFactors = firstFactors.filter((factorId) => allAvailableFirstFactors.includes(factorId) || !Object.values(types_1.FactorIds).includes(factorId)); // Filter based on enabled recipes in the core is no more required // if (tenantConfig.emailPassword.enabled === false) { // configuredFirstFactors = configuredFirstFactors.filter( diff --git a/lib/build/recipe/oauth2client/api/implementation.js b/lib/build/recipe/oauth2client/api/implementation.js index 5a5e1d56f..ba3cc13cb 100644 --- a/lib/build/recipe/oauth2client/api/implementation.js +++ b/lib/build/recipe/oauth2client/api/implementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const session_1 = __importDefault(require("../../session")); function getAPIInterface() { @@ -13,9 +11,7 @@ function getAPIInterface() { let normalisedClientId = clientId; if (normalisedClientId === undefined) { if (options.config.providerConfigs.length > 1) { - throw new Error( - "Should never come here: clientId is undefined and there are multiple providerConfigs" - ); + throw new Error("Should never come here: clientId is undefined and there are multiple providerConfigs"); } normalisedClientId = options.config.providerConfigs[0].clientId; } @@ -30,9 +26,11 @@ function getAPIInterface() { redirectURIInfo: input.redirectURIInfo, userContext, }); - } else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { + } + else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { oAuthTokensToUse = input.oAuthTokens; - } else { + } + else { throw Error("should never come here"); } const { userId, rawUserInfo } = await options.recipeImplementation.getUserInfo({ @@ -47,15 +45,7 @@ function getAPIInterface() { oAuthTokens: oAuthTokensToUse, userContext, }); - const session = await session_1.default.createNewSession( - options.req, - options.res, - tenantId, - recipeUserId, - undefined, - undefined, - userContext - ); + const session = await session_1.default.createNewSession(options.req, options.res, tenantId, recipeUserId, undefined, undefined, userContext); return { status: "OK", user, diff --git a/lib/build/recipe/oauth2client/api/signin.d.ts b/lib/build/recipe/oauth2client/api/signin.d.ts index 72cd6e46b..079e9b8fb 100644 --- a/lib/build/recipe/oauth2client/api/signin.d.ts +++ b/lib/build/recipe/oauth2client/api/signin.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function signInAPI( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signInAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2client/api/signin.js b/lib/build/recipe/oauth2client/api/signin.js index 57155dc81..2ba871909 100644 --- a/lib/build/recipe/oauth2client/api/signin.js +++ b/lib/build/recipe/oauth2client/api/signin.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../../error")); const utils_1 = require("../../../utils"); @@ -42,9 +40,11 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { }); } redirectURIInfo = bodyParams.redirectURIInfo; - } else if (bodyParams.oAuthTokens !== undefined) { + } + else if (bodyParams.oAuthTokens !== undefined) { oAuthTokens = bodyParams.oAuthTokens; - } else { + } + else { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "Please provide one of redirectURIInfo or oAuthTokens in the request body", @@ -59,14 +59,9 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { userContext, }); if (result.status === "OK") { - utils_1.send200Response( - options.res, - Object.assign( - { status: result.status }, - utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext) - ) - ); - } else { + utils_1.send200Response(options.res, Object.assign({ status: result.status }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); + } + else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/oauth2client/index.d.ts b/lib/build/recipe/oauth2client/index.d.ts index ff01c3827..9f66e4f71 100644 --- a/lib/build/recipe/oauth2client/index.d.ts +++ b/lib/build/recipe/oauth2client/index.d.ts @@ -3,19 +3,12 @@ import Recipe from "./recipe"; import { RecipeInterface, APIInterface, APIOptions, OAuthTokens } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static exchangeAuthCodeForOAuthTokens( - redirectURIInfo: { - redirectURI: string; - redirectURIQueryParams: any; - pkceCodeVerifier?: string | undefined; - }, - clientId?: string, - userContext?: Record - ): Promise; - static getUserInfo( - oAuthTokens: OAuthTokens, - userContext?: Record - ): Promise; + static exchangeAuthCodeForOAuthTokens(redirectURIInfo: { + redirectURI: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string | undefined; + }, clientId?: string, userContext?: Record): Promise; + static getUserInfo(oAuthTokens: OAuthTokens, userContext?: Record): Promise; } export declare let init: typeof Recipe.init; export declare let exchangeAuthCodeForOAuthTokens: typeof Wrapper.exchangeAuthCodeForOAuthTokens; diff --git a/lib/build/recipe/oauth2client/index.js b/lib/build/recipe/oauth2client/index.js index e24e2d323..1d2fb99db 100644 --- a/lib/build/recipe/oauth2client/index.js +++ b/lib/build/recipe/oauth2client/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getUserInfo = exports.exchangeAuthCodeForOAuthTokens = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/oauth2client/recipe.d.ts b/lib/build/recipe/oauth2client/recipe.d.ts index 180227169..deae2b60a 100644 --- a/lib/build/recipe/oauth2client/recipe.d.ts +++ b/lib/build/recipe/oauth2client/recipe.d.ts @@ -12,26 +12,12 @@ export default class Recipe extends RecipeModule { recipeInterfaceImpl: RecipeInterface; apiImpl: APIInterface; isInServerlessEnv: boolean; - constructor( - recipeId: string, - appInfo: NormalisedAppinfo, - isInServerlessEnv: boolean, - config: TypeInput, - _recipes: {} - ); + constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput, _recipes: {}); static init(config: TypeInput): RecipeListFunction; static getInstanceOrThrowError(): Recipe; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - _path: NormalisedURLPath, - _method: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/oauth2client/recipe.js b/lib/build/recipe/oauth2client/recipe.js index daf6b07f3..33b7e36c3 100644 --- a/lib/build/recipe/oauth2client/recipe.js +++ b/lib/build/recipe/oauth2client/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const utils_1 = require("./utils"); @@ -69,9 +67,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -84,7 +80,8 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config, {}); return Recipe.instance; - } else { + } + else { throw new Error("OAuth2Client recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/oauth2client/recipeImplementation.js b/lib/build/recipe/oauth2client/recipeImplementation.js index 351fa3a45..3e23a280c 100644 --- a/lib/build/recipe/oauth2client/recipeImplementation.js +++ b/lib/build/recipe/oauth2client/recipeImplementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const thirdpartyUtils_1 = require("../../thirdpartyUtils"); @@ -13,7 +11,7 @@ const jose_1 = require("jose"); function getRecipeImplementation(_querier, config) { let providerConfigsWithOIDCInfo = {}; return { - signIn: async function ({ userId, tenantId, userContext, oAuthTokens, rawUserInfo }) { + signIn: async function ({ userId, tenantId, userContext, oAuthTokens, rawUserInfo, }) { const user = await __1.getUser(userId, userContext); if (user === undefined) { throw new Error(`Failed to getUser from the userId ${userId} in the ${tenantId} tenant`); @@ -30,9 +28,7 @@ function getRecipeImplementation(_querier, config) { if (providerConfigsWithOIDCInfo[clientId] !== undefined) { return providerConfigsWithOIDCInfo[clientId]; } - const providerConfig = config.providerConfigs.find( - (providerConfig) => providerConfig.clientId === clientId - ); + const providerConfig = config.providerConfigs.find((providerConfig) => providerConfig.clientId === clientId); const oidcInfo = await thirdpartyUtils_1.getOIDCDiscoveryInfo(providerConfig.oidcDiscoveryEndpoint); if (oidcInfo.authorization_endpoint === undefined) { throw new Error("Failed to authorization_endpoint from the oidcDiscoveryEndpoint."); @@ -46,12 +42,7 @@ function getRecipeImplementation(_querier, config) { if (oidcInfo.jwks_uri === undefined) { throw new Error("Failed to jwks_uri from the oidcDiscoveryEndpoint."); } - providerConfigsWithOIDCInfo[clientId] = Object.assign(Object.assign({}, providerConfig), { - authorizationEndpoint: oidcInfo.authorization_endpoint, - tokenEndpoint: oidcInfo.token_endpoint, - userInfoEndpoint: oidcInfo.userinfo_endpoint, - jwksURI: oidcInfo.jwks_uri, - }); + providerConfigsWithOIDCInfo[clientId] = Object.assign(Object.assign({}, providerConfig), { authorizationEndpoint: oidcInfo.authorization_endpoint, tokenEndpoint: oidcInfo.token_endpoint, userInfoEndpoint: oidcInfo.userinfo_endpoint, jwksURI: oidcInfo.jwks_uri }); return providerConfigsWithOIDCInfo[clientId]; }, exchangeAuthCodeForOAuthTokens: async function ({ providerConfig, redirectURIInfo }) { @@ -73,12 +64,8 @@ function getRecipeImplementation(_querier, config) { } const tokenResponse = await thirdpartyUtils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); if (tokenResponse.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` - ); - throw new Error( - `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); + throw new Error(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); } return tokenResponse.jsonResponse; }, @@ -95,38 +82,27 @@ function getRecipeImplementation(_querier, config) { if (jwks === undefined) { jwks = jose_1.createRemoteJWKSet(new URL(providerConfig.jwksURI)); } - rawUserInfo.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( - idToken, - jwks, - { - audience: providerConfig.clientId, - } - ); + rawUserInfo.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload(idToken, jwks, { + audience: providerConfig.clientId, + }); } if (accessToken && providerConfig.userInfoEndpoint !== undefined) { const headers = { Authorization: "Bearer " + accessToken, }; const queryParams = {}; - const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( - providerConfig.userInfoEndpoint, - queryParams, - headers - ); + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest(providerConfig.userInfoEndpoint, queryParams, headers); if (userInfoFromAccessToken.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` - ); - throw new Error( - `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); + throw new Error(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); } rawUserInfo.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; } let userId = undefined; if (((_a = rawUserInfo.fromIdTokenPayload) === null || _a === void 0 ? void 0 : _a.sub) !== undefined) { userId = rawUserInfo.fromIdTokenPayload["sub"]; - } else if (((_b = rawUserInfo.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.sub) !== undefined) { + } + else if (((_b = rawUserInfo.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.sub) !== undefined) { userId = rawUserInfo.fromUserInfoAPI["sub"]; } if (userId === undefined) { diff --git a/lib/build/recipe/oauth2client/types.d.ts b/lib/build/recipe/oauth2client/types.d.ts index 84fab1847..14e21c0af 100644 --- a/lib/build/recipe/oauth2client/types.d.ts +++ b/lib/build/recipe/oauth2client/types.d.ts @@ -42,25 +42,22 @@ export declare type OAuthTokenResponse = { export declare type TypeInput = { providerConfigs: ProviderConfigInput[]; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { providerConfigs: ProviderConfigInput[]; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type RecipeInterface = { - getProviderConfig(input: { clientId: string; userContext: UserContext }): Promise; + getProviderConfig(input: { + clientId: string; + userContext: UserContext; + }): Promise; signIn(input: { userId: string; oAuthTokens: OAuthTokens; @@ -113,43 +110,35 @@ export declare type APIOptions = { appInfo: NormalisedAppinfo; }; export declare type APIInterface = { - signInPOST: ( - input: { - tenantId: string; - clientId?: string; - options: APIOptions; - userContext: UserContext; - } & ( - | { - redirectURIInfo: { - redirectURI: string; - redirectURIQueryParams: any; - pkceCodeVerifier?: string; - }; - } - | { - oAuthTokens: { - [key: string]: any; - }; - } - ) - ) => Promise< - | { - status: "OK"; - user: User; - session: SessionContainerInterface; - oAuthTokens: { - [key: string]: any; - }; - rawUserInfo: { - fromIdTokenPayload?: { - [key: string]: any; - }; - fromUserInfoAPI?: { - [key: string]: any; - }; - }; - } - | GeneralErrorResponse - >; + signInPOST: (input: { + tenantId: string; + clientId?: string; + options: APIOptions; + userContext: UserContext; + } & ({ + redirectURIInfo: { + redirectURI: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string; + }; + } | { + oAuthTokens: { + [key: string]: any; + }; + })) => Promise<{ + status: "OK"; + user: User; + session: SessionContainerInterface; + oAuthTokens: { + [key: string]: any; + }; + rawUserInfo: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + } | GeneralErrorResponse>; }; diff --git a/lib/build/recipe/oauth2client/utils.d.ts b/lib/build/recipe/oauth2client/utils.d.ts index 6a930e641..5e9f5c6dc 100644 --- a/lib/build/recipe/oauth2client/utils.d.ts +++ b/lib/build/recipe/oauth2client/utils.d.ts @@ -1,7 +1,4 @@ // @ts-nocheck import { NormalisedAppinfo } from "../../types"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput( - _appInfo: NormalisedAppinfo, - config: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(_appInfo: NormalisedAppinfo, config: TypeInput): TypeNormalisedInput; diff --git a/lib/build/recipe/oauth2client/utils.js b/lib/build/recipe/oauth2client/utils.js index 25e759254..1f2287985 100644 --- a/lib/build/recipe/oauth2client/utils.js +++ b/lib/build/recipe/oauth2client/utils.js @@ -23,20 +23,12 @@ function validateAndNormaliseUserInput(_appInfo, config) { throw new Error("Please pass clientId for all providerConfigs."); } if (!config.providerConfigs.every((providerConfig) => providerConfig.clientId.startsWith("stcl_"))) { - throw new Error( - `Only Supertokens OAuth ClientIds are supported in the OAuth2Client recipe. For any other OAuth Clients use the ThirdParty recipe.` - ); + throw new Error(`Only Supertokens OAuth ClientIds are supported in the OAuth2Client recipe. For any other OAuth Clients use the ThirdParty recipe.`); } if (config.providerConfigs.some((providerConfig) => providerConfig.oidcDiscoveryEndpoint === undefined)) { throw new Error("Please pass oidcDiscoveryEndpoint for all providerConfigs."); } - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { providerConfigs: config.providerConfigs, override, diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts index 7ffba30b1..3d5d6c0a6 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts +++ b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts @@ -139,33 +139,6 @@ export declare class OAuth2Client { * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. */ metadata: Record; - constructor({ - clientId, - clientSecret, - clientName, - scope, - redirectUris, - postLogoutRedirectUris, - authorizationCodeGrantAccessTokenLifespan, - authorizationCodeGrantIdTokenLifespan, - authorizationCodeGrantRefreshTokenLifespan, - clientCredentialsGrantAccessTokenLifespan, - implicitGrantAccessTokenLifespan, - implicitGrantIdTokenLifespan, - refreshTokenGrantAccessTokenLifespan, - refreshTokenGrantIdTokenLifespan, - refreshTokenGrantRefreshTokenLifespan, - tokenEndpointAuthMethod, - clientUri, - audience, - grantTypes, - responseTypes, - logoUri, - policyUri, - tosUri, - createdAt, - updatedAt, - metadata, - }: OAuth2ClientOptions); + constructor({ clientId, clientSecret, clientName, scope, redirectUris, postLogoutRedirectUris, authorizationCodeGrantAccessTokenLifespan, authorizationCodeGrantIdTokenLifespan, authorizationCodeGrantRefreshTokenLifespan, clientCredentialsGrantAccessTokenLifespan, implicitGrantAccessTokenLifespan, implicitGrantIdTokenLifespan, refreshTokenGrantAccessTokenLifespan, refreshTokenGrantIdTokenLifespan, refreshTokenGrantRefreshTokenLifespan, tokenEndpointAuthMethod, clientUri, audience, grantTypes, responseTypes, logoUri, policyUri, tosUri, createdAt, updatedAt, metadata, }: OAuth2ClientOptions); static fromAPIResponse(response: any): OAuth2Client; } diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.js b/lib/build/recipe/oauth2provider/OAuth2Client.js index 4c700f04f..d247b4fea 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.js +++ b/lib/build/recipe/oauth2provider/OAuth2Client.js @@ -17,34 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuth2Client = void 0; const utils_1 = require("../../utils"); class OAuth2Client { - constructor({ - clientId, - clientSecret, - clientName, - scope, - redirectUris = null, - postLogoutRedirectUris, - authorizationCodeGrantAccessTokenLifespan = null, - authorizationCodeGrantIdTokenLifespan = null, - authorizationCodeGrantRefreshTokenLifespan = null, - clientCredentialsGrantAccessTokenLifespan = null, - implicitGrantAccessTokenLifespan = null, - implicitGrantIdTokenLifespan = null, - refreshTokenGrantAccessTokenLifespan = null, - refreshTokenGrantIdTokenLifespan = null, - refreshTokenGrantRefreshTokenLifespan = null, - tokenEndpointAuthMethod, - clientUri = "", - audience = [], - grantTypes = null, - responseTypes = null, - logoUri = "", - policyUri = "", - tosUri = "", - createdAt, - updatedAt, - metadata = {}, - }) { + constructor({ clientId, clientSecret, clientName, scope, redirectUris = null, postLogoutRedirectUris, authorizationCodeGrantAccessTokenLifespan = null, authorizationCodeGrantIdTokenLifespan = null, authorizationCodeGrantRefreshTokenLifespan = null, clientCredentialsGrantAccessTokenLifespan = null, implicitGrantAccessTokenLifespan = null, implicitGrantIdTokenLifespan = null, refreshTokenGrantAccessTokenLifespan = null, refreshTokenGrantIdTokenLifespan = null, refreshTokenGrantRefreshTokenLifespan = null, tokenEndpointAuthMethod, clientUri = "", audience = [], grantTypes = null, responseTypes = null, logoUri = "", policyUri = "", tosUri = "", createdAt, updatedAt, metadata = {}, }) { /** * Metadata - JSON object * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. diff --git a/lib/build/recipe/oauth2provider/api/auth.d.ts b/lib/build/recipe/oauth2provider/api/auth.d.ts index 059876918..bd8b4a391 100644 --- a/lib/build/recipe/oauth2provider/api/auth.d.ts +++ b/lib/build/recipe/oauth2provider/api/auth.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function authGET( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function authGET(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/auth.js b/lib/build/recipe/oauth2provider/api/auth.js index b5c0e4afc..16c4b64f4 100644 --- a/lib/build/recipe/oauth2provider/api/auth.js +++ b/lib/build/recipe/oauth2provider/api/auth.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const set_cookie_parser_1 = __importDefault(require("set-cookie-parser")); @@ -35,11 +33,13 @@ async function authGET(apiImplementation, options, userContext) { try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); shouldTryRefresh = false; - } catch (error) { + } + catch (error) { session = undefined; if (error_1.default.isErrorFromSuperTokens(error) && error.type === error_1.default.TRY_REFRESH_TOKEN) { shouldTryRefresh = true; - } else { + } + else { // This should generally not happen, but we can handle this as if the session is not present, // because then we redirect to the frontend, which should handle the validation error shouldTryRefresh = false; @@ -58,25 +58,18 @@ async function authGET(apiImplementation, options, userContext) { const cookieStr = set_cookie_parser_1.default.splitCookiesString(response.cookies); const cookies = set_cookie_parser_1.default.parse(cookieStr); for (const cookie of cookies) { - options.res.setCookie( - cookie.name, - cookie.value, - cookie.domain, - !!cookie.secure, - !!cookie.httpOnly, - new Date(cookie.expires).getTime(), - cookie.path || "/", - cookie.sameSite - ); + options.res.setCookie(cookie.name, cookie.value, cookie.domain, !!cookie.secure, !!cookie.httpOnly, new Date(cookie.expires).getTime(), cookie.path || "/", cookie.sameSite); } } options.res.original.redirect(response.redirectTo); - } else if ("statusCode" in response) { + } + else if ("statusCode" in response) { utils_1.sendNon200Response(options.res, (_a = response.statusCode) !== null && _a !== void 0 ? _a : 400, { error: response.error, error_description: response.errorDescription, }); - } else { + } + else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/endSession.d.ts b/lib/build/recipe/oauth2provider/api/endSession.d.ts index 1f454cbd0..0f161e541 100644 --- a/lib/build/recipe/oauth2provider/api/endSession.d.ts +++ b/lib/build/recipe/oauth2provider/api/endSession.d.ts @@ -1,13 +1,5 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export declare function endSessionGET( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; -export declare function endSessionPOST( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export declare function endSessionGET(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export declare function endSessionPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/endSession.js b/lib/build/recipe/oauth2provider/api/endSession.js index f0d1da3b2..eb16ff12a 100644 --- a/lib/build/recipe/oauth2provider/api/endSession.js +++ b/lib/build/recipe/oauth2provider/api/endSession.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.endSessionPOST = exports.endSessionGET = void 0; const utils_1 = require("../../../utils"); @@ -31,12 +29,7 @@ async function endSessionGET(apiImplementation, options, userContext) { const origURL = options.req.getOriginalURL(); const splitURL = origURL.split("?"); const params = new URLSearchParams(splitURL[1]); - return endSessionCommon( - Object.fromEntries(params.entries()), - apiImplementation.endSessionGET, - options, - userContext - ); + return endSessionCommon(Object.fromEntries(params.entries()), apiImplementation.endSessionGET, options, userContext); } exports.endSessionGET = endSessionGET; async function endSessionPOST(apiImplementation, options, userContext) { @@ -56,13 +49,15 @@ async function endSessionCommon(params, apiImplementation, options, userContext) try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); shouldTryRefresh = false; - } catch (error) { + } + catch (error) { // We can handle this as if the session is not present, because then we redirect to the frontend, // which should handle the validation error session = undefined; if (error_1.default.isErrorFromSuperTokens(error) && error.type === error_2.default.TRY_REFRESH_TOKEN) { shouldTryRefresh = true; - } else { + } + else { shouldTryRefresh = false; } } @@ -75,12 +70,14 @@ async function endSessionCommon(params, apiImplementation, options, userContext) }); if ("redirectTo" in response) { options.res.original.redirect(response.redirectTo); - } else if ("error" in response) { + } + else if ("error" in response) { utils_1.sendNon200Response(options.res, (_a = response.statusCode) !== null && _a !== void 0 ? _a : 400, { error: response.error, error_description: response.errorDescription, }); - } else { + } + else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index fa4232d17..0bf97f712 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -109,14 +109,16 @@ function getAPIImplementation() { authorizationHeader: input.authorizationHeader, userContext: input.userContext, }); - } else if ("clientId" in input && input.clientId !== undefined) { + } + else if ("clientId" in input && input.clientId !== undefined) { return input.options.recipeImplementation.revokeToken({ token: input.token, clientId: input.clientId, clientSecret: input.clientSecret, userContext: input.userContext, }); - } else { + } + else { throw new Error(`Either of 'authorizationHeader' or 'clientId' must be provided`); } }, diff --git a/lib/build/recipe/oauth2provider/api/introspectToken.d.ts b/lib/build/recipe/oauth2provider/api/introspectToken.d.ts index 3d2972c0d..135880d83 100644 --- a/lib/build/recipe/oauth2provider/api/introspectToken.d.ts +++ b/lib/build/recipe/oauth2provider/api/introspectToken.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function introspectTokenPOST( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function introspectTokenPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/login.d.ts b/lib/build/recipe/oauth2provider/api/login.d.ts index 6f4253cef..1a1417232 100644 --- a/lib/build/recipe/oauth2provider/api/login.d.ts +++ b/lib/build/recipe/oauth2provider/api/login.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function login( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function login(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/login.js b/lib/build/recipe/oauth2provider/api/login.js index 186337139..d507416c0 100644 --- a/lib/build/recipe/oauth2provider/api/login.js +++ b/lib/build/recipe/oauth2provider/api/login.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const set_cookie_parser_1 = __importDefault(require("set-cookie-parser")); const utils_1 = require("../../../utils"); @@ -33,20 +31,19 @@ async function login(apiImplementation, options, userContext) { try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); shouldTryRefresh = false; - } catch (error) { + } + catch (error) { // We can handle this as if the session is not present, because then we redirect to the frontend, // which should handle the validation error session = undefined; if (error_1.default.isErrorFromSuperTokens(error) && error.type === error_2.default.TRY_REFRESH_TOKEN) { shouldTryRefresh = true; - } else { + } + else { shouldTryRefresh = false; } } - const loginChallenge = - (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 - ? _a - : options.req.getKeyValueFromQuery("loginChallenge"); + const loginChallenge = (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 ? _a : options.req.getKeyValueFromQuery("loginChallenge"); if (loginChallenge === undefined) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, @@ -65,27 +62,20 @@ async function login(apiImplementation, options, userContext) { const cookieStr = set_cookie_parser_1.default.splitCookiesString(response.cookies); const cookies = set_cookie_parser_1.default.parse(cookieStr); for (const cookie of cookies) { - options.res.setCookie( - cookie.name, - cookie.value, - cookie.domain, - !!cookie.secure, - !!cookie.httpOnly, - new Date(cookie.expires).getTime(), - cookie.path || "/", - cookie.sameSite - ); + options.res.setCookie(cookie.name, cookie.value, cookie.domain, !!cookie.secure, !!cookie.httpOnly, new Date(cookie.expires).getTime(), cookie.path || "/", cookie.sameSite); } } utils_1.send200Response(options.res, { frontendRedirectTo: response.frontendRedirectTo, }); - } else if ("statusCode" in response) { + } + else if ("statusCode" in response) { utils_1.sendNon200Response(options.res, (_b = response.statusCode) !== null && _b !== void 0 ? _b : 400, { error: response.error, error_description: response.errorDescription, }); - } else { + } + else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/loginInfo.d.ts b/lib/build/recipe/oauth2provider/api/loginInfo.d.ts index 536858263..e9817552e 100644 --- a/lib/build/recipe/oauth2provider/api/loginInfo.d.ts +++ b/lib/build/recipe/oauth2provider/api/loginInfo.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function loginInfoGET( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function loginInfoGET(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/loginInfo.js b/lib/build/recipe/oauth2provider/api/loginInfo.js index 15b9da808..e0f5be6c4 100644 --- a/lib/build/recipe/oauth2provider/api/loginInfo.js +++ b/lib/build/recipe/oauth2provider/api/loginInfo.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../../../error")); @@ -26,10 +24,7 @@ async function loginInfoGET(apiImplementation, options, userContext) { if (apiImplementation.loginInfoGET === undefined) { return false; } - const loginChallenge = - (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 - ? _a - : options.req.getKeyValueFromQuery("loginChallenge"); + const loginChallenge = (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 ? _a : options.req.getKeyValueFromQuery("loginChallenge"); if (loginChallenge === undefined) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, diff --git a/lib/build/recipe/oauth2provider/api/logout.d.ts b/lib/build/recipe/oauth2provider/api/logout.d.ts index 339a73e77..e53197994 100644 --- a/lib/build/recipe/oauth2provider/api/logout.d.ts +++ b/lib/build/recipe/oauth2provider/api/logout.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export declare function logoutPOST( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export declare function logoutPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/logout.js b/lib/build/recipe/oauth2provider/api/logout.js index 16a51b511..0f4e3c295 100644 --- a/lib/build/recipe/oauth2provider/api/logout.js +++ b/lib/build/recipe/oauth2provider/api/logout.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.logoutPOST = void 0; const utils_1 = require("../../../utils"); @@ -31,7 +29,8 @@ async function logoutPOST(apiImplementation, options, userContext) { let session; try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); - } catch (_b) { + } + catch (_b) { session = undefined; } const body = await options.req.getBodyAsJSONOrFormData(); @@ -49,12 +48,14 @@ async function logoutPOST(apiImplementation, options, userContext) { }); if ("status" in response && response.status === "OK") { utils_1.send200Response(options.res, response); - } else if ("statusCode" in response) { + } + else if ("statusCode" in response) { utils_1.sendNon200Response(options.res, (_a = response.statusCode) !== null && _a !== void 0 ? _a : 400, { error: response.error, error_description: response.errorDescription, }); - } else { + } + else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/revokeToken.d.ts b/lib/build/recipe/oauth2provider/api/revokeToken.d.ts index 902e734d5..d79a68c45 100644 --- a/lib/build/recipe/oauth2provider/api/revokeToken.d.ts +++ b/lib/build/recipe/oauth2provider/api/revokeToken.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function revokeTokenPOST( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function revokeTokenPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/revokeToken.js b/lib/build/recipe/oauth2provider/api/revokeToken.js index 7a190ae5d..86437176e 100644 --- a/lib/build/recipe/oauth2provider/api/revokeToken.js +++ b/lib/build/recipe/oauth2provider/api/revokeToken.js @@ -27,11 +27,7 @@ async function revokeTokenPOST(apiImplementation, options, userContext) { } const authorizationHeader = options.req.getHeaderValue("authorization"); if (authorizationHeader !== undefined && (body.client_id !== undefined || body.client_secret !== undefined)) { - utils_1.sendNon200ResponseWithMessage( - options.res, - "Only one of authorization header or client_id and client_secret can be provided", - 400 - ); + utils_1.sendNon200ResponseWithMessage(options.res, "Only one of authorization header or client_id and client_secret can be provided", 400); return true; } let response = await apiImplementation.revokeTokenPOST({ @@ -47,7 +43,8 @@ async function revokeTokenPOST(apiImplementation, options, userContext) { error: response.error, error_description: response.errorDescription, }); - } else { + } + else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/token.d.ts b/lib/build/recipe/oauth2provider/api/token.d.ts index c697b7744..68d95e24e 100644 --- a/lib/build/recipe/oauth2provider/api/token.d.ts +++ b/lib/build/recipe/oauth2provider/api/token.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function tokenPOST( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function tokenPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/token.js b/lib/build/recipe/oauth2provider/api/token.js index 9aaa0bd38..8f1b3274f 100644 --- a/lib/build/recipe/oauth2provider/api/token.js +++ b/lib/build/recipe/oauth2provider/api/token.js @@ -32,7 +32,8 @@ async function tokenPOST(apiImplementation, options, userContext) { error: response.error, error_description: response.errorDescription, }); - } else { + } + else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/userInfo.d.ts b/lib/build/recipe/oauth2provider/api/userInfo.d.ts index d0b8cdf4e..92f0a98be 100644 --- a/lib/build/recipe/oauth2provider/api/userInfo.d.ts +++ b/lib/build/recipe/oauth2provider/api/userInfo.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function userInfoGET( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function userInfoGET(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/oauth2provider/api/userInfo.js b/lib/build/recipe/oauth2provider/api/userInfo.js index cfa8f704b..9d39b3549 100644 --- a/lib/build/recipe/oauth2provider/api/userInfo.js +++ b/lib/build/recipe/oauth2provider/api/userInfo.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../recipe")); const utils_1 = require("../../../utils"); @@ -34,25 +32,22 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { const accessToken = authHeader.replace(/^Bearer /, "").trim(); let accessTokenPayload; try { - const { - payload, - } = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ + const { payload, } = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ token: accessToken, userContext, }); accessTokenPayload = payload; - } catch (error) { + } + catch (error) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); utils_1.sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 401); return true; } - if ( - accessTokenPayload === null || + if (accessTokenPayload === null || typeof accessTokenPayload !== "object" || typeof accessTokenPayload.sub !== "string" || - !Array.isArray(accessTokenPayload.scp) - ) { + !Array.isArray(accessTokenPayload.scp)) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); utils_1.sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 401); @@ -63,11 +58,7 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { if (user === undefined) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); - utils_1.sendNon200ResponseWithMessage( - options.res, - "Couldn't find any user associated with the access token", - 401 - ); + utils_1.sendNon200ResponseWithMessage(options.res, "Couldn't find any user associated with the access token", 401); return true; } const response = await apiImplementation.userInfoGET({ diff --git a/lib/build/recipe/oauth2provider/api/utils.d.ts b/lib/build/recipe/oauth2provider/api/utils.d.ts index 1bf243b1a..acc1c2a03 100644 --- a/lib/build/recipe/oauth2provider/api/utils.d.ts +++ b/lib/build/recipe/oauth2provider/api/utils.d.ts @@ -2,15 +2,7 @@ import { UserContext } from "../../../types"; import { SessionContainerInterface } from "../../session/types"; import { ErrorOAuth2, RecipeInterface } from "../types"; -export declare function loginGET({ - recipeImplementation, - loginChallenge, - shouldTryRefresh, - session, - cookies, - isDirectCall, - userContext, -}: { +export declare function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh, session, cookies, isDirectCall, userContext, }: { recipeImplementation: RecipeInterface; loginChallenge: string; session?: SessionContainerInterface; @@ -18,27 +10,16 @@ export declare function loginGET({ cookies?: string; userContext: UserContext; isDirectCall: boolean; -}): Promise< - | ErrorOAuth2 - | { - status: string; - redirectTo: string; - cookies: string | undefined; - } - | { - redirectTo: string; - cookies: string | undefined; - status?: undefined; - } ->; -export declare function handleLoginInternalRedirects({ - response, - recipeImplementation, - session, - shouldTryRefresh, - cookie, - userContext, -}: { +}): Promise; +export declare function handleLoginInternalRedirects({ response, recipeImplementation, session, shouldTryRefresh, cookie, userContext, }: { response: { redirectTo: string; cookies?: string; @@ -48,28 +29,17 @@ export declare function handleLoginInternalRedirects({ shouldTryRefresh: boolean; cookie?: string; userContext: UserContext; -}): Promise< - | { - redirectTo: string; - cookies?: string; - } - | ErrorOAuth2 ->; -export declare function handleLogoutInternalRedirects({ - response, - recipeImplementation, - session, - userContext, -}: { +}): Promise<{ + redirectTo: string; + cookies?: string; +} | ErrorOAuth2>; +export declare function handleLogoutInternalRedirects({ response, recipeImplementation, session, userContext, }: { response: { redirectTo: string; }; recipeImplementation: RecipeInterface; session?: SessionContainerInterface; userContext: UserContext; -}): Promise< - | { - redirectTo: string; - } - | ErrorOAuth2 ->; +}): Promise<{ + redirectTo: string; +} | ErrorOAuth2>; diff --git a/lib/build/recipe/oauth2provider/api/utils.js b/lib/build/recipe/oauth2provider/api/utils.js index 64d5fe32d..1fd0e53ce 100644 --- a/lib/build/recipe/oauth2provider/api/utils.js +++ b/lib/build/recipe/oauth2provider/api/utils.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.handleLogoutInternalRedirects = exports.handleLoginInternalRedirects = exports.loginGET = void 0; const supertokens_1 = __importDefault(require("../../../supertokens")); @@ -13,15 +11,7 @@ const constants_2 = require("../constants"); const set_cookie_parser_1 = __importDefault(require("set-cookie-parser")); // API implementation for the loginGET function. // Extracted for use in both apiImplementation and handleInternalRedirects. -async function loginGET({ - recipeImplementation, - loginChallenge, - shouldTryRefresh, - session, - cookies, - isDirectCall, - userContext, -}) { +async function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh, session, cookies, isDirectCall, userContext, }) { var _a, _b; const loginRequest = await recipeImplementation.getLoginRequest({ challenge: loginChallenge, @@ -30,20 +20,12 @@ async function loginGET({ if (loginRequest.status === "ERROR") { return loginRequest; } - const sessionInfo = - session !== undefined - ? await session_1.getSessionInformation( - session === null || session === void 0 ? void 0 : session.getHandle() - ) - : undefined; + const sessionInfo = session !== undefined ? await session_1.getSessionInformation(session === null || session === void 0 ? void 0 : session.getHandle()) : undefined; if (!sessionInfo) { session = undefined; } const incomingAuthUrlQueryParams = new URLSearchParams(loginRequest.requestUrl.split("?")[1]); - const promptParam = - (_a = incomingAuthUrlQueryParams.get("prompt")) !== null && _a !== void 0 - ? _a - : incomingAuthUrlQueryParams.get("st_prompt"); + const promptParam = (_a = incomingAuthUrlQueryParams.get("prompt")) !== null && _a !== void 0 ? _a : incomingAuthUrlQueryParams.get("st_prompt"); const maxAgeParam = incomingAuthUrlQueryParams.get("max_age"); if (maxAgeParam !== null) { try { @@ -72,7 +54,8 @@ async function loginGET({ }); return { status: "REDIRECT", redirectTo: reject.redirectTo, cookies }; } - } catch (_c) { + } + catch (_c) { const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, error: { @@ -86,15 +69,13 @@ async function loginGET({ } } const tenantIdParam = incomingAuthUrlQueryParams.get("tenant_id"); - if ( - session && + if (session && (["", undefined].includes(loginRequest.subject) || session.getUserId() === loginRequest.subject) && (["", null].includes(tenantIdParam) || session.getTenantId() === tenantIdParam) && (promptParam !== "login" || isDirectCall) && (maxAgeParam === null || (maxAgeParam === "0" && isDirectCall) || - Number.parseInt(maxAgeParam) * 1000 > Date.now() - sessionInfo.timeCreated) - ) { + Number.parseInt(maxAgeParam) * 1000 > Date.now() - sessionInfo.timeCreated)) { const accept = await recipeImplementation.acceptLoginRequest({ challenge: loginChallenge, subject: session.getUserId(), @@ -119,8 +100,7 @@ async function loginGET({ error: { status: "ERROR", error: "login_required", - errorDescription: - "The Authorization Server requires End-User authentication. Prompt 'none' was requested, but no existing or expired login session was found.", + errorDescription: "The Authorization Server requires End-User authentication. Prompt 'none' was requested, but no existing or expired login session was found.", }, userContext, }); @@ -132,8 +112,7 @@ async function loginGET({ type: "login", loginChallenge, forceFreshAuth: session !== undefined || promptParam === "login", - tenantId: - tenantIdParam !== null && tenantIdParam !== void 0 ? tenantIdParam : constants_1.DEFAULT_TENANT_ID, + tenantId: tenantIdParam !== null && tenantIdParam !== void 0 ? tenantIdParam : constants_1.DEFAULT_TENANT_ID, hint: (_b = loginRequest.oidcContext) === null || _b === void 0 ? void 0 : _b.login_hint, userContext, }), @@ -153,7 +132,8 @@ function getMergedCookies({ origCookies = "", newCookies }) { for (const { name, value, expires } of setCookies) { if (expires && new Date(expires) < new Date()) { delete cookieMap[name]; - } else { + } + else { cookieMap[name] = value; } } @@ -183,14 +163,7 @@ function isLogoutInternalRedirect(redirectTo) { // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. // Currently, we only need to handle redirects to /oauth/login and /oauth/auth endpoints in the login flow. -async function handleLoginInternalRedirects({ - response, - recipeImplementation, - session, - shouldTryRefresh, - cookie = "", - userContext, -}) { +async function handleLoginInternalRedirects({ response, recipeImplementation, session, shouldTryRefresh, cookie = "", userContext, }) { var _a; if (!isLoginInternalRedirect(response.redirectTo)) { return response; @@ -204,8 +177,7 @@ async function handleLoginInternalRedirects({ const queryString = response.redirectTo.split("?")[1]; const params = new URLSearchParams(queryString); if (response.redirectTo.includes(constants_2.LOGIN_PATH)) { - const loginChallenge = - (_a = params.get("login_challenge")) !== null && _a !== void 0 ? _a : params.get("loginChallenge"); + const loginChallenge = (_a = params.get("login_challenge")) !== null && _a !== void 0 ? _a : params.get("loginChallenge"); if (!loginChallenge) { throw new Error(`Expected loginChallenge in ${response.redirectTo}`); } @@ -225,7 +197,8 @@ async function handleLoginInternalRedirects({ redirectTo: loginRes.redirectTo, cookies: mergeSetCookieHeaders(loginRes.cookies, response.cookies), }; - } else if (response.redirectTo.includes(constants_2.AUTH_PATH)) { + } + else if (response.redirectTo.includes(constants_2.AUTH_PATH)) { const authRes = await recipeImplementation.authorization({ params: Object.fromEntries(params.entries()), cookies: cookie, @@ -239,7 +212,8 @@ async function handleLoginInternalRedirects({ redirectTo: authRes.redirectTo, cookies: mergeSetCookieHeaders(authRes.cookies, response.cookies), }; - } else { + } + else { throw new Error(`Unexpected internal redirect ${response.redirectTo}`); } redirectCount++; @@ -250,7 +224,7 @@ exports.handleLoginInternalRedirects = handleLoginInternalRedirects; // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. // Currently, we only need to handle redirects to /oauth/end_session endpoint in the logout flow. -async function handleLogoutInternalRedirects({ response, recipeImplementation, session, userContext }) { +async function handleLogoutInternalRedirects({ response, recipeImplementation, session, userContext, }) { if (!isLogoutInternalRedirect(response.redirectTo)) { return response; } @@ -275,7 +249,8 @@ async function handleLogoutInternalRedirects({ response, recipeImplementation, s return endSessionRes; } response = endSessionRes; - } else { + } + else { throw new Error(`Unexpected internal redirect ${response.redirectTo}`); } redirectCount++; diff --git a/lib/build/recipe/oauth2provider/index.d.ts b/lib/build/recipe/oauth2provider/index.d.ts index f859cac0d..585a3bf5d 100644 --- a/lib/build/recipe/oauth2provider/index.d.ts +++ b/lib/build/recipe/oauth2provider/index.d.ts @@ -1,134 +1,67 @@ // @ts-nocheck import Recipe from "./recipe"; -import { - APIInterface, - RecipeInterface, - APIOptions, - CreateOAuth2ClientInput, - UpdateOAuth2ClientInput, - DeleteOAuth2ClientInput, - GetOAuth2ClientsInput, -} from "./types"; +import { APIInterface, RecipeInterface, APIOptions, CreateOAuth2ClientInput, UpdateOAuth2ClientInput, DeleteOAuth2ClientInput, GetOAuth2ClientsInput } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static getOAuth2Client( - clientId: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - client: import("./OAuth2Client").OAuth2Client; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - static getOAuth2Clients( - input: GetOAuth2ClientsInput, - userContext?: Record - ): Promise< - | { - status: "OK"; - clients: import("./OAuth2Client").OAuth2Client[]; - nextPaginationToken?: string | undefined; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - static createOAuth2Client( - input: CreateOAuth2ClientInput, - userContext?: Record - ): Promise< - | { - status: "OK"; - client: import("./OAuth2Client").OAuth2Client; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - static updateOAuth2Client( - input: UpdateOAuth2ClientInput, - userContext?: Record - ): Promise< - | { - status: "OK"; - client: import("./OAuth2Client").OAuth2Client; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - static deleteOAuth2Client( - input: DeleteOAuth2ClientInput, - userContext?: Record - ): Promise< - | { - status: "OK"; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - static validateOAuth2AccessToken( - token: string, - requirements?: { - clientId?: string; - scopes?: string[]; - audience?: string; - }, - checkDatabase?: boolean, - userContext?: Record - ): Promise<{ + static getOAuth2Client(clientId: string, userContext?: Record): Promise<{ + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + static getOAuth2Clients(input: GetOAuth2ClientsInput, userContext?: Record): Promise<{ + status: "OK"; + clients: import("./OAuth2Client").OAuth2Client[]; + nextPaginationToken?: string | undefined; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + static createOAuth2Client(input: CreateOAuth2ClientInput, userContext?: Record): Promise<{ + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + static updateOAuth2Client(input: UpdateOAuth2ClientInput, userContext?: Record): Promise<{ + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + static deleteOAuth2Client(input: DeleteOAuth2ClientInput, userContext?: Record): Promise<{ + status: "OK"; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + static validateOAuth2AccessToken(token: string, requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }, checkDatabase?: boolean, userContext?: Record): Promise<{ status: "OK"; payload: import("../usermetadata").JSONObject; }>; - static createTokenForClientCredentials( - clientId: string, - clientSecret: string, - scope?: string[], - audience?: string, - userContext?: Record - ): Promise; - static revokeToken( - token: string, - clientId: string, - clientSecret?: string, - userContext?: Record - ): Promise< - | import("./types").ErrorOAuth2 - | { - status: "OK"; - } - >; - static revokeTokensByClientId( - clientId: string, - userContext?: Record - ): Promise<{ + static createTokenForClientCredentials(clientId: string, clientSecret: string, scope?: string[], audience?: string, userContext?: Record): Promise; + static revokeToken(token: string, clientId: string, clientSecret?: string, userContext?: Record): Promise; + static revokeTokensByClientId(clientId: string, userContext?: Record): Promise<{ status: "OK"; }>; - static revokeTokensBySessionHandle( - sessionHandle: string, - userContext?: Record - ): Promise<{ + static revokeTokensBySessionHandle(sessionHandle: string, userContext?: Record): Promise<{ status: "OK"; }>; - static validateOAuth2RefreshToken( - token: string, - scopes?: string[], - userContext?: Record - ): Promise; + static validateOAuth2RefreshToken(token: string, scopes?: string[], userContext?: Record): Promise; } export declare let init: typeof Recipe.init; export declare let getOAuth2Client: typeof Wrapper.getOAuth2Client; diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index ee4d9b5ed..656ec45f8 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.revokeTokensBySessionHandle = exports.revokeTokensByClientId = exports.revokeToken = exports.createTokenForClientCredentials = exports.validateOAuth2RefreshToken = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.getOAuth2Client = exports.init = void 0; const utils_1 = require("../../utils"); @@ -30,32 +28,16 @@ class Wrapper { }); } static async getOAuth2Clients(input, userContext) { - return await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.getOAuth2Clients( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) - ); + return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Clients(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) })); } static async createOAuth2Client(input, userContext) { - return await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.createOAuth2Client( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) - ); + return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) })); } static async updateOAuth2Client(input, userContext) { - return await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.updateOAuth2Client( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) - ); + return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.updateOAuth2Client(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) })); } static async deleteOAuth2Client(input, userContext) { - return await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.deleteOAuth2Client( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) - ); + return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.deleteOAuth2Client(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) })); } static validateOAuth2AccessToken(token, requirements, checkDatabase, userContext) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ @@ -88,7 +70,8 @@ class Wrapper { const { tokenEndpointAuthMethod } = res.client; if (tokenEndpointAuthMethod === "none") { authorizationHeader = "Basic " + Buffer.from(clientId + ":").toString("base64"); - } else if (tokenEndpointAuthMethod === "client_secret_basic") { + } + else if (tokenEndpointAuthMethod === "client_secret_basic") { authorizationHeader = "Basic " + Buffer.from(clientId + ":" + clientSecret).toString("base64"); } if (authorizationHeader !== undefined) { diff --git a/lib/build/recipe/oauth2provider/recipe.d.ts b/lib/build/recipe/oauth2provider/recipe.d.ts index d2739dbf7..8948a0c20 100644 --- a/lib/build/recipe/oauth2provider/recipe.d.ts +++ b/lib/build/recipe/oauth2provider/recipe.d.ts @@ -4,15 +4,7 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import NormalisedURLPath from "../../normalisedURLPath"; import RecipeModule from "../../recipeModule"; import { APIHandled, HTTPMethod, JSONObject, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; -import { - APIInterface, - PayloadBuilderFunction, - RecipeInterface, - TypeInput, - TypeNormalisedInput, - UserInfo, - UserInfoBuilderFunction, -} from "./types"; +import { APIInterface, PayloadBuilderFunction, RecipeInterface, TypeInput, TypeNormalisedInput, UserInfo, UserInfoBuilderFunction } from "./types"; import { User } from "../../user"; export default class Recipe extends RecipeModule { static RECIPE_ID: string; @@ -33,35 +25,11 @@ export default class Recipe extends RecipeModule { addAccessTokenBuilderFromOtherRecipe: (accessTokenBuilders: PayloadBuilderFunction) => void; addIdTokenBuilderFromOtherRecipe: (idTokenBuilder: PayloadBuilderFunction) => void; getAPIsHandled(): APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - _path: NormalisedURLPath, - method: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; - getDefaultAccessTokenPayload( - user: User, - scopes: string[], - sessionHandle: string, - userContext: UserContext - ): Promise; - getDefaultIdTokenPayload( - user: User, - scopes: string[], - sessionHandle: string, - userContext: UserContext - ): Promise; - getDefaultUserInfoPayload( - user: User, - accessTokenPayload: JSONObject, - scopes: string[], - tenantId: string, - userContext: UserContext - ): Promise; + getDefaultAccessTokenPayload(user: User, scopes: string[], sessionHandle: string, userContext: UserContext): Promise; + getDefaultIdTokenPayload(user: User, scopes: string[], sessionHandle: string, userContext: UserContext): Promise; + getDefaultUserInfoPayload(user: User, accessTokenPayload: JSONObject, scopes: string[], tenantId: string, userContext: UserContext): Promise; } diff --git a/lib/build/recipe/oauth2provider/recipe.js b/lib/build/recipe/oauth2provider/recipe.js index 4c31ae8cc..a8b1c1020 100644 --- a/lib/build/recipe/oauth2provider/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -97,16 +95,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default( - querier_1.Querier.getNewInstanceOrThrowError(recipeId), - this.config, - appInfo, - this.getDefaultAccessTokenPayload.bind(this), - this.getDefaultIdTokenPayload.bind(this), - this.getDefaultUserInfoPayload.bind(this) - ) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo, this.getDefaultAccessTokenPayload.bind(this), this.getDefaultIdTokenPayload.bind(this), this.getDefaultUserInfoPayload.bind(this))); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -129,7 +118,8 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } else { + } + else { throw new Error("OAuth2Provider recipe has already been initialised. Please check your code for bugs."); } }; @@ -218,7 +208,7 @@ class Recipe extends recipeModule_1.default { async getDefaultAccessTokenPayload(user, scopes, sessionHandle, userContext) { let payload = {}; for (const fn of this.accessTokenBuilders) { - payload = Object.assign(Object.assign({}, payload), await fn(user, scopes, sessionHandle, userContext)); + payload = Object.assign(Object.assign({}, payload), (await fn(user, scopes, sessionHandle, userContext))); } return payload; } @@ -226,22 +216,16 @@ class Recipe extends recipeModule_1.default { let payload = {}; if (scopes.includes("email")) { payload.email = user === null || user === void 0 ? void 0 : user.emails[0]; - payload.email_verified = user.loginMethods.some( - (lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified - ); + payload.email_verified = user.loginMethods.some((lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified); payload.emails = user.emails; } if (scopes.includes("phoneNumber")) { payload.phoneNumber = user === null || user === void 0 ? void 0 : user.phoneNumbers[0]; - payload.phoneNumber_verified = user.loginMethods.some( - (lm) => - lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && - lm.verified - ); + payload.phoneNumber_verified = user.loginMethods.some((lm) => lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && lm.verified); payload.phoneNumbers = user.phoneNumbers; } for (const fn of this.idTokenBuilders) { - payload = Object.assign(Object.assign({}, payload), await fn(user, scopes, sessionHandle, userContext)); + payload = Object.assign(Object.assign({}, payload), (await fn(user, scopes, sessionHandle, userContext))); } return payload; } @@ -252,25 +236,16 @@ class Recipe extends recipeModule_1.default { if (scopes.includes("email")) { // TODO: try and get the email based on the user id of the entire user object payload.email = user === null || user === void 0 ? void 0 : user.emails[0]; - payload.email_verified = user.loginMethods.some( - (lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified - ); + payload.email_verified = user.loginMethods.some((lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified); payload.emails = user.emails; } if (scopes.includes("phoneNumber")) { payload.phoneNumber = user === null || user === void 0 ? void 0 : user.phoneNumbers[0]; - payload.phoneNumber_verified = user.loginMethods.some( - (lm) => - lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && - lm.verified - ); + payload.phoneNumber_verified = user.loginMethods.some((lm) => lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && lm.verified); payload.phoneNumbers = user.phoneNumbers; } for (const fn of this.userInfoBuilders) { - payload = Object.assign( - Object.assign({}, payload), - await fn(user, accessTokenPayload, scopes, tenantId, userContext) - ); + payload = Object.assign(Object.assign({}, payload), (await fn(user, accessTokenPayload, scopes, tenantId, userContext))); } return payload; } diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.d.ts b/lib/build/recipe/oauth2provider/recipeImplementation.d.ts index 4ecaeef69..571e44ab4 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.d.ts +++ b/lib/build/recipe/oauth2provider/recipeImplementation.d.ts @@ -2,11 +2,4 @@ import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; import { RecipeInterface, TypeNormalisedInput, PayloadBuilderFunction, UserInfoBuilderFunction } from "./types"; -export default function getRecipeInterface( - querier: Querier, - _config: TypeNormalisedInput, - appInfo: NormalisedAppinfo, - getDefaultAccessTokenPayload: PayloadBuilderFunction, - getDefaultIdTokenPayload: PayloadBuilderFunction, - getDefaultUserInfoPayload: UserInfoBuilderFunction -): RecipeInterface; +export default function getRecipeInterface(querier: Querier, _config: TypeNormalisedInput, appInfo: NormalisedAppinfo, getDefaultAccessTokenPayload: PayloadBuilderFunction, getDefaultIdTokenPayload: PayloadBuilderFunction, getDefaultUserInfoPayload: UserInfoBuilderFunction): RecipeInterface; diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index cde274874..db6b45638 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -13,47 +13,28 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const jose = __importStar(require("jose")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -64,10 +45,7 @@ const recipe_1 = __importDefault(require("../session/recipe")); const recipe_2 = __importDefault(require("../openid/recipe")); const constants_1 = require("../multitenancy/constants"); function getUpdatedRedirectTo(appInfo, redirectTo) { - return redirectTo.replace( - "{apiDomain}", - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ); + return redirectTo.replace("{apiDomain}", appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous()); } function copyAndCleanRequestBodyInput(input) { let result = Object.assign({}, input); @@ -76,21 +54,10 @@ function copyAndCleanRequestBodyInput(input) { delete result.session; return result; } -function getRecipeInterface( - querier, - _config, - appInfo, - getDefaultAccessTokenPayload, - getDefaultIdTokenPayload, - getDefaultUserInfoPayload -) { +function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayload, getDefaultIdTokenPayload, getDefaultUserInfoPayload) { return { getLoginRequest: async function (input) { - const resp = await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/oauth/auth/requests/login"), - { loginChallenge: input.challenge }, - input.userContext - ); + const resp = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/oauth/auth/requests/login"), { loginChallenge: input.challenge }, input.userContext); if (resp.status !== "OK") { return { status: "ERROR", @@ -113,48 +80,34 @@ function getRecipeInterface( }; }, acceptLoginRequest: async function (input) { - const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/accept`), - { - acr: input.acr, - amr: input.amr, - context: input.context, - extendSessionLifespan: input.extendSessionLifespan, - identityProviderSessionId: input.identityProviderSessionId, - subject: input.subject, - }, - { - loginChallenge: input.challenge, - }, - input.userContext - ); + const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/accept`), { + acr: input.acr, + amr: input.amr, + context: input.context, + extendSessionLifespan: input.extendSessionLifespan, + identityProviderSessionId: input.identityProviderSessionId, + subject: input.subject, + }, { + loginChallenge: input.challenge, + }, input.userContext); return { redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, rejectLoginRequest: async function (input) { - const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/reject`), - { - error: input.error.error, - errorDescription: input.error.errorDescription, - statusCode: input.error.statusCode, - }, - { - login_challenge: input.challenge, - }, - input.userContext - ); + const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/reject`), { + error: input.error.error, + errorDescription: input.error.errorDescription, + statusCode: input.error.statusCode, + }, { + login_challenge: input.challenge, + }, input.userContext); return { redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, getConsentRequest: async function (input) { - const resp = await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/oauth/auth/requests/consent"), - { consentChallenge: input.challenge }, - input.userContext - ); + const resp = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/oauth/auth/requests/consent"), { consentChallenge: input.challenge }, input.userContext); return { acr: resp.acr, amr: resp.amr, @@ -171,42 +124,32 @@ function getRecipeInterface( }; }, acceptConsentRequest: async function (input) { - const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/accept`), - { - context: input.context, - grantAccessTokenAudience: input.grantAccessTokenAudience, - grantScope: input.grantScope, - handledAt: input.handledAt, - iss: await recipe_2.default.getIssuer(input.userContext), - tId: input.tenantId, - rsub: input.rsub, - sessionHandle: input.sessionHandle, - initialAccessTokenPayload: input.initialAccessTokenPayload, - initialIdTokenPayload: input.initialIdTokenPayload, - }, - { - consentChallenge: input.challenge, - }, - input.userContext - ); + const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/accept`), { + context: input.context, + grantAccessTokenAudience: input.grantAccessTokenAudience, + grantScope: input.grantScope, + handledAt: input.handledAt, + iss: await recipe_2.default.getIssuer(input.userContext), + tId: input.tenantId, + rsub: input.rsub, + sessionHandle: input.sessionHandle, + initialAccessTokenPayload: input.initialAccessTokenPayload, + initialIdTokenPayload: input.initialIdTokenPayload, + }, { + consentChallenge: input.challenge, + }, input.userContext); return { redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, rejectConsentRequest: async function (input) { - const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/reject`), - { - error: input.error.error, - errorDescription: input.error.errorDescription, - statusCode: input.error.statusCode, - }, - { - consentChallenge: input.challenge, - }, - input.userContext - ); + const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/reject`), { + error: input.error.error, + errorDescription: input.error.errorDescription, + statusCode: input.error.statusCode, + }, { + consentChallenge: input.challenge, + }, input.userContext); return { redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; @@ -234,11 +177,7 @@ function getRecipeInterface( sessionHandle: (_c = input.session) === null || _c === void 0 ? void 0 : _c.getHandle(), userContext: input.userContext, }); - const responseTypes = - (_e = (_d = input.params.response_type) === null || _d === void 0 ? void 0 : _d.split(" ")) !== null && - _e !== void 0 - ? _e - : []; + const responseTypes = (_e = (_d = input.params.response_type) === null || _d === void 0 ? void 0 : _d.split(" ")) !== null && _e !== void 0 ? _e : []; if (input.session !== undefined) { const clientInfo = await this.getOAuth2Client({ clientId: input.params.client_id, @@ -264,40 +203,34 @@ function getRecipeInterface( } // These default to an empty objects, because we want to keep them as a required input // but they'll not be actually used in the flows where we are not building them. - const idToken = - scopes.includes("openid") && (responseTypes.includes("id_token") || responseTypes.includes("code")) - ? await this.buildIdTokenPayload({ - user, - client, - sessionHandle: input.session.getHandle(), - scopes, - userContext: input.userContext, - }) - : {}; - const accessToken = - responseTypes.includes("token") || responseTypes.includes("code") - ? await this.buildAccessTokenPayload({ - user, - client, - sessionHandle: input.session.getHandle(), - scopes, - userContext: input.userContext, - }) - : {}; + const idToken = scopes.includes("openid") && (responseTypes.includes("id_token") || responseTypes.includes("code")) + ? await this.buildIdTokenPayload({ + user, + client, + sessionHandle: input.session.getHandle(), + scopes, + userContext: input.userContext, + }) + : {}; + const accessToken = responseTypes.includes("token") || responseTypes.includes("code") + ? await this.buildAccessTokenPayload({ + user, + client, + sessionHandle: input.session.getHandle(), + scopes, + userContext: input.userContext, + }) + : {}; payloads = { idToken, accessToken, }; } - const resp = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/auth`), - { - params: Object.assign(Object.assign({}, input.params), { scope: scopes.join(" ") }), - cookies: input.cookies, - session: payloads, - }, - input.userContext - ); + const resp = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth`), { + params: Object.assign(Object.assign({}, input.params), { scope: scopes.join(" ") }), + cookies: input.cookies, + session: payloads, + }, input.userContext); if (resp.status === "CLIENT_NOT_FOUND_ERROR") { return { status: "ERROR", @@ -367,11 +300,7 @@ function getRecipeInterface( errorDescription: "client_id is required", }; } - const scopes = - (_b = (_a = input.body.scope) === null || _a === void 0 ? void 0 : _a.split(" ")) !== null && - _b !== void 0 - ? _b - : []; + const scopes = (_b = (_a = input.body.scope) === null || _a === void 0 ? void 0 : _a.split(" ")) !== null && _b !== void 0 ? _b : []; const clientInfo = await this.getOAuth2Client({ clientId: input.body.client_id, userContext: input.userContext, @@ -401,11 +330,7 @@ function getRecipeInterface( }); } if (input.body.grant_type === "refresh_token") { - const scopes = - (_d = (_c = input.body.scope) === null || _c === void 0 ? void 0 : _c.split(" ")) !== null && - _d !== void 0 - ? _d - : []; + const scopes = (_d = (_c = input.body.scope) === null || _c === void 0 ? void 0 : _c.split(" ")) !== null && _d !== void 0 ? _d : []; const tokenInfo = await this.introspectToken({ token: input.body.refresh_token, scopes, @@ -454,11 +379,7 @@ function getRecipeInterface( if (input.authorizationHeader) { body["authorizationHeader"] = input.authorizationHeader; } - const res = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/token`), - body, - input.userContext - ); + const res = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/token`), body, input.userContext); if (res.status !== "OK") { return { status: "ERROR", @@ -470,23 +391,19 @@ function getRecipeInterface( return res; }, getOAuth2Clients: async function (input) { - let response = await querier.sendGetRequestWithResponseHeaders( - new normalisedURLPath_1.default(`/recipe/oauth/clients/list`), - { - pageSize: input.pageSize, - clientName: input.clientName, - pageToken: input.paginationToken, - }, - {}, - input.userContext - ); + let response = await querier.sendGetRequestWithResponseHeaders(new normalisedURLPath_1.default(`/recipe/oauth/clients/list`), { + pageSize: input.pageSize, + clientName: input.clientName, + pageToken: input.paginationToken, + }, {}, input.userContext); if (response.body.status === "OK") { return { status: "OK", clients: response.body.clients.map((client) => OAuth2Client_1.OAuth2Client.fromAPIResponse(client)), nextPaginationToken: response.body.nextPaginationToken, }; - } else { + } + else { return { status: "ERROR", error: response.body.error, @@ -495,24 +412,21 @@ function getRecipeInterface( } }, getOAuth2Client: async function (input) { - let response = await querier.sendGetRequestWithResponseHeaders( - new normalisedURLPath_1.default(`/recipe/oauth/clients`), - { clientId: input.clientId }, - {}, - input.userContext - ); + let response = await querier.sendGetRequestWithResponseHeaders(new normalisedURLPath_1.default(`/recipe/oauth/clients`), { clientId: input.clientId }, {}, input.userContext); if (response.body.status === "OK") { return { status: "OK", client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response.body), }; - } else if (response.body.status === "CLIENT_NOT_FOUND_ERROR") { + } + else if (response.body.status === "CLIENT_NOT_FOUND_ERROR") { return { status: "ERROR", error: "invalid_request", errorDescription: "The provided client_id is not valid or unknown", }; - } else { + } + else { return { status: "ERROR", error: response.body.error, @@ -521,17 +435,14 @@ function getRecipeInterface( } }, createOAuth2Client: async function (input) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/clients`), - copyAndCleanRequestBodyInput(input), - input.userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/clients`), copyAndCleanRequestBodyInput(input), input.userContext); if (response.status === "OK") { return { status: "OK", client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response), }; - } else { + } + else { return { status: "ERROR", error: response.error, @@ -540,18 +451,14 @@ function getRecipeInterface( } }, updateOAuth2Client: async function (input) { - let response = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth/clients`), - copyAndCleanRequestBodyInput(input), - { clientId: input.clientId }, - input.userContext - ); + let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/clients`), copyAndCleanRequestBodyInput(input), { clientId: input.clientId }, input.userContext); if (response.status === "OK") { return { status: "OK", client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response), }; - } else { + } + else { return { status: "ERROR", error: response.error, @@ -560,14 +467,11 @@ function getRecipeInterface( } }, deleteOAuth2Client: async function (input) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/clients/remove`), - { clientId: input.clientId }, - input.userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/clients/remove`), { clientId: input.clientId }, input.userContext); if (response.status === "OK") { return { status: "OK" }; - } else { + } + else { return { status: "ERROR", error: response.error, @@ -612,55 +516,39 @@ function getRecipeInterface( queryParams.set("forceFreshAuth", "true"); } return `${websiteDomain}${websiteBasePath}?${queryParams.toString()}`; - } else if (input.type === "try-refresh") { + } + else if (input.type === "try-refresh") { return `${websiteDomain}${websiteBasePath}/try-refresh?loginChallenge=${input.loginChallenge}`; - } else if (input.type === "post-logout-fallback") { + } + else if (input.type === "post-logout-fallback") { return `${websiteDomain}${websiteBasePath}`; - } else if (input.type === "logout-confirmation") { + } + else if (input.type === "logout-confirmation") { return `${websiteDomain}${websiteBasePath}/oauth/logout?logoutChallenge=${input.logoutChallenge}`; } throw new Error("This should never happen: invalid type passed to getFrontendRedirectionURL"); }, validateOAuth2AccessToken: async function (input) { var _a, _b, _c; - const payload = ( - await jose.jwtVerify( - input.token, - combinedRemoteJWKSet_1.getCombinedJWKS(recipe_1.default.getInstanceOrThrowError().config) - ) - ).payload; + const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS(recipe_1.default.getInstanceOrThrowError().config))).payload; if (payload.stt !== 1) { throw new Error("Wrong token type"); } - if ( - ((_a = input.requirements) === null || _a === void 0 ? void 0 : _a.clientId) !== undefined && - payload.client_id !== input.requirements.clientId - ) { - throw new Error( - `The token doesn't belong to the specified client (${input.requirements.clientId} !== ${payload.client_id})` - ); - } - if ( - ((_b = input.requirements) === null || _b === void 0 ? void 0 : _b.scopes) !== undefined && - input.requirements.scopes.some((scope) => !payload.scp.includes(scope)) - ) { + if (((_a = input.requirements) === null || _a === void 0 ? void 0 : _a.clientId) !== undefined && payload.client_id !== input.requirements.clientId) { + throw new Error(`The token doesn't belong to the specified client (${input.requirements.clientId} !== ${payload.client_id})`); + } + if (((_b = input.requirements) === null || _b === void 0 ? void 0 : _b.scopes) !== undefined && + input.requirements.scopes.some((scope) => !payload.scp.includes(scope))) { throw new Error("The token is missing some required scopes"); } const aud = payload.aud instanceof Array ? payload.aud : [payload.aud]; - if ( - ((_c = input.requirements) === null || _c === void 0 ? void 0 : _c.audience) !== undefined && - !aud.includes(input.requirements.audience) - ) { + if (((_c = input.requirements) === null || _c === void 0 ? void 0 : _c.audience) !== undefined && !aud.includes(input.requirements.audience)) { throw new Error("The token doesn't belong to the specified audience"); } if (input.checkDatabase) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/introspect`), - { - token: input.token, - }, - input.userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/introspect`), { + token: input.token, + }, input.userContext); if (response.active !== true) { throw new Error("The token is expired, invalid or has been revoked"); } @@ -673,7 +561,8 @@ function getRecipeInterface( }; if ("authorizationHeader" in input && input.authorizationHeader !== undefined) { requestBody.authorizationHeader = input.authorizationHeader; - } else { + } + else { if ("clientId" in input && input.clientId !== undefined) { requestBody.client_id = input.clientId; } @@ -681,11 +570,7 @@ function getRecipeInterface( requestBody.client_secret = input.clientSecret; } } - const res = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/token/revoke`), - requestBody, - input.userContext - ); + const res = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/token/revoke`), requestBody, input.userContext); if (res.status !== "OK") { return { status: "ERROR", @@ -697,19 +582,11 @@ function getRecipeInterface( return { status: "OK" }; }, revokeTokensBySessionHandle: async function (input) { - await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/session/revoke`), - { sessionHandle: input.sessionHandle }, - input.userContext - ); + await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/session/revoke`), { sessionHandle: input.sessionHandle }, input.userContext); return { status: "OK" }; }, revokeTokensByClientId: async function (input) { - await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/tokens/revoke`), - { clientId: input.clientId }, - input.userContext - ); + await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/tokens/revoke`), { clientId: input.clientId }, input.userContext); return { status: "OK" }; }, introspectToken: async function ({ token, scopes, userContext }) { @@ -725,20 +602,17 @@ function getRecipeInterface( checkDatabase: false, userContext, }); - } catch (error) { + } + catch (error) { return { active: false }; } } // For tokens that passed local validation or if it's a refresh token, // validate the token with the database by calling the core introspection endpoint - const res = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth/introspect`), - { - token, - scope: scopes ? scopes.join(" ") : undefined, - }, - userContext - ); + const res = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/introspect`), { + token, + scope: scopes ? scopes.join(" ") : undefined, + }, userContext); return res; }, endSession: async function (input) { @@ -754,17 +628,13 @@ function getRecipeInterface( * CASE 3: `end_session` request with a `logout_verifier` (after accepting the logout request) * - Redirects to the `post_logout_redirect_uri` or the default logout fallback page. */ - const resp = await querier.sendGetRequest( - new normalisedURLPath_1.default(`/recipe/oauth/sessions/logout`), - { - clientId: input.params.client_id, - idTokenHint: input.params.id_token_hint, - postLogoutRedirectUri: input.params.post_logout_redirect_uri, - state: input.params.state, - logoutVerifier: input.params.logout_verifier, - }, - input.userContext - ); + const resp = await querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/oauth/sessions/logout`), { + clientId: input.params.client_id, + idTokenHint: input.params.id_token_hint, + postLogoutRedirectUri: input.params.post_logout_redirect_uri, + state: input.params.state, + logoutVerifier: input.params.logout_verifier, + }, input.userContext); if ("error" in resp) { return { status: "ERROR", @@ -787,14 +657,13 @@ function getRecipeInterface( userContext: input.userContext, }), }; - } else { + } + else { // Accept the logout challenge immediately as there is no supertokens session - redirectTo = ( - await this.acceptLogoutRequest({ - challenge: logoutChallenge, - userContext: input.userContext, - }) - ).redirectTo; + redirectTo = (await this.acceptLogoutRequest({ + challenge: logoutChallenge, + userContext: input.userContext, + })).redirectTo; } } // CASE 2 or 3 (See above notes) @@ -811,12 +680,7 @@ function getRecipeInterface( return { redirectTo }; }, acceptLogoutRequest: async function (input) { - const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/logout/accept`), - { challenge: input.challenge }, - {}, - input.userContext - ); + const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/logout/accept`), { challenge: input.challenge }, {}, input.userContext); const redirectTo = getUpdatedRedirectTo(appInfo, resp.redirectTo); if (redirectTo.endsWith("/fallbacks/logout/callback")) { return { @@ -829,12 +693,7 @@ function getRecipeInterface( return { redirectTo }; }, rejectLogoutRequest: async function (input) { - const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/logout/reject`), - {}, - { challenge: input.challenge }, - input.userContext - ); + const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/logout/reject`), {}, { challenge: input.challenge }, input.userContext); if (resp.status != "OK") { throw new Error(resp.error); } diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index 7d3987138..9e8c0c43a 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -8,19 +8,13 @@ import { User } from "../../user"; import RecipeUserId from "../../recipeUserId"; export declare type TypeInput = { override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -88,32 +82,30 @@ export declare type UserInfo = { phoneNumber_verified?: boolean; [key: string]: JSONValue; }; -export declare type InstrospectTokenResponse = - | { - active: false; - } - | ({ - active: true; - } & JSONObject); +export declare type InstrospectTokenResponse = { + active: false; +} | ({ + active: true; +} & JSONObject); export declare type RecipeInterface = { authorization(input: { params: Record; cookies: string | undefined; session: SessionContainerInterface | undefined; userContext: UserContext; - }): Promise< - | { - redirectTo: string; - cookies: string | undefined; - } - | ErrorOAuth2 - >; + }): Promise<{ + redirectTo: string; + cookies: string | undefined; + } | ErrorOAuth2>; tokenExchange(input: { authorizationHeader?: string; body: Record; userContext: UserContext; }): Promise; - getConsentRequest(input: { challenge: string; userContext: UserContext }): Promise; + getConsentRequest(input: { + challenge: string; + userContext: UserContext; + }): Promise; acceptConsentRequest(input: { challenge: string; context?: any; @@ -139,12 +131,9 @@ export declare type RecipeInterface = { getLoginRequest(input: { challenge: string; userContext: UserContext; - }): Promise< - | (LoginRequest & { - status: "OK"; - }) - | ErrorOAuth2 - >; + }): Promise<(LoginRequest & { + status: "OK"; + }) | ErrorOAuth2>; acceptLoginRequest(input: { challenge: string; acr?: string; @@ -167,77 +156,54 @@ export declare type RecipeInterface = { getOAuth2Client(input: { clientId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - client: OAuth2Client; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - getOAuth2Clients( - input: GetOAuth2ClientsInput & { - userContext: UserContext; - } - ): Promise< - | { - status: "OK"; - clients: Array; - nextPaginationToken?: string; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - createOAuth2Client( - input: CreateOAuth2ClientInput & { - userContext: UserContext; - } - ): Promise< - | { - status: "OK"; - client: OAuth2Client; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - updateOAuth2Client( - input: UpdateOAuth2ClientInput & { - userContext: UserContext; - } - ): Promise< - | { - status: "OK"; - client: OAuth2Client; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; - deleteOAuth2Client( - input: DeleteOAuth2ClientInput & { - userContext: UserContext; - } - ): Promise< - | { - status: "OK"; - } - | { - status: "ERROR"; - error: string; - errorDescription: string; - } - >; + }): Promise<{ + status: "OK"; + client: OAuth2Client; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + getOAuth2Clients(input: GetOAuth2ClientsInput & { + userContext: UserContext; + }): Promise<{ + status: "OK"; + clients: Array; + nextPaginationToken?: string; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + createOAuth2Client(input: CreateOAuth2ClientInput & { + userContext: UserContext; + }): Promise<{ + status: "OK"; + client: OAuth2Client; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + updateOAuth2Client(input: UpdateOAuth2ClientInput & { + userContext: UserContext; + }): Promise<{ + status: "OK"; + client: OAuth2Client; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; + deleteOAuth2Client(input: DeleteOAuth2ClientInput & { + userContext: UserContext; + }): Promise<{ + status: "OK"; + } | { + status: "ERROR"; + error: string; + errorDescription: string; + }>; validateOAuth2AccessToken(input: { token: string; requirements?: { @@ -279,50 +245,36 @@ export declare type RecipeInterface = { tenantId: string; userContext: UserContext; }): Promise; - getFrontendRedirectionURL( - input: - | { - type: "login"; - loginChallenge: string; - tenantId: string; - forceFreshAuth: boolean; - hint: string | undefined; - userContext: UserContext; - } - | { - type: "try-refresh"; - loginChallenge: string; - userContext: UserContext; - } - | { - type: "logout-confirmation"; - logoutChallenge: string; - userContext: UserContext; - } - | { - type: "post-logout-fallback"; - userContext: UserContext; - } - ): Promise; - revokeToken( - input: { - token: string; - userContext: UserContext; - } & ( - | { - authorizationHeader: string; - } - | { - clientId: string; - clientSecret?: string; - } - ) - ): Promise< - | { - status: "OK"; - } - | ErrorOAuth2 - >; + getFrontendRedirectionURL(input: { + type: "login"; + loginChallenge: string; + tenantId: string; + forceFreshAuth: boolean; + hint: string | undefined; + userContext: UserContext; + } | { + type: "try-refresh"; + loginChallenge: string; + userContext: UserContext; + } | { + type: "logout-confirmation"; + logoutChallenge: string; + userContext: UserContext; + } | { + type: "post-logout-fallback"; + userContext: UserContext; + }): Promise; + revokeToken(input: { + token: string; + userContext: UserContext; + } & ({ + authorizationHeader: string; + } | { + clientId: string; + clientSecret?: string; + })): Promise<{ + status: "OK"; + } | ErrorOAuth2>; revokeTokensByClientId(input: { clientId: string; userContext: UserContext; @@ -345,12 +297,9 @@ export declare type RecipeInterface = { session?: SessionContainerInterface; shouldTryRefresh: boolean; userContext: UserContext; - }): Promise< - | { - redirectTo: string; - } - | ErrorOAuth2 - >; + }): Promise<{ + redirectTo: string; + } | ErrorOAuth2>; acceptLogoutRequest(input: { challenge: string; userContext: UserContext; @@ -365,146 +314,94 @@ export declare type RecipeInterface = { }>; }; export declare type APIInterface = { - loginGET: - | undefined - | ((input: { - loginChallenge: string; - options: APIOptions; - session?: SessionContainerInterface; - shouldTryRefresh: boolean; - userContext: UserContext; - }) => Promise< - | { - frontendRedirectTo: string; - cookies?: string; - } - | ErrorOAuth2 - | GeneralErrorResponse - >); - authGET: - | undefined - | ((input: { - params: any; - cookie: string | undefined; - session: SessionContainerInterface | undefined; - shouldTryRefresh: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - redirectTo: string; - cookies?: string; - } - | ErrorOAuth2 - | GeneralErrorResponse - >); - tokenPOST: - | undefined - | ((input: { - authorizationHeader?: string; - body: any; - options: APIOptions; - userContext: UserContext; - }) => Promise); - loginInfoGET: - | undefined - | ((input: { - loginChallenge: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - info: LoginInfo; - } - | ErrorOAuth2 - | GeneralErrorResponse - >); - userInfoGET: - | undefined - | ((input: { - accessTokenPayload: JSONObject; - user: User; - scopes: string[]; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise); - revokeTokenPOST: - | undefined - | (( - input: { - token: string; - options: APIOptions; - userContext: UserContext; - } & ( - | { - authorizationHeader: string; - } - | { - clientId: string; - clientSecret?: string; - } - ) - ) => Promise< - | { - status: "OK"; - } - | ErrorOAuth2 - >); - introspectTokenPOST: - | undefined - | ((input: { - token: string; - scopes?: string[]; - options: APIOptions; - userContext: UserContext; - }) => Promise); - endSessionGET: - | undefined - | ((input: { - params: Record; - session?: SessionContainerInterface; - shouldTryRefresh: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - redirectTo: string; - } - | ErrorOAuth2 - | GeneralErrorResponse - >); - endSessionPOST: - | undefined - | ((input: { - params: Record; - session?: SessionContainerInterface; - shouldTryRefresh: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - redirectTo: string; - } - | ErrorOAuth2 - | GeneralErrorResponse - >); - logoutPOST: - | undefined - | ((input: { - logoutChallenge: string; - options: APIOptions; - session?: SessionContainerInterface; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - frontendRedirectTo: string; - } - | ErrorOAuth2 - | GeneralErrorResponse - >); + loginGET: undefined | ((input: { + loginChallenge: string; + options: APIOptions; + session?: SessionContainerInterface; + shouldTryRefresh: boolean; + userContext: UserContext; + }) => Promise<{ + frontendRedirectTo: string; + cookies?: string; + } | ErrorOAuth2 | GeneralErrorResponse>); + authGET: undefined | ((input: { + params: any; + cookie: string | undefined; + session: SessionContainerInterface | undefined; + shouldTryRefresh: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + redirectTo: string; + cookies?: string; + } | ErrorOAuth2 | GeneralErrorResponse>); + tokenPOST: undefined | ((input: { + authorizationHeader?: string; + body: any; + options: APIOptions; + userContext: UserContext; + }) => Promise); + loginInfoGET: undefined | ((input: { + loginChallenge: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + info: LoginInfo; + } | ErrorOAuth2 | GeneralErrorResponse>); + userInfoGET: undefined | ((input: { + accessTokenPayload: JSONObject; + user: User; + scopes: string[]; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise); + revokeTokenPOST: undefined | ((input: { + token: string; + options: APIOptions; + userContext: UserContext; + } & ({ + authorizationHeader: string; + } | { + clientId: string; + clientSecret?: string; + })) => Promise<{ + status: "OK"; + } | ErrorOAuth2>); + introspectTokenPOST: undefined | ((input: { + token: string; + scopes?: string[]; + options: APIOptions; + userContext: UserContext; + }) => Promise); + endSessionGET: undefined | ((input: { + params: Record; + session?: SessionContainerInterface; + shouldTryRefresh: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + redirectTo: string; + } | ErrorOAuth2 | GeneralErrorResponse>); + endSessionPOST: undefined | ((input: { + params: Record; + session?: SessionContainerInterface; + shouldTryRefresh: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + redirectTo: string; + } | ErrorOAuth2 | GeneralErrorResponse>); + logoutPOST: undefined | ((input: { + logoutChallenge: string; + options: APIOptions; + session?: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + frontendRedirectTo: string; + } | ErrorOAuth2 | GeneralErrorResponse>); }; export declare type OAuth2ClientOptions = { clientId: string; @@ -548,12 +445,8 @@ export declare type GetOAuth2ClientsInput = { */ clientName?: string; }; -export declare type CreateOAuth2ClientInput = Partial< - Omit ->; -export declare type UpdateOAuth2ClientInput = NonNullableProperties< - Omit -> & { +export declare type CreateOAuth2ClientInput = Partial>; +export declare type UpdateOAuth2ClientInput = NonNullableProperties> & { clientId: string; redirectUris?: string[] | null; grantTypes?: string[] | null; @@ -563,16 +456,5 @@ export declare type UpdateOAuth2ClientInput = NonNullableProperties< export declare type DeleteOAuth2ClientInput = { clientId: string; }; -export declare type PayloadBuilderFunction = ( - user: User, - scopes: string[], - sessionHandle: string, - userContext: UserContext -) => Promise; -export declare type UserInfoBuilderFunction = ( - user: User, - accessTokenPayload: JSONObject, - scopes: string[], - tenantId: string, - userContext: UserContext -) => Promise; +export declare type PayloadBuilderFunction = (user: User, scopes: string[], sessionHandle: string, userContext: UserContext) => Promise; +export declare type UserInfoBuilderFunction = (user: User, accessTokenPayload: JSONObject, scopes: string[], tenantId: string, userContext: UserContext) => Promise; diff --git a/lib/build/recipe/oauth2provider/utils.d.ts b/lib/build/recipe/oauth2provider/utils.d.ts index 4025b1b44..133d4840f 100644 --- a/lib/build/recipe/oauth2provider/utils.d.ts +++ b/lib/build/recipe/oauth2provider/utils.d.ts @@ -2,8 +2,4 @@ import { NormalisedAppinfo } from "../../types"; import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput( - _: Recipe, - __: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(_: Recipe, __: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; diff --git a/lib/build/recipe/oauth2provider/utils.js b/lib/build/recipe/oauth2provider/utils.js index f0bbf7edd..00f9f3586 100644 --- a/lib/build/recipe/oauth2provider/utils.js +++ b/lib/build/recipe/oauth2provider/utils.js @@ -16,13 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(_, __, config) { - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { override, }; diff --git a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.d.ts b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.d.ts index 45955e60f..31f793b8c 100644 --- a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.d.ts +++ b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { UserContext } from "../../../types"; import { APIInterface, APIOptions } from "../types"; -export default function getOpenIdDiscoveryConfiguration( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function getOpenIdDiscoveryConfiguration(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js index 05b9f8403..fb7ef2e2e 100644 --- a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js +++ b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js @@ -24,7 +24,8 @@ async function getOpenIdDiscoveryConfiguration(apiImplementation, options, userC id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, response_types_supported: result.response_types_supported, }); - } else { + } + else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/openid/index.d.ts b/lib/build/recipe/openid/index.d.ts index 84b55bd8c..7e921973b 100644 --- a/lib/build/recipe/openid/index.d.ts +++ b/lib/build/recipe/openid/index.d.ts @@ -2,9 +2,7 @@ import OpenIdRecipe from "./recipe"; export default class OpenIdRecipeWrapper { static init: typeof OpenIdRecipe.init; - static getOpenIdDiscoveryConfiguration( - userContext?: Record - ): Promise<{ + static getOpenIdDiscoveryConfiguration(userContext?: Record): Promise<{ status: "OK"; issuer: string; jwks_uri: string; diff --git a/lib/build/recipe/openid/index.js b/lib/build/recipe/openid/index.js index 227ea73f7..4b605be32 100644 --- a/lib/build/recipe/openid/index.js +++ b/lib/build/recipe/openid/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getOpenIdDiscoveryConfiguration = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/openid/recipe.d.ts b/lib/build/recipe/openid/recipe.d.ts index cac8551d3..6990632f1 100644 --- a/lib/build/recipe/openid/recipe.d.ts +++ b/lib/build/recipe/openid/recipe.d.ts @@ -17,15 +17,7 @@ export default class OpenIdRecipe extends RecipeModule { static reset(): void; static getIssuer(userContext: UserContext): Promise; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - _tenantId: string, - req: BaseRequest, - response: BaseResponse, - _path: normalisedURLPath, - _method: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, _tenantId: string, req: BaseRequest, response: BaseResponse, _path: normalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; handleError: (error: STError) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/openid/recipe.js b/lib/build/recipe/openid/recipe.js index ab6336364..d54384f40 100644 --- a/lib/build/recipe/openid/recipe.js +++ b/lib/build/recipe/openid/recipe.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -52,7 +50,8 @@ class OpenIdRecipe extends recipeModule_1.default { }; if (id === constants_1.GET_DISCOVERY_CONFIG_URL) { return await getOpenIdDiscoveryConfiguration_1.default(this.apiImpl, apiOptions, userContext); - } else { + } + else { return false; } }; @@ -82,7 +81,8 @@ class OpenIdRecipe extends recipeModule_1.default { if (OpenIdRecipe.instance === undefined) { OpenIdRecipe.instance = new OpenIdRecipe(OpenIdRecipe.RECIPE_ID, appInfo, config); return OpenIdRecipe.instance; - } else { + } + else { throw new Error("OpenId recipe has already been initialised. Please check your code for bugs."); } }; @@ -94,9 +94,7 @@ class OpenIdRecipe extends recipeModule_1.default { OpenIdRecipe.instance = undefined; } static async getIssuer(userContext) { - return ( - await this.getInstanceOrThrowError().recipeImplementation.getOpenIdDiscoveryConfiguration({ userContext }) - ).issuer; + return (await this.getInstanceOrThrowError().recipeImplementation.getOpenIdDiscoveryConfiguration({ userContext })).issuer; } } exports.default = OpenIdRecipe; diff --git a/lib/build/recipe/openid/recipeImplementation.js b/lib/build/recipe/openid/recipeImplementation.js index 746c52a35..3ae2b7917 100644 --- a/lib/build/recipe/openid/recipeImplementation.js +++ b/lib/build/recipe/openid/recipeImplementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../jwt/recipe")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -13,11 +11,8 @@ function getRecipeInterface(appInfo) { return { getOpenIdDiscoveryConfiguration: async function () { let issuer = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - let jwks_uri = - appInfo.apiDomain.getAsStringDangerous() + - appInfo.apiBasePath - .appendPath(new normalisedURLPath_1.default(constants_1.GET_JWKS_API)) - .getAsStringDangerous(); + let jwks_uri = appInfo.apiDomain.getAsStringDangerous() + + appInfo.apiBasePath.appendPath(new normalisedURLPath_1.default(constants_1.GET_JWKS_API)).getAsStringDangerous(); const apiBasePath = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); return { status: "OK", @@ -34,7 +29,7 @@ function getRecipeInterface(appInfo) { response_types_supported: ["code", "id_token", "id_token token"], }; }, - createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext }) { + createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext, }) { payload = payload === undefined || payload === null ? {} : payload; let issuer = (await this.getOpenIdDiscoveryConfiguration({ userContext })).issuer; return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createJWT({ diff --git a/lib/build/recipe/openid/types.d.ts b/lib/build/recipe/openid/types.d.ts index b0dda95fd..4a8181b12 100644 --- a/lib/build/recipe/openid/types.d.ts +++ b/lib/build/recipe/openid/types.d.ts @@ -4,19 +4,13 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import { GeneralErrorResponse, UserContext } from "../../types"; export declare type TypeInput = { override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -28,28 +22,23 @@ export declare type APIOptions = { res: BaseResponse; }; export declare type APIInterface = { - getOpenIdDiscoveryConfigurationGET: - | undefined - | ((input: { - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - issuer: string; - jwks_uri: string; - authorization_endpoint: string; - token_endpoint: string; - userinfo_endpoint: string; - revocation_endpoint: string; - token_introspection_endpoint: string; - end_session_endpoint: string; - subject_types_supported: string[]; - id_token_signing_alg_values_supported: string[]; - response_types_supported: string[]; - } - | GeneralErrorResponse - >); + getOpenIdDiscoveryConfigurationGET: undefined | ((input: { + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + issuer: string; + jwks_uri: string; + authorization_endpoint: string; + token_endpoint: string; + userinfo_endpoint: string; + revocation_endpoint: string; + token_introspection_endpoint: string; + end_session_endpoint: string; + subject_types_supported: string[]; + id_token_signing_alg_values_supported: string[]; + response_types_supported: string[]; + } | GeneralErrorResponse>); }; export declare type RecipeInterface = { getOpenIdDiscoveryConfiguration(input: { @@ -73,13 +62,10 @@ export declare type RecipeInterface = { validitySeconds?: number; useStaticSigningKey?: boolean; userContext: UserContext; - }): Promise< - | { - status: "OK"; - jwt: string; - } - | { - status: "UNSUPPORTED_ALGORITHM_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + jwt: string; + } | { + status: "UNSUPPORTED_ALGORITHM_ERROR"; + }>; }; diff --git a/lib/build/recipe/openid/utils.js b/lib/build/recipe/openid/utils.js index ad70f404c..9e51218c2 100644 --- a/lib/build/recipe/openid/utils.js +++ b/lib/build/recipe/openid/utils.js @@ -2,13 +2,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(config) { - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { override, }; diff --git a/lib/build/recipe/passwordless/api/consumeCode.d.ts b/lib/build/recipe/passwordless/api/consumeCode.d.ts index a07ff159f..95eaca99c 100644 --- a/lib/build/recipe/passwordless/api/consumeCode.d.ts +++ b/lib/build/recipe/passwordless/api/consumeCode.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function consumeCode( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function consumeCode(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/passwordless/api/consumeCode.js b/lib/build/recipe/passwordless/api/consumeCode.js index 7179971cf..470b125a2 100644 --- a/lib/build/recipe/passwordless/api/consumeCode.js +++ b/lib/build/recipe/passwordless/api/consumeCode.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -50,49 +48,40 @@ async function consumeCode(apiImplementation, tenantId, options, userContext) { message: "Please provide both deviceId and userInputCode", }); } - } else if (linkCode === undefined) { + } + else if (linkCode === undefined) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "Please provide one of (linkCode) or (deviceId+userInputCode) and not both", }); } const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( - options.req, - options.res, - shouldTryLinkingWithSessionUser, - userContext - ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); if (session !== undefined) { tenantId = session.getTenantId(); } - let result = await apiImplementation.consumeCodePOST( - deviceId !== undefined - ? { - deviceId, - userInputCode, - preAuthSessionId, - tenantId, - session, - shouldTryLinkingWithSessionUser, - options, - userContext, - } - : { - linkCode, - options, - preAuthSessionId, - tenantId, - session, - shouldTryLinkingWithSessionUser, - userContext, - } - ); + let result = await apiImplementation.consumeCodePOST(deviceId !== undefined + ? { + deviceId, + userInputCode, + preAuthSessionId, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + } + : { + linkCode, + options, + preAuthSessionId, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }); if (result.status === "OK") { - result = Object.assign( - Object.assign({}, result), - utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext) - ); + result = Object.assign(Object.assign({}, result), utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)); delete result.session; } utils_1.send200Response(options.res, result); diff --git a/lib/build/recipe/passwordless/api/createCode.d.ts b/lib/build/recipe/passwordless/api/createCode.d.ts index 1d2619c75..593fdf54e 100644 --- a/lib/build/recipe/passwordless/api/createCode.d.ts +++ b/lib/build/recipe/passwordless/api/createCode.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function createCode( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function createCode(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/passwordless/api/createCode.js b/lib/build/recipe/passwordless/api/createCode.js index 13617a262..90bd2994d 100644 --- a/lib/build/recipe/passwordless/api/createCode.js +++ b/lib/build/recipe/passwordless/api/createCode.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -49,10 +47,8 @@ async function createCode(apiImplementation, tenantId, options, userContext) { }); } // normalise and validate format of input - if ( - email !== undefined && - (options.config.contactMethod === "EMAIL" || options.config.contactMethod === "EMAIL_OR_PHONE") - ) { + if (email !== undefined && + (options.config.contactMethod === "EMAIL" || options.config.contactMethod === "EMAIL_OR_PHONE")) { email = email.trim(); const validateError = await options.config.validateEmailAddress(email, tenantId); if (validateError !== undefined) { @@ -63,10 +59,8 @@ async function createCode(apiImplementation, tenantId, options, userContext) { return true; } } - if ( - phoneNumber !== undefined && - (options.config.contactMethod === "PHONE" || options.config.contactMethod === "EMAIL_OR_PHONE") - ) { + if (phoneNumber !== undefined && + (options.config.contactMethod === "PHONE" || options.config.contactMethod === "EMAIL_OR_PHONE")) { const validateError = await options.config.validatePhoneNumber(phoneNumber, tenantId); if (validateError !== undefined) { utils_1.send200Response(options.res, { @@ -80,25 +74,19 @@ async function createCode(apiImplementation, tenantId, options, userContext) { // this can come here if the user has provided their own impl of validatePhoneNumber and // the phone number is valid according to their impl, but not according to the libphonenumber-js lib. phoneNumber = phoneNumber.trim(); - } else { + } + else { phoneNumber = parsedPhoneNumber.format("E.164"); } } const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( - options.req, - options.res, - shouldTryLinkingWithSessionUser, - userContext - ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); if (session !== undefined) { tenantId = session.getTenantId(); } - let result = await apiImplementation.createCodePOST( - email !== undefined - ? { email, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } - : { phoneNumber: phoneNumber, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } - ); + let result = await apiImplementation.createCodePOST(email !== undefined + ? { email, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } + : { phoneNumber: phoneNumber, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext }); utils_1.send200Response(options.res, result); return true; } diff --git a/lib/build/recipe/passwordless/api/emailExists.d.ts b/lib/build/recipe/passwordless/api/emailExists.d.ts index 2f55b6d3b..478175dec 100644 --- a/lib/build/recipe/passwordless/api/emailExists.d.ts +++ b/lib/build/recipe/passwordless/api/emailExists.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function emailExists( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function emailExists(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/passwordless/api/emailExists.js b/lib/build/recipe/passwordless/api/emailExists.js index 312816062..360b195d9 100644 --- a/lib/build/recipe/passwordless/api/emailExists.js +++ b/lib/build/recipe/passwordless/api/emailExists.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/passwordless/api/implementation.js b/lib/build/recipe/passwordless/api/implementation.js index 0936acc2c..d64fc1275 100644 --- a/lib/build/recipe/passwordless/api/implementation.js +++ b/lib/build/recipe/passwordless/api/implementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("../../../logger"); const authUtils_1 = require("../../../authUtils"); @@ -18,19 +16,14 @@ function getAPIImplementation() { consumeCodePOST: async function (input) { var _a, _b, _c; 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_002)", - SIGN_IN_NOT_ALLOWED: - "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)", + SIGN_UP_NOT_ALLOWED: "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)", + SIGN_IN_NOT_ALLOWED: "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)", LINKING_TO_SESSION_USER_FAILED: { // We should never get an email verification error here, since pwless automatically marks the user // email as verified - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_017)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_018)", - 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_019)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_017)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_018)", + 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_019)", }, }; const deviceInfo = await input.options.recipeImplementation.listCodesByPreAuthSessionId({ @@ -44,47 +37,42 @@ function getAPIImplementation() { }; } const recipeId = "passwordless"; - const accountInfo = - deviceInfo.phoneNumber !== undefined - ? { - phoneNumber: deviceInfo.phoneNumber, - } - : { - email: deviceInfo.email, - }; + const accountInfo = deviceInfo.phoneNumber !== undefined + ? { + phoneNumber: deviceInfo.phoneNumber, + } + : { + email: deviceInfo.email, + }; let checkCredentialsResponseProm; let checkCredentials = async () => { if (checkCredentialsResponseProm === undefined) { - checkCredentialsResponseProm = input.options.recipeImplementation.checkCode( - "deviceId" in input - ? { - preAuthSessionId: input.preAuthSessionId, - deviceId: input.deviceId, - userInputCode: input.userInputCode, - tenantId: input.tenantId, - userContext: input.userContext, - } - : { - preAuthSessionId: input.preAuthSessionId, - linkCode: input.linkCode, - tenantId: input.tenantId, - userContext: input.userContext, - } - ); + checkCredentialsResponseProm = input.options.recipeImplementation.checkCode("deviceId" in input + ? { + preAuthSessionId: input.preAuthSessionId, + deviceId: input.deviceId, + userInputCode: input.userInputCode, + tenantId: input.tenantId, + userContext: input.userContext, + } + : { + preAuthSessionId: input.preAuthSessionId, + linkCode: input.linkCode, + tenantId: input.tenantId, + userContext: input.userContext, + }); } const checkCredentialsResponse = await checkCredentialsResponseProm; return checkCredentialsResponse.status === "OK"; }; - const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( - { - accountInfo, - recipeId, - userContext: input.userContext, - session: input.session, - tenantId: input.tenantId, - checkCredentialsOnTenant: checkCredentials, - } - ); + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ + accountInfo, + recipeId, + userContext: input.userContext, + session: input.session, + tenantId: input.tenantId, + checkCredentialsOnTenant: checkCredentials, + }); const emailVerificationInstance = recipe_2.default.getInstance(); // If we have a session and emailverification was initialized plus this code was sent to an email // then we check if we can/should verify this email address for the session user. @@ -92,11 +80,9 @@ function getAPIImplementation() { // and making a user primary if they are verified, but the verification process itself involves account linking. // If a valid code was submitted, we can take that as the session (and the session user) having access to the email // which means that we can verify their email address - if ( - accountInfo.email !== undefined && + if (accountInfo.email !== undefined && input.session !== undefined && - emailVerificationInstance !== undefined - ) { + emailVerificationInstance !== undefined) { // We first load the session user, so we can check if verification is required // We do this first, it is better for caching if we group the post calls together (verifyIng the code and the email address) const sessionUser = await __1.getUser(input.session.getUserId(), input.userContext); @@ -106,9 +92,7 @@ function getAPIImplementation() { message: "Session user not found", }); } - const loginMethod = sessionUser.loginMethods.find( - (lm) => lm.recipeUserId.getAsString() === input.session.getRecipeUserId().getAsString() - ); + const loginMethod = sessionUser.loginMethods.find((lm) => lm.recipeUserId.getAsString() === input.session.getRecipeUserId().getAsString()); if (loginMethod === undefined) { throw new error_1.default({ type: error_1.default.UNAUTHORISED, @@ -120,14 +104,12 @@ function getAPIImplementation() { if (loginMethod.hasSameEmailAs(accountInfo.email) && !loginMethod.verified) { // We first check that the submitted code is actually valid if (await checkCredentials()) { - const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( - { - tenantId: input.tenantId, - recipeUserId: loginMethod.recipeUserId, - email: accountInfo.email, - userContext: input.userContext, - } - ); + const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken({ + tenantId: input.tenantId, + recipeUserId: loginMethod.recipeUserId, + email: accountInfo.email, + userContext: input.userContext, + }); if (tokenResponse.status === "OK") { await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ tenantId: input.tenantId, @@ -145,13 +127,16 @@ function getAPIImplementation() { if (deviceInfo.email !== undefined) { if ("userInputCode" in input) { factorId = multifactorauth_1.FactorIds.OTP_EMAIL; - } else { + } + else { factorId = multifactorauth_1.FactorIds.LINK_EMAIL; } - } else { + } + else { if ("userInputCode" in input) { factorId = multifactorauth_1.FactorIds.OTP_PHONE; - } else { + } + else { factorId = multifactorauth_1.FactorIds.LINK_PHONE; } } @@ -163,16 +148,9 @@ function getAPIImplementation() { phoneNumber: deviceInfo.phoneNumber, }, factorIds: [factorId], - authenticatingUser: - authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, isSignUp, - isVerified: - (_a = - authenticatingUser === null || authenticatingUser === void 0 - ? void 0 - : authenticatingUser.loginMethod.verified) !== null && _a !== void 0 - ? _a - : true, + isVerified: (_a = authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.loginMethod.verified) !== null && _a !== void 0 ? _a : true, signInVerifiesLoginMethod: true, skipSessionUserUpdateInCore: false, tenantId: input.tenantId, @@ -183,11 +161,7 @@ function getAPIImplementation() { if (preAuthChecks.status !== "OK") { // 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" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthChecks, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); } if (checkCredentialsResponseProm !== undefined) { // We need to cast this because otherwise TS thinks that this is never updated for some reason. @@ -197,39 +171,31 @@ function getAPIImplementation() { return checkCredentialsResponse; } } - let response = await input.options.recipeImplementation.consumeCode( - "deviceId" in input - ? { - preAuthSessionId: input.preAuthSessionId, - deviceId: input.deviceId, - userInputCode: input.userInputCode, - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - tenantId: input.tenantId, - userContext: input.userContext, - } - : { - preAuthSessionId: input.preAuthSessionId, - linkCode: input.linkCode, - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - tenantId: input.tenantId, - userContext: input.userContext, - } - ); - if ( - response.status === "RESTART_FLOW_ERROR" || + let response = await input.options.recipeImplementation.consumeCode("deviceId" in input + ? { + preAuthSessionId: input.preAuthSessionId, + deviceId: input.deviceId, + userInputCode: input.userInputCode, + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + tenantId: input.tenantId, + userContext: input.userContext, + } + : { + preAuthSessionId: input.preAuthSessionId, + linkCode: input.linkCode, + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + tenantId: input.tenantId, + userContext: input.userContext, + }); + if (response.status === "RESTART_FLOW_ERROR" || response.status === "INCORRECT_USER_INPUT_CODE_ERROR" || - response.status === "EXPIRED_USER_INPUT_CODE_ERROR" - ) { + response.status === "EXPIRED_USER_INPUT_CODE_ERROR") { return response; } if (response.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - response, - errorCodeMap, - "SIGN_IN_UP_NOT_ALLOWED" - ); + 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 @@ -241,10 +207,7 @@ function getAPIImplementation() { factorId, isSignUp, authenticatedUser: (_b = response.user) !== null && _b !== void 0 ? _b : authenticatingUser.user, - recipeUserId: - (_c = response.recipeUserId) !== null && _c !== void 0 - ? _c - : authenticatingUser.loginMethod.recipeUserId, + recipeUserId: (_c = response.recipeUserId) !== null && _c !== void 0 ? _c : authenticatingUser.loginMethod.recipeUserId, req: input.options.req, res: input.options.res, tenantId: input.tenantId, @@ -252,11 +215,7 @@ function getAPIImplementation() { session: input.session, }); if (postAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - postAuthChecks, - errorCodeMap, - "SIGN_IN_UP_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); } return { status: "OK", @@ -268,11 +227,9 @@ function getAPIImplementation() { createCodePOST: async function (input) { var _a; 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_002)", + SIGN_UP_NOT_ALLOWED: "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)", LINKING_TO_SESSION_USER_FAILED: { - 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_019)", + 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_019)", }, }; const accountInfo = {}; @@ -285,42 +242,30 @@ function getAPIImplementation() { // Here we use do not use the helper from AuthUtil to check if this is going to be a sign in or up, because: // 1. At this point we have no way to check credentials // 2. We do not want to associate the relevant recipe user with the current tenant (yet) - const userWithMatchingLoginMethod = await getPasswordlessUserByAccountInfo( - Object.assign(Object.assign({}, input), { accountInfo }) - ); + const userWithMatchingLoginMethod = await getPasswordlessUserByAccountInfo(Object.assign(Object.assign({}, input), { accountInfo })); let factorIds; if (input.session !== undefined) { if (accountInfo.email !== undefined) { factorIds = [multifactorauth_1.FactorIds.OTP_EMAIL]; - } else { + } + else { factorIds = [multifactorauth_1.FactorIds.OTP_PHONE]; } - } else { + } + else { factorIds = utils_1.getEnabledPwlessFactors(input.options.config); if (accountInfo.email !== undefined) { - factorIds = factorIds.filter((factor) => - [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.LINK_EMAIL].includes(factor) - ); - } else { - factorIds = factorIds.filter((factor) => - [multifactorauth_1.FactorIds.OTP_PHONE, multifactorauth_1.FactorIds.LINK_PHONE].includes(factor) - ); + factorIds = factorIds.filter((factor) => [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.LINK_EMAIL].includes(factor)); + } + else { + factorIds = factorIds.filter((factor) => [multifactorauth_1.FactorIds.OTP_PHONE, multifactorauth_1.FactorIds.LINK_PHONE].includes(factor)); } } const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ authenticatingAccountInfo: Object.assign(Object.assign({}, accountInfo), { recipeId: "passwordless" }), isSignUp: userWithMatchingLoginMethod === undefined, - authenticatingUser: - userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 - ? void 0 - : userWithMatchingLoginMethod.user, - isVerified: - (_a = - userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 - ? void 0 - : userWithMatchingLoginMethod.loginMethod.verified) !== null && _a !== void 0 - ? _a - : true, + authenticatingUser: userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 ? void 0 : userWithMatchingLoginMethod.user, + isVerified: (_a = userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 ? void 0 : userWithMatchingLoginMethod.loginMethod.verified) !== null && _a !== void 0 ? _a : true, signInVerifiesLoginMethod: true, skipSessionUserUpdateInCore: true, tenantId: input.tenantId, @@ -332,49 +277,31 @@ function getAPIImplementation() { if (preAuthChecks.status !== "OK") { // 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" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthChecks, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); } - let response = await input.options.recipeImplementation.createCode( - "email" in input - ? { - userContext: input.userContext, - email: input.email, - userInputCode: - input.options.config.getCustomUserInputCode === undefined - ? undefined - : await input.options.config.getCustomUserInputCode( - input.tenantId, - input.userContext - ), - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - tenantId: input.tenantId, - } - : { - userContext: input.userContext, - phoneNumber: input.phoneNumber, - userInputCode: - input.options.config.getCustomUserInputCode === undefined - ? undefined - : await input.options.config.getCustomUserInputCode( - input.tenantId, - input.userContext - ), - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - tenantId: input.tenantId, - } - ); + let response = await input.options.recipeImplementation.createCode("email" in input + ? { + userContext: input.userContext, + email: input.email, + userInputCode: input.options.config.getCustomUserInputCode === undefined + ? undefined + : await input.options.config.getCustomUserInputCode(input.tenantId, input.userContext), + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + tenantId: input.tenantId, + } + : { + userContext: input.userContext, + phoneNumber: input.phoneNumber, + userInputCode: input.options.config.getCustomUserInputCode === undefined + ? undefined + : await input.options.config.getCustomUserInputCode(input.tenantId, input.userContext), + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + tenantId: input.tenantId, + }); if (response.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - response, - errorCodeMap, - "SIGN_IN_UP_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(response, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); } // now we send the email / text message. let magicLink = undefined; @@ -382,27 +309,29 @@ function getAPIImplementation() { let flowType = input.options.config.flowType; if (preAuthChecks.validFactorIds.every((id) => id.startsWith("link"))) { flowType = "MAGIC_LINK"; - } else if (preAuthChecks.validFactorIds.every((id) => id.startsWith("otp"))) { + } + else if (preAuthChecks.validFactorIds.every((id) => id.startsWith("otp"))) { flowType = "USER_INPUT_CODE"; - } else { + } + else { flowType = "USER_INPUT_CODE_AND_MAGIC_LINK"; } if (flowType === "MAGIC_LINK" || flowType === "USER_INPUT_CODE_AND_MAGIC_LINK") { magicLink = input.options.appInfo .getOrigin({ - request: input.options.req, - userContext: input.userContext, - }) + request: input.options.req, + userContext: input.userContext, + }) .getAsStringDangerous() + - input.options.appInfo.websiteBasePath.getAsStringDangerous() + - "/verify" + - "?preAuthSessionId=" + - response.preAuthSessionId + - "&tenantId=" + - input.tenantId + - "#" + - response.linkCode; + input.options.appInfo.websiteBasePath.getAsStringDangerous() + + "/verify" + + "?preAuthSessionId=" + + response.preAuthSessionId + + "&tenantId=" + + input.tenantId + + "#" + + response.linkCode; } if (flowType === "USER_INPUT_CODE" || flowType === "USER_INPUT_CODE_AND_MAGIC_LINK") { userInputCode = response.userInputCode; @@ -410,10 +339,8 @@ function getAPIImplementation() { // we don't do something special for serverless env here // cause we want to wait for service's reply since it can show // a UI error message for if sending an SMS / email failed or not. - if ( - input.options.config.contactMethod === "PHONE" || - (input.options.config.contactMethod === "EMAIL_OR_PHONE" && "phoneNumber" in input) - ) { + if (input.options.config.contactMethod === "PHONE" || + (input.options.config.contactMethod === "EMAIL_OR_PHONE" && "phoneNumber" in input)) { logger_1.logDebugMessage(`Sending passwordless login SMS to ${input.phoneNumber}`); await input.options.smsDelivery.ingredientInterfaceImpl.sendSms({ type: "PASSWORDLESS_LOGIN", @@ -426,7 +353,8 @@ function getAPIImplementation() { tenantId: input.tenantId, userContext: input.userContext, }); - } else { + } + else { logger_1.logDebugMessage(`Sending passwordless login email to ${input.email}`); await input.options.emailDelivery.ingredientInterfaceImpl.sendEmail({ type: "PASSWORDLESS_LOGIN", @@ -456,24 +384,17 @@ function getAPIImplementation() { doUnionOfAccountInfo: false, userContext: input.userContext, }); - const userExists = users.some((u) => - u.loginMethods.some((lm) => lm.recipeId === "passwordless" && lm.hasSameEmailAs(input.email)) - ); + const userExists = users.some((u) => u.loginMethods.some((lm) => lm.recipeId === "passwordless" && lm.hasSameEmailAs(input.email))); return { exists: userExists, status: "OK", }; }, phoneNumberExistsGET: async function (input) { - let users = await __1.listUsersByAccountInfo( - input.tenantId, - { - phoneNumber: input.phoneNumber, - // tenantId: input.tenantId, - }, - false, - input.userContext - ); + let users = await __1.listUsersByAccountInfo(input.tenantId, { + phoneNumber: input.phoneNumber, + // tenantId: input.tenantId, + }, false, input.userContext); return { exists: users.length > 0, status: "OK", @@ -490,31 +411,18 @@ function getAPIImplementation() { status: "RESTART_FLOW_ERROR", }; } - if ( - (input.options.config.contactMethod === "PHONE" && deviceInfo.phoneNumber === undefined) || - (input.options.config.contactMethod === "EMAIL" && deviceInfo.email === undefined) - ) { + if ((input.options.config.contactMethod === "PHONE" && deviceInfo.phoneNumber === undefined) || + (input.options.config.contactMethod === "EMAIL" && deviceInfo.email === undefined)) { return { status: "RESTART_FLOW_ERROR", }; } - const userWithMatchingLoginMethod = await getPasswordlessUserByAccountInfo( - Object.assign(Object.assign({}, input), { accountInfo: deviceInfo }) - ); - const authTypeInfo = await authUtils_1.AuthUtils.checkAuthTypeAndLinkingStatus( - input.session, - input.shouldTryLinkingWithSessionUser, - { - recipeId: "passwordless", - email: deviceInfo.email, - phoneNumber: deviceInfo.phoneNumber, - }, - userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 - ? void 0 - : userWithMatchingLoginMethod.user, - true, - input.userContext - ); + const userWithMatchingLoginMethod = await getPasswordlessUserByAccountInfo(Object.assign(Object.assign({}, input), { accountInfo: deviceInfo })); + const authTypeInfo = await authUtils_1.AuthUtils.checkAuthTypeAndLinkingStatus(input.session, input.shouldTryLinkingWithSessionUser, { + recipeId: "passwordless", + email: deviceInfo.email, + phoneNumber: deviceInfo.phoneNumber, + }, userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 ? void 0 : userWithMatchingLoginMethod.user, true, input.userContext); if (authTypeInfo.status === "LINKING_TO_SESSION_USER_FAILED") { // This can happen in the following edge-cases: // 1. Either the session didn't exist during createCode or the app didn't want to link to the session user @@ -533,10 +441,9 @@ function getAPIImplementation() { let response = await input.options.recipeImplementation.createNewCodeForDevice({ userContext: input.userContext, deviceId: input.deviceId, - userInputCode: - input.options.config.getCustomUserInputCode === undefined - ? undefined - : await input.options.config.getCustomUserInputCode(input.tenantId, input.userContext), + userInputCode: input.options.config.getCustomUserInputCode === undefined + ? undefined + : await input.options.config.getCustomUserInputCode(input.tenantId, input.userContext), tenantId: input.tenantId, }); if (response.status === "USER_INPUT_CODE_ALREADY_USED_ERROR") { @@ -557,46 +464,45 @@ function getAPIImplementation() { if (!authTypeInfo.isFirstFactor) { if (deviceInfo.email !== undefined) { factorIds = [multifactorauth_1.FactorIds.OTP_EMAIL]; - } else { + } + else { factorIds = [multifactorauth_1.FactorIds.OTP_PHONE]; } // We do not do further filtering here, since we know the exact factor id and the fact that it was created // which means it was allowed and the user is allowed to re-send it. // We will execute all check when the code is consumed anyway. - } else { + } + else { factorIds = utils_1.getEnabledPwlessFactors(input.options.config); - factorIds = await authUtils_1.AuthUtils.filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid( - factorIds, - input.tenantId, - false, - input.userContext - ); + factorIds = await authUtils_1.AuthUtils.filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid(factorIds, input.tenantId, false, input.userContext); } // This is correct because in createCodePOST we only allow OTP_EMAIL let flowType = input.options.config.flowType; if (factorIds.every((id) => id.startsWith("link"))) { flowType = "MAGIC_LINK"; - } else if (factorIds.every((id) => id.startsWith("otp"))) { + } + else if (factorIds.every((id) => id.startsWith("otp"))) { flowType = "USER_INPUT_CODE"; - } else { + } + else { flowType = "USER_INPUT_CODE_AND_MAGIC_LINK"; } if (flowType === "MAGIC_LINK" || flowType === "USER_INPUT_CODE_AND_MAGIC_LINK") { magicLink = input.options.appInfo .getOrigin({ - request: input.options.req, - userContext: input.userContext, - }) + request: input.options.req, + userContext: input.userContext, + }) .getAsStringDangerous() + - input.options.appInfo.websiteBasePath.getAsStringDangerous() + - "/verify" + - "?preAuthSessionId=" + - response.preAuthSessionId + - "&tenantId=" + - input.tenantId + - "#" + - response.linkCode; + input.options.appInfo.websiteBasePath.getAsStringDangerous() + + "/verify" + + "?preAuthSessionId=" + + response.preAuthSessionId + + "&tenantId=" + + input.tenantId + + "#" + + response.linkCode; } if (flowType === "USER_INPUT_CODE" || flowType === "USER_INPUT_CODE_AND_MAGIC_LINK") { userInputCode = response.userInputCode; @@ -604,11 +510,9 @@ function getAPIImplementation() { // we don't do something special for serverless env here // cause we want to wait for service's reply since it can show // a UI error message for if sending an SMS / email failed or not. - if ( - input.options.config.contactMethod === "PHONE" || + if (input.options.config.contactMethod === "PHONE" || (input.options.config.contactMethod === "EMAIL_OR_PHONE" && - deviceInfo.phoneNumber !== undefined) - ) { + deviceInfo.phoneNumber !== undefined)) { logger_1.logDebugMessage(`Sending passwordless login SMS to ${input.phoneNumber}`); await input.options.smsDelivery.ingredientInterfaceImpl.sendSms({ type: "PASSWORDLESS_LOGIN", @@ -621,7 +525,8 @@ function getAPIImplementation() { tenantId: input.tenantId, userContext: input.userContext, }); - } else { + } + else { logger_1.logDebugMessage(`Sending passwordless login email to ${input.email}`); await input.options.emailDelivery.ingredientInterfaceImpl.sendEmail({ type: "PASSWORDLESS_LOGIN", @@ -651,29 +556,18 @@ async function getPasswordlessUserByAccountInfo(input) { doUnionOfAccountInfo: false, userContext: input.userContext, }); - logger_1.logDebugMessage( - `getPasswordlessUserByAccountInfo got ${existingUsers.length} from core resp ${JSON.stringify( - input.accountInfo - )}` - ); + logger_1.logDebugMessage(`getPasswordlessUserByAccountInfo got ${existingUsers.length} from core resp ${JSON.stringify(input.accountInfo)}`); const usersWithMatchingLoginMethods = existingUsers .map((user) => ({ - user, - loginMethod: user.loginMethods.find( - (lm) => - lm.recipeId === "passwordless" && - (lm.hasSameEmailAs(input.accountInfo.email) || - lm.hasSamePhoneNumberAs(input.accountInfo.phoneNumber)) - ), - })) + user, + loginMethod: user.loginMethods.find((lm) => lm.recipeId === "passwordless" && + (lm.hasSameEmailAs(input.accountInfo.email) || + lm.hasSamePhoneNumberAs(input.accountInfo.phoneNumber))), + })) .filter(({ loginMethod }) => loginMethod !== undefined); - logger_1.logDebugMessage( - `getPasswordlessUserByAccountInfo ${usersWithMatchingLoginMethods.length} has matching login methods` - ); + logger_1.logDebugMessage(`getPasswordlessUserByAccountInfo ${usersWithMatchingLoginMethods.length} has matching login methods`); if (usersWithMatchingLoginMethods.length > 1) { - throw new Error( - "This should never happen: multiple users exist matching the accountInfo in passwordless createCode" - ); + throw new Error("This should never happen: multiple users exist matching the accountInfo in passwordless createCode"); } return usersWithMatchingLoginMethods[0]; } diff --git a/lib/build/recipe/passwordless/api/phoneNumberExists.d.ts b/lib/build/recipe/passwordless/api/phoneNumberExists.d.ts index 9d545f9dc..5ab652f23 100644 --- a/lib/build/recipe/passwordless/api/phoneNumberExists.d.ts +++ b/lib/build/recipe/passwordless/api/phoneNumberExists.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function phoneNumberExists( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function phoneNumberExists(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/passwordless/api/phoneNumberExists.js b/lib/build/recipe/passwordless/api/phoneNumberExists.js index 887522533..a471555f0 100644 --- a/lib/build/recipe/passwordless/api/phoneNumberExists.js +++ b/lib/build/recipe/passwordless/api/phoneNumberExists.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/passwordless/api/resendCode.d.ts b/lib/build/recipe/passwordless/api/resendCode.d.ts index d6fd98191..3453d3e1a 100644 --- a/lib/build/recipe/passwordless/api/resendCode.d.ts +++ b/lib/build/recipe/passwordless/api/resendCode.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function resendCode( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function resendCode(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/passwordless/api/resendCode.js b/lib/build/recipe/passwordless/api/resendCode.js index 131c04f89..a44f9239b 100644 --- a/lib/build/recipe/passwordless/api/resendCode.js +++ b/lib/build/recipe/passwordless/api/resendCode.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -42,12 +40,7 @@ async function resendCode(apiImplementation, tenantId, options, userContext) { }); } const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( - options.req, - options.res, - shouldTryLinkingWithSessionUser, - userContext - ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); let result = await apiImplementation.resendCodePOST({ deviceId, preAuthSessionId, diff --git a/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.d.ts index 3c47b9d5a..0ea2b47d1 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.d.ts @@ -2,13 +2,10 @@ import { TypePasswordlessEmailDeliveryInput } from "../../../types"; import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; import { NormalisedAppinfo, UserContext } from "../../../../../types"; -export default class BackwardCompatibilityService - implements EmailDeliveryInterface { +export default class BackwardCompatibilityService implements EmailDeliveryInterface { private appInfo; constructor(appInfo: NormalisedAppinfo); - sendEmail: ( - input: TypePasswordlessEmailDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: TypePasswordlessEmailDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.js index aacff4c5d..d4ba3f038 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.js @@ -5,25 +5,20 @@ async function createAndSendEmailUsingSupertokensService(input) { if (utils_1.isTestEnv()) { return; } - const result = await utils_1.postWithFetch( - "https://api.supertokens.io/0/st/auth/passwordless/login", - { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, - { - email: input.email, - appName: input.appInfo.appName, - codeLifetime: input.codeLifetime, - urlWithLinkCode: input.urlWithLinkCode, - userInputCode: input.userInputCode, - // isFirstFactor: input.isFirstFactor, - }, - { - successLog: `Email sent to ${input.email}`, - errorLogHeader: "Error sending passwordless login email", - } - ); + const result = await utils_1.postWithFetch("https://api.supertokens.io/0/st/auth/passwordless/login", { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, { + email: input.email, + appName: input.appInfo.appName, + codeLifetime: input.codeLifetime, + urlWithLinkCode: input.urlWithLinkCode, + userInputCode: input.userInputCode, + // isFirstFactor: input.isFirstFactor, + }, { + successLog: `Email sent to ${input.email}`, + errorLogHeader: "Error sending passwordless login email", + }); if ("error" in result) { throw result.error; } @@ -34,7 +29,8 @@ async function createAndSendEmailUsingSupertokensService(input) { * will be of type `{err: string}` */ throw new Error(result.resp.body.err); - } else { + } + else { throw new Error(`Request failed with status code ${result.resp.status}`); } } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/index.js b/lib/build/recipe/passwordless/emaildelivery/services/index.js index 7e07f6706..3e4b76c1d 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/index.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMTPService = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.d.ts index 188d41809..68fe55216 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.d.ts +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.d.ts @@ -6,9 +6,7 @@ import { UserContext } from "../../../../../types"; export default class SMTPService implements EmailDeliveryInterface { serviceImpl: ServiceInterface; constructor(config: TypeInput); - sendEmail: ( - input: TypePasswordlessEmailDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: TypePasswordlessEmailDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.js b/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.js index dedcbe33f..da1ee9c8a 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const nodemailer_1 = require("nodemailer"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); @@ -12,9 +10,7 @@ class SMTPService { constructor(config) { this.sendEmail = async (input) => { let content = await this.serviceImpl.getContent(input); - await this.serviceImpl.sendRawEmail( - Object.assign(Object.assign({}, content), { userContext: input.userContext }) - ); + await this.serviceImpl.sendRawEmail(Object.assign(Object.assign({}, content), { userContext: input.userContext })); }; const transporter = nodemailer_1.createTransport({ host: config.smtpSettings.host, @@ -25,9 +21,7 @@ class SMTPService { }, secure: config.smtpSettings.secure, }); - let builder = new supertokens_js_override_1.default( - serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) - ); + let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from)); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.d.ts b/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.d.ts index e3ae65d56..d33aac81d 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.d.ts +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.d.ts @@ -2,10 +2,4 @@ import { TypePasswordlessEmailDeliveryInput } from "../../../types"; import { GetContentResult } from "../../../../../ingredients/emaildelivery/services/smtp"; export default function getPasswordlessLoginEmailContent(input: TypePasswordlessEmailDeliveryInput): GetContentResult; -export declare function getPasswordlessLoginEmailHTML( - appName: string, - email: string, - codeLifetime: number, - urlWithLinkCode?: string, - userInputCode?: string -): string; +export declare function getPasswordlessLoginEmailHTML(appName: string, email: string, codeLifetime: number, urlWithLinkCode?: string, userInputCode?: string): string; diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.js b/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.js index 6d0aae92f..0616ac918 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPasswordlessLoginEmailHTML = void 0; const supertokens_1 = __importDefault(require("../../../../../supertokens")); @@ -11,13 +9,7 @@ const utils_1 = require("../../../../../utils"); function getPasswordlessLoginEmailContent(input) { let supertokens = supertokens_1.default.getInstanceOrThrowError(); let appName = supertokens.appInfo.appName; - let body = getPasswordlessLoginEmailHTML( - appName, - input.email, - input.codeLifetime, - input.urlWithLinkCode, - input.userInputCode - ); + let body = getPasswordlessLoginEmailHTML(appName, input.email, input.codeLifetime, input.urlWithLinkCode, input.userInputCode); return { body, toEmail: input.email, @@ -2852,24 +2844,13 @@ function getPasswordlessLoginOTPAndURLLinkBody(appName, email, codeLifetime, url } function getPasswordlessLoginEmailHTML(appName, email, codeLifetime, urlWithLinkCode, userInputCode) { if (urlWithLinkCode !== undefined && userInputCode !== undefined) { - return getPasswordlessLoginOTPAndURLLinkBody( - appName, - email, - utils_1.humaniseMilliseconds(codeLifetime), - urlWithLinkCode, - userInputCode - ); + return getPasswordlessLoginOTPAndURLLinkBody(appName, email, utils_1.humaniseMilliseconds(codeLifetime), urlWithLinkCode, userInputCode); } if (userInputCode !== undefined) { return getPasswordlessLoginOTPBody(appName, email, utils_1.humaniseMilliseconds(codeLifetime), userInputCode); } if (urlWithLinkCode !== undefined) { - return getPasswordlessLoginURLLinkBody( - appName, - email, - utils_1.humaniseMilliseconds(codeLifetime), - urlWithLinkCode - ); + return getPasswordlessLoginURLLinkBody(appName, email, utils_1.humaniseMilliseconds(codeLifetime), urlWithLinkCode); } throw Error("this should never be thrown"); } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.d.ts b/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.d.ts index 7a58ac4e4..894b09ef1 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.d.ts +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.d.ts @@ -2,10 +2,7 @@ import { TypePasswordlessEmailDeliveryInput } from "../../../types"; import { Transporter } from "nodemailer"; import { ServiceInterface } from "../../../../../ingredients/emaildelivery/services/smtp"; -export declare function getServiceImplementation( - transporter: Transporter, - from: { - name: string; - email: string; - } -): ServiceInterface; +export declare function getServiceImplementation(transporter: Transporter, from: { + name: string; + email: string; +}): ServiceInterface; diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.js b/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.js index abb00642c..844e35ec1 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const passwordlessLogin_1 = __importDefault(require("./passwordlessLogin")); @@ -31,7 +29,8 @@ function getServiceImplementation(transporter, from) { subject: input.subject, html: input.body, }); - } else { + } + else { await transporter.sendMail({ from: `${from.name} <${from.email}>`, to: input.toEmail, diff --git a/lib/build/recipe/passwordless/error.d.ts b/lib/build/recipe/passwordless/error.d.ts index 486758b61..d6412505c 100644 --- a/lib/build/recipe/passwordless/error.d.ts +++ b/lib/build/recipe/passwordless/error.d.ts @@ -1,5 +1,8 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); + constructor(options: { + type: "BAD_INPUT_ERROR"; + message: string; + }); } diff --git a/lib/build/recipe/passwordless/error.js b/lib/build/recipe/passwordless/error.js index 852278b6d..3748d17e3 100644 --- a/lib/build/recipe/passwordless/error.js +++ b/lib/build/recipe/passwordless/error.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/passwordless/index.d.ts b/lib/build/recipe/passwordless/index.d.ts index e757af855..220cac810 100644 --- a/lib/build/recipe/passwordless/index.d.ts +++ b/lib/build/recipe/passwordless/index.d.ts @@ -1,34 +1,23 @@ // @ts-nocheck import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { - RecipeInterface, - APIOptions, - APIInterface, - TypePasswordlessEmailDeliveryInput, - TypePasswordlessSmsDeliveryInput, -} from "./types"; +import { RecipeInterface, APIOptions, APIInterface, TypePasswordlessEmailDeliveryInput, TypePasswordlessSmsDeliveryInput } from "./types"; import RecipeUserId from "../../recipeUserId"; import { SessionContainerInterface } from "../session/types"; import { User } from "../../types"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static createCode( - input: ( - | { - email: string; - } - | { - phoneNumber: string; - } - ) & { - tenantId: string; - userInputCode?: string; - session?: SessionContainerInterface; - userContext?: Record; - } - ): Promise<{ + static createCode(input: ({ + email: string; + } | { + phoneNumber: string; + }) & { + tenantId: string; + userInputCode?: string; + session?: SessionContainerInterface; + userContext?: Record; + }): Promise<{ status: "OK"; preAuthSessionId: string; codeId: string; @@ -43,113 +32,89 @@ export default class Wrapper { userInputCode?: string; tenantId: string; userContext?: Record; - }): Promise< - | { - status: "OK"; - preAuthSessionId: string; - codeId: string; - deviceId: string; - userInputCode: string; - linkCode: string; - codeLifetime: number; - timeCreated: number; - } - | { - status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + preAuthSessionId: string; + codeId: string; + deviceId: string; + userInputCode: string; + linkCode: string; + codeLifetime: number; + timeCreated: number; + } | { + status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR"; + }>; /** * 1. verifies the code * 2. creates the user if it doesn't exist * 3. tries to link it * 4. marks the email as verified */ - static consumeCode( - input: - | { - preAuthSessionId: string; - userInputCode: string; - deviceId: string; - session?: undefined; - tenantId: string; - userContext?: Record; - } - | { - preAuthSessionId: string; - linkCode: string; - session?: undefined; - tenantId: string; - userContext?: Record; - } - ): Promise< - | { - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } - | { - status: "RESTART_FLOW_ERROR"; - } - >; - static consumeCode( - input: - | { - preAuthSessionId: string; - userInputCode: string; - deviceId: string; - session: SessionContainerInterface; - tenantId: string; - userContext?: Record; - } - | { - preAuthSessionId: string; - linkCode: string; - session: SessionContainerInterface; - tenantId: string; - userContext?: Record; - } - ): Promise< - | { - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } - | { - status: "RESTART_FLOW_ERROR"; - } - | { - 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"; - } - >; + static consumeCode(input: { + preAuthSessionId: string; + userInputCode: string; + deviceId: string; + session?: undefined; + tenantId: string; + userContext?: Record; + } | { + preAuthSessionId: string; + linkCode: string; + session?: undefined; + tenantId: string; + userContext?: Record; + }): Promise<{ + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } | { + status: "RESTART_FLOW_ERROR"; + }>; + static consumeCode(input: { + preAuthSessionId: string; + userInputCode: string; + deviceId: string; + session: SessionContainerInterface; + tenantId: string; + userContext?: Record; + } | { + preAuthSessionId: string; + linkCode: string; + session: SessionContainerInterface; + tenantId: string; + userContext?: Record; + }): Promise<{ + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } | { + status: "RESTART_FLOW_ERROR"; + } | { + 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 will only verify the code (not consume it), and: * NOT create a new user if it doesn't exist @@ -157,86 +122,63 @@ export default class Wrapper { * NOT do any linking * NOT delete the code unless it returned RESTART_FLOW_ERROR */ - static checkCode( - input: - | { - preAuthSessionId: string; - userInputCode: string; - deviceId: string; - tenantId: string; - userContext?: Record; - } - | { - preAuthSessionId: string; - linkCode: string; - tenantId: string; - userContext?: Record; - } - ): Promise< - | { - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - } - | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } - | { - status: "RESTART_FLOW_ERROR"; - } - >; + static checkCode(input: { + preAuthSessionId: string; + userInputCode: string; + deviceId: string; + tenantId: string; + userContext?: Record; + } | { + preAuthSessionId: string; + linkCode: string; + tenantId: string; + userContext?: Record; + }): Promise<{ + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + } | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } | { + status: "RESTART_FLOW_ERROR"; + }>; static updateUser(input: { recipeUserId: RecipeUserId; email?: string | null; phoneNumber?: string | null; userContext?: Record; - }): Promise< - | { - status: - | "OK" - | "UNKNOWN_USER_ID_ERROR" - | "EMAIL_ALREADY_EXISTS_ERROR" - | "PHONE_NUMBER_ALREADY_EXISTS_ERROR"; - } - | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } - >; - static revokeAllCodes( - input: - | { - email: string; - tenantId: string; - userContext?: Record; - } - | { - phoneNumber: string; - tenantId: string; - userContext?: Record; - } - ): Promise<{ + }): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR"; + } | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + }>; + static revokeAllCodes(input: { + email: string; + tenantId: string; + userContext?: Record; + } | { + phoneNumber: string; + tenantId: string; + userContext?: Record; + }): Promise<{ status: "OK"; }>; - static revokeCode( - input: - | { - codeId: string; - tenantId: string; - userContext?: Record; - } - | { - preAuthSessionId: string; - tenantId: string; - userContext?: Record; - } - ): Promise<{ + static revokeCode(input: { + codeId: string; + tenantId: string; + userContext?: Record; + } | { + preAuthSessionId: string; + tenantId: string; + userContext?: Record; + }): Promise<{ status: "OK"; }>; static listCodesByEmail(input: { @@ -259,49 +201,37 @@ export default class Wrapper { tenantId: string; userContext?: Record; }): Promise; - static createMagicLink( - input: - | { - email: string; - tenantId: string; - userContext?: Record; - } - | { - phoneNumber: string; - tenantId: string; - userContext?: Record; - } - ): Promise; - static signInUp( - input: - | { - email: string; - tenantId: string; - session?: SessionContainerInterface; - userContext?: Record; - } - | { - phoneNumber: string; - tenantId: string; - session?: SessionContainerInterface; - userContext?: Record; - } - ): Promise<{ + static createMagicLink(input: { + email: string; + tenantId: string; + userContext?: Record; + } | { + phoneNumber: string; + tenantId: string; + userContext?: Record; + }): Promise; + static signInUp(input: { + email: string; + tenantId: string; + session?: SessionContainerInterface; + userContext?: Record; + } | { + phoneNumber: string; + tenantId: string; + session?: SessionContainerInterface; + userContext?: Record; + }): Promise<{ status: string; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; user: User; }>; - static sendEmail( - input: TypePasswordlessEmailDeliveryInput & { - userContext?: Record; - } - ): Promise; - static sendSms( - input: TypePasswordlessSmsDeliveryInput & { - userContext?: Record; - } - ): Promise; + static sendEmail(input: TypePasswordlessEmailDeliveryInput & { + userContext?: Record; + }): Promise; + static sendSms(input: TypePasswordlessSmsDeliveryInput & { + userContext?: Record; + }): Promise; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/passwordless/index.js b/lib/build/recipe/passwordless/index.js index 285ccb9ed..8974b55bc 100644 --- a/lib/build/recipe/passwordless/index.js +++ b/lib/build/recipe/passwordless/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.sendSms = exports.sendEmail = exports.checkCode = exports.signInUp = exports.createMagicLink = exports.revokeCode = exports.revokeAllCodes = exports.updateUser = exports.createNewCodeForDevice = exports.listCodesByPreAuthSessionId = exports.listCodesByPhoneNumber = exports.listCodesByEmail = exports.listCodesByDeviceId = exports.consumeCode = exports.createCode = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -26,29 +24,13 @@ const __1 = require("../.."); const utils_1 = require("../../utils"); class Wrapper { static createCode(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createCode( - Object.assign(Object.assign({}, input), { - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - userContext: utils_1.getUserContext(input.userContext), - }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createCode(Object.assign(Object.assign({}, input), { session: input.session, shouldTryLinkingWithSessionUser: !!input.session, userContext: utils_1.getUserContext(input.userContext) })); } static createNewCodeForDevice(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.createNewCodeForDevice( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createNewCodeForDevice(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static consumeCode(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeCode( - Object.assign(Object.assign({}, input), { - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - userContext: utils_1.getUserContext(input.userContext), - }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeCode(Object.assign(Object.assign({}, input), { session: input.session, shouldTryLinkingWithSessionUser: !!input.session, userContext: utils_1.getUserContext(input.userContext) })); } /** * This function will only verify the code (not consume it), and: @@ -58,90 +40,41 @@ class Wrapper { * NOT delete the code unless it returned RESTART_FLOW_ERROR */ static checkCode(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.checkCode( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.checkCode(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static updateUser(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.updateUser( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.updateUser(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static revokeAllCodes(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.revokeAllCodes( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.revokeAllCodes(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static revokeCode(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.revokeCode( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.revokeCode(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static listCodesByEmail(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.listCodesByEmail( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.listCodesByEmail(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static listCodesByPhoneNumber(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.listCodesByPhoneNumber( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.listCodesByPhoneNumber(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static listCodesByDeviceId(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.listCodesByDeviceId( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.listCodesByDeviceId(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static listCodesByPreAuthSessionId(input) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.listCodesByPreAuthSessionId( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.listCodesByPreAuthSessionId(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static createMagicLink(input) { const ctx = utils_1.getUserContext(input.userContext); - return recipe_1.default.getInstanceOrThrowError().createMagicLink( - Object.assign(Object.assign({}, input), { - request: __1.getRequestFromUserContext(ctx), - userContext: ctx, - }) - ); + return recipe_1.default.getInstanceOrThrowError().createMagicLink(Object.assign(Object.assign({}, input), { request: __1.getRequestFromUserContext(ctx), userContext: ctx })); } static signInUp(input) { - return recipe_1.default - .getInstanceOrThrowError() - .signInUp( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return recipe_1.default.getInstanceOrThrowError().signInUp(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static async sendEmail(input) { - return await recipe_1.default - .getInstanceOrThrowError() - .emailDelivery.ingredientInterfaceImpl.sendEmail( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return await recipe_1.default.getInstanceOrThrowError().emailDelivery.ingredientInterfaceImpl.sendEmail(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } static async sendSms(input) { - return await recipe_1.default - .getInstanceOrThrowError() - .smsDelivery.ingredientInterfaceImpl.sendSms( - Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) - ); + return await recipe_1.default.getInstanceOrThrowError().smsDelivery.ingredientInterfaceImpl.sendSms(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); } } exports.default = Wrapper; diff --git a/lib/build/recipe/passwordless/recipe.d.ts b/lib/build/recipe/passwordless/recipe.d.ts index 9b3e95488..bcc885d48 100644 --- a/lib/build/recipe/passwordless/recipe.d.ts +++ b/lib/build/recipe/passwordless/recipe.d.ts @@ -18,64 +18,42 @@ export default class Recipe extends RecipeModule { isInServerlessEnv: boolean; emailDelivery: EmailDeliveryIngredient; smsDelivery: SmsDeliveryIngredient; - constructor( - recipeId: string, - appInfo: NormalisedAppinfo, - isInServerlessEnv: boolean, - config: TypeInput, - ingredients: { - emailDelivery: EmailDeliveryIngredient | undefined; - smsDelivery: SmsDeliveryIngredient | undefined; - } - ); + constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput, ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + smsDelivery: SmsDeliveryIngredient | undefined; + }); static getInstanceOrThrowError(): Recipe; static init(config: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - _: NormalisedURLPath, - __: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; - createMagicLink: ( - input: - | { - email: string; - tenantId: string; - session?: SessionContainerInterface; - request: BaseRequest | undefined; - userContext: UserContext; - } - | { - phoneNumber: string; - tenantId: string; - session?: SessionContainerInterface; - request: BaseRequest | undefined; - userContext: UserContext; - } - ) => Promise; - signInUp: ( - input: - | { - email: string; - tenantId: string; - session?: SessionContainerInterface; - userContext: UserContext; - } - | { - phoneNumber: string; - tenantId: string; - session?: SessionContainerInterface; - userContext: UserContext; - } - ) => Promise<{ + createMagicLink: (input: { + email: string; + tenantId: string; + session?: SessionContainerInterface; + request: BaseRequest | undefined; + userContext: UserContext; + } | { + phoneNumber: string; + tenantId: string; + session?: SessionContainerInterface; + request: BaseRequest | undefined; + userContext: UserContext; + }) => Promise; + signInUp: (input: { + email: string; + tenantId: string; + session?: SessionContainerInterface; + userContext: UserContext; + } | { + phoneNumber: string; + tenantId: string; + session?: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ status: string; createdNewRecipeUser: boolean; recipeUserId: import("../..").RecipeUserId; diff --git a/lib/build/recipe/passwordless/recipe.js b/lib/build/recipe/passwordless/recipe.js index 6ff155b8b..1f5146f26 100644 --- a/lib/build/recipe/passwordless/recipe.js +++ b/lib/build/recipe/passwordless/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -81,9 +79,7 @@ class Recipe extends recipeModule_1.default { id: constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD, disabled: this.apiImpl.phoneNumberExistsGET === undefined, method: "get", - pathWithoutApiBasePath: new normalisedURLPath_1.default( - constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD), }, { id: constants_1.RESEND_CODE_API, @@ -107,16 +103,17 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.CONSUME_CODE_API) { return await consumeCode_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.CREATE_CODE_API) { + } + else if (id === constants_1.CREATE_CODE_API) { return await createCode_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.DOES_EMAIL_EXIST_API || id === constants_1.DOES_EMAIL_EXIST_API_OLD) { + } + else if (id === constants_1.DOES_EMAIL_EXIST_API || id === constants_1.DOES_EMAIL_EXIST_API_OLD) { return await emailExists_1.default(this.apiImpl, tenantId, options, userContext); - } else if ( - id === constants_1.DOES_PHONE_NUMBER_EXIST_API || - id === constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD - ) { + } + else if (id === constants_1.DOES_PHONE_NUMBER_EXIST_API || id === constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD) { return await phoneNumberExists_1.default(this.apiImpl, tenantId, options, userContext); - } else { + } + else { return await resendCode_1.default(this.apiImpl, tenantId, options, userContext); } }; @@ -131,40 +128,36 @@ class Recipe extends recipeModule_1.default { }; // helper functions below... this.createMagicLink = async (input) => { - let userInputCode = - this.config.getCustomUserInputCode !== undefined - ? await this.config.getCustomUserInputCode(input.tenantId, input.userContext) - : undefined; - const codeInfo = await this.recipeInterfaceImpl.createCode( - "email" in input - ? { - email: input.email, - userInputCode, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - tenantId: input.tenantId, - userContext: input.userContext, - } - : { - phoneNumber: input.phoneNumber, - userInputCode, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - tenantId: input.tenantId, - userContext: input.userContext, - } - ); + let userInputCode = this.config.getCustomUserInputCode !== undefined + ? await this.config.getCustomUserInputCode(input.tenantId, input.userContext) + : undefined; + const codeInfo = await this.recipeInterfaceImpl.createCode("email" in input + ? { + email: input.email, + userInputCode, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + tenantId: input.tenantId, + userContext: input.userContext, + } + : { + phoneNumber: input.phoneNumber, + userInputCode, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + tenantId: input.tenantId, + userContext: input.userContext, + }); if (codeInfo.status !== "OK") { throw new Error("Failed to create user. Please retry"); } const appInfo = this.getAppInfo(); - let magicLink = - appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() + + let magicLink = appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + appInfo.websiteBasePath.getAsStringDangerous() + "/verify" + "?preAuthSessionId=" + @@ -176,46 +169,42 @@ class Recipe extends recipeModule_1.default { return magicLink; }; this.signInUp = async (input) => { - let codeInfo = await this.recipeInterfaceImpl.createCode( - "email" in input - ? { - email: input.email, - tenantId: input.tenantId, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - userContext: input.userContext, - } - : { - phoneNumber: input.phoneNumber, - tenantId: input.tenantId, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - userContext: input.userContext, - } - ); + let codeInfo = await this.recipeInterfaceImpl.createCode("email" in input + ? { + email: input.email, + tenantId: input.tenantId, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + userContext: input.userContext, + } + : { + phoneNumber: input.phoneNumber, + tenantId: input.tenantId, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + userContext: input.userContext, + }); if (codeInfo.status !== "OK") { throw new Error("Failed to create user. Please retry"); } - let consumeCodeResponse = await this.recipeInterfaceImpl.consumeCode( - this.config.flowType === "MAGIC_LINK" - ? { - preAuthSessionId: codeInfo.preAuthSessionId, - linkCode: codeInfo.linkCode, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - tenantId: input.tenantId, - userContext: input.userContext, - } - : { - preAuthSessionId: codeInfo.preAuthSessionId, - deviceId: codeInfo.deviceId, - userInputCode: codeInfo.userInputCode, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - tenantId: input.tenantId, - userContext: input.userContext, - } - ); + let consumeCodeResponse = await this.recipeInterfaceImpl.consumeCode(this.config.flowType === "MAGIC_LINK" + ? { + preAuthSessionId: codeInfo.preAuthSessionId, + linkCode: codeInfo.linkCode, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + tenantId: input.tenantId, + userContext: input.userContext, + } + : { + preAuthSessionId: codeInfo.preAuthSessionId, + deviceId: codeInfo.deviceId, + userInputCode: codeInfo.userInputCode, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + tenantId: input.tenantId, + userContext: input.userContext, + }); if (consumeCodeResponse.status === "OK") { return { status: "OK", @@ -223,16 +212,15 @@ class Recipe extends recipeModule_1.default { recipeUserId: consumeCodeResponse.recipeUserId, user: consumeCodeResponse.user, }; - } else { + } + else { throw new Error("Failed to create user. Please retry"); } }; this.isInServerlessEnv = isInServerlessEnv; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId)) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId))); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -274,18 +262,12 @@ class Recipe extends recipeModule_1.default { // so that the frontend asks the user to enter an email, // or uses the email of another login method. if (loginMethod.email !== undefined && !utils_2.isFakeEmail(loginMethod.email)) { - if ( - factorId === multifactorauth_1.FactorIds.OTP_EMAIL || - factorId === multifactorauth_1.FactorIds.LINK_EMAIL - ) { + if (factorId === multifactorauth_1.FactorIds.OTP_EMAIL || factorId === multifactorauth_1.FactorIds.LINK_EMAIL) { return true; } } if (loginMethod.phoneNumber !== undefined) { - if ( - factorId === multifactorauth_1.FactorIds.OTP_PHONE || - factorId === multifactorauth_1.FactorIds.LINK_PHONE - ) { + if (factorId === multifactorauth_1.FactorIds.OTP_PHONE || factorId === multifactorauth_1.FactorIds.LINK_PHONE) { return true; } } @@ -320,15 +302,11 @@ class Recipe extends recipeModule_1.default { // we want to ask the user to enter their email, or to use // another login method that has no fake email. if (orderedLoginMethodsByTimeJoinedOldestFirst[i].recipeId === Recipe.RECIPE_ID) { - if ( - orderedLoginMethodsByTimeJoinedOldestFirst[i].email !== undefined && - !utils_2.isFakeEmail(orderedLoginMethodsByTimeJoinedOldestFirst[i].email) - ) { + if (orderedLoginMethodsByTimeJoinedOldestFirst[i].email !== undefined && + !utils_2.isFakeEmail(orderedLoginMethodsByTimeJoinedOldestFirst[i].email)) { // loginmethods for passwordless are guaranteed to have unique emails // across all the loginmethods for a user. - nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.push( - orderedLoginMethodsByTimeJoinedOldestFirst[i].email - ); + nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.push(orderedLoginMethodsByTimeJoinedOldestFirst[i].email); } } } @@ -350,10 +328,8 @@ class Recipe extends recipeModule_1.default { emailsResult = [sessionLoginMethod.email]; } for (let i = 0; i < orderedLoginMethodsByTimeJoinedOldestFirst.length; i++) { - if ( - orderedLoginMethodsByTimeJoinedOldestFirst[i].email !== undefined && - !utils_2.isFakeEmail(orderedLoginMethodsByTimeJoinedOldestFirst[i].email) - ) { + if (orderedLoginMethodsByTimeJoinedOldestFirst[i].email !== undefined && + !utils_2.isFakeEmail(orderedLoginMethodsByTimeJoinedOldestFirst[i].email)) { // we have the if check below cause different loginMethods // across different recipes can have the same email. if (!emailsResult.includes(orderedLoginMethodsByTimeJoinedOldestFirst[i].email)) { @@ -372,20 +348,17 @@ class Recipe extends recipeModule_1.default { status: "OK", factorIdToEmailsMap, }; - } else if (nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.length === 1) { + } + else if (nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.length === 1) { // we return just this email and not others cause we want to // not create more loginMethods with passwordless for the user // object. let factorIdToEmailsMap = {}; if (allFactors.includes(multifactorauth_1.FactorIds.OTP_EMAIL)) { - factorIdToEmailsMap[ - multifactorauth_1.FactorIds.OTP_EMAIL - ] = nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined; + factorIdToEmailsMap[multifactorauth_1.FactorIds.OTP_EMAIL] = nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined; } if (allFactors.includes(multifactorauth_1.FactorIds.LINK_EMAIL)) { - factorIdToEmailsMap[ - multifactorauth_1.FactorIds.LINK_EMAIL - ] = nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined; + factorIdToEmailsMap[multifactorauth_1.FactorIds.LINK_EMAIL] = nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined; } return { status: "OK", @@ -397,10 +370,8 @@ class Recipe extends recipeModule_1.default { // if the session's email is in the list of // nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined (for better UX) let emailsResult = []; - if ( - sessionLoginMethod.email !== undefined && - nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.includes(sessionLoginMethod.email) - ) { + if (sessionLoginMethod.email !== undefined && + nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.includes(sessionLoginMethod.email)) { emailsResult = [sessionLoginMethod.email]; } for (let i = 0; i < nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.length; i++) { @@ -449,9 +420,7 @@ class Recipe extends recipeModule_1.default { if (orderedLoginMethodsByTimeJoinedOldestFirst[i].phoneNumber !== undefined) { // loginmethods for passwordless are guaranteed to have unique phone numbers // across all the loginmethods for a user. - phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.push( - orderedLoginMethodsByTimeJoinedOldestFirst[i].phoneNumber - ); + phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.push(orderedLoginMethodsByTimeJoinedOldestFirst[i].phoneNumber); } } } @@ -492,20 +461,17 @@ class Recipe extends recipeModule_1.default { status: "OK", factorIdToPhoneNumberMap, }; - } else if (phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.length === 1) { + } + else if (phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.length === 1) { // we return just this phone number and not others cause we want to // not create more loginMethods with passwordless for the user // object. let factorIdToPhoneNumberMap = {}; if (allFactors.includes(multifactorauth_1.FactorIds.OTP_PHONE)) { - factorIdToPhoneNumberMap[ - multifactorauth_1.FactorIds.OTP_PHONE - ] = phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined; + factorIdToPhoneNumberMap[multifactorauth_1.FactorIds.OTP_PHONE] = phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined; } if (allFactors.includes(multifactorauth_1.FactorIds.LINK_PHONE)) { - factorIdToPhoneNumberMap[ - multifactorauth_1.FactorIds.LINK_PHONE - ] = phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined; + factorIdToPhoneNumberMap[multifactorauth_1.FactorIds.LINK_PHONE] = phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined; } return { status: "OK", @@ -517,12 +483,8 @@ class Recipe extends recipeModule_1.default { // if the session's phone is in the list of // phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined (for better UX) let phonesResult = []; - if ( - sessionLoginMethod.phoneNumber !== undefined && - phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.includes( - sessionLoginMethod.phoneNumber - ) - ) { + if (sessionLoginMethod.phoneNumber !== undefined && + phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.includes(sessionLoginMethod.phoneNumber)) { phonesResult = [sessionLoginMethod.phoneNumber]; } for (let i = 0; i < phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.length; i++) { @@ -565,7 +527,8 @@ class Recipe extends recipeModule_1.default { smsDelivery: undefined, }); return Recipe.instance; - } else { + } + else { throw new Error("Passwordless recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/passwordless/recipeImplementation.js b/lib/build/recipe/passwordless/recipeImplementation.js index d2fb36376..d0921cebb 100644 --- a/lib/build/recipe/passwordless/recipeImplementation.js +++ b/lib/build/recipe/passwordless/recipeImplementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../accountlinking/recipe")); const recipe_2 = __importDefault(require("../emailverification/recipe")); @@ -26,11 +24,7 @@ function getRecipeInterface(querier) { } return { consumeCode: async function (input) { - const response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/consume`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + const response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/consume`), copyAndRemoveUserContextAndTenantId(input), input.userContext); if (response.status !== "OK") { return response; } @@ -39,34 +33,23 @@ function getRecipeInterface(querier) { response.recipeUserId = new recipeUserId_1.default(response.recipeUserId); // Attempt account linking (this is a sign up) let updatedUser = response.user; - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( - { - tenantId: input.tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - userContext: input.userContext, - } - ); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + tenantId: input.tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + userContext: input.userContext, + }); if (linkResult.status !== "OK") { return linkResult; } updatedUser = linkResult.user; response.user = updatedUser; - return Object.assign(Object.assign({}, response), { - consumedDevice: response.consumedDevice, - createdNewRecipeUser: response.createdNewUser, - user: response.user, - recipeUserId: response.recipeUserId, - }); + return Object.assign(Object.assign({}, response), { consumedDevice: response.consumedDevice, createdNewRecipeUser: response.createdNewUser, user: response.user, recipeUserId: response.recipeUserId }); }, checkCode: async function (input) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/check`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/check`), copyAndRemoveUserContextAndTenantId(input), input.userContext); if (response.status !== "OK") { return response; } @@ -74,69 +57,37 @@ function getRecipeInterface(querier) { return response; }, createCode: async function (input) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code`), copyAndRemoveUserContextAndTenantId(input), input.userContext); return response; }, createNewCodeForDevice: async function (input) { - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code`), copyAndRemoveUserContextAndTenantId(input), input.userContext); return response; }, listCodesByDeviceId: async function (input) { - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), copyAndRemoveUserContextAndTenantId(input), input.userContext); return response.devices.length === 1 ? response.devices[0] : undefined; }, listCodesByEmail: async function (input) { - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), copyAndRemoveUserContextAndTenantId(input), input.userContext); return response.devices; }, listCodesByPhoneNumber: async function (input) { - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), copyAndRemoveUserContextAndTenantId(input), input.userContext); return response.devices; }, listCodesByPreAuthSessionId: async function (input) { - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), copyAndRemoveUserContextAndTenantId(input), input.userContext); return response.devices.length === 1 ? response.devices[0] : undefined; }, revokeAllCodes: async function (input) { - await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes/remove`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes/remove`), copyAndRemoveUserContextAndTenantId(input), input.userContext); return { status: "OK", }; }, revokeCode: async function (input) { - await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/remove`), - copyAndRemoveUserContextAndTenantId(input), - input.userContext - ); + await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/remove`), copyAndRemoveUserContextAndTenantId(input), input.userContext); return { status: "OK" }; }, updateUser: async function (input) { @@ -165,19 +116,13 @@ function getRecipeInterface(querier) { 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.", + 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.", }; } } - let response = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/user`), - copyAndRemoveUserContextAndTenantId(input), - {}, - input.userContext - ); + let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/user`), copyAndRemoveUserContextAndTenantId(input), {}, input.userContext); if (response.status !== "OK") { return response; } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.d.ts index a0e2dbe84..edd8c7e51 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.d.ts @@ -4,9 +4,7 @@ import { SmsDeliveryInterface } from "../../../../../ingredients/smsdelivery/typ import { UserContext } from "../../../../../types"; export default class BackwardCompatibilityService implements SmsDeliveryInterface { constructor(); - sendSms: ( - input: TypePasswordlessSmsDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendSms: (input: TypePasswordlessSmsDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.js b/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.js index 7b557bdf0..a6e7cf0ed 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_1 = require("../../../../../ingredients/smsdelivery/services/supertokens"); const supertokens_2 = __importDefault(require("../../../../../supertokens")); @@ -11,28 +9,23 @@ const utils_1 = require("../../../../../utils"); async function createAndSendSmsUsingSupertokensService(input) { let supertokens = supertokens_2.default.getInstanceOrThrowError(); let appName = supertokens.appInfo.appName; - const result = await utils_1.postWithFetch( - supertokens_1.SUPERTOKENS_SMS_SERVICE_URL, - { - "api-version": "0", - "content-type": "application/json; charset=utf-8", + const result = await utils_1.postWithFetch(supertokens_1.SUPERTOKENS_SMS_SERVICE_URL, { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, { + smsInput: { + appName, + type: "PASSWORDLESS_LOGIN", + phoneNumber: input.phoneNumber, + userInputCode: input.userInputCode, + urlWithLinkCode: input.urlWithLinkCode, + codeLifetime: input.codeLifetime, + // isFirstFactor: input.isFirstFactor, }, - { - smsInput: { - appName, - type: "PASSWORDLESS_LOGIN", - phoneNumber: input.phoneNumber, - userInputCode: input.userInputCode, - urlWithLinkCode: input.urlWithLinkCode, - codeLifetime: input.codeLifetime, - // isFirstFactor: input.isFirstFactor, - }, - }, - { - successLog: `Passwordless login SMS sent to ${input.phoneNumber}`, - errorLogHeader: "Error sending passwordless login SMS", - } - ); + }, { + successLog: `Passwordless login SMS sent to ${input.phoneNumber}`, + errorLogHeader: "Error sending passwordless login SMS", + }); if ("error" in result) { throw result.error; } @@ -44,10 +37,12 @@ async function createAndSendSmsUsingSupertokensService(input) { * will be of type `{err: string}` */ throw new Error(result.resp.body.err); - } else { + } + else { throw new Error(`Request failed with status code ${result.resp.status}`); } - } else { + } + else { /** * if we do console.log(`SMS content: ${input}`); * Output would be: @@ -73,9 +68,7 @@ async function createAndSendSmsUsingSupertokensService(input) { * "b": 2 * } */ - console.log( - "Free daily SMS quota reached. If you want to use SuperTokens to send SMS, please sign up on supertokens.com to get your SMS API key, else you can also define your own method by overriding the service. For now, we are logging it below:" - ); + console.log("Free daily SMS quota reached. If you want to use SuperTokens to send SMS, please sign up on supertokens.com to get your SMS API key, else you can also define your own method by overriding the service. For now, we are logging it below:"); console.log(`\nSMS content: ${JSON.stringify(input, null, 2)}`); } } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/index.js b/lib/build/recipe/passwordless/smsdelivery/services/index.js index f85fb8900..c4ba9a912 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/index.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.SupertokensService = exports.TwilioService = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. diff --git a/lib/build/recipe/passwordless/smsdelivery/services/supertokens/index.js b/lib/build/recipe/passwordless/smsdelivery/services/supertokens/index.js index 847991a4a..dde086329 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/supertokens/index.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/supertokens/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -27,29 +25,24 @@ class SupertokensService { this.sendSms = async (input) => { let supertokens = supertokens_2.default.getInstanceOrThrowError(); let appName = supertokens.appInfo.appName; - const res = await utils_1.postWithFetch( - supertokens_1.SUPERTOKENS_SMS_SERVICE_URL, - { - "api-version": "0", - "content-type": "application/json; charset=utf-8", + const res = await utils_1.postWithFetch(supertokens_1.SUPERTOKENS_SMS_SERVICE_URL, { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, { + apiKey: this.apiKey, + smsInput: { + type: input.type, + phoneNumber: input.phoneNumber, + userInputCode: input.userInputCode, + urlWithLinkCode: input.urlWithLinkCode, + codeLifetime: input.codeLifetime, + isFirstFactor: input.isFirstFactor, + appName, }, - { - apiKey: this.apiKey, - smsInput: { - type: input.type, - phoneNumber: input.phoneNumber, - userInputCode: input.userInputCode, - urlWithLinkCode: input.urlWithLinkCode, - codeLifetime: input.codeLifetime, - isFirstFactor: input.isFirstFactor, - appName, - }, - }, - { - successLog: `Passwordless login SMS sent to ${input.phoneNumber}`, - errorLogHeader: "Error sending SMS", - } - ); + }, { + successLog: `Passwordless login SMS sent to ${input.phoneNumber}`, + errorLogHeader: "Error sending SMS", + }); if ("error" in res) { throw res.error; } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.d.ts b/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.d.ts index d5e84942d..3f5458100 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.d.ts +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.d.ts @@ -7,9 +7,7 @@ export default class TwilioService implements SmsDeliveryInterface; private config; constructor(config: TypeInput); - sendSms: ( - input: TypePasswordlessSmsDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendSms: (input: TypePasswordlessSmsDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.js b/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.js index 21dabe0bc..320a82cad 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -28,30 +26,15 @@ class TwilioService { this.sendSms = async (input) => { let content = await this.serviceImpl.getContent(input); if ("from" in this.config.twilioSettings) { - await this.serviceImpl.sendRawSms( - Object.assign(Object.assign({}, content), { - userContext: input.userContext, - from: this.config.twilioSettings.from, - }) - ); - } else { - await this.serviceImpl.sendRawSms( - Object.assign(Object.assign({}, content), { - userContext: input.userContext, - messagingServiceSid: this.config.twilioSettings.messagingServiceSid, - }) - ); + await this.serviceImpl.sendRawSms(Object.assign(Object.assign({}, content), { userContext: input.userContext, from: this.config.twilioSettings.from })); + } + else { + await this.serviceImpl.sendRawSms(Object.assign(Object.assign({}, content), { userContext: input.userContext, messagingServiceSid: this.config.twilioSettings.messagingServiceSid })); } }; this.config = twilio_1.normaliseUserInputConfig(config); - const twilioClient = twilio_2.default( - config.twilioSettings.accountSid, - config.twilioSettings.authToken, - config.twilioSettings.opts - ); - let builder = new supertokens_js_override_1.default( - serviceImplementation_1.getServiceImplementation(twilioClient) - ); + const twilioClient = twilio_2.default(config.twilioSettings.accountSid, config.twilioSettings.authToken, config.twilioSettings.opts); + let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(twilioClient)); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/passwordlessLogin.js b/lib/build/recipe/passwordless/smsdelivery/services/twilio/passwordlessLogin.js index 070ca9c75..2d6f5ce47 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/passwordlessLogin.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/passwordlessLogin.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../../../utils"); const supertokens_1 = __importDefault(require("../../../../../supertokens")); @@ -21,9 +19,11 @@ function getPasswordlessLoginSmsBody(appName, codeLifetime, urlWithLinkCode, use let message = ""; if (urlWithLinkCode !== undefined && userInputCode !== undefined) { message += `OTP to login is ${userInputCode} for ${appName}\n\nOR click ${urlWithLinkCode} to login.\n\n`; - } else if (urlWithLinkCode !== undefined) { + } + else if (urlWithLinkCode !== undefined) { message += `Click ${urlWithLinkCode} to login to ${appName}\n\n`; - } else { + } + else { message += `OTP to login is ${userInputCode} for ${appName}\n\n`; } const humanisedCodeLifetime = utils_1.humaniseMilliseconds(codeLifetime); diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.d.ts b/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.d.ts index 6aa22d4d2..7f73b5364 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.d.ts +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.d.ts @@ -2,6 +2,4 @@ import { TypePasswordlessSmsDeliveryInput } from "../../../types"; import Twilio from "twilio/lib/rest/Twilio"; import { ServiceInterface } from "../../../../../ingredients/smsdelivery/services/twilio"; -export declare function getServiceImplementation( - twilioClient: Twilio -): ServiceInterface; +export declare function getServiceImplementation(twilioClient: Twilio): ServiceInterface; diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.js b/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.js index a9a078029..f53201945 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const passwordlessLogin_1 = __importDefault(require("./passwordlessLogin")); @@ -30,7 +28,8 @@ function getServiceImplementation(twilioClient) { body: input.body, from: input.from, }); - } else { + } + else { await twilioClient.messages.create({ to: input.toPhoneNumber, body: input.body, diff --git a/lib/build/recipe/passwordless/types.d.ts b/lib/build/recipe/passwordless/types.d.ts index 434f7902e..54bed39ae 100644 --- a/lib/build/recipe/passwordless/types.d.ts +++ b/lib/build/recipe/passwordless/types.d.ts @@ -2,101 +2,64 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; import { SessionContainerInterface } from "../session/types"; -import { - TypeInput as EmailDeliveryTypeInput, - TypeInputWithService as EmailDeliveryTypeInputWithService, -} from "../../ingredients/emaildelivery/types"; +import { TypeInput as EmailDeliveryTypeInput, TypeInputWithService as EmailDeliveryTypeInputWithService } from "../../ingredients/emaildelivery/types"; import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; -import { - TypeInput as SmsDeliveryTypeInput, - TypeInputWithService as SmsDeliveryTypeInputWithService, -} from "../../ingredients/smsdelivery/types"; +import { TypeInput as SmsDeliveryTypeInput, TypeInputWithService as SmsDeliveryTypeInputWithService } from "../../ingredients/smsdelivery/types"; import SmsDeliveryIngredient from "../../ingredients/smsdelivery"; import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; -export declare type TypeInput = ( - | { - contactMethod: "PHONE"; - validatePhoneNumber?: ( - phoneNumber: string, - tenantId: string - ) => Promise | string | undefined; - } - | { - contactMethod: "EMAIL"; - validateEmailAddress?: (email: string, tenantId: string) => Promise | string | undefined; - } - | { - contactMethod: "EMAIL_OR_PHONE"; - validateEmailAddress?: (email: string, tenantId: string) => Promise | string | undefined; - validatePhoneNumber?: ( - phoneNumber: string, - tenantId: string - ) => Promise | string | undefined; - } -) & { +export declare type TypeInput = ({ + contactMethod: "PHONE"; + validatePhoneNumber?: (phoneNumber: string, tenantId: string) => Promise | string | undefined; +} | { + contactMethod: "EMAIL"; + validateEmailAddress?: (email: string, tenantId: string) => Promise | string | undefined; +} | { + contactMethod: "EMAIL_OR_PHONE"; + validateEmailAddress?: (email: string, tenantId: string) => Promise | string | undefined; + validatePhoneNumber?: (phoneNumber: string, tenantId: string) => Promise | string | undefined; +}) & { flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; emailDelivery?: EmailDeliveryTypeInput; smsDelivery?: SmsDeliveryTypeInput; getCustomUserInputCode?: (tenantId: string, userContext: UserContext) => Promise | string; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder: OverrideableBuilder) => APIInterface; }; }; -export declare type TypeNormalisedInput = ( - | { - contactMethod: "PHONE"; - validatePhoneNumber: ( - phoneNumber: string, - tenantId: string - ) => Promise | string | undefined; - } - | { - contactMethod: "EMAIL"; - validateEmailAddress: (email: string, tenantId: string) => Promise | string | undefined; - } - | { - contactMethod: "EMAIL_OR_PHONE"; - validateEmailAddress: (email: string, tenantId: string) => Promise | string | undefined; - validatePhoneNumber: ( - phoneNumber: string, - tenantId: string - ) => Promise | string | undefined; - } -) & { +export declare type TypeNormalisedInput = ({ + contactMethod: "PHONE"; + validatePhoneNumber: (phoneNumber: string, tenantId: string) => Promise | string | undefined; +} | { + contactMethod: "EMAIL"; + validateEmailAddress: (email: string, tenantId: string) => Promise | string | undefined; +} | { + contactMethod: "EMAIL_OR_PHONE"; + validateEmailAddress: (email: string, tenantId: string) => Promise | string | undefined; + validatePhoneNumber: (phoneNumber: string, tenantId: string) => Promise | string | undefined; +}) & { flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; getCustomUserInputCode?: (tenantId: string, userContext: UserContext) => Promise | string; getSmsDeliveryConfig: () => SmsDeliveryTypeInputWithService; getEmailDeliveryConfig: () => EmailDeliveryTypeInputWithService; override: { - functions: ( - originalImplementation: RecipeInterface, - builder: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder: OverrideableBuilder) => APIInterface; }; }; export declare type RecipeInterface = { - createCode: ( - input: ( - | { - email: string; - } - | { - phoneNumber: string; - } - ) & { - userInputCode?: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - tenantId: string; - userContext: UserContext; - } - ) => Promise<{ + createCode: (input: ({ + email: string; + } | { + phoneNumber: string; + }) & { + userInputCode?: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + }) => Promise<{ status: "OK"; preAuthSessionId: string; codeId: string; @@ -111,153 +74,118 @@ export declare type RecipeInterface = { userInputCode?: string; tenantId: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - preAuthSessionId: string; - codeId: string; - deviceId: string; - userInputCode: string; - linkCode: string; - codeLifetime: number; - timeCreated: number; - } - | { - status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR"; - } - >; - consumeCode: ( - input: - | { - userInputCode: string; - deviceId: string; - preAuthSessionId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - tenantId: string; - userContext: UserContext; - } - | { - linkCode: string; - preAuthSessionId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - tenantId: string; - userContext: UserContext; - } - ) => Promise< - | { - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } - | { - status: "RESTART_FLOW_ERROR"; - } - | { - 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"; - } - >; - checkCode: ( - input: - | { - userInputCode: string; - deviceId: string; - preAuthSessionId: string; - tenantId: string; - userContext: UserContext; - } - | { - linkCode: string; - preAuthSessionId: string; - tenantId: string; - userContext: UserContext; - } - ) => Promise< - | { - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - } - | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } - | { - status: "RESTART_FLOW_ERROR"; - } - >; + }) => Promise<{ + status: "OK"; + preAuthSessionId: string; + codeId: string; + deviceId: string; + userInputCode: string; + linkCode: string; + codeLifetime: number; + timeCreated: number; + } | { + status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR"; + }>; + consumeCode: (input: { + userInputCode: string; + deviceId: string; + preAuthSessionId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + } | { + linkCode: string; + preAuthSessionId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } | { + status: "RESTART_FLOW_ERROR"; + } | { + 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"; + }>; + checkCode: (input: { + userInputCode: string; + deviceId: string; + preAuthSessionId: string; + tenantId: string; + userContext: UserContext; + } | { + linkCode: string; + preAuthSessionId: string; + tenantId: string; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + } | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } | { + status: "RESTART_FLOW_ERROR"; + }>; updateUser: (input: { recipeUserId: RecipeUserId; email?: string | null; phoneNumber?: string | null; userContext: UserContext; - }) => Promise< - | { - status: - | "OK" - | "UNKNOWN_USER_ID_ERROR" - | "EMAIL_ALREADY_EXISTS_ERROR" - | "PHONE_NUMBER_ALREADY_EXISTS_ERROR"; - } - | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } - >; - revokeAllCodes: ( - input: - | { - email: string; - tenantId: string; - userContext: UserContext; - } - | { - phoneNumber: string; - tenantId: string; - userContext: UserContext; - } - ) => Promise<{ + }) => Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR"; + } | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + }>; + revokeAllCodes: (input: { + email: string; + tenantId: string; + userContext: UserContext; + } | { + phoneNumber: string; + tenantId: string; + userContext: UserContext; + }) => Promise<{ status: "OK"; }>; - revokeCode: ( - input: - | { - codeId: string; - tenantId: string; - userContext: UserContext; - } - | { - preAuthSessionId: string; - tenantId: string; - userContext: UserContext; - } - ) => Promise<{ + revokeCode: (input: { + codeId: string; + tenantId: string; + userContext: UserContext; + } | { + preAuthSessionId: string; + tenantId: string; + userContext: UserContext; + }) => Promise<{ status: "OK"; }>; - listCodesByEmail: (input: { email: string; tenantId: string; userContext: UserContext }) => Promise; + listCodesByEmail: (input: { + email: string; + tenantId: string; + userContext: UserContext; + }) => Promise; listCodesByPhoneNumber: (input: { phoneNumber: string; tenantId: string; @@ -297,114 +225,83 @@ export declare type APIOptions = { smsDelivery: SmsDeliveryIngredient; }; export declare type APIInterface = { - createCodePOST?: ( - input: ( - | { - email: string; - } - | { - phoneNumber: string; - } - ) & { - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - } - ) => Promise< - | { - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; - } - | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } - | GeneralErrorResponse - >; - resendCodePOST?: ( - input: { - deviceId: string; - preAuthSessionId: string; - } & { - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - } - ) => Promise< - | GeneralErrorResponse - | { - status: "RESTART_FLOW_ERROR" | "OK"; - } - >; - consumeCodePOST?: ( - input: ( - | { - userInputCode: string; - deviceId: string; - preAuthSessionId: string; - } - | { - linkCode: string; - preAuthSessionId: string; - } - ) & { - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - } - ) => Promise< - | { - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - session: SessionContainerInterface; - } - | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } - | { - status: "RESTART_FLOW_ERROR"; - } - | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } - | GeneralErrorResponse - >; + createCodePOST?: (input: ({ + email: string; + } | { + phoneNumber: string; + }) & { + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + deviceId: string; + preAuthSessionId: string; + flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; + } | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse>; + resendCodePOST?: (input: { + deviceId: string; + preAuthSessionId: string; + } & { + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise; + consumeCodePOST?: (input: ({ + userInputCode: string; + deviceId: string; + preAuthSessionId: string; + } | { + linkCode: string; + preAuthSessionId: string; + }) & { + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + session: SessionContainerInterface; + } | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } | { + status: "RESTART_FLOW_ERROR"; + } | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse>; emailExistsGET?: (input: { email: string; tenantId: string; options: APIOptions; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - exists: boolean; - } - | GeneralErrorResponse - >; + }) => Promise<{ + status: "OK"; + exists: boolean; + } | GeneralErrorResponse>; phoneNumberExistsGET?: (input: { phoneNumber: string; tenantId: string; options: APIOptions; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - exists: boolean; - } - | GeneralErrorResponse - >; + }) => Promise<{ + status: "OK"; + exists: boolean; + } | GeneralErrorResponse>; }; export declare type TypePasswordlessEmailDeliveryInput = { type: "PASSWORDLESS_LOGIN"; diff --git a/lib/build/recipe/passwordless/utils.d.ts b/lib/build/recipe/passwordless/utils.d.ts index 8d1b4b9f2..e72ef0708 100644 --- a/lib/build/recipe/passwordless/utils.d.ts +++ b/lib/build/recipe/passwordless/utils.d.ts @@ -2,11 +2,7 @@ import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; import { NormalisedAppinfo } from "../../types"; -export declare function validateAndNormaliseUserInput( - _: Recipe, - appInfo: NormalisedAppinfo, - config: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(_: Recipe, appInfo: NormalisedAppinfo, config: TypeInput): TypeNormalisedInput; export declare function defaultValidatePhoneNumber(value: string): Promise | string | undefined; export declare function defaultValidateEmail(value: string): Promise | string | undefined; export declare function getEnabledPwlessFactors(config: TypeInput): string[]; diff --git a/lib/build/recipe/passwordless/utils.js b/lib/build/recipe/passwordless/utils.js index 951335f9f..6e7649f70 100644 --- a/lib/build/recipe/passwordless/utils.js +++ b/lib/build/recipe/passwordless/utils.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEnabledPwlessFactors = exports.defaultValidateEmail = exports.defaultValidatePhoneNumber = exports.validateAndNormaliseUserInput = void 0; const max_1 = __importDefault(require("libphonenumber-js/max")); @@ -25,23 +23,15 @@ const backwardCompatibility_1 = __importDefault(require("./emaildelivery/service const backwardCompatibility_2 = __importDefault(require("./smsdelivery/services/backwardCompatibility")); const multifactorauth_1 = require("../multifactorauth"); function validateAndNormaliseUserInput(_, appInfo, config) { - if ( - config.contactMethod !== "PHONE" && + if (config.contactMethod !== "PHONE" && config.contactMethod !== "EMAIL" && - config.contactMethod !== "EMAIL_OR_PHONE" - ) { + config.contactMethod !== "EMAIL_OR_PHONE") { throw new Error('Please pass one of "PHONE", "EMAIL" or "EMAIL_OR_PHONE" as the contactMethod'); } if (config.flowType === undefined) { throw new Error("Please pass flowType argument in the config"); } - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config.override); function getEmailDeliveryConfig() { var _a; let emailService = (_a = config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; @@ -53,7 +43,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { if (emailService === undefined) { emailService = new backwardCompatibility_1.default(appInfo); } - let emailDelivery = Object.assign(Object.assign({}, config.emailDelivery), { + let emailDelivery = Object.assign(Object.assign({}, config.emailDelivery), { /** * if we do * let emailDelivery = { @@ -65,8 +55,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { * it it again get set to undefined, so we * set service at the end */ - service: emailService, - }); + service: emailService }); return emailDelivery; } function getSmsDeliveryConfig() { @@ -82,7 +71,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { if (smsService === undefined) { smsService = new backwardCompatibility_2.default(); } - let smsDelivery = Object.assign(Object.assign({}, config.smsDelivery), { + let smsDelivery = Object.assign(Object.assign({}, config.smsDelivery), { /** * if we do * let smsDelivery = { @@ -94,8 +83,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { * it it again get set to undefined, so we * set service at the end */ - service: smsService, - }); + service: smsService }); return smsDelivery; } if (config.contactMethod === "EMAIL") { @@ -105,32 +93,30 @@ function validateAndNormaliseUserInput(_, appInfo, config) { getSmsDeliveryConfig, flowType: config.flowType, contactMethod: "EMAIL", - validateEmailAddress: - config.validateEmailAddress === undefined ? defaultValidateEmail : config.validateEmailAddress, + validateEmailAddress: config.validateEmailAddress === undefined ? defaultValidateEmail : config.validateEmailAddress, getCustomUserInputCode: config.getCustomUserInputCode, }; - } else if (config.contactMethod === "PHONE") { + } + else if (config.contactMethod === "PHONE") { return { override, getEmailDeliveryConfig, getSmsDeliveryConfig, flowType: config.flowType, contactMethod: "PHONE", - validatePhoneNumber: - config.validatePhoneNumber === undefined ? defaultValidatePhoneNumber : config.validatePhoneNumber, + validatePhoneNumber: config.validatePhoneNumber === undefined ? defaultValidatePhoneNumber : config.validatePhoneNumber, getCustomUserInputCode: config.getCustomUserInputCode, }; - } else { + } + else { return { override, getEmailDeliveryConfig, getSmsDeliveryConfig, flowType: config.flowType, contactMethod: "EMAIL_OR_PHONE", - validateEmailAddress: - config.validateEmailAddress === undefined ? defaultValidateEmail : config.validateEmailAddress, - validatePhoneNumber: - config.validatePhoneNumber === undefined ? defaultValidatePhoneNumber : config.validatePhoneNumber, + validateEmailAddress: config.validateEmailAddress === undefined ? defaultValidateEmail : config.validateEmailAddress, + validatePhoneNumber: config.validatePhoneNumber === undefined ? defaultValidatePhoneNumber : config.validatePhoneNumber, getCustomUserInputCode: config.getCustomUserInputCode, }; } @@ -155,11 +141,7 @@ function defaultValidateEmail(value) { if (typeof value !== "string") { return "Development bug: Please make sure the email field is a string"; } - if ( - value.match( - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - ) === null - ) { + if (value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) === null) { return "Email is invalid"; } return undefined; @@ -170,31 +152,34 @@ function getEnabledPwlessFactors(config) { if (config.flowType === "MAGIC_LINK") { if (config.contactMethod === "EMAIL") { allFactors = [multifactorauth_1.FactorIds.LINK_EMAIL]; - } else if (config.contactMethod === "PHONE") { + } + else if (config.contactMethod === "PHONE") { allFactors = [multifactorauth_1.FactorIds.LINK_PHONE]; - } else { + } + else { allFactors = [multifactorauth_1.FactorIds.LINK_EMAIL, multifactorauth_1.FactorIds.LINK_PHONE]; } - } else if (config.flowType === "USER_INPUT_CODE") { + } + else if (config.flowType === "USER_INPUT_CODE") { if (config.contactMethod === "EMAIL") { allFactors = [multifactorauth_1.FactorIds.OTP_EMAIL]; - } else if (config.contactMethod === "PHONE") { + } + else if (config.contactMethod === "PHONE") { allFactors = [multifactorauth_1.FactorIds.OTP_PHONE]; - } else { + } + else { allFactors = [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.OTP_PHONE]; } - } else { + } + else { if (config.contactMethod === "EMAIL") { allFactors = [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.LINK_EMAIL]; - } else if (config.contactMethod === "PHONE") { + } + else if (config.contactMethod === "PHONE") { allFactors = [multifactorauth_1.FactorIds.OTP_PHONE, multifactorauth_1.FactorIds.LINK_PHONE]; - } else { - allFactors = [ - multifactorauth_1.FactorIds.OTP_EMAIL, - multifactorauth_1.FactorIds.OTP_PHONE, - multifactorauth_1.FactorIds.LINK_EMAIL, - multifactorauth_1.FactorIds.LINK_PHONE, - ]; + } + else { + allFactors = [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.OTP_PHONE, multifactorauth_1.FactorIds.LINK_EMAIL, multifactorauth_1.FactorIds.LINK_PHONE]; } } return allFactors; diff --git a/lib/build/recipe/session/accessToken.d.ts b/lib/build/recipe/session/accessToken.d.ts index 8b0685ec8..01a4ece91 100644 --- a/lib/build/recipe/session/accessToken.d.ts +++ b/lib/build/recipe/session/accessToken.d.ts @@ -2,11 +2,7 @@ import { ParsedJWTInfo } from "./jwt"; import * as jose from "jose"; import RecipeUserId from "../../recipeUserId"; -export declare function getInfoFromAccessToken( - jwtInfo: ParsedJWTInfo, - jwks: jose.JWTVerifyGetKey, - doAntiCsrfCheck: boolean -): Promise<{ +export declare function getInfoFromAccessToken(jwtInfo: ParsedJWTInfo, jwks: jose.JWTVerifyGetKey, doAntiCsrfCheck: boolean): Promise<{ sessionHandle: string; userId: string; recipeUserId: RecipeUserId; diff --git a/lib/build/recipe/session/accessToken.js b/lib/build/recipe/session/accessToken.js index 37249db05..472b47a78 100644 --- a/lib/build/recipe/session/accessToken.js +++ b/lib/build/recipe/session/accessToken.js @@ -13,79 +13,35 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; -var __asyncValues = - (this && this.__asyncValues) || - function (o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], - i; - return m - ? m.call(o) - : ((o = typeof __values === "function" ? __values(o) : o[Symbol.iterator]()), - (i = {}), - verb("next"), - verb("throw"), - verb("return"), - (i[Symbol.asyncIterator] = function () { - return this; - }), - i); - function verb(n) { - i[n] = - o[n] && - function (v) { - return new Promise(function (resolve, reject) { - (v = o[n](v)), settle(resolve, reject, v.done, v.value); - }); - }; - } - function settle(resolve, reject, d, v) { - Promise.resolve(v).then(function (v) { - resolve({ value: v, done: d }); - }, reject); - } - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.sanitizeNumberInput = exports.validateAccessTokenStructure = exports.getInfoFromAccessToken = void 0; const error_1 = __importDefault(require("./error")); @@ -102,48 +58,40 @@ async function getInfoFromAccessToken(jwtInfo, jwks, doAntiCsrfCheck) { let payload = undefined; try { payload = (await jose.jwtVerify(jwtInfo.rawTokenString, jwks)).payload; - } catch (error) { + } + catch (error) { // We only want to opt-into this for V2 access tokens - if ( - jwtInfo.version === 2 && - (error === null || error === void 0 ? void 0 : error.code) === "ERR_JWKS_MULTIPLE_MATCHING_KEYS" - ) { + if (jwtInfo.version === 2 && (error === null || error === void 0 ? void 0 : error.code) === "ERR_JWKS_MULTIPLE_MATCHING_KEYS") { processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.MULTI_JWKS_VALIDATION); try { // We are trying to validate the token with each key. // Since the kid is missing from v2 tokens, this basically means we try all keys present in the cache. - for ( - var error_2 = __asyncValues(error), error_2_1; - (error_2_1 = await error_2.next()), !error_2_1.done; - - ) { + for (var error_2 = __asyncValues(error), error_2_1; error_2_1 = await error_2.next(), !error_2_1.done;) { const publicKey = error_2_1.value; try { payload = (await jose.jwtVerify(jwtInfo.rawTokenString, publicKey)).payload; break; - } catch (innerError) { - if ( - (innerError === null || innerError === void 0 ? void 0 : innerError.code) === - "ERR_JWS_SIGNATURE_VERIFICATION_FAILED" - ) { + } + catch (innerError) { + if ((innerError === null || innerError === void 0 ? void 0 : innerError.code) === "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") { continue; } throw innerError; } } - } catch (e_1_1) { - e_1 = { error: e_1_1 }; - } finally { + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { try { if (error_2_1 && !error_2_1.done && (_a = error_2.return)) await _a.call(error_2); - } finally { - if (e_1) throw e_1.error; } + finally { if (e_1) throw e_1.error; } } if (payload === undefined) { throw new jose.errors.JWSSignatureVerificationFailed(); } - } else { + } + else { throw error; } } @@ -151,16 +99,14 @@ async function getInfoFromAccessToken(jwtInfo, jwks, doAntiCsrfCheck) { validateAccessTokenStructure(payload, jwtInfo.version); // We can mark these as defined (the ! after the calls), since validateAccessTokenPayload checks this let userId = jwtInfo.version === 2 ? sanitizeStringInput(payload.userId) : sanitizeStringInput(payload.sub); - let expiryTime = - jwtInfo.version === 2 ? sanitizeNumberInput(payload.expiryTime) : sanitizeNumberInput(payload.exp) * 1000; - let timeCreated = - jwtInfo.version === 2 ? sanitizeNumberInput(payload.timeCreated) : sanitizeNumberInput(payload.iat) * 1000; + let expiryTime = jwtInfo.version === 2 ? sanitizeNumberInput(payload.expiryTime) : sanitizeNumberInput(payload.exp) * 1000; + let timeCreated = jwtInfo.version === 2 + ? sanitizeNumberInput(payload.timeCreated) + : sanitizeNumberInput(payload.iat) * 1000; let userData = jwtInfo.version === 2 ? payload.userData : payload; let sessionHandle = sanitizeStringInput(payload.sessionHandle); // we use ?? below cause recipeUserId may be undefined for JWTs that are of an older version. - let recipeUserId = new recipeUserId_1.default( - (_b = sanitizeStringInput(payload.rsub)) !== null && _b !== void 0 ? _b : userId - ); + let recipeUserId = new recipeUserId_1.default((_b = sanitizeStringInput(payload.rsub)) !== null && _b !== void 0 ? _b : userId); let refreshTokenHash1 = sanitizeStringInput(payload.refreshTokenHash1); let parentRefreshTokenHash1 = sanitizeStringInput(payload.parentRefreshTokenHash1); let antiCsrfToken = sanitizeStringInput(payload.antiCsrfToken); @@ -186,11 +132,10 @@ async function getInfoFromAccessToken(jwtInfo, jwks, doAntiCsrfCheck) { recipeUserId, tenantId, }; - } catch (err) { - logger_1.logDebugMessage( - "getInfoFromAccessToken: Returning TRY_REFRESH_TOKEN because access token validation failed - " + - err.message - ); + } + catch (err) { + logger_1.logDebugMessage("getInfoFromAccessToken: Returning TRY_REFRESH_TOKEN because access token validation failed - " + + err.message); throw new error_1.default({ message: "Failed to verify access token", type: error_1.default.TRY_REFRESH_TOKEN, @@ -203,40 +148,36 @@ function validateAccessTokenStructure(payload, version) { throw Error("Wrong token type"); } if (version >= 5) { - if ( - typeof payload.sub !== "string" || + if (typeof payload.sub !== "string" || typeof payload.exp !== "number" || typeof payload.iat !== "number" || typeof payload.sessionHandle !== "string" || typeof payload.refreshTokenHash1 !== "string" || - typeof payload.rsub !== "string" - ) { + typeof payload.rsub !== "string") { logger_1.logDebugMessage("validateAccessTokenStructure: Access token is using version >= 4"); // The error message below will be logged by the error handler that translates this into a TRY_REFRESH_TOKEN_ERROR // it would come here if we change the structure of the JWT. throw Error("Access token does not contain all the information. Maybe the structure has changed?"); } - } else if (version >= 4) { - if ( - typeof payload.sub !== "string" || + } + else if (version >= 4) { + if (typeof payload.sub !== "string" || typeof payload.exp !== "number" || typeof payload.iat !== "number" || typeof payload.sessionHandle !== "string" || - typeof payload.refreshTokenHash1 !== "string" - ) { + typeof payload.refreshTokenHash1 !== "string") { logger_1.logDebugMessage("validateAccessTokenStructure: Access token is using version >= 4"); // The error message below will be logged by the error handler that translates this into a TRY_REFRESH_TOKEN_ERROR // it would come here if we change the structure of the JWT. throw Error("Access token does not contain all the information. Maybe the structure has changed?"); } - } else if (version >= 3) { - if ( - typeof payload.sub !== "string" || + } + else if (version >= 3) { + if (typeof payload.sub !== "string" || typeof payload.exp !== "number" || typeof payload.iat !== "number" || typeof payload.sessionHandle !== "string" || - typeof payload.refreshTokenHash1 !== "string" - ) { + typeof payload.refreshTokenHash1 !== "string") { logger_1.logDebugMessage("validateAccessTokenStructure: Access token is using version >= 3"); // The error message below will be logged by the error handler that translates this into a TRY_REFRESH_TOKEN_ERROR // it would come here if we change the structure of the JWT. @@ -247,14 +188,13 @@ function validateAccessTokenStructure(payload, version) { throw Error("Access token does not contain all the information. Maybe the structure has changed?"); } } - } else if ( - typeof payload.sessionHandle !== "string" || + } + else if (typeof payload.sessionHandle !== "string" || typeof payload.userId !== "string" || typeof payload.refreshTokenHash1 !== "string" || payload.userData === undefined || typeof payload.expiryTime !== "number" || - typeof payload.timeCreated !== "number" - ) { + typeof payload.timeCreated !== "number") { logger_1.logDebugMessage("validateAccessTokenStructure: Access token is using version < 3"); // The error message below will be logged by the error handler that translates this into a TRY_REFRESH_TOKEN_ERROR // it would come here if we change the structure of the JWT. @@ -272,7 +212,8 @@ function sanitizeStringInput(field) { try { let result = field.trim(); return result; - } catch (err) {} + } + catch (err) { } return undefined; } function sanitizeNumberInput(field) { diff --git a/lib/build/recipe/session/api/implementation.js b/lib/build/recipe/session/api/implementation.js index d079f77e9..8410f9ac9 100644 --- a/lib/build/recipe/session/api/implementation.js +++ b/lib/build/recipe/session/api/implementation.js @@ -1,16 +1,14 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); const sessionRequestFunctions_1 = require("../sessionRequestFunctions"); function getAPIInterface() { return { - refreshPOST: async function ({ options, userContext }) { + refreshPOST: async function ({ options, userContext, }) { return sessionRequestFunctions_1.refreshSessionInRequest({ req: options.req, res: options.res, @@ -19,7 +17,7 @@ function getAPIInterface() { recipeInterfaceImpl: options.recipeImplementation, }); }, - verifySession: async function ({ verifySessionOptions, options, userContext }) { + verifySession: async function ({ verifySessionOptions, options, userContext, }) { let method = utils_1.normaliseHttpMethod(options.req.getMethod()); if (method === "options" || method === "trace") { return undefined; @@ -34,7 +32,8 @@ function getAPIInterface() { config: options.config, recipeInterfaceImpl: options.recipeImplementation, }); - } else { + } + else { return sessionRequestFunctions_1.getSessionFromRequest({ req: options.req, res: options.res, @@ -45,7 +44,7 @@ function getAPIInterface() { }); } }, - signOutPOST: async function ({ session, userContext }) { + signOutPOST: async function ({ session, userContext, }) { await session.revokeSession(userContext); return { status: "OK", diff --git a/lib/build/recipe/session/api/refresh.d.ts b/lib/build/recipe/session/api/refresh.d.ts index 63910c21a..5fb27d06f 100644 --- a/lib/build/recipe/session/api/refresh.d.ts +++ b/lib/build/recipe/session/api/refresh.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function handleRefreshAPI( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function handleRefreshAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/session/api/signout.d.ts b/lib/build/recipe/session/api/signout.d.ts index 758ce9ed6..128c06920 100644 --- a/lib/build/recipe/session/api/signout.d.ts +++ b/lib/build/recipe/session/api/signout.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function signOutAPI( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signOutAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/session/claimBaseClasses/booleanClaim.js b/lib/build/recipe/session/claimBaseClasses/booleanClaim.js index 37e233d3f..f3d21b4de 100644 --- a/lib/build/recipe/session/claimBaseClasses/booleanClaim.js +++ b/lib/build/recipe/session/claimBaseClasses/booleanClaim.js @@ -5,10 +5,7 @@ const primitiveClaim_1 = require("./primitiveClaim"); class BooleanClaim extends primitiveClaim_1.PrimitiveClaim { constructor(conf) { super(conf); - this.validators = Object.assign(Object.assign({}, this.validators), { - isTrue: (maxAge, id) => this.validators.hasValue(true, maxAge, id), - isFalse: (maxAge, id) => this.validators.hasValue(false, maxAge, id), - }); + this.validators = Object.assign(Object.assign({}, this.validators), { isTrue: (maxAge, id) => this.validators.hasValue(true, maxAge, id), isFalse: (maxAge, id) => this.validators.hasValue(false, maxAge, id) }); } } exports.BooleanClaim = BooleanClaim; diff --git a/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.d.ts b/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.d.ts index 83db6df32..075688786 100644 --- a/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.d.ts +++ b/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.d.ts @@ -3,15 +3,13 @@ import RecipeUserId from "../../../recipeUserId"; import { JSONObject, JSONPrimitive, UserContext } from "../../../types"; import { SessionClaim, SessionClaimValidator } from "../types"; export declare class PrimitiveArrayClaim extends SessionClaim { - readonly fetchValue: ( - userId: string, - recipeUserId: RecipeUserId, - tenantId: string, - currentPayload: JSONObject | undefined, - userContext: UserContext - ) => Promise | T[] | undefined; + readonly fetchValue: (userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext) => Promise | T[] | undefined; readonly defaultMaxAgeInSeconds: number | undefined; - constructor(config: { key: string; fetchValue: SessionClaim["fetchValue"]; defaultMaxAgeInSeconds?: number }); + constructor(config: { + key: string; + fetchValue: SessionClaim["fetchValue"]; + defaultMaxAgeInSeconds?: number; + }); addToPayload_internal(payload: any, value: T[], _userContext: UserContext): any; removeFromPayloadByMerge_internal(payload: any, _userContext: UserContext): any; removeFromPayload(payload: any, _userContext: UserContext): any; diff --git a/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.js b/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.js index d057552b5..67004373b 100644 --- a/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.js +++ b/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.js @@ -10,8 +10,7 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => - this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -19,11 +18,7 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { if (claimVal === undefined) { return { isValid: false, - reason: { - message: "value does not exist", - expectedToInclude: val, - actualValue: claimVal, - }, + reason: { message: "value does not exist", expectedToInclude: val, actualValue: claimVal }, }; } const ageInSeconds = (Date.now() - this.getLastRefetchTime(payload, ctx)) / 1000; @@ -51,8 +46,7 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => - this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -92,8 +86,7 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => - this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -101,11 +94,7 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { if (claimVal === undefined) { return { isValid: false, - reason: { - message: "value does not exist", - expectedToInclude: val, - actualValue: claimVal, - }, + reason: { message: "value does not exist", expectedToInclude: val, actualValue: claimVal }, }; } const ageInSeconds = (Date.now() - this.getLastRefetchTime(payload, ctx)) / 1000; @@ -124,9 +113,9 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return isValid ? { isValid } : { - isValid, - reason: { message: "wrong value", expectedToInclude: val, actualValue: claimVal }, - }; + isValid, + reason: { message: "wrong value", expectedToInclude: val, actualValue: claimVal }, + }; }, }; }, @@ -134,8 +123,7 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => - this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -166,13 +154,13 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return isValid ? { isValid: isValid } : { - isValid, - reason: { - message: "wrong value", - expectedToIncludeAtLeastOneOf: val, - actualValue: claimVal, - }, - }; + isValid, + reason: { + message: "wrong value", + expectedToIncludeAtLeastOneOf: val, + actualValue: claimVal, + }, + }; }, }; }, @@ -180,8 +168,7 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => - this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -212,9 +199,9 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return isValid ? { isValid: isValid } : { - isValid, - reason: { message: "wrong value", expectedToNotInclude: val, actualValue: claimVal }, - }; + isValid, + reason: { message: "wrong value", expectedToNotInclude: val, actualValue: claimVal }, + }; }, }; }, @@ -223,12 +210,10 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { this.defaultMaxAgeInSeconds = config.defaultMaxAgeInSeconds; } addToPayload_internal(payload, value, _userContext) { - return Object.assign(Object.assign({}, payload), { - [this.key]: { + return Object.assign(Object.assign({}, payload), { [this.key]: { v: value, t: Date.now(), - }, - }); + } }); } removeFromPayloadByMerge_internal(payload, _userContext) { const res = Object.assign(Object.assign({}, payload), { [this.key]: null }); diff --git a/lib/build/recipe/session/claimBaseClasses/primitiveClaim.d.ts b/lib/build/recipe/session/claimBaseClasses/primitiveClaim.d.ts index 62c5bfe32..327ac6033 100644 --- a/lib/build/recipe/session/claimBaseClasses/primitiveClaim.d.ts +++ b/lib/build/recipe/session/claimBaseClasses/primitiveClaim.d.ts @@ -3,15 +3,13 @@ import RecipeUserId from "../../../recipeUserId"; import { JSONObject, JSONPrimitive, UserContext } from "../../../types"; import { SessionClaim, SessionClaimValidator } from "../types"; export declare class PrimitiveClaim extends SessionClaim { - readonly fetchValue: ( - userId: string, - recipeUserId: RecipeUserId, - tenantId: string, - currentPayload: JSONObject | undefined, - userContext: UserContext - ) => Promise | T | undefined; + readonly fetchValue: (userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext) => Promise | T | undefined; readonly defaultMaxAgeInSeconds: number | undefined; - constructor(config: { key: string; fetchValue: SessionClaim["fetchValue"]; defaultMaxAgeInSeconds?: number }); + constructor(config: { + key: string; + fetchValue: SessionClaim["fetchValue"]; + defaultMaxAgeInSeconds?: number; + }); addToPayload_internal(payload: any, value: T, _userContext: UserContext): any; removeFromPayloadByMerge_internal(payload: any, _userContext: UserContext): any; removeFromPayload(payload: any, _userContext: UserContext): any; diff --git a/lib/build/recipe/session/claimBaseClasses/primitiveClaim.js b/lib/build/recipe/session/claimBaseClasses/primitiveClaim.js index 12407fabc..35eee64fe 100644 --- a/lib/build/recipe/session/claimBaseClasses/primitiveClaim.js +++ b/lib/build/recipe/session/claimBaseClasses/primitiveClaim.js @@ -10,8 +10,7 @@ class PrimitiveClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => - this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || (maxAgeInSeconds !== undefined && // We know payload[this.id] is defined since the value is not undefined in this branch payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -48,12 +47,10 @@ class PrimitiveClaim extends types_1.SessionClaim { this.defaultMaxAgeInSeconds = config.defaultMaxAgeInSeconds; } addToPayload_internal(payload, value, _userContext) { - return Object.assign(Object.assign({}, payload), { - [this.key]: { + return Object.assign(Object.assign({}, payload), { [this.key]: { v: value, t: Date.now(), - }, - }); + } }); } removeFromPayloadByMerge_internal(payload, _userContext) { const res = Object.assign(Object.assign({}, payload), { [this.key]: null }); diff --git a/lib/build/recipe/session/claims.js b/lib/build/recipe/session/claims.js index 30fae1663..4a547fb2e 100644 --- a/lib/build/recipe/session/claims.js +++ b/lib/build/recipe/session/claims.js @@ -2,30 +2,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.BooleanClaim = exports.PrimitiveArrayClaim = exports.PrimitiveClaim = exports.SessionClaim = void 0; var types_1 = require("./types"); -Object.defineProperty(exports, "SessionClaim", { - enumerable: true, - get: function () { - return types_1.SessionClaim; - }, -}); +Object.defineProperty(exports, "SessionClaim", { enumerable: true, get: function () { return types_1.SessionClaim; } }); var primitiveClaim_1 = require("./claimBaseClasses/primitiveClaim"); -Object.defineProperty(exports, "PrimitiveClaim", { - enumerable: true, - get: function () { - return primitiveClaim_1.PrimitiveClaim; - }, -}); +Object.defineProperty(exports, "PrimitiveClaim", { enumerable: true, get: function () { return primitiveClaim_1.PrimitiveClaim; } }); var primitiveArrayClaim_1 = require("./claimBaseClasses/primitiveArrayClaim"); -Object.defineProperty(exports, "PrimitiveArrayClaim", { - enumerable: true, - get: function () { - return primitiveArrayClaim_1.PrimitiveArrayClaim; - }, -}); +Object.defineProperty(exports, "PrimitiveArrayClaim", { enumerable: true, get: function () { return primitiveArrayClaim_1.PrimitiveArrayClaim; } }); var booleanClaim_1 = require("./claimBaseClasses/booleanClaim"); -Object.defineProperty(exports, "BooleanClaim", { - enumerable: true, - get: function () { - return booleanClaim_1.BooleanClaim; - }, -}); +Object.defineProperty(exports, "BooleanClaim", { enumerable: true, get: function () { return booleanClaim_1.BooleanClaim; } }); diff --git a/lib/build/recipe/session/cookieAndHeaders.d.ts b/lib/build/recipe/session/cookieAndHeaders.d.ts index 804655add..7b08a4ec0 100644 --- a/lib/build/recipe/session/cookieAndHeaders.d.ts +++ b/lib/build/recipe/session/cookieAndHeaders.d.ts @@ -2,19 +2,8 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import { UserContext } from "../../types"; import { TokenTransferMethod, TokenType, TypeNormalisedInput } from "./types"; -export declare function clearSessionFromAllTokenTransferMethods( - config: TypeNormalisedInput, - res: BaseResponse, - request: BaseRequest | undefined, - userContext: UserContext -): void; -export declare function clearSession( - config: TypeNormalisedInput, - res: BaseResponse, - transferMethod: TokenTransferMethod, - request: BaseRequest | undefined, - userContext: UserContext -): void; +export declare function clearSessionFromAllTokenTransferMethods(config: TypeNormalisedInput, res: BaseResponse, request: BaseRequest | undefined, userContext: UserContext): void; +export declare function clearSession(config: TypeNormalisedInput, res: BaseResponse, transferMethod: TokenTransferMethod, request: BaseRequest | undefined, userContext: UserContext): void; export declare function getAntiCsrfTokenFromHeaders(req: BaseRequest): string | undefined; export declare function setAntiCsrfTokenInHeaders(res: BaseResponse, antiCsrfToken: string): void; export declare function buildFrontToken(userId: string, atExpiry: number, accessTokenPayload: any): string; @@ -22,21 +11,8 @@ export declare function setFrontTokenInHeaders(res: BaseResponse, frontToken: st export declare function getCORSAllowedHeaders(): string[]; export declare function getCookieNameFromTokenType(tokenType: TokenType): "sAccessToken" | "sRefreshToken"; export declare function getResponseHeaderNameForTokenType(tokenType: TokenType): "st-access-token" | "st-refresh-token"; -export declare function getToken( - req: BaseRequest, - tokenType: TokenType, - transferMethod: TokenTransferMethod -): string | undefined; -export declare function setToken( - config: TypeNormalisedInput, - res: BaseResponse, - tokenType: TokenType, - value: string, - expires: number, - transferMethod: TokenTransferMethod, - req: BaseRequest | undefined, - userContext: UserContext -): void; +export declare function getToken(req: BaseRequest, tokenType: TokenType, transferMethod: TokenTransferMethod): string | undefined; +export declare function setToken(config: TypeNormalisedInput, res: BaseResponse, tokenType: TokenType, value: string, expires: number, transferMethod: TokenTransferMethod, req: BaseRequest | undefined, userContext: UserContext): void; export declare function setHeader(res: BaseResponse, name: string, value: string): void; /** * @@ -49,16 +25,7 @@ export declare function setHeader(res: BaseResponse, name: string, value: string * @param expires * @param path */ -export declare function setCookie( - config: TypeNormalisedInput, - res: BaseResponse, - name: string, - value: string, - expires: number, - pathType: "refreshTokenPath" | "accessTokenPath", - req: BaseRequest | undefined, - userContext: UserContext -): void; +export declare function setCookie(config: TypeNormalisedInput, res: BaseResponse, name: string, value: string, expires: number, pathType: "refreshTokenPath" | "accessTokenPath", req: BaseRequest | undefined, userContext: UserContext): void; export declare function getAuthModeFromHeader(req: BaseRequest): string | undefined; /** * This function addresses an edge case where changing the cookieDomain config on the server can @@ -72,12 +39,7 @@ export declare function getAuthModeFromHeader(req: BaseRequest): string | undefi * * This function checks for multiple cookies with the same name and clears the cookies for the older domain */ -export declare function clearSessionCookiesFromOlderCookieDomain({ - req, - res, - config, - userContext, -}: { +export declare function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContext, }: { req: BaseRequest; res: BaseResponse; config: TypeNormalisedInput; diff --git a/lib/build/recipe/session/cookieAndHeaders.js b/lib/build/recipe/session/cookieAndHeaders.js index 26c1d3475..c029eca52 100644 --- a/lib/build/recipe/session/cookieAndHeaders.js +++ b/lib/build/recipe/session/cookieAndHeaders.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.hasMultipleCookiesForTokenType = exports.clearSessionCookiesFromOlderCookieDomain = exports.getAuthModeFromHeader = exports.setCookie = exports.setHeader = exports.setToken = exports.getToken = exports.getResponseHeaderNameForTokenType = exports.getCookieNameFromTokenType = exports.getCORSAllowedHeaders = exports.setFrontTokenInHeaders = exports.buildFrontToken = exports.setAntiCsrfTokenInHeaders = exports.getAntiCsrfTokenFromHeaders = exports.clearSession = exports.clearSessionFromAllTokenTransferMethods = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -109,13 +107,15 @@ exports.getResponseHeaderNameForTokenType = getResponseHeaderNameForTokenType; function getToken(req, tokenType, transferMethod) { if (transferMethod === "cookie") { return req.getCookieValue(getCookieNameFromTokenType(tokenType)); - } else if (transferMethod === "header") { + } + else if (transferMethod === "header") { const value = req.getHeaderValue(authorizationHeaderKey); if (value === undefined || !value.startsWith("Bearer ")) { return undefined; } return value.replace(/^Bearer /, "").trim(); - } else { + } + else { throw new Error("Should never happen: Unknown transferMethod: " + transferMethod); } } @@ -123,17 +123,9 @@ exports.getToken = getToken; function setToken(config, res, tokenType, value, expires, transferMethod, req, userContext) { logger_1.logDebugMessage(`setToken: Setting ${tokenType} token as ${transferMethod}`); if (transferMethod === "cookie") { - setCookie( - config, - res, - getCookieNameFromTokenType(tokenType), - value, - expires, - tokenType === "refresh" ? "refreshTokenPath" : "accessTokenPath", - req, - userContext - ); - } else if (transferMethod === "header") { + setCookie(config, res, getCookieNameFromTokenType(tokenType), value, expires, tokenType === "refresh" ? "refreshTokenPath" : "accessTokenPath", req, userContext); + } + else if (transferMethod === "header") { setHeader(res, getResponseHeaderNameForTokenType(tokenType), value); } } @@ -164,7 +156,8 @@ function setCookie(config, res, name, value, expires, pathType, req, userContext let path = ""; if (pathType === "refreshTokenPath") { path = config.refreshTokenPath.getAsStringDangerous(); - } else if (pathType === "accessTokenPath") { + } + else if (pathType === "accessTokenPath") { path = config.accessTokenPath.getAsStringDangerous() === "" ? "/" : config.accessTokenPath.getAsStringDangerous(); } @@ -189,7 +182,7 @@ exports.getAuthModeFromHeader = getAuthModeFromHeader; * * This function checks for multiple cookies with the same name and clears the cookies for the older domain */ -function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContext }) { +function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContext, }) { const allowedTransferMethod = config.getTokenTransferMethod({ req, forCreateNewSession: false, @@ -208,30 +201,16 @@ function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContex // Using the wrong cookie can cause an infinite refresh loop. To avoid this, // we throw a 500 error asking the user to set 'olderCookieDomain'. if (config.olderCookieDomain === undefined) { - throw new Error( - `The request contains multiple session cookies. This may happen if you've changed the 'cookieDomain' value in your configuration. To clear tokens from the previous domain, set 'olderCookieDomain' in your config.` - ); + throw new Error(`The request contains multiple session cookies. This may happen if you've changed the 'cookieDomain' value in your configuration. To clear tokens from the previous domain, set 'olderCookieDomain' in your config.`); } - logger_1.logDebugMessage( - `clearSessionCookiesFromOlderCookieDomain: Clearing duplicate ${token} cookie with domain ${config.olderCookieDomain}` - ); - setToken( - Object.assign(Object.assign({}, config), { cookieDomain: config.olderCookieDomain }), - res, - token, - "", - 0, - "cookie", - req, - userContext - ); + logger_1.logDebugMessage(`clearSessionCookiesFromOlderCookieDomain: Clearing duplicate ${token} cookie with domain ${config.olderCookieDomain}`); + setToken(Object.assign(Object.assign({}, config), { cookieDomain: config.olderCookieDomain }), res, token, "", 0, "cookie", req, userContext); didClearCookies = true; } } if (didClearCookies) { throw new error_1.default({ - message: - "The request contains multiple session cookies. We are clearing the cookie from olderCookieDomain. Session will be refreshed in the next refresh call.", + message: "The request contains multiple session cookies. We are clearing the cookie from olderCookieDomain. Session will be refreshed in the next refresh call.", type: error_1.default.CLEAR_DUPLICATE_SESSION_COOKIES, }); } @@ -257,17 +236,19 @@ function parseCookieStringFromRequestHeaderAllowingDuplicates(cookieString) { .trim() .split("=") .map((part) => { - try { - return decodeURIComponent(part); - } catch (e) { - // this is there in case the cookie value is not encoded. This can happe - // if the user has set their own cookie in a different format. - return part; - } - }); + try { + return decodeURIComponent(part); + } + catch (e) { + // this is there in case the cookie value is not encoded. This can happe + // if the user has set their own cookie in a different format. + return part; + } + }); if (cookies.hasOwnProperty(name)) { cookies[name].push(value); - } else { + } + else { cookies[name] = [value]; } } diff --git a/lib/build/recipe/session/error.d.ts b/lib/build/recipe/session/error.d.ts index fae7a4fd4..8e472802c 100644 --- a/lib/build/recipe/session/error.d.ts +++ b/lib/build/recipe/session/error.d.ts @@ -8,36 +8,29 @@ export default class SessionError extends STError { static TOKEN_THEFT_DETECTED: "TOKEN_THEFT_DETECTED"; static INVALID_CLAIMS: "INVALID_CLAIMS"; static CLEAR_DUPLICATE_SESSION_COOKIES: "CLEAR_DUPLICATE_SESSION_COOKIES"; - constructor( - options: - | { - message: string; - type: "UNAUTHORISED"; - payload?: { - clearTokens: boolean; - }; - } - | { - message: string; - type: "TRY_REFRESH_TOKEN"; - } - | { - message: string; - type: "TOKEN_THEFT_DETECTED"; - payload: { - userId: string; - recipeUserId: RecipeUserId; - sessionHandle: string; - }; - } - | { - message: string; - type: "INVALID_CLAIMS"; - payload: ClaimValidationError[]; - } - | { - message: string; - type: "CLEAR_DUPLICATE_SESSION_COOKIES"; - } - ); + constructor(options: { + message: string; + type: "UNAUTHORISED"; + payload?: { + clearTokens: boolean; + }; + } | { + message: string; + type: "TRY_REFRESH_TOKEN"; + } | { + message: string; + type: "TOKEN_THEFT_DETECTED"; + payload: { + userId: string; + recipeUserId: RecipeUserId; + sessionHandle: string; + }; + } | { + message: string; + type: "INVALID_CLAIMS"; + payload: ClaimValidationError[]; + } | { + message: string; + type: "CLEAR_DUPLICATE_SESSION_COOKIES"; + }); } diff --git a/lib/build/recipe/session/error.js b/lib/build/recipe/session/error.js index 620ec994a..803151c21 100644 --- a/lib/build/recipe/session/error.js +++ b/lib/build/recipe/session/error.js @@ -13,24 +13,17 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { constructor(options) { - super( - options.type === "UNAUTHORISED" && options.payload === undefined - ? Object.assign(Object.assign({}, options), { - payload: { - clearTokens: true, - }, - }) - : Object.assign({}, options) - ); + super(options.type === "UNAUTHORISED" && options.payload === undefined + ? Object.assign(Object.assign({}, options), { payload: { + clearTokens: true, + } }) : Object.assign({}, options)); this.fromRecipe = "session"; } } diff --git a/lib/build/recipe/session/framework/awsLambda.js b/lib/build/recipe/session/framework/awsLambda.js index 1e6b919f7..ce8a217b3 100644 --- a/lib/build/recipe/session/framework/awsLambda.js +++ b/lib/build/recipe/session/framework/awsLambda.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; const framework_1 = require("../../../framework/awsLambda/framework"); @@ -21,7 +19,8 @@ function verifySession(handler, verifySessionOptions) { event.session = await sessionRecipe.verifySession(verifySessionOptions, request, response, userContext); let handlerResult = await handler(event, context, callback); return response.sendResponse(handlerResult); - } catch (err) { + } + catch (err) { await supertokens.errorHandler(err, request, response, userContext); if (response.responseSet) { return response.sendResponse({}); diff --git a/lib/build/recipe/session/framework/custom.d.ts b/lib/build/recipe/session/framework/custom.d.ts index 23c82a5c2..c35f771cd 100644 --- a/lib/build/recipe/session/framework/custom.d.ts +++ b/lib/build/recipe/session/framework/custom.d.ts @@ -3,8 +3,6 @@ import type { VerifySessionOptions } from ".."; import { BaseRequest, BaseResponse } from "../../../framework"; import { NextFunction } from "../../../framework/custom/framework"; import { SessionContainerInterface } from "../types"; -export declare function verifySession< - T extends BaseRequest & { - session?: SessionContainerInterface; - } ->(options?: VerifySessionOptions): (req: T, res: BaseResponse, next?: NextFunction | undefined) => Promise; +export declare function verifySession(options?: VerifySessionOptions): (req: T, res: BaseResponse, next?: NextFunction | undefined) => Promise; diff --git a/lib/build/recipe/session/framework/custom.js b/lib/build/recipe/session/framework/custom.js index 0615fe8b7..4e39856a2 100644 --- a/lib/build/recipe/session/framework/custom.js +++ b/lib/build/recipe/session/framework/custom.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -33,12 +31,14 @@ function verifySession(options) { next(); } return undefined; - } catch (err) { + } + catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, req, res, userContext); return undefined; - } catch (_a) { + } + catch (_a) { if (next !== undefined) { next(err); } diff --git a/lib/build/recipe/session/framework/express.d.ts b/lib/build/recipe/session/framework/express.d.ts index bcb0bf234..48db51d80 100644 --- a/lib/build/recipe/session/framework/express.d.ts +++ b/lib/build/recipe/session/framework/express.d.ts @@ -2,6 +2,4 @@ import type { VerifySessionOptions } from ".."; import type { SessionRequest } from "../../../framework/express/framework"; import type { NextFunction, Response } from "express"; -export declare function verifySession( - options?: VerifySessionOptions -): (req: SessionRequest, res: Response, next: NextFunction) => Promise; +export declare function verifySession(options?: VerifySessionOptions): (req: SessionRequest, res: Response, next: NextFunction) => Promise; diff --git a/lib/build/recipe/session/framework/express.js b/lib/build/recipe/session/framework/express.js index a99ef67ce..885a7522e 100644 --- a/lib/build/recipe/session/framework/express.js +++ b/lib/build/recipe/session/framework/express.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -33,11 +31,13 @@ function verifySession(options) { const sessionRecipe = recipe_1.default.getInstanceOrThrowError(); req.session = await sessionRecipe.verifySession(options, request, response, userContext); next(); - } catch (err) { + } + catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); - } catch (_a) { + } + catch (_a) { next(err); } } diff --git a/lib/build/recipe/session/framework/fastify.d.ts b/lib/build/recipe/session/framework/fastify.d.ts index 74724d38d..1dc347fa7 100644 --- a/lib/build/recipe/session/framework/fastify.d.ts +++ b/lib/build/recipe/session/framework/fastify.d.ts @@ -2,6 +2,4 @@ import { VerifySessionOptions } from ".."; import { SessionRequest } from "../../../framework/fastify/framework"; import { FastifyReply, FastifyRequest as OriginalFastifyRequest } from "../../../framework/fastify/types"; -export declare function verifySession( - options?: VerifySessionOptions -): (req: SessionRequest, res: FastifyReply) => Promise; +export declare function verifySession(options?: VerifySessionOptions): (req: SessionRequest, res: FastifyReply) => Promise; diff --git a/lib/build/recipe/session/framework/fastify.js b/lib/build/recipe/session/framework/fastify.js index 119d5dd24..cfa4e4161 100644 --- a/lib/build/recipe/session/framework/fastify.js +++ b/lib/build/recipe/session/framework/fastify.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -32,7 +30,8 @@ function verifySession(options) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { req.session = await sessionRecipe.verifySession(options, request, response, userContext); - } catch (err) { + } + catch (err) { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); throw err; diff --git a/lib/build/recipe/session/framework/hapi.d.ts b/lib/build/recipe/session/framework/hapi.d.ts index 97456f934..8241dc4a2 100644 --- a/lib/build/recipe/session/framework/hapi.d.ts +++ b/lib/build/recipe/session/framework/hapi.d.ts @@ -2,6 +2,4 @@ import { VerifySessionOptions } from ".."; import { ResponseToolkit } from "@hapi/hapi"; import { SessionRequest } from "../../../framework/hapi/framework"; -export declare function verifySession( - options?: VerifySessionOptions -): (req: SessionRequest, h: ResponseToolkit) => Promise; +export declare function verifySession(options?: VerifySessionOptions): (req: SessionRequest, h: ResponseToolkit) => Promise; diff --git a/lib/build/recipe/session/framework/hapi.js b/lib/build/recipe/session/framework/hapi.js index be8581c64..9b1353c8f 100644 --- a/lib/build/recipe/session/framework/hapi.js +++ b/lib/build/recipe/session/framework/hapi.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -32,7 +30,8 @@ function verifySession(options) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { req.session = await sessionRecipe.verifySession(options, request, response, userContext); - } catch (err) { + } + catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); @@ -43,7 +42,8 @@ function verifySession(options) { }); return resObj.takeover(); } - } catch (_a) { + } + catch (_a) { // We catch and ignore since we want to re-throw the original error if handling wasn't successful throw err; } diff --git a/lib/build/recipe/session/framework/index.js b/lib/build/recipe/session/framework/index.js index 8aa3b653e..a5541f367 100644 --- a/lib/build/recipe/session/framework/index.js +++ b/lib/build/recipe/session/framework/index.js @@ -1,40 +1,23 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.awsLambda = exports.koa = exports.loopback = exports.hapi = exports.fastify = exports.express = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. diff --git a/lib/build/recipe/session/framework/koa.d.ts b/lib/build/recipe/session/framework/koa.d.ts index 9e23ae80b..7a73cb667 100644 --- a/lib/build/recipe/session/framework/koa.d.ts +++ b/lib/build/recipe/session/framework/koa.d.ts @@ -2,6 +2,4 @@ import type { VerifySessionOptions } from ".."; import type { Next } from "koa"; import type { SessionContext } from "../../../framework/koa/framework"; -export declare function verifySession( - options?: VerifySessionOptions -): (ctx: SessionContext, next: Next) => Promise; +export declare function verifySession(options?: VerifySessionOptions): (ctx: SessionContext, next: Next) => Promise; diff --git a/lib/build/recipe/session/framework/koa.js b/lib/build/recipe/session/framework/koa.js index 12f1d474a..179c641a1 100644 --- a/lib/build/recipe/session/framework/koa.js +++ b/lib/build/recipe/session/framework/koa.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -32,12 +30,14 @@ function verifySession(options) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { ctx.session = await sessionRecipe.verifySession(options, request, response, userContext); - } catch (err) { + } + catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); return; - } catch (_a) { + } + catch (_a) { // We catch and ignore since we want to re-throw the original error if handling wasn't successful throw err; } diff --git a/lib/build/recipe/session/framework/loopback.js b/lib/build/recipe/session/framework/loopback.js index 7b4feb2da..07aee4b41 100644 --- a/lib/build/recipe/session/framework/loopback.js +++ b/lib/build/recipe/session/framework/loopback.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -33,12 +31,14 @@ function verifySession(options) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { middlewareCtx.session = await sessionRecipe.verifySession(options, request, response, userContext); - } catch (err) { + } + catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); return; - } catch (_a) { + } + catch (_a) { // We catch and ignore since we want to re-throw the original error if handling wasn't successful throw err; } diff --git a/lib/build/recipe/session/index.d.ts b/lib/build/recipe/session/index.d.ts index 8115d4727..257dba1ff 100644 --- a/lib/build/recipe/session/index.d.ts +++ b/lib/build/recipe/session/index.d.ts @@ -1,79 +1,28 @@ // @ts-nocheck import SuperTokensError from "./error"; -import { - VerifySessionOptions, - SessionContainerInterface as SessionContainer, - SessionInformation, - APIInterface, - APIOptions, - SessionClaimValidator, - SessionClaim, - ClaimValidationError, - RecipeInterface, -} from "./types"; +import { VerifySessionOptions, SessionContainerInterface as SessionContainer, SessionInformation, APIInterface, APIOptions, SessionClaimValidator, SessionClaim, ClaimValidationError, RecipeInterface } from "./types"; import Recipe from "./recipe"; import { JSONObject, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; export default class SessionWrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static createNewSession( - req: any, - res: any, - tenantId: string, - recipeUserId: RecipeUserId, - accessTokenPayload?: any, - sessionDataInDatabase?: any, - userContext?: Record - ): Promise; - static createNewSessionWithoutRequestResponse( - tenantId: string, - recipeUserId: RecipeUserId, - accessTokenPayload?: any, - sessionDataInDatabase?: any, - disableAntiCsrf?: boolean, - userContext?: Record - ): Promise; - static validateClaimsForSessionHandle( - sessionHandle: string, - overrideGlobalClaimValidators?: ( - globalClaimValidators: SessionClaimValidator[], - sessionInfo: SessionInformation, - userContext: UserContext - ) => Promise | SessionClaimValidator[], - userContext?: Record - ): Promise< - | { - status: "SESSION_DOES_NOT_EXIST_ERROR"; - } - | { - status: "OK"; - invalidClaims: ClaimValidationError[]; - } - >; + static createNewSession(req: any, res: any, tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, userContext?: Record): Promise; + static createNewSessionWithoutRequestResponse(tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, disableAntiCsrf?: boolean, userContext?: Record): Promise; + static validateClaimsForSessionHandle(sessionHandle: string, overrideGlobalClaimValidators?: (globalClaimValidators: SessionClaimValidator[], sessionInfo: SessionInformation, userContext: UserContext) => Promise | SessionClaimValidator[], userContext?: Record): Promise<{ + status: "SESSION_DOES_NOT_EXIST_ERROR"; + } | { + status: "OK"; + invalidClaims: ClaimValidationError[]; + }>; static getSession(req: any, res: any): Promise; - static getSession( - req: any, - res: any, - options?: VerifySessionOptions & { - sessionRequired?: true; - }, - userContext?: Record - ): Promise; - static getSession( - req: any, - res: any, - options?: VerifySessionOptions & { - sessionRequired: false; - }, - userContext?: Record - ): Promise; - static getSession( - req: any, - res: any, - options?: VerifySessionOptions, - userContext?: Record - ): Promise; + static getSession(req: any, res: any, options?: VerifySessionOptions & { + sessionRequired?: true; + }, userContext?: Record): Promise; + static getSession(req: any, res: any, options?: VerifySessionOptions & { + sessionRequired: false; + }, userContext?: Record): Promise; + static getSession(req: any, res: any, options?: VerifySessionOptions, userContext?: Record): Promise; /** * Tries to validate an access token and build a Session object from it. * @@ -95,86 +44,33 @@ export default class SessionWrapper { * @param userContext User context */ static getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string): Promise; - static getSessionWithoutRequestResponse( - accessToken: string, - antiCsrfToken?: string, - options?: VerifySessionOptions & { - sessionRequired?: true; - }, - userContext?: Record - ): Promise; - static getSessionWithoutRequestResponse( - accessToken: string, - antiCsrfToken?: string, - options?: VerifySessionOptions & { - sessionRequired: false; - }, - userContext?: Record - ): Promise; - static getSessionWithoutRequestResponse( - accessToken: string, - antiCsrfToken?: string, - options?: VerifySessionOptions, - userContext?: Record - ): Promise; - static getSessionInformation( - sessionHandle: string, - userContext?: Record - ): Promise; + static getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { + sessionRequired?: true; + }, userContext?: Record): Promise; + static getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { + sessionRequired: false; + }, userContext?: Record): Promise; + static getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions, userContext?: Record): Promise; + static getSessionInformation(sessionHandle: string, userContext?: Record): Promise; static refreshSession(req: any, res: any, userContext?: Record): Promise; - static refreshSessionWithoutRequestResponse( - refreshToken: string, - disableAntiCsrf?: boolean, - antiCsrfToken?: string, - userContext?: Record - ): Promise; - static revokeAllSessionsForUser( - userId: string, - revokeSessionsForLinkedAccounts?: boolean, - tenantId?: string, - userContext?: Record - ): Promise; - static getAllSessionHandlesForUser( - userId: string, - fetchSessionsForAllLinkedAccounts?: boolean, - tenantId?: string, - userContext?: Record - ): Promise; + static refreshSessionWithoutRequestResponse(refreshToken: string, disableAntiCsrf?: boolean, antiCsrfToken?: string, userContext?: Record): Promise; + static revokeAllSessionsForUser(userId: string, revokeSessionsForLinkedAccounts?: boolean, tenantId?: string, userContext?: Record): Promise; + static getAllSessionHandlesForUser(userId: string, fetchSessionsForAllLinkedAccounts?: boolean, tenantId?: string, userContext?: Record): Promise; static revokeSession(sessionHandle: string, userContext?: Record): Promise; static revokeMultipleSessions(sessionHandles: string[], userContext?: Record): Promise; - static updateSessionDataInDatabase( - sessionHandle: string, - newSessionData: any, - userContext?: Record - ): Promise; - static mergeIntoAccessTokenPayload( - sessionHandle: string, - accessTokenPayloadUpdate: JSONObject, - userContext?: Record - ): Promise; - static createJWT( - payload?: any, - validitySeconds?: number, - useStaticSigningKey?: boolean, - userContext?: Record - ): Promise< - | { - status: "OK"; - jwt: string; - } - | { - status: "UNSUPPORTED_ALGORITHM_ERROR"; - } - >; - static getJWKS( - userContext?: Record - ): Promise<{ + static updateSessionDataInDatabase(sessionHandle: string, newSessionData: any, userContext?: Record): Promise; + static mergeIntoAccessTokenPayload(sessionHandle: string, accessTokenPayloadUpdate: JSONObject, userContext?: Record): Promise; + static createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record): Promise<{ + status: "OK"; + jwt: string; + } | { + status: "UNSUPPORTED_ALGORITHM_ERROR"; + }>; + static getJWKS(userContext?: Record): Promise<{ keys: import("../jwt").JsonWebKey[]; validityInSeconds?: number | undefined; }>; - static getOpenIdDiscoveryConfiguration( - userContext?: Record - ): Promise<{ + static getOpenIdDiscoveryConfiguration(userContext?: Record): Promise<{ status: "OK"; issuer: string; jwks_uri: string; @@ -188,35 +84,15 @@ export default class SessionWrapper { id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; }>; - static fetchAndSetClaim( - sessionHandle: string, - claim: SessionClaim, - userContext?: Record - ): Promise; - static setClaimValue( - sessionHandle: string, - claim: SessionClaim, - value: T, - userContext?: Record - ): Promise; - static getClaimValue( - sessionHandle: string, - claim: SessionClaim, - userContext?: Record - ): Promise< - | { - status: "SESSION_DOES_NOT_EXIST_ERROR"; - } - | { - status: "OK"; - value: T | undefined; - } - >; - static removeClaim( - sessionHandle: string, - claim: SessionClaim, - userContext?: Record - ): Promise; + static fetchAndSetClaim(sessionHandle: string, claim: SessionClaim, userContext?: Record): Promise; + static setClaimValue(sessionHandle: string, claim: SessionClaim, value: T, userContext?: Record): Promise; + static getClaimValue(sessionHandle: string, claim: SessionClaim, userContext?: Record): Promise<{ + status: "SESSION_DOES_NOT_EXIST_ERROR"; + } | { + status: "OK"; + value: T | undefined; + }>; + static removeClaim(sessionHandle: string, claim: SessionClaim, userContext?: Record): Promise; } export declare let init: typeof Recipe.init; export declare let createNewSession: typeof SessionWrapper.createNewSession; @@ -241,12 +117,4 @@ export declare let Error: typeof SuperTokensError; export declare let createJWT: typeof SessionWrapper.createJWT; export declare let getJWKS: typeof SessionWrapper.getJWKS; export declare let getOpenIdDiscoveryConfiguration: typeof SessionWrapper.getOpenIdDiscoveryConfiguration; -export type { - VerifySessionOptions, - RecipeInterface, - SessionContainer, - APIInterface, - APIOptions, - SessionInformation, - SessionClaimValidator, -}; +export type { VerifySessionOptions, RecipeInterface, SessionContainer, APIInterface, APIOptions, SessionInformation, SessionClaimValidator, }; diff --git a/lib/build/recipe/session/index.js b/lib/build/recipe/session/index.js index ad7855e98..6aa1bb705 100644 --- a/lib/build/recipe/session/index.js +++ b/lib/build/recipe/session/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getOpenIdDiscoveryConfiguration = exports.getJWKS = exports.createJWT = exports.Error = exports.validateClaimsForSessionHandle = exports.removeClaim = exports.getClaimValue = exports.setClaimValue = exports.fetchAndSetClaim = exports.mergeIntoAccessTokenPayload = exports.updateSessionDataInDatabase = exports.revokeMultipleSessions = exports.revokeSession = exports.getAllSessionHandlesForUser = exports.revokeAllSessionsForUser = exports.refreshSessionWithoutRequestResponse = exports.refreshSession = exports.getSessionInformation = exports.getSessionWithoutRequestResponse = exports.getSession = exports.createNewSessionWithoutRequestResponse = exports.createNewSession = exports.init = void 0; const error_1 = __importDefault(require("./error")); @@ -31,15 +29,7 @@ const constants_1 = require("../multitenancy/constants"); const constants_2 = require("./constants"); const utils_2 = require("../../utils"); class SessionWrapper { - static async createNewSession( - req, - res, - tenantId, - recipeUserId, - accessTokenPayload = {}, - sessionDataInDatabase = {}, - userContext - ) { + static async createNewSession(req, res, tenantId, recipeUserId, accessTokenPayload = {}, sessionDataInDatabase = {}, userContext) { const recipeInstance = recipe_1.default.getInstanceOrThrowError(); const config = recipeInstance.config; const appInfo = recipeInstance.getAppInfo(); @@ -62,14 +52,7 @@ class SessionWrapper { tenantId, }); } - static async createNewSessionWithoutRequestResponse( - tenantId, - recipeUserId, - accessTokenPayload = {}, - sessionDataInDatabase = {}, - disableAntiCsrf = false, - userContext - ) { + static async createNewSessionWithoutRequestResponse(tenantId, recipeUserId, accessTokenPayload = {}, sessionDataInDatabase = {}, disableAntiCsrf = false, userContext) { const ctx = utils_2.getUserContext(userContext); const recipeInstance = recipe_1.default.getInstanceOrThrowError(); const claimsAddedByOtherRecipes = recipeInstance.getClaimsAddedByOtherRecipes(); @@ -109,9 +92,7 @@ class SessionWrapper { status: "SESSION_DOES_NOT_EXIST_ERROR", }; } - const claimValidatorsAddedByOtherRecipes = recipe_1.default - .getInstanceOrThrowError() - .getClaimValidatorsAddedByOtherRecipes(); + const claimValidatorsAddedByOtherRecipes = recipe_1.default.getInstanceOrThrowError().getClaimValidatorsAddedByOtherRecipes(); const globalClaimValidators = await recipeImpl.getGlobalClaimValidators({ userId: sessionInfo.userId, recipeUserId: sessionInfo.recipeUserId, @@ -119,10 +100,9 @@ class SessionWrapper { claimValidatorsAddedByOtherRecipes, userContext: ctx, }); - const claimValidators = - overrideGlobalClaimValidators !== undefined - ? await overrideGlobalClaimValidators(globalClaimValidators, sessionInfo, ctx) - : globalClaimValidators; + const claimValidators = overrideGlobalClaimValidators !== undefined + ? await overrideGlobalClaimValidators(globalClaimValidators, sessionInfo, ctx) + : globalClaimValidators; let claimValidationResponse = await recipeImpl.validateClaims({ userId: sessionInfo.userId, recipeUserId: sessionInfo.recipeUserId, @@ -131,13 +111,11 @@ class SessionWrapper { userContext: ctx, }); if (claimValidationResponse.accessTokenPayloadUpdate !== undefined) { - if ( - !(await recipeImpl.mergeIntoAccessTokenPayload({ - sessionHandle, - accessTokenPayloadUpdate: claimValidationResponse.accessTokenPayloadUpdate, - userContext: ctx, - })) - ) { + if (!(await recipeImpl.mergeIntoAccessTokenPayload({ + sessionHandle, + accessTokenPayloadUpdate: claimValidationResponse.accessTokenPayloadUpdate, + userContext: ctx, + }))) { return { status: "SESSION_DOES_NOT_EXIST_ERROR", }; @@ -171,11 +149,7 @@ class SessionWrapper { userContext: ctx, }); if (session !== undefined) { - const claimValidators = await utils_1.getRequiredClaimValidators( - session, - options === null || options === void 0 ? void 0 : options.overrideGlobalClaimValidators, - ctx - ); + const claimValidators = await utils_1.getRequiredClaimValidators(session, options === null || options === void 0 ? void 0 : options.overrideGlobalClaimValidators, ctx); await session.assertClaims(claimValidators, ctx); } return session; diff --git a/lib/build/recipe/session/jwt.js b/lib/build/recipe/session/jwt.js index 8d8fd813e..fa9ecd03d 100644 --- a/lib/build/recipe/session/jwt.js +++ b/lib/build/recipe/session/jwt.js @@ -18,20 +18,16 @@ exports.parseJWTWithoutSignatureVerification = void 0; const logger_1 = require("../../logger"); const utils_1 = require("../../utils"); const HEADERS = new Set([ - utils_1.encodeBase64( - JSON.stringify({ - alg: "RS256", - typ: "JWT", - version: "1", - }) - ), - utils_1.encodeBase64( - JSON.stringify({ - alg: "RS256", - typ: "JWT", - version: "2", - }) - ), + utils_1.encodeBase64(JSON.stringify({ + alg: "RS256", + typ: "JWT", + version: "1", + })), + utils_1.encodeBase64(JSON.stringify({ + alg: "RS256", + typ: "JWT", + version: "2", + })), ]); function parseJWTWithoutSignatureVerification(jwt) { const splittedInput = jwt.split("."); @@ -53,10 +49,9 @@ function parseJWTWithoutSignatureVerification(jwt) { } version = Number.parseInt(parsedHeader.version); logger_1.logDebugMessage("parseJWTWithoutSignatureVerification: version from header: " + version); - } else { - logger_1.logDebugMessage( - "parseJWTWithoutSignatureVerification: assuming latest version (3) because version header is missing" - ); + } + else { + logger_1.logDebugMessage("parseJWTWithoutSignatureVerification: assuming latest version (3) because version header is missing"); version = latestVersion; } kid = parsedHeader.kid; diff --git a/lib/build/recipe/session/recipe.d.ts b/lib/build/recipe/session/recipe.d.ts index ecb163142..10d3ea7e8 100644 --- a/lib/build/recipe/session/recipe.d.ts +++ b/lib/build/recipe/session/recipe.d.ts @@ -1,14 +1,6 @@ // @ts-nocheck import RecipeModule from "../../recipeModule"; -import { - TypeInput, - TypeNormalisedInput, - RecipeInterface, - APIInterface, - VerifySessionOptions, - SessionClaimValidator, - SessionClaim, -} from "./types"; +import { TypeInput, TypeNormalisedInput, RecipeInterface, APIInterface, VerifySessionOptions, SessionClaimValidator, SessionClaim } from "./types"; import STError from "./error"; import { NormalisedAppinfo, RecipeListFunction, APIHandled, HTTPMethod, UserContext } from "../../types"; import NormalisedURLPath from "../../normalisedURLPath"; @@ -31,28 +23,10 @@ export default class SessionRecipe extends RecipeModule { addClaimValidatorFromOtherRecipe: (builder: SessionClaimValidator) => void; getClaimValidatorsAddedByOtherRecipes: () => SessionClaimValidator[]; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - _tenantId: string, - req: BaseRequest, - res: BaseResponse, - _path: NormalisedURLPath, - _method: HTTPMethod, - userContext: UserContext - ) => Promise; - handleError: ( - err: STError, - request: BaseRequest, - response: BaseResponse, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, _tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; + handleError: (err: STError, request: BaseRequest, response: BaseResponse, userContext: UserContext) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; - verifySession: ( - options: VerifySessionOptions | undefined, - request: BaseRequest, - response: BaseResponse, - userContext: UserContext - ) => Promise; + verifySession: (options: VerifySessionOptions | undefined, request: BaseRequest, response: BaseResponse, userContext: UserContext) => Promise; getNormalisedOverwriteSessionDuringSignInUp: (req: any) => boolean; } diff --git a/lib/build/recipe/session/recipe.js b/lib/build/recipe/session/recipe.js index fcec65ac9..716d00e35 100644 --- a/lib/build/recipe/session/recipe.js +++ b/lib/build/recipe/session/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -87,9 +85,11 @@ class SessionRecipe extends recipeModule_1.default { }; if (id === constants_1.REFRESH_API_PATH) { return await refresh_1.default(this.apiImpl, options, userContext); - } else if (id === constants_1.SIGNOUT_API_PATH) { + } + else if (id === constants_1.SIGNOUT_API_PATH) { return await signout_1.default(this.apiImpl, options, userContext); - } else { + } + else { return false; } }; @@ -97,63 +97,40 @@ class SessionRecipe extends recipeModule_1.default { if (err.fromRecipe === SessionRecipe.RECIPE_ID) { if (err.type === error_1.default.UNAUTHORISED) { logger_1.logDebugMessage("errorHandler: returning UNAUTHORISED"); - if ( - err.payload === undefined || + if (err.payload === undefined || err.payload.clearTokens === undefined || - err.payload.clearTokens === true - ) { + err.payload.clearTokens === true) { logger_1.logDebugMessage("errorHandler: Clearing tokens because of UNAUTHORISED response"); - cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods( - this.config, - response, - request, - userContext - ); + cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods(this.config, response, request, userContext); } return await this.config.errorHandlers.onUnauthorised(err.message, request, response, userContext); - } else if (err.type === error_1.default.TRY_REFRESH_TOKEN) { + } + else if (err.type === error_1.default.TRY_REFRESH_TOKEN) { logger_1.logDebugMessage("errorHandler: returning TRY_REFRESH_TOKEN"); - return await this.config.errorHandlers.onTryRefreshToken( - err.message, - request, - response, - userContext - ); - } else if (err.type === error_1.default.TOKEN_THEFT_DETECTED) { + return await this.config.errorHandlers.onTryRefreshToken(err.message, request, response, userContext); + } + else if (err.type === error_1.default.TOKEN_THEFT_DETECTED) { logger_1.logDebugMessage("errorHandler: returning TOKEN_THEFT_DETECTED"); logger_1.logDebugMessage("errorHandler: Clearing tokens because of TOKEN_THEFT_DETECTED response"); - cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods( - this.config, - response, - request, - userContext - ); - return await this.config.errorHandlers.onTokenTheftDetected( - err.payload.sessionHandle, - err.payload.userId, - err.payload.recipeUserId, - request, - response, - userContext - ); - } else if (err.type === error_1.default.INVALID_CLAIMS) { + cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods(this.config, response, request, userContext); + return await this.config.errorHandlers.onTokenTheftDetected(err.payload.sessionHandle, err.payload.userId, err.payload.recipeUserId, request, response, userContext); + } + else if (err.type === error_1.default.INVALID_CLAIMS) { return await this.config.errorHandlers.onInvalidClaim(err.payload, request, response, userContext); - } else if (err.type === error_1.default.CLEAR_DUPLICATE_SESSION_COOKIES) { + } + else if (err.type === error_1.default.CLEAR_DUPLICATE_SESSION_COOKIES) { logger_1.logDebugMessage("errorHandler: returning CLEAR_DUPLICATE_SESSION_COOKIES"); // This error occurs in the `refreshPOST` API when multiple session // cookies are found in the request and the user has set `olderCookieDomain`. // We remove session cookies from the olderCookieDomain. The response must return `200 OK` // to avoid logging out the user, allowing the session to continue with the valid cookie. - return await this.config.errorHandlers.onClearDuplicateSessionCookies( - err.message, - request, - response, - userContext - ); - } else { + return await this.config.errorHandlers.onClearDuplicateSessionCookies(err.message, request, response, userContext); + } + else { throw err; } - } else { + } + else { throw err; } }; @@ -181,35 +158,23 @@ class SessionRecipe extends recipeModule_1.default { this.getNormalisedOverwriteSessionDuringSignInUp = (req) => { var _a; const supportsFDI31 = utils_2.hasGreaterThanEqualToFDI(req, "3.1"); - const res = - (_a = this.config.overwriteSessionDuringSignInUp) !== null && _a !== void 0 ? _a : supportsFDI31; + const res = (_a = this.config.overwriteSessionDuringSignInUp) !== null && _a !== void 0 ? _a : supportsFDI31; logger_1.logDebugMessage("getNormalisedOverwriteSessionDuringSignInUp returning: " + res); return res; }; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); - const antiCsrfToLog = - typeof this.config.antiCsrfFunctionOrString === "string" - ? this.config.antiCsrfFunctionOrString - : "function"; + const antiCsrfToLog = typeof this.config.antiCsrfFunctionOrString === "string" + ? this.config.antiCsrfFunctionOrString + : "function"; logger_1.logDebugMessage("session init: antiCsrf: " + antiCsrfToLog); logger_1.logDebugMessage("session init: cookieDomain: " + this.config.cookieDomain); - const sameSiteToPrint = - config !== undefined && config.cookieSameSite !== undefined ? config.cookieSameSite : "default function"; + const sameSiteToPrint = config !== undefined && config.cookieSameSite !== undefined ? config.cookieSameSite : "default function"; logger_1.logDebugMessage("session init: cookieSameSite: " + sameSiteToPrint); logger_1.logDebugMessage("session init: cookieSecure: " + this.config.cookieSecure); - logger_1.logDebugMessage( - "session init: refreshTokenPath: " + this.config.refreshTokenPath.getAsStringDangerous() - ); + logger_1.logDebugMessage("session init: refreshTokenPath: " + this.config.refreshTokenPath.getAsStringDangerous()); logger_1.logDebugMessage("session init: sessionExpiredStatusCode: " + this.config.sessionExpiredStatusCode); this.isInServerlessEnv = isInServerlessEnv; - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default( - querier_1.Querier.getNewInstanceOrThrowError(recipeId), - this.config, - this.getAppInfo(), - () => this.recipeInterfaceImpl - ) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, this.getAppInfo(), () => this.recipeInterfaceImpl)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); { let builder = new supertokens_js_override_1.default(implementation_1.default()); @@ -220,16 +185,15 @@ class SessionRecipe extends recipeModule_1.default { if (SessionRecipe.instance !== undefined) { return SessionRecipe.instance; } - throw new Error( - "Initialisation not done. Did you forget to call the SuperTokens.init or Session.init function?" - ); + throw new Error("Initialisation not done. Did you forget to call the SuperTokens.init or Session.init function?"); } static init(config) { return (appInfo, isInServerlessEnv) => { if (SessionRecipe.instance === undefined) { SessionRecipe.instance = new SessionRecipe(SessionRecipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return SessionRecipe.instance; - } else { + } + else { throw new Error("Session recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/session/recipeImplementation.d.ts b/lib/build/recipe/session/recipeImplementation.d.ts index df95edba8..3ca3c3147 100644 --- a/lib/build/recipe/session/recipeImplementation.d.ts +++ b/lib/build/recipe/session/recipeImplementation.d.ts @@ -8,9 +8,4 @@ export declare type Helpers = { appInfo: NormalisedAppinfo; getRecipeImpl: () => RecipeInterface; }; -export default function getRecipeInterface( - querier: Querier, - config: TypeNormalisedInput, - appInfo: NormalisedAppinfo, - getRecipeImplAfterOverrides: () => RecipeInterface -): RecipeInterface; +export default function getRecipeInterface(querier: Querier, config: TypeNormalisedInput, appInfo: NormalisedAppinfo, getRecipeImplAfterOverrides: () => RecipeInterface): RecipeInterface; diff --git a/lib/build/recipe/session/recipeImplementation.js b/lib/build/recipe/session/recipeImplementation.js index 7dbba30ec..22d8144c8 100644 --- a/lib/build/recipe/session/recipeImplementation.js +++ b/lib/build/recipe/session/recipeImplementation.js @@ -1,45 +1,26 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const SessionFunctions = __importStar(require("./sessionFunctions")); const cookieAndHeaders_1 = require("./cookieAndHeaders"); @@ -56,68 +37,33 @@ const constants_2 = require("./constants"); const utils_2 = require("../../utils"); function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverrides) { let obj = { - createNewSession: async function ({ - recipeUserId, - accessTokenPayload = {}, - sessionDataInDatabase = {}, - disableAntiCsrf, - tenantId, - userContext, - }) { + createNewSession: async function ({ recipeUserId, accessTokenPayload = {}, sessionDataInDatabase = {}, disableAntiCsrf, tenantId, userContext, }) { logger_1.logDebugMessage("createNewSession: Started"); - let response = await SessionFunctions.createNewSession( - helpers, - tenantId, - recipeUserId, - disableAntiCsrf === true, - accessTokenPayload, - sessionDataInDatabase, - userContext - ); + let response = await SessionFunctions.createNewSession(helpers, tenantId, recipeUserId, disableAntiCsrf === true, accessTokenPayload, sessionDataInDatabase, userContext); logger_1.logDebugMessage("createNewSession: Finished"); const payload = jwt_1.parseJWTWithoutSignatureVerification(response.accessToken.token).payload; - return new sessionClass_1.default( - helpers, - response.accessToken.token, - cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken.expiry, payload), - response.refreshToken, - response.antiCsrfToken, - response.session.handle, - response.session.userId, - response.session.recipeUserId, - payload, - undefined, - true, - tenantId - ); + return new sessionClass_1.default(helpers, response.accessToken.token, cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken.expiry, payload), response.refreshToken, response.antiCsrfToken, response.session.handle, response.session.userId, response.session.recipeUserId, payload, undefined, true, tenantId); }, getGlobalClaimValidators: async function (input) { return input.claimValidatorsAddedByOtherRecipes; }, - getSession: async function ({ accessToken: accessTokenString, antiCsrfToken, options, userContext }) { - if ( - (options === null || options === void 0 ? void 0 : options.antiCsrfCheck) !== false && + getSession: async function ({ accessToken: accessTokenString, antiCsrfToken, options, userContext, }) { + if ((options === null || options === void 0 ? void 0 : options.antiCsrfCheck) !== false && typeof config.antiCsrfFunctionOrString === "string" && - config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER" - ) { - throw new Error( - "Since the anti-csrf mode is VIA_CUSTOM_HEADER getSession can't check the CSRF token. Please either use VIA_TOKEN or set antiCsrfCheck to false" - ); + config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER") { + throw new Error("Since the anti-csrf mode is VIA_CUSTOM_HEADER getSession can't check the CSRF token. Please either use VIA_TOKEN or set antiCsrfCheck to false"); } logger_1.logDebugMessage("getSession: Started"); if (accessTokenString === undefined) { if ((options === null || options === void 0 ? void 0 : options.sessionRequired) === false) { - logger_1.logDebugMessage( - "getSession: returning undefined because accessToken is undefined and sessionRequired is false" - ); + logger_1.logDebugMessage("getSession: returning undefined because accessToken is undefined and sessionRequired is false"); // there is no session that exists here, and the user wants session verification // to be optional. So we return undefined. return undefined; } logger_1.logDebugMessage("getSession: UNAUTHORISED because accessToken in request is undefined"); throw new error_1.default({ - message: - "Session does not exist. Are you sending the session tokens in the request with the appropriate token transfer method?", + message: "Session does not exist. Are you sending the session tokens in the request with the appropriate token transfer method?", type: error_1.default.UNAUTHORISED, payload: { // we do not clear the session here because of a @@ -130,56 +76,28 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride try { accessToken = jwt_1.parseJWTWithoutSignatureVerification(accessTokenString); accessToken_1.validateAccessTokenStructure(accessToken.payload, accessToken.version); - } catch (error) { + } + catch (error) { if ((options === null || options === void 0 ? void 0 : options.sessionRequired) === false) { - logger_1.logDebugMessage( - "getSession: Returning undefined because parsing failed and sessionRequired is false" - ); + logger_1.logDebugMessage("getSession: Returning undefined because parsing failed and sessionRequired is false"); return undefined; } - logger_1.logDebugMessage( - "getSession: UNAUTHORISED because the accessToken couldn't be parsed or had an invalid structure" - ); + logger_1.logDebugMessage("getSession: UNAUTHORISED because the accessToken couldn't be parsed or had an invalid structure"); throw new error_1.default({ message: "Token parsing failed", type: "UNAUTHORISED", payload: { clearTokens: false }, }); } - const response = await SessionFunctions.getSession( - helpers, - accessToken, - antiCsrfToken, - (options === null || options === void 0 ? void 0 : options.antiCsrfCheck) !== false, - (options === null || options === void 0 ? void 0 : options.checkDatabase) === true, - config, - userContext - ); + const response = await SessionFunctions.getSession(helpers, accessToken, antiCsrfToken, (options === null || options === void 0 ? void 0 : options.antiCsrfCheck) !== false, (options === null || options === void 0 ? void 0 : options.checkDatabase) === true, config, userContext); logger_1.logDebugMessage("getSession: Success!"); - const payload = - accessToken.version >= 3 - ? response.accessToken !== undefined - ? jwt_1.parseJWTWithoutSignatureVerification(response.accessToken.token).payload - : accessToken.payload - : response.session.userDataInJWT; - const session = new sessionClass_1.default( - helpers, - response.accessToken !== undefined ? response.accessToken.token : accessTokenString, - cookieAndHeaders_1.buildFrontToken( - response.session.userId, - response.accessToken !== undefined ? response.accessToken.expiry : response.session.expiryTime, - payload - ), - undefined, // refresh - antiCsrfToken, - response.session.handle, - response.session.userId, - response.session.recipeUserId, - payload, - undefined, - response.accessToken !== undefined, - response.session.tenantId - ); + const payload = accessToken.version >= 3 + ? response.accessToken !== undefined + ? jwt_1.parseJWTWithoutSignatureVerification(response.accessToken.token).payload + : accessToken.payload + : response.session.userDataInJWT; + const session = new sessionClass_1.default(helpers, response.accessToken !== undefined ? response.accessToken.token : accessTokenString, cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken !== undefined ? response.accessToken.expiry : response.session.expiryTime, payload), undefined, // refresh + antiCsrfToken, response.session.handle, response.session.userId, response.session.recipeUserId, payload, undefined, response.accessToken !== undefined, response.session.tenantId); return session; }, validateClaims: async function (input) { @@ -190,90 +108,45 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride logger_1.logDebugMessage("updateClaimsInPayloadIfNeeded checking shouldRefetch for " + validator.id); if ("claim" in validator && (await validator.shouldRefetch(accessTokenPayload, input.userContext))) { logger_1.logDebugMessage("updateClaimsInPayloadIfNeeded refetching " + validator.id); - const value = await validator.claim.fetchValue( - input.userId, - input.recipeUserId, - accessTokenPayload.tId === undefined ? constants_1.DEFAULT_TENANT_ID : accessTokenPayload.tId, - accessTokenPayload, - input.userContext - ); - logger_1.logDebugMessage( - "updateClaimsInPayloadIfNeeded " + validator.id + " refetch result " + JSON.stringify(value) - ); + const value = await validator.claim.fetchValue(input.userId, input.recipeUserId, accessTokenPayload.tId === undefined ? constants_1.DEFAULT_TENANT_ID : accessTokenPayload.tId, accessTokenPayload, input.userContext); + logger_1.logDebugMessage("updateClaimsInPayloadIfNeeded " + validator.id + " refetch result " + JSON.stringify(value)); if (value !== undefined) { - accessTokenPayload = validator.claim.addToPayload_internal( - accessTokenPayload, - value, - input.userContext - ); + accessTokenPayload = validator.claim.addToPayload_internal(accessTokenPayload, value, input.userContext); } } } if (JSON.stringify(accessTokenPayload) !== origSessionClaimPayloadJSON) { accessTokenPayloadUpdate = accessTokenPayload; } - const invalidClaims = await utils_1.validateClaimsInPayload( - input.claimValidators, - accessTokenPayload, - input.userContext - ); + const invalidClaims = await utils_1.validateClaimsInPayload(input.claimValidators, accessTokenPayload, input.userContext); return { invalidClaims, accessTokenPayloadUpdate, }; }, - getSessionInformation: async function ({ sessionHandle, userContext }) { + getSessionInformation: async function ({ sessionHandle, userContext, }) { return SessionFunctions.getSessionInformation(helpers, sessionHandle, userContext); }, - refreshSession: async function ({ refreshToken, antiCsrfToken, disableAntiCsrf, userContext }) { - if ( - disableAntiCsrf !== true && + refreshSession: async function ({ refreshToken, antiCsrfToken, disableAntiCsrf, userContext, }) { + if (disableAntiCsrf !== true && typeof config.antiCsrfFunctionOrString === "string" && - config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER" - ) { - throw new Error( - "Since the anti-csrf mode is VIA_CUSTOM_HEADER getSession can't check the CSRF token. Please either use VIA_TOKEN or set antiCsrfCheck to false" - ); + config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER") { + throw new Error("Since the anti-csrf mode is VIA_CUSTOM_HEADER getSession can't check the CSRF token. Please either use VIA_TOKEN or set antiCsrfCheck to false"); } logger_1.logDebugMessage("refreshSession: Started"); - const response = await SessionFunctions.refreshSession( - helpers, - refreshToken, - antiCsrfToken, - disableAntiCsrf, - config.useDynamicAccessTokenSigningKey, - userContext - ); + const response = await SessionFunctions.refreshSession(helpers, refreshToken, antiCsrfToken, disableAntiCsrf, config.useDynamicAccessTokenSigningKey, userContext); logger_1.logDebugMessage("refreshSession: Success!"); const payload = jwt_1.parseJWTWithoutSignatureVerification(response.accessToken.token).payload; - return new sessionClass_1.default( - helpers, - response.accessToken.token, - cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken.expiry, payload), - response.refreshToken, - response.antiCsrfToken, - response.session.handle, - response.session.userId, - response.session.recipeUserId, - payload, - undefined, - true, - payload.tId - ); + return new sessionClass_1.default(helpers, response.accessToken.token, cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken.expiry, payload), response.refreshToken, response.antiCsrfToken, response.session.handle, response.session.userId, response.session.recipeUserId, payload, undefined, true, payload.tId); }, regenerateAccessToken: async function (input) { - let newAccessTokenPayload = - input.newAccessTokenPayload === null || input.newAccessTokenPayload === undefined - ? {} - : input.newAccessTokenPayload; - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/session/regenerate"), - { - accessToken: input.accessToken, - userDataInJWT: newAccessTokenPayload, - }, - input.userContext - ); + let newAccessTokenPayload = input.newAccessTokenPayload === null || input.newAccessTokenPayload === undefined + ? {} + : input.newAccessTokenPayload; + let response = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/session/regenerate"), { + accessToken: input.accessToken, + userDataInJWT: newAccessTokenPayload, + }, input.userContext); if (response.status === "UNAUTHORISED") { return undefined; } @@ -289,48 +162,22 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride accessToken: response.accessToken, }; }, - revokeAllSessionsForUser: function ({ - userId, - tenantId, - revokeAcrossAllTenants, - revokeSessionsForLinkedAccounts, - userContext, - }) { - return SessionFunctions.revokeAllSessionsForUser( - helpers, - userId, - revokeSessionsForLinkedAccounts, - tenantId, - revokeAcrossAllTenants, - userContext - ); + revokeAllSessionsForUser: function ({ userId, tenantId, revokeAcrossAllTenants, revokeSessionsForLinkedAccounts, userContext, }) { + return SessionFunctions.revokeAllSessionsForUser(helpers, userId, revokeSessionsForLinkedAccounts, tenantId, revokeAcrossAllTenants, userContext); }, - getAllSessionHandlesForUser: function ({ - userId, - fetchSessionsForAllLinkedAccounts, - tenantId, - fetchAcrossAllTenants, - userContext, - }) { - return SessionFunctions.getAllSessionHandlesForUser( - helpers, - userId, - fetchSessionsForAllLinkedAccounts, - tenantId, - fetchAcrossAllTenants, - userContext - ); + getAllSessionHandlesForUser: function ({ userId, fetchSessionsForAllLinkedAccounts, tenantId, fetchAcrossAllTenants, userContext, }) { + return SessionFunctions.getAllSessionHandlesForUser(helpers, userId, fetchSessionsForAllLinkedAccounts, tenantId, fetchAcrossAllTenants, userContext); }, - revokeSession: function ({ sessionHandle, userContext }) { + revokeSession: function ({ sessionHandle, userContext, }) { return SessionFunctions.revokeSession(helpers, sessionHandle, userContext); }, - revokeMultipleSessions: function ({ sessionHandles, userContext }) { + revokeMultipleSessions: function ({ sessionHandles, userContext, }) { return SessionFunctions.revokeMultipleSessions(helpers, sessionHandles, userContext); }, - updateSessionDataInDatabase: function ({ sessionHandle, newSessionData, userContext }) { + updateSessionDataInDatabase: function ({ sessionHandle, newSessionData, userContext, }) { return SessionFunctions.updateSessionDataInDatabase(helpers, sessionHandle, newSessionData, userContext); }, - mergeIntoAccessTokenPayload: async function ({ sessionHandle, accessTokenPayloadUpdate, userContext }) { + mergeIntoAccessTokenPayload: async function ({ sessionHandle, accessTokenPayloadUpdate, userContext, }) { const sessionInfo = await this.getSessionInformation({ sessionHandle, userContext }); if (sessionInfo === undefined) { return false; @@ -345,12 +192,7 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride delete newAccessTokenPayload[key]; } } - return SessionFunctions.updateAccessTokenPayload( - helpers, - sessionHandle, - newAccessTokenPayload, - userContext - ); + return SessionFunctions.updateAccessTokenPayload(helpers, sessionHandle, newAccessTokenPayload, userContext); }, fetchAndSetClaim: async function (input) { const sessionInfo = await this.getSessionInformation({ @@ -360,13 +202,7 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride if (sessionInfo === undefined) { return false; } - const accessTokenPayloadUpdate = await input.claim.build( - sessionInfo.userId, - sessionInfo.recipeUserId, - sessionInfo.tenantId, - sessionInfo.customClaimsInAccessTokenPayload, - input.userContext - ); + const accessTokenPayloadUpdate = await input.claim.build(sessionInfo.userId, sessionInfo.recipeUserId, sessionInfo.tenantId, sessionInfo.customClaimsInAccessTokenPayload, input.userContext); return this.mergeIntoAccessTokenPayload({ sessionHandle: input.sessionHandle, accessTokenPayloadUpdate, diff --git a/lib/build/recipe/session/sessionClass.d.ts b/lib/build/recipe/session/sessionClass.d.ts index 2a1ea04f2..2d586ade3 100644 --- a/lib/build/recipe/session/sessionClass.d.ts +++ b/lib/build/recipe/session/sessionClass.d.ts @@ -15,20 +15,7 @@ export default class Session implements SessionContainerInterface { protected reqResInfo: ReqResInfo | undefined; protected accessTokenUpdated: boolean; protected tenantId: string; - constructor( - helpers: Helpers, - accessToken: string, - frontToken: string, - refreshToken: TokenInfo | undefined, - antiCsrfToken: string | undefined, - sessionHandle: string, - userId: string, - recipeUserId: RecipeUserId, - userDataInAccessToken: any, - reqResInfo: ReqResInfo | undefined, - accessTokenUpdated: boolean, - tenantId: string - ); + constructor(helpers: Helpers, accessToken: string, frontToken: string, refreshToken: TokenInfo | undefined, antiCsrfToken: string | undefined, sessionHandle: string, userId: string, recipeUserId: RecipeUserId, userDataInAccessToken: any, reqResInfo: ReqResInfo | undefined, accessTokenUpdated: boolean, tenantId: string); getRecipeUserId(_userContext?: Record): RecipeUserId; revokeSession(userContext?: Record): Promise; getSessionDataFromDatabase(userContext?: Record): Promise; diff --git a/lib/build/recipe/session/sessionClass.js b/lib/build/recipe/session/sessionClass.js index 771a39d9f..ae6ee0f06 100644 --- a/lib/build/recipe/session/sessionClass.js +++ b/lib/build/recipe/session/sessionClass.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -27,20 +25,7 @@ const logger_1 = require("../../logger"); const constants_1 = require("./constants"); const utils_2 = require("../../utils"); class Session { - constructor( - helpers, - accessToken, - frontToken, - refreshToken, - antiCsrfToken, - sessionHandle, - userId, - recipeUserId, - userDataInAccessToken, - reqResInfo, - accessTokenUpdated, - tenantId - ) { + constructor(helpers, accessToken, frontToken, refreshToken, antiCsrfToken, sessionHandle, userId, recipeUserId, userDataInAccessToken, reqResInfo, accessTokenUpdated, tenantId) { this.helpers = helpers; this.accessToken = accessToken; this.frontToken = frontToken; @@ -58,10 +43,9 @@ class Session { return this.recipeUserId; } async revokeSession(userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); await this.helpers.getRecipeImpl().revokeSession({ sessionHandle: this.sessionHandle, userContext: ctx, @@ -73,28 +57,19 @@ class Session { // If we instead clear the cookies only when revokeSession // returns true, it can cause this kind of a bug: // https://github.com/supertokens/supertokens-node/issues/343 - cookieAndHeaders_1.clearSession( - this.helpers.config, - this.reqResInfo.res, - this.reqResInfo.transferMethod, - this.reqResInfo.req, - ctx - ); + cookieAndHeaders_1.clearSession(this.helpers.config, this.reqResInfo.res, this.reqResInfo.transferMethod, this.reqResInfo.req, ctx); } } async getSessionDataFromDatabase(userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let sessionInfo = await this.helpers.getRecipeImpl().getSessionInformation({ sessionHandle: this.sessionHandle, userContext: ctx, }); if (sessionInfo === undefined) { - logger_1.logDebugMessage( - "getSessionDataFromDatabase: Throwing UNAUTHORISED because session does not exist anymore" - ); + logger_1.logDebugMessage("getSessionDataFromDatabase: Throwing UNAUTHORISED because session does not exist anymore"); throw new error_1.default({ message: "Session does not exist anymore", type: error_1.default.UNAUTHORISED, @@ -103,20 +78,15 @@ class Session { return sessionInfo.sessionDataInDatabase; } async updateSessionDataInDatabase(newSessionData, userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); - if ( - !(await this.helpers.getRecipeImpl().updateSessionDataInDatabase({ - sessionHandle: this.sessionHandle, - newSessionData, - userContext: ctx, - })) - ) { - logger_1.logDebugMessage( - "updateSessionDataInDatabase: Throwing UNAUTHORISED because session does not exist anymore" - ); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); + if (!(await this.helpers.getRecipeImpl().updateSessionDataInDatabase({ + sessionHandle: this.sessionHandle, + newSessionData, + userContext: ctx, + }))) { + logger_1.logDebugMessage("updateSessionDataInDatabase: Throwing UNAUTHORISED because session does not exist anymore"); throw new error_1.default({ message: "Session does not exist anymore", type: error_1.default.UNAUTHORISED, @@ -150,10 +120,9 @@ class Session { } // Any update to this function should also be reflected in the respective JWT version async mergeIntoAccessTokenPayload(accessTokenPayloadUpdate, userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let newAccessTokenPayload = Object.assign({}, this.getAccessTokenPayload(ctx)); for (const key of constants_1.protectedProps) { delete newAccessTokenPayload[key]; @@ -170,9 +139,7 @@ class Session { userContext: ctx, }); if (response === undefined) { - logger_1.logDebugMessage( - "mergeIntoAccessTokenPayload: Throwing UNAUTHORISED because session does not exist anymore" - ); + logger_1.logDebugMessage("mergeIntoAccessTokenPayload: Throwing UNAUTHORISED because session does not exist anymore"); throw new error_1.default({ message: "Session does not exist anymore", type: error_1.default.UNAUTHORISED, @@ -187,31 +154,20 @@ class Session { this.accessTokenUpdated = true; if (this.reqResInfo !== undefined) { // We need to cast to let TS know that the accessToken in the response is defined (and we don't overwrite it with undefined) - utils_1.setAccessTokenInResponse( - this.reqResInfo.res, - this.accessToken, - this.frontToken, - this.helpers.config, - this.reqResInfo.transferMethod, - this.reqResInfo.req, - ctx - ); + utils_1.setAccessTokenInResponse(this.reqResInfo.res, this.accessToken, this.frontToken, this.helpers.config, this.reqResInfo.transferMethod, this.reqResInfo.req, ctx); } - } else { + } + else { // This case means that the access token has expired between the validation and this update // We can't update the access token on the FE, as it will need to call refresh anyway but we handle this as a successful update during this request. // the changes will be reflected on the FE after refresh is called - this.userDataInAccessToken = Object.assign( - Object.assign({}, this.getAccessTokenPayload(ctx)), - response.session.userDataInJWT - ); + this.userDataInAccessToken = Object.assign(Object.assign({}, this.getAccessTokenPayload(ctx)), response.session.userDataInJWT); } } async getTimeCreated(userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let sessionInfo = await this.helpers.getRecipeImpl().getSessionInformation({ sessionHandle: this.sessionHandle, userContext: ctx, @@ -226,10 +182,9 @@ class Session { return sessionInfo.timeCreated; } async getExpiry(userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let sessionInfo = await this.helpers.getRecipeImpl().getSessionInformation({ sessionHandle: this.sessionHandle, userContext: ctx, @@ -245,10 +200,9 @@ class Session { } // Any update to this function should also be reflected in the respective JWT version async assertClaims(claimValidators, userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let validateClaimResponse = await this.helpers.getRecipeImpl().validateClaims({ accessTokenPayload: this.getAccessTokenPayload(ctx), userId: this.getUserId(ctx), @@ -272,42 +226,32 @@ class Session { } // Any update to this function should also be reflected in the respective JWT version async fetchAndSetClaim(claim, userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); - const update = await claim.build( - this.getUserId(ctx), - this.getRecipeUserId(ctx), - this.getTenantId(ctx), - this.getAccessTokenPayload(ctx), - ctx - ); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); + const update = await claim.build(this.getUserId(ctx), this.getRecipeUserId(ctx), this.getTenantId(ctx), this.getAccessTokenPayload(ctx), ctx); return this.mergeIntoAccessTokenPayload(update, ctx); } // Any update to this function should also be reflected in the respective JWT version setClaimValue(claim, value, userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); const update = claim.addToPayload_internal({}, value, utils_2.getUserContext(ctx)); return this.mergeIntoAccessTokenPayload(update, ctx); } // Any update to this function should also be reflected in the respective JWT version async getClaimValue(claim, userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); return claim.getValueFromPayload(await this.getAccessTokenPayload(ctx), ctx); } // Any update to this function should also be reflected in the respective JWT version removeClaim(claim, userContext) { - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); const update = claim.removeFromPayloadByMerge_internal({}, ctx); return this.mergeIntoAccessTokenPayload(update, ctx); } @@ -315,30 +259,12 @@ class Session { this.reqResInfo = info; if (this.accessTokenUpdated) { const { res, transferMethod } = info; - const ctx = - userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); - utils_1.setAccessTokenInResponse( - res, - this.accessToken, - this.frontToken, - this.helpers.config, - transferMethod, - info.req, - ctx - ); + const ctx = userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); + utils_1.setAccessTokenInResponse(res, this.accessToken, this.frontToken, this.helpers.config, transferMethod, info.req, ctx); if (this.refreshToken !== undefined) { - cookieAndHeaders_1.setToken( - this.helpers.config, - res, - "refresh", - this.refreshToken.token, - this.refreshToken.expiry, - transferMethod, - info.req, - ctx - ); + cookieAndHeaders_1.setToken(this.helpers.config, res, "refresh", this.refreshToken.token, this.refreshToken.expiry, transferMethod, info.req, ctx); } if (this.antiCsrfToken !== undefined) { cookieAndHeaders_1.setAntiCsrfTokenInHeaders(res, this.antiCsrfToken); diff --git a/lib/build/recipe/session/sessionFunctions.d.ts b/lib/build/recipe/session/sessionFunctions.d.ts index 601e51917..366bb8b34 100644 --- a/lib/build/recipe/session/sessionFunctions.d.ts +++ b/lib/build/recipe/session/sessionFunctions.d.ts @@ -7,27 +7,11 @@ import { UserContext } from "../../types"; /** * @description call this to "login" a user. */ -export declare function createNewSession( - helpers: Helpers, - tenantId: string, - recipeUserId: RecipeUserId, - disableAntiCsrf: boolean, - accessTokenPayload: any, - sessionDataInDatabase: any, - userContext: UserContext -): Promise; +export declare function createNewSession(helpers: Helpers, tenantId: string, recipeUserId: RecipeUserId, disableAntiCsrf: boolean, accessTokenPayload: any, sessionDataInDatabase: any, userContext: UserContext): Promise; /** * @description authenticates a session. To be used in APIs that require authentication */ -export declare function getSession( - helpers: Helpers, - parsedAccessToken: ParsedJWTInfo, - antiCsrfToken: string | undefined, - doAntiCsrfCheck: boolean, - alwaysCheckCore: boolean, - config: TypeNormalisedInput, - userContext: UserContext -): Promise<{ +export declare function getSession(helpers: Helpers, parsedAccessToken: ParsedJWTInfo, antiCsrfToken: string | undefined, doAntiCsrfCheck: boolean, alwaysCheckCore: boolean, config: TypeNormalisedInput, userContext: UserContext): Promise<{ session: { handle: string; userId: string; @@ -46,76 +30,33 @@ export declare function getSession( * @description Retrieves session information from storage for a given session handle * @returns session data stored in the database, including userData and access token payload, or undefined if sessionHandle is invalid */ -export declare function getSessionInformation( - helpers: Helpers, - sessionHandle: string, - userContext: UserContext -): Promise; +export declare function getSessionInformation(helpers: Helpers, sessionHandle: string, userContext: UserContext): Promise; /** * @description generates new access and refresh tokens for a given refresh token. Called when client's access token has expired. * @sideEffects calls onTokenTheftDetection if token theft is detected. */ -export declare function refreshSession( - helpers: Helpers, - refreshToken: string, - antiCsrfToken: string | undefined, - disableAntiCsrf: boolean, - useDynamicAccessTokenSigningKey: boolean, - userContext: UserContext -): Promise; +export declare function refreshSession(helpers: Helpers, refreshToken: string, antiCsrfToken: string | undefined, disableAntiCsrf: boolean, useDynamicAccessTokenSigningKey: boolean, userContext: UserContext): Promise; /** * @description deletes session info of a user from db. This only invalidates the refresh token. Not the access token. * Access tokens cannot be immediately invalidated. Unless we add a blacklisting method. Or changed the private key to sign them. */ -export declare function revokeAllSessionsForUser( - helpers: Helpers, - userId: string, - revokeSessionsForLinkedAccounts: boolean, - tenantId: string | undefined, - revokeAcrossAllTenants: boolean | undefined, - userContext: UserContext -): Promise; +export declare function revokeAllSessionsForUser(helpers: Helpers, userId: string, revokeSessionsForLinkedAccounts: boolean, tenantId: string | undefined, revokeAcrossAllTenants: boolean | undefined, userContext: UserContext): Promise; /** * @description gets all session handles for current user. Please do not call this unless this user is authenticated. */ -export declare function getAllSessionHandlesForUser( - helpers: Helpers, - userId: string, - fetchSessionsForAllLinkedAccounts: boolean, - tenantId: string | undefined, - fetchAcrossAllTenants: boolean | undefined, - userContext: UserContext -): Promise; +export declare function getAllSessionHandlesForUser(helpers: Helpers, userId: string, fetchSessionsForAllLinkedAccounts: boolean, tenantId: string | undefined, fetchAcrossAllTenants: boolean | undefined, userContext: UserContext): Promise; /** * @description call to destroy one session * @returns true if session was deleted from db. Else false in case there was nothing to delete */ -export declare function revokeSession( - helpers: Helpers, - sessionHandle: string, - userContext: UserContext -): Promise; +export declare function revokeSession(helpers: Helpers, sessionHandle: string, userContext: UserContext): Promise; /** * @description call to destroy multiple sessions * @returns list of sessions revoked */ -export declare function revokeMultipleSessions( - helpers: Helpers, - sessionHandles: string[], - userContext: UserContext -): Promise; +export declare function revokeMultipleSessions(helpers: Helpers, sessionHandles: string[], userContext: UserContext): Promise; /** * @description: It provides no locking mechanism in case other processes are updating session data for this session as well. */ -export declare function updateSessionDataInDatabase( - helpers: Helpers, - sessionHandle: string, - newSessionData: any, - userContext: UserContext -): Promise; -export declare function updateAccessTokenPayload( - helpers: Helpers, - sessionHandle: string, - newAccessTokenPayload: any, - userContext: UserContext -): Promise; +export declare function updateSessionDataInDatabase(helpers: Helpers, sessionHandle: string, newSessionData: any, userContext: UserContext): Promise; +export declare function updateAccessTokenPayload(helpers: Helpers, sessionHandle: string, newAccessTokenPayload: any, userContext: UserContext): Promise; diff --git a/lib/build/recipe/session/sessionFunctions.js b/lib/build/recipe/session/sessionFunctions.js index f939f0367..70f02dca0 100644 --- a/lib/build/recipe/session/sessionFunctions.js +++ b/lib/build/recipe/session/sessionFunctions.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateAccessTokenPayload = exports.updateSessionDataInDatabase = exports.revokeMultipleSessions = exports.revokeSession = exports.getAllSessionHandlesForUser = exports.revokeAllSessionsForUser = exports.refreshSession = exports.getSessionInformation = exports.getSession = exports.createNewSession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -32,15 +30,7 @@ const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); /** * @description call this to "login" a user. */ -async function createNewSession( - helpers, - tenantId, - recipeUserId, - disableAntiCsrf, - accessTokenPayload, - sessionDataInDatabase, - userContext -) { +async function createNewSession(helpers, tenantId, recipeUserId, disableAntiCsrf, accessTokenPayload, sessionDataInDatabase, userContext) { accessTokenPayload = accessTokenPayload === null || accessTokenPayload === undefined ? {} : accessTokenPayload; sessionDataInDatabase = sessionDataInDatabase === null || sessionDataInDatabase === undefined ? {} : sessionDataInDatabase; @@ -52,11 +42,7 @@ async function createNewSession( // We dont need to check if anti csrf is a function here because checking for "VIA_TOKEN" is enough enableAntiCsrf: !disableAntiCsrf && helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN", }; - let response = await helpers.querier.sendPostRequest( - new normalisedURLPath_1.default(`/${tenantId}/recipe/session`), - requestBody, - userContext - ); + let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/session`), requestBody, userContext); return { session: { handle: response.session.handle, @@ -82,27 +68,16 @@ exports.createNewSession = createNewSession; /** * @description authenticates a session. To be used in APIs that require authentication */ -async function getSession( - helpers, - parsedAccessToken, - antiCsrfToken, - doAntiCsrfCheck, - alwaysCheckCore, - config, - userContext -) { +async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfCheck, alwaysCheckCore, config, userContext) { var _a, _b; let accessTokenInfo; try { /** * get access token info using jwks */ - accessTokenInfo = await accessToken_1.getInfoFromAccessToken( - parsedAccessToken, - combinedRemoteJWKSet_1.getCombinedJWKS(config), - helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN" && doAntiCsrfCheck - ); - } catch (err) { + accessTokenInfo = await accessToken_1.getInfoFromAccessToken(parsedAccessToken, combinedRemoteJWKSet_1.getCombinedJWKS(config), helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN" && doAntiCsrfCheck); + } + catch (err) { /** * if error type is not TRY_REFRESH_TOKEN, we return the * error to the user @@ -143,7 +118,8 @@ async function getSession( if (timeCreated <= Date.now() - config.jwksRefreshIntervalSec * 1000) { throw err; } - } else { + } + else { // Since v3 (and above) tokens contain a kid we can trust the cache-refresh mechanism of the jose library. // This means we do not need to call the core since the signature wouldn't pass verification anyway. throw err; @@ -152,9 +128,7 @@ async function getSession( if (parsedAccessToken.version >= 3) { const tokenUsesDynamicKey = parsedAccessToken.kid.startsWith("d-"); if (tokenUsesDynamicKey !== helpers.config.useDynamicAccessTokenSigningKey) { - logger_1.logDebugMessage( - "getSession: Returning TRY_REFRESH_TOKEN because the access token doesn't match the useDynamicAccessTokenSigningKey in the config" - ); + logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because the access token doesn't match the useDynamicAccessTokenSigningKey in the config"); throw new error_1.default({ message: "The access token doesn't match the useDynamicAccessTokenSigningKey setting", type: error_1.default.TRY_REFRESH_TOKEN, @@ -171,18 +145,14 @@ async function getSession( if (accessTokenInfo !== undefined) { if (antiCsrfToken === undefined || antiCsrfToken !== accessTokenInfo.antiCsrfToken) { if (antiCsrfToken === undefined) { - logger_1.logDebugMessage( - "getSession: Returning TRY_REFRESH_TOKEN because antiCsrfToken is missing from request" - ); + logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because antiCsrfToken is missing from request"); throw new error_1.default({ - message: - "Provided antiCsrfToken is undefined. If you do not want anti-csrf check for this API, please set doAntiCsrfCheck to false for this API", + message: "Provided antiCsrfToken is undefined. If you do not want anti-csrf check for this API, please set doAntiCsrfCheck to false for this API", type: error_1.default.TRY_REFRESH_TOKEN, }); - } else { - logger_1.logDebugMessage( - "getSession: Returning TRY_REFRESH_TOKEN because the passed antiCsrfToken is not the same as in the access token" - ); + } + else { + logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because the passed antiCsrfToken is not the same as in the access token"); throw new error_1.default({ message: "anti-csrf check failed", type: error_1.default.TRY_REFRESH_TOKEN, @@ -190,10 +160,9 @@ async function getSession( } } } - } else if ( - typeof helpers.config.antiCsrfFunctionOrString === "string" && - helpers.config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER" - ) { + } + else if (typeof helpers.config.antiCsrfFunctionOrString === "string" && + helpers.config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER") { // The function should never be called by this (we check this outside the function as well) // There we can add a bit more information to the error, so that's the primary check, this is just making sure. throw new Error("Please either use VIA_TOKEN, NONE or call with doAntiCsrfCheck false"); @@ -219,36 +188,28 @@ async function getSession( enableAntiCsrf: helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN", checkDatabase: alwaysCheckCore, }; - let response = await helpers.querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/session/verify"), - requestBody, - userContext - ); + let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/session/verify"), requestBody, userContext); if (response.status === "OK") { delete response.status; - return Object.assign(Object.assign({}, response), { - session: { + return Object.assign(Object.assign({}, response), { session: { handle: response.session.handle, userId: response.session.userId, recipeUserId: new recipeUserId_1.default(response.session.recipeUserId), - expiryTime: - ((_a = response.accessToken) === null || _a === void 0 ? void 0 : _a.expiry) || // if we got a new accesstoken we take the expiry time from there + expiryTime: ((_a = response.accessToken) === null || _a === void 0 ? void 0 : _a.expiry) || // if we got a new accesstoken we take the expiry time from there (accessTokenInfo === null || accessTokenInfo === void 0 ? void 0 : accessTokenInfo.expiryTime) || // if we didn't get a new access token but could validate the token take that info (alwaysCheckCore === true, or parentRefreshTokenHash1 !== null) parsedAccessToken.payload["expiryTime"], - tenantId: - ((_b = response.session) === null || _b === void 0 ? void 0 : _b.tenantId) || - (accessTokenInfo === null || accessTokenInfo === void 0 ? void 0 : accessTokenInfo.tenantId) || - constants_1.DEFAULT_TENANT_ID, + tenantId: ((_b = response.session) === null || _b === void 0 ? void 0 : _b.tenantId) || (accessTokenInfo === null || accessTokenInfo === void 0 ? void 0 : accessTokenInfo.tenantId) || constants_1.DEFAULT_TENANT_ID, userDataInJWT: response.session.userDataInJWT, - }, - }); - } else if (response.status === "UNAUTHORISED") { + } }); + } + else if (response.status === "UNAUTHORISED") { logger_1.logDebugMessage("getSession: Returning UNAUTHORISED because of core response"); throw new error_1.default({ message: response.message, type: error_1.default.UNAUTHORISED, }); - } else { + } + else { logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because of core response."); throw new error_1.default({ message: response.message, @@ -266,13 +227,9 @@ async function getSessionInformation(helpers, sessionHandle, userContext) { if (utils_1.maxVersion(apiVersion, "2.7") === "2.7") { throw new Error("Please use core version >= 3.5 to call this function."); } - let response = await helpers.querier.sendGetRequest( - new normalisedURLPath_1.default(`/recipe/session`), - { - sessionHandle, - }, - userContext - ); + let response = await helpers.querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/session`), { + sessionHandle, + }, userContext); if (response.status === "OK") { // Change keys to make them more readable return { @@ -285,7 +242,8 @@ async function getSessionInformation(helpers, sessionHandle, userContext) { customClaimsInAccessTokenPayload: response.userDataInJWT, tenantId: response.tenantId, }; - } else { + } + else { return undefined; } } @@ -294,34 +252,21 @@ exports.getSessionInformation = getSessionInformation; * @description generates new access and refresh tokens for a given refresh token. Called when client's access token has expired. * @sideEffects calls onTokenTheftDetection if token theft is detected. */ -async function refreshSession( - helpers, - refreshToken, - antiCsrfToken, - disableAntiCsrf, - useDynamicAccessTokenSigningKey, - userContext -) { +async function refreshSession(helpers, refreshToken, antiCsrfToken, disableAntiCsrf, useDynamicAccessTokenSigningKey, userContext) { let requestBody = { refreshToken, antiCsrfToken, enableAntiCsrf: !disableAntiCsrf && helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN", useDynamicSigningKey: useDynamicAccessTokenSigningKey, }; - if ( - typeof helpers.config.antiCsrfFunctionOrString === "string" && + if (typeof helpers.config.antiCsrfFunctionOrString === "string" && helpers.config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER" && - !disableAntiCsrf - ) { + !disableAntiCsrf) { // The function should never be called by this (we check this outside the function as well) // There we can add a bit more information to the error, so that's the primary check, this is just making sure. throw new Error("Please either use VIA_TOKEN, NONE or call with doAntiCsrfCheck false"); } - let response = await helpers.querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/session/refresh"), - requestBody, - userContext - ); + let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/session/refresh"), requestBody, userContext); if (response.status === "OK") { return { session: { @@ -343,13 +288,15 @@ async function refreshSession( }, antiCsrfToken: response.antiCsrfToken, }; - } else if (response.status === "UNAUTHORISED") { + } + else if (response.status === "UNAUTHORISED") { logger_1.logDebugMessage("refreshSession: Returning UNAUTHORISED because of core response"); throw new error_1.default({ message: response.message, type: error_1.default.UNAUTHORISED, }); - } else { + } + else { logger_1.logDebugMessage("refreshSession: Returning TOKEN_THEFT_DETECTED because of core response"); throw new error_1.default({ message: "Token theft detected", @@ -367,56 +314,30 @@ exports.refreshSession = refreshSession; * @description deletes session info of a user from db. This only invalidates the refresh token. Not the access token. * Access tokens cannot be immediately invalidated. Unless we add a blacklisting method. Or changed the private key to sign them. */ -async function revokeAllSessionsForUser( - helpers, - userId, - revokeSessionsForLinkedAccounts, - tenantId, - revokeAcrossAllTenants, - userContext -) { +async function revokeAllSessionsForUser(helpers, userId, revokeSessionsForLinkedAccounts, tenantId, revokeAcrossAllTenants, userContext) { if (tenantId === undefined) { tenantId = constants_1.DEFAULT_TENANT_ID; } - let response = await helpers.querier.sendPostRequest( - new normalisedURLPath_1.default( - revokeAcrossAllTenants ? `/recipe/session/remove` : `/${tenantId}/recipe/session/remove` - ), - { - userId, - revokeSessionsForLinkedAccounts, - revokeAcrossAllTenants, - }, - userContext - ); + let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default(revokeAcrossAllTenants ? `/recipe/session/remove` : `/${tenantId}/recipe/session/remove`), { + userId, + revokeSessionsForLinkedAccounts, + revokeAcrossAllTenants, + }, userContext); return response.sessionHandlesRevoked; } exports.revokeAllSessionsForUser = revokeAllSessionsForUser; /** * @description gets all session handles for current user. Please do not call this unless this user is authenticated. */ -async function getAllSessionHandlesForUser( - helpers, - userId, - fetchSessionsForAllLinkedAccounts, - tenantId, - fetchAcrossAllTenants, - userContext -) { +async function getAllSessionHandlesForUser(helpers, userId, fetchSessionsForAllLinkedAccounts, tenantId, fetchAcrossAllTenants, userContext) { if (tenantId === undefined) { tenantId = constants_1.DEFAULT_TENANT_ID; } - let response = await helpers.querier.sendGetRequest( - new normalisedURLPath_1.default( - fetchAcrossAllTenants ? `/recipe/session/user` : `/${tenantId}/recipe/session/user` - ), - { - userId, - fetchSessionsForAllLinkedAccounts, - fetchAcrossAllTenants, - }, - userContext - ); + let response = await helpers.querier.sendGetRequest(new normalisedURLPath_1.default(fetchAcrossAllTenants ? `/recipe/session/user` : `/${tenantId}/recipe/session/user`), { + userId, + fetchSessionsForAllLinkedAccounts, + fetchAcrossAllTenants, + }, userContext); return response.sessionHandles; } exports.getAllSessionHandlesForUser = getAllSessionHandlesForUser; @@ -425,13 +346,9 @@ exports.getAllSessionHandlesForUser = getAllSessionHandlesForUser; * @returns true if session was deleted from db. Else false in case there was nothing to delete */ async function revokeSession(helpers, sessionHandle, userContext) { - let response = await helpers.querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/session/remove"), - { - sessionHandles: [sessionHandle], - }, - userContext - ); + let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/session/remove"), { + sessionHandles: [sessionHandle], + }, userContext); return response.sessionHandlesRevoked.length === 1; } exports.revokeSession = revokeSession; @@ -440,13 +357,9 @@ exports.revokeSession = revokeSession; * @returns list of sessions revoked */ async function revokeMultipleSessions(helpers, sessionHandles, userContext) { - let response = await helpers.querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/session/remove`), - { - sessionHandles, - }, - userContext - ); + let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/session/remove`), { + sessionHandles, + }, userContext); return response.sessionHandlesRevoked; } exports.revokeMultipleSessions = revokeMultipleSessions; @@ -455,15 +368,10 @@ exports.revokeMultipleSessions = revokeMultipleSessions; */ async function updateSessionDataInDatabase(helpers, sessionHandle, newSessionData, userContext) { newSessionData = newSessionData === null || newSessionData === undefined ? {} : newSessionData; - let response = await helpers.querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/session/data`), - { - sessionHandle, - userDataInDatabase: newSessionData, - }, - {}, - userContext - ); + let response = await helpers.querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/session/data`), { + sessionHandle, + userDataInDatabase: newSessionData, + }, {}, userContext); if (response.status === "UNAUTHORISED") { return false; } @@ -473,15 +381,10 @@ exports.updateSessionDataInDatabase = updateSessionDataInDatabase; async function updateAccessTokenPayload(helpers, sessionHandle, newAccessTokenPayload, userContext) { newAccessTokenPayload = newAccessTokenPayload === null || newAccessTokenPayload === undefined ? {} : newAccessTokenPayload; - let response = await helpers.querier.sendPutRequest( - new normalisedURLPath_1.default("/recipe/jwt/data"), - { - sessionHandle, - userDataInJWT: newAccessTokenPayload, - }, - {}, - userContext - ); + let response = await helpers.querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/jwt/data"), { + sessionHandle, + userDataInJWT: newAccessTokenPayload, + }, {}, userContext); if (response.status === "UNAUTHORISED") { return false; } diff --git a/lib/build/recipe/session/sessionRequestFunctions.d.ts b/lib/build/recipe/session/sessionRequestFunctions.d.ts index 3002f3bd2..66be53883 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.d.ts +++ b/lib/build/recipe/session/sessionRequestFunctions.d.ts @@ -1,23 +1,10 @@ // @ts-nocheck import Recipe from "./recipe"; -import { - VerifySessionOptions, - RecipeInterface, - TokenTransferMethod, - TypeNormalisedInput, - SessionContainerInterface, -} from "./types"; +import { VerifySessionOptions, RecipeInterface, TokenTransferMethod, TypeNormalisedInput, SessionContainerInterface } from "./types"; import { ParsedJWTInfo } from "./jwt"; import { NormalisedAppinfo, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; -export declare function getSessionFromRequest({ - req, - res, - config, - recipeInterfaceImpl, - options, - userContext, -}: { +export declare function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, options, userContext, }: { req: any; res: any; config: TypeNormalisedInput; @@ -25,40 +12,19 @@ export declare function getSessionFromRequest({ options?: VerifySessionOptions; userContext: UserContext; }): Promise; -export declare function getAccessTokenFromRequest( - req: any, - allowedTransferMethod: TokenTransferMethod | "any" -): { +export declare function getAccessTokenFromRequest(req: any, allowedTransferMethod: TokenTransferMethod | "any"): { requestTransferMethod: TokenTransferMethod | undefined; accessToken: ParsedJWTInfo | undefined; allowedTransferMethod: TokenTransferMethod | "any"; }; -export declare function refreshSessionInRequest({ - res, - req, - userContext, - config, - recipeInterfaceImpl, -}: { +export declare function refreshSessionInRequest({ res, req, userContext, config, recipeInterfaceImpl, }: { res: any; req: any; userContext: UserContext; config: TypeNormalisedInput; recipeInterfaceImpl: RecipeInterface; }): Promise; -export declare function createNewSessionInRequest({ - req, - res, - userContext, - recipeInstance, - accessTokenPayload, - userId, - recipeUserId, - config, - appInfo, - sessionDataInDatabase, - tenantId, -}: { +export declare function createNewSessionInRequest({ req, res, userContext, recipeInstance, accessTokenPayload, userId, recipeUserId, config, appInfo, sessionDataInDatabase, tenantId, }: { req: any; res: any; userContext: UserContext; diff --git a/lib/build/recipe/session/sessionRequestFunctions.js b/lib/build/recipe/session/sessionRequestFunctions.js index df0a56f0e..657e023a7 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.js +++ b/lib/build/recipe/session/sessionRequestFunctions.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.createNewSessionInRequest = exports.refreshSessionInRequest = exports.getAccessTokenFromRequest = exports.getSessionFromRequest = void 0; const framework_1 = __importDefault(require("../../framework")); @@ -19,7 +17,7 @@ const accessToken_1 = require("./accessToken"); const error_1 = __importDefault(require("./error")); // We are defining this here (and not exporting it) to reduce the scope of legacy code const LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME = "sIdRefreshToken"; -async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, options, userContext }) { +async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, options, userContext, }) { logger_1.logDebugMessage("getSession: Started"); const configuredFramework = supertokens_1.default.getInstanceOrThrowError().framework; if (configuredFramework !== "custom") { @@ -34,9 +32,7 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op logger_1.logDebugMessage("getSession: Wrapping done"); // This token isn't handled by getToken to limit the scope of this legacy/migration code if (req.getCookieValue(LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME) !== undefined) { - logger_1.logDebugMessage( - "getSession: Throwing TRY_REFRESH_TOKEN because the request is using a legacy session" - ); + logger_1.logDebugMessage("getSession: Throwing TRY_REFRESH_TOKEN because the request is using a legacy session"); // This could create a spike on refresh calls during the update of the backend SDK throw new error_1.default({ message: "using legacy session, please call the refresh API", @@ -74,12 +70,9 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op if (doAntiCsrfCheck && antiCsrf === "VIA_CUSTOM_HEADER") { if (antiCsrf === "VIA_CUSTOM_HEADER") { if (utils_2.getRidFromHeader(req) === undefined) { - logger_1.logDebugMessage( - "getSession: Returning TRY_REFRESH_TOKEN because custom header (rid) was not passed" - ); + logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because custom header (rid) was not passed"); throw new error_1.default({ - message: - "anti-csrf check failed. Please pass 'rid: \"session\"' header in the request, or set doAntiCsrfCheck to false for this API", + message: "anti-csrf check failed. Please pass 'rid: \"session\"' header in the request, or set doAntiCsrfCheck to false for this API", type: error_1.default.TRY_REFRESH_TOKEN, }); } @@ -95,30 +88,22 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op userContext, }); if (session !== undefined) { - const claimValidators = await utils_1.getRequiredClaimValidators( - session, - options === null || options === void 0 ? void 0 : options.overrideGlobalClaimValidators, - userContext - ); + const claimValidators = await utils_1.getRequiredClaimValidators(session, options === null || options === void 0 ? void 0 : options.overrideGlobalClaimValidators, userContext); await session.assertClaims(claimValidators, userContext); // requestTransferMethod can only be undefined here if the user overridden getSession // to load the session by a custom method in that (very niche) case they also need to // override how the session is attached to the response. // In that scenario the transferMethod passed to attachToRequestResponse likely doesn't // matter, still, we follow the general fallback logic - await session.attachToRequestResponse( - { - req, - res, - transferMethod: - requestTransferMethod !== undefined - ? requestTransferMethod - : allowedTransferMethod !== "any" - ? allowedTransferMethod - : "header", - }, - userContext - ); + await session.attachToRequestResponse({ + req, + res, + transferMethod: requestTransferMethod !== undefined + ? requestTransferMethod + : allowedTransferMethod !== "any" + ? allowedTransferMethod + : "header", + }, userContext); } return session; } @@ -134,35 +119,29 @@ function getAccessTokenFromRequest(req, allowedTransferMethod) { accessToken_1.validateAccessTokenStructure(info.payload, info.version); logger_1.logDebugMessage("getSession: got access token from " + transferMethod); accessTokens[transferMethod] = info; - } catch (_a) { - logger_1.logDebugMessage( - `getSession: ignoring token in ${transferMethod}, because it doesn't match our access token structure` - ); + } + catch (_a) { + logger_1.logDebugMessage(`getSession: ignoring token in ${transferMethod}, because it doesn't match our access token structure`); } } } let requestTransferMethod; let accessToken; - if ( - (allowedTransferMethod === "any" || allowedTransferMethod === "header") && - accessTokens["header"] !== undefined - ) { + if ((allowedTransferMethod === "any" || allowedTransferMethod === "header") && + accessTokens["header"] !== undefined) { logger_1.logDebugMessage("getSession: using header transfer method"); requestTransferMethod = "header"; accessToken = accessTokens["header"]; - } else if ( - (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && - accessTokens["cookie"] !== undefined - ) { + } + else if ((allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && + accessTokens["cookie"] !== undefined) { logger_1.logDebugMessage("getSession: using cookie transfer method"); // If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN. // This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set). // ensuring outdated token payload isn't used. const hasMultipleAccessTokenCookies = cookieAndHeaders_1.hasMultipleCookiesForTokenType(req, "access"); if (hasMultipleAccessTokenCookies) { - logger_1.logDebugMessage( - "getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies" - ); + logger_1.logDebugMessage("getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies"); throw new error_1.default({ message: "Multiple access tokens present in the request cookies.", type: error_1.default.TRY_REFRESH_TOKEN, @@ -178,7 +157,7 @@ exports.getAccessTokenFromRequest = getAccessTokenFromRequest; In all cases: if sIdRefreshToken token exists (so it's a legacy session) we clear it. Check http://localhost:3002/docs/contribute/decisions/session/0008 for further details and a table of expected behaviours */ -async function refreshSessionInRequest({ res, req, userContext, config, recipeInterfaceImpl }) { +async function refreshSessionInRequest({ res, req, userContext, config, recipeInterfaceImpl, }) { logger_1.logDebugMessage("refreshSession: Started"); const configuredFramework = supertokens_1.default.getInstanceOrThrowError().framework; if (configuredFramework !== "custom") { @@ -209,46 +188,31 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn logger_1.logDebugMessage("refreshSession: getTokenTransferMethod returned " + allowedTransferMethod); let requestTransferMethod; let refreshToken; - if ( - (allowedTransferMethod === "any" || allowedTransferMethod === "header") && - refreshTokens["header"] !== undefined - ) { + if ((allowedTransferMethod === "any" || allowedTransferMethod === "header") && + refreshTokens["header"] !== undefined) { logger_1.logDebugMessage("refreshSession: using header transfer method"); requestTransferMethod = "header"; refreshToken = refreshTokens["header"]; - } else if ((allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && refreshTokens["cookie"]) { + } + else if ((allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && refreshTokens["cookie"]) { logger_1.logDebugMessage("refreshSession: using cookie transfer method"); requestTransferMethod = "cookie"; refreshToken = refreshTokens["cookie"]; - } else { + } + else { // This token isn't handled by getToken/setToken to limit the scope of this legacy/migration code if (req.getCookieValue(LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME) !== undefined) { - logger_1.logDebugMessage( - "refreshSession: cleared legacy id refresh token because refresh token was not found" - ); - cookieAndHeaders_1.setCookie( - config, - res, - LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, - "", - 0, - "accessTokenPath", - req, - userContext - ); + logger_1.logDebugMessage("refreshSession: cleared legacy id refresh token because refresh token was not found"); + cookieAndHeaders_1.setCookie(config, res, LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, "", 0, "accessTokenPath", req, userContext); } // We need to clear the access token cookie if // - the refresh token is not found, and // - the allowedTransferMethod is 'cookie' or 'any', and // - an access token cookie exists (otherwise it'd be a no-op) // See: https://github.com/supertokens/supertokens-node/issues/790 - if ( - (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && - cookieAndHeaders_1.getToken(req, "access", "cookie") !== undefined - ) { - logger_1.logDebugMessage( - "refreshSession: cleared all session tokens and returning UNAUTHORISED because refresh token in request is undefined" - ); + if ((allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && + cookieAndHeaders_1.getToken(req, "access", "cookie") !== undefined) { + logger_1.logDebugMessage("refreshSession: cleared all session tokens and returning UNAUTHORISED because refresh token in request is undefined"); // We're clearing all session tokens instead of just the access token and then throwing an UNAUTHORISED // error with `clearTokens: false`. This approach avoids confusion and we don't want to retain session // tokens on the client in any case if the refresh API is called without a refresh token but with an access token. @@ -279,9 +243,7 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn } if (antiCsrf === "VIA_CUSTOM_HEADER" && !disableAntiCsrf) { if (utils_2.getRidFromHeader(req) === undefined) { - logger_1.logDebugMessage( - "refreshSession: Returning UNAUTHORISED because custom header (rid) was not passed" - ); + logger_1.logDebugMessage("refreshSession: Returning UNAUTHORISED because custom header (rid) was not passed"); throw new error_1.default({ message: "anti-csrf check failed. Please pass 'rid: \"session\"' header in the request.", type: error_1.default.UNAUTHORISED, @@ -300,27 +262,15 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn disableAntiCsrf, userContext, }); - } catch (ex) { - if ( - error_1.default.isErrorFromSuperTokens(ex) && - (ex.type === error_1.default.TOKEN_THEFT_DETECTED || ex.payload.clearTokens === true) - ) { + } + catch (ex) { + if (error_1.default.isErrorFromSuperTokens(ex) && + (ex.type === error_1.default.TOKEN_THEFT_DETECTED || ex.payload.clearTokens === true)) { // We clear the LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME here because we want to limit the scope of this legacy/migration code // so the token clearing functions in the error handlers do not if (req.getCookieValue(LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME) !== undefined) { - logger_1.logDebugMessage( - "refreshSession: cleared legacy id refresh token because refresh is clearing other tokens" - ); - cookieAndHeaders_1.setCookie( - config, - res, - LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, - "", - 0, - "accessTokenPath", - req, - userContext - ); + logger_1.logDebugMessage("refreshSession: cleared legacy id refresh token because refresh is clearing other tokens"); + cookieAndHeaders_1.setCookie(config, res, LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, "", 0, "accessTokenPath", req, userContext); } } throw ex; @@ -332,45 +282,21 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn cookieAndHeaders_1.clearSession(config, res, transferMethod, req, userContext); } } - await session.attachToRequestResponse( - { - req, - res, - transferMethod: requestTransferMethod, - }, - userContext - ); + await session.attachToRequestResponse({ + req, + res, + transferMethod: requestTransferMethod, + }, userContext); logger_1.logDebugMessage("refreshSession: Success!"); // This token isn't handled by getToken/setToken to limit the scope of this legacy/migration code if (req.getCookieValue(LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME) !== undefined) { logger_1.logDebugMessage("refreshSession: cleared legacy id refresh token after successful refresh"); - cookieAndHeaders_1.setCookie( - config, - res, - LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, - "", - 0, - "accessTokenPath", - req, - userContext - ); + cookieAndHeaders_1.setCookie(config, res, LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, "", 0, "accessTokenPath", req, userContext); } return session; } exports.refreshSessionInRequest = refreshSessionInRequest; -async function createNewSessionInRequest({ - req, - res, - userContext, - recipeInstance, - accessTokenPayload, - userId, - recipeUserId, - config, - appInfo, - sessionDataInDatabase, - tenantId, -}) { +async function createNewSessionInRequest({ req, res, userContext, recipeInstance, accessTokenPayload, userId, recipeUserId, config, appInfo, sessionDataInDatabase, tenantId, }) { logger_1.logDebugMessage("createNewSession: Started"); const configuredFramework = supertokens_1.default.getInstanceOrThrowError().framework; if (configuredFramework !== "custom") { @@ -400,37 +326,30 @@ async function createNewSessionInRequest({ // We default to header if we can't "parse" it or if it's undefined if (authModeHeader === "cookie") { outputTransferMethod = authModeHeader; - } else { + } + else { outputTransferMethod = "header"; } } logger_1.logDebugMessage("createNewSession: using transfer method " + outputTransferMethod); - if ( - outputTransferMethod === "cookie" && + if (outputTransferMethod === "cookie" && config.getCookieSameSite({ request: req, userContext, }) === "none" && !config.cookieSecure && - !( - (appInfo.topLevelAPIDomain === "localhost" || utils_2.isAnIpAddress(appInfo.topLevelAPIDomain)) && + !((appInfo.topLevelAPIDomain === "localhost" || utils_2.isAnIpAddress(appInfo.topLevelAPIDomain)) && (appInfo.getTopLevelWebsiteDomain({ request: req, userContext, }) === "localhost" || - utils_2.isAnIpAddress( - appInfo.getTopLevelWebsiteDomain({ - request: req, - userContext, - }) - )) - ) - ) { + utils_2.isAnIpAddress(appInfo.getTopLevelWebsiteDomain({ + request: req, + userContext, + }))))) { // We can allow insecure cookie when both website & API domain are localhost or an IP // When either of them is a different domain, API domain needs to have https and a secure cookie to work - throw new Error( - "Since your API and website domain are different, for sessions to work, please use https on your apiDomain and dont set cookieSecure to false." - ); + throw new Error("Since your API and website domain are different, for sessions to work, please use https on your apiDomain and dont set cookieSecure to false."); } const disableAntiCsrf = outputTransferMethod === "header"; const session = await recipeInstance.recipeInterfaceImpl.createNewSession({ @@ -444,22 +363,16 @@ async function createNewSessionInRequest({ }); logger_1.logDebugMessage("createNewSession: Session created in core built"); for (const transferMethod of constants_1.availableTokenTransferMethods) { - if ( - transferMethod !== outputTransferMethod && - cookieAndHeaders_1.getToken(req, "access", transferMethod) !== undefined - ) { + if (transferMethod !== outputTransferMethod && cookieAndHeaders_1.getToken(req, "access", transferMethod) !== undefined) { cookieAndHeaders_1.clearSession(config, res, transferMethod, req, userContext); } } logger_1.logDebugMessage("createNewSession: Cleared old tokens"); - await session.attachToRequestResponse( - { - req, - res, - transferMethod: outputTransferMethod, - }, - userContext - ); + await session.attachToRequestResponse({ + req, + res, + transferMethod: outputTransferMethod, + }, userContext); logger_1.logDebugMessage("createNewSession: Attached new tokens to res"); return session; } diff --git a/lib/build/recipe/session/types.d.ts b/lib/build/recipe/session/types.d.ts index b573925ed..cbfaf78b0 100644 --- a/lib/build/recipe/session/types.d.ts +++ b/lib/build/recipe/session/types.d.ts @@ -57,10 +57,7 @@ export declare type TypeInput = { exposeAccessTokenToFrontendInCookieBasedAuth?: boolean; jwksRefreshIntervalSec?: number; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -78,11 +75,10 @@ export declare type TypeNormalisedInput = { sessionExpiredStatusCode: number; errorHandlers: NormalisedErrorHandlers; overwriteSessionDuringSignInUp: boolean | undefined; - antiCsrfFunctionOrString: - | "VIA_TOKEN" - | "VIA_CUSTOM_HEADER" - | "NONE" - | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => "VIA_CUSTOM_HEADER" | "NONE"); + antiCsrfFunctionOrString: "VIA_TOKEN" | "VIA_CUSTOM_HEADER" | "NONE" | ((input: { + request: BaseRequest | undefined; + userContext: UserContext; + }) => "VIA_CUSTOM_HEADER" | "NONE"); getTokenTransferMethod: (input: { req: BaseRequest; forCreateNewSession: boolean; @@ -92,10 +88,7 @@ export declare type TypeNormalisedInput = { exposeAccessTokenToFrontendInCookieBasedAuth: boolean; jwksRefreshIntervalSec: number; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -106,22 +99,10 @@ export interface ErrorHandlerMiddleware { (message: string, request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; } export interface TokenTheftErrorHandlerMiddleware { - ( - sessionHandle: string, - userId: string, - recipeUserId: RecipeUserId, - request: BaseRequest, - response: BaseResponse, - userContext: UserContext - ): Promise; + (sessionHandle: string, userId: string, recipeUserId: RecipeUserId, request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; } export interface InvalidClaimErrorHandlerMiddleware { - ( - validatorErrors: ClaimValidationError[], - request: BaseRequest, - response: BaseResponse, - userContext: UserContext - ): Promise; + (validatorErrors: ClaimValidationError[], request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; } export interface NormalisedErrorHandlers { onUnauthorised: ErrorHandlerMiddleware; @@ -134,11 +115,7 @@ export interface VerifySessionOptions { antiCsrfCheck?: boolean; sessionRequired?: boolean; checkDatabase?: boolean; - overrideGlobalClaimValidators?: ( - globalClaimValidators: SessionClaimValidator[], - session: SessionContainerInterface, - userContext: UserContext - ) => Promise | SessionClaimValidator[]; + overrideGlobalClaimValidators?: (globalClaimValidators: SessionClaimValidator[], session: SessionContainerInterface, userContext: UserContext) => Promise | SessionClaimValidator[]; } export declare type RecipeInterface = { createNewSession(input: { @@ -194,8 +171,14 @@ export declare type RecipeInterface = { fetchAcrossAllTenants?: boolean; userContext: UserContext; }): Promise; - revokeSession(input: { sessionHandle: string; userContext: UserContext }): Promise; - revokeMultipleSessions(input: { sessionHandles: string[]; userContext: UserContext }): Promise; + revokeSession(input: { + sessionHandle: string; + userContext: UserContext; + }): Promise; + revokeMultipleSessions(input: { + sessionHandles: string[]; + userContext: UserContext; + }): Promise; updateSessionDataInDatabase(input: { sessionHandle: string; newSessionData: any; @@ -213,24 +196,21 @@ export declare type RecipeInterface = { accessToken: string; newAccessTokenPayload?: any; userContext: UserContext; - }): Promise< - | { - status: "OK"; - session: { - handle: string; - userId: string; - recipeUserId: RecipeUserId; - userDataInJWT: any; - tenantId: string; - }; - accessToken?: { - token: string; - expiry: number; - createdTime: number; - }; - } - | undefined - >; + }): Promise<{ + status: "OK"; + session: { + handle: string; + userId: string; + recipeUserId: RecipeUserId; + userDataInJWT: any; + tenantId: string; + }; + accessToken?: { + token: string; + expiry: number; + createdTime: number; + }; + } | undefined>; validateClaims(input: { userId: string; recipeUserId: RecipeUserId; @@ -256,16 +236,17 @@ export declare type RecipeInterface = { sessionHandle: string; claim: SessionClaim; userContext: UserContext; - }): Promise< - | { - status: "SESSION_DOES_NOT_EXIST_ERROR"; - } - | { - status: "OK"; - value: T | undefined; - } - >; - removeClaim(input: { sessionHandle: string; claim: SessionClaim; userContext: UserContext }): Promise; + }): Promise<{ + status: "SESSION_DOES_NOT_EXIST_ERROR"; + } | { + status: "OK"; + value: T | undefined; + }>; + removeClaim(input: { + sessionHandle: string; + claim: SessionClaim; + userContext: UserContext; + }): Promise; }; export interface SessionContainerInterface { revokeSession(userContext?: Record): Promise; @@ -308,21 +289,17 @@ export declare type APIInterface = { * since it's not something that is directly called by the user on the * frontend anyway */ - refreshPOST: - | undefined - | ((input: { options: APIOptions; userContext: UserContext }) => Promise); - signOutPOST: - | undefined - | ((input: { - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - } - | GeneralErrorResponse - >); + refreshPOST: undefined | ((input: { + options: APIOptions; + userContext: UserContext; + }) => Promise); + signOutPOST: undefined | ((input: { + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + } | GeneralErrorResponse>); verifySession(input: { verifySessionOptions: VerifySessionOptions | undefined; options: APIOptions; @@ -339,30 +316,25 @@ export declare type SessionInformation = { timeCreated: number; tenantId: string; }; -export declare type ClaimValidationResult = - | { - isValid: true; - } - | { - isValid: false; - reason?: JSONValue; - }; +export declare type ClaimValidationResult = { + isValid: true; +} | { + isValid: false; + reason?: JSONValue; +}; export declare type ClaimValidationError = { id: string; reason?: JSONValue; }; -export declare type SessionClaimValidator = ( - | // We split the type like this to express that either both claim and shouldRefetch is defined or neither. - { - claim: SessionClaim; - /** - * Decides if we need to refetch the claim value before checking the payload with `isValid`. - * E.g.: if the information in the payload is expired, or is not sufficient for this check. - */ - shouldRefetch: (payload: any, userContext: UserContext) => Promise | boolean; - } - | {} -) & { +export declare type SessionClaimValidator = (// We split the type like this to express that either both claim and shouldRefetch is defined or neither. +{ + claim: SessionClaim; + /** + * Decides if we need to refetch the claim value before checking the payload with `isValid`. + * E.g.: if the information in the payload is expired, or is not sufficient for this check. + */ + shouldRefetch: (payload: any, userContext: UserContext) => Promise | boolean; +} | {}) & { id: string; /** * Decides if the claim is valid based on the payload (and not checking DB or anything else) @@ -377,13 +349,7 @@ export declare abstract class SessionClaim { * The undefined return value signifies that we don't want to update the claim payload and or the claim value is not present in the database * This can happen for example with a second factor auth claim, where we don't want to add the claim to the session automatically. */ - abstract fetchValue( - userId: string, - recipeUserId: RecipeUserId, - tenantId: string, - currentPayload: JSONObject | undefined, - userContext: UserContext - ): Promise | T | undefined; + abstract fetchValue(userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext): Promise | T | undefined; /** * Saves the provided value into the payload, by cloning and updating the entire object. * @@ -408,13 +374,7 @@ export declare abstract class SessionClaim { * @returns Claim value */ abstract getValueFromPayload(payload: JSONObject, userContext: UserContext): T | undefined; - build( - userId: string, - recipeUserId: RecipeUserId, - tenantId: string, - currentPayload: JSONObject | undefined, - userContext: UserContext - ): Promise; + build(userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext): Promise; } export declare type ReqResInfo = { res: BaseResponse; diff --git a/lib/build/recipe/session/utils.d.ts b/lib/build/recipe/session/utils.d.ts index 24ce87cb5..f48759c19 100644 --- a/lib/build/recipe/session/utils.d.ts +++ b/lib/build/recipe/session/utils.d.ts @@ -1,76 +1,20 @@ // @ts-nocheck -import { - TypeInput, - TypeNormalisedInput, - ClaimValidationError, - SessionClaimValidator, - SessionContainerInterface, - VerifySessionOptions, - TokenTransferMethod, -} from "./types"; +import { TypeInput, TypeNormalisedInput, ClaimValidationError, SessionClaimValidator, SessionContainerInterface, VerifySessionOptions, TokenTransferMethod } from "./types"; import SessionRecipe from "./recipe"; import { NormalisedAppinfo, UserContext } from "../../types"; import type { BaseRequest, BaseResponse } from "../../framework"; import RecipeUserId from "../../recipeUserId"; -export declare function sendTryRefreshTokenResponse( - recipeInstance: SessionRecipe, - _: string, - __: BaseRequest, - response: BaseResponse, - ___: UserContext -): Promise; -export declare function sendUnauthorisedResponse( - recipeInstance: SessionRecipe, - _: string, - __: BaseRequest, - response: BaseResponse, - ___: UserContext -): Promise; -export declare function sendInvalidClaimResponse( - recipeInstance: SessionRecipe, - claimValidationErrors: ClaimValidationError[], - __: BaseRequest, - response: BaseResponse, - ___: UserContext -): Promise; -export declare function sendTokenTheftDetectedResponse( - recipeInstance: SessionRecipe, - sessionHandle: string, - _: string, - __: RecipeUserId, - ___: BaseRequest, - response: BaseResponse, - userContext: UserContext -): Promise; +export declare function sendTryRefreshTokenResponse(recipeInstance: SessionRecipe, _: string, __: BaseRequest, response: BaseResponse, ___: UserContext): Promise; +export declare function sendUnauthorisedResponse(recipeInstance: SessionRecipe, _: string, __: BaseRequest, response: BaseResponse, ___: UserContext): Promise; +export declare function sendInvalidClaimResponse(recipeInstance: SessionRecipe, claimValidationErrors: ClaimValidationError[], __: BaseRequest, response: BaseResponse, ___: UserContext): Promise; +export declare function sendTokenTheftDetectedResponse(recipeInstance: SessionRecipe, sessionHandle: string, _: string, __: RecipeUserId, ___: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; export declare function normaliseSessionScopeOrThrowError(sessionScope: string): string; export declare function getURLProtocol(url: string): string; -export declare function validateAndNormaliseUserInput( - recipeInstance: SessionRecipe, - appInfo: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(recipeInstance: SessionRecipe, appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; export declare function normaliseSameSiteOrThrowError(sameSite: string): "strict" | "lax" | "none"; -export declare function setAccessTokenInResponse( - res: BaseResponse, - accessToken: string, - frontToken: string, - config: TypeNormalisedInput, - transferMethod: TokenTransferMethod, - req: BaseRequest | undefined, - userContext: UserContext -): void; -export declare function getRequiredClaimValidators( - session: SessionContainerInterface, - overrideGlobalClaimValidators: VerifySessionOptions["overrideGlobalClaimValidators"], - userContext: UserContext -): Promise; -export declare function validateClaimsInPayload( - claimValidators: SessionClaimValidator[], - newAccessTokenPayload: any, - userContext: UserContext -): Promise< - { - id: string; - reason: import("../../types").JSONValue; - }[] ->; +export declare function setAccessTokenInResponse(res: BaseResponse, accessToken: string, frontToken: string, config: TypeNormalisedInput, transferMethod: TokenTransferMethod, req: BaseRequest | undefined, userContext: UserContext): void; +export declare function getRequiredClaimValidators(session: SessionContainerInterface, overrideGlobalClaimValidators: VerifySessionOptions["overrideGlobalClaimValidators"], userContext: UserContext): Promise; +export declare function validateClaimsInPayload(claimValidators: SessionClaimValidator[], newAccessTokenPayload: any, userContext: UserContext): Promise<{ + id: string; + reason: import("../../types").JSONValue; +}[]>; diff --git a/lib/build/recipe/session/utils.js b/lib/build/recipe/session/utils.js index e4bfc3e2b..ef0387d79 100644 --- a/lib/build/recipe/session/utils.js +++ b/lib/build/recipe/session/utils.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateClaimsInPayload = exports.getRequiredClaimValidators = exports.setAccessTokenInResponse = exports.normaliseSameSiteOrThrowError = exports.validateAndNormaliseUserInput = exports.getURLProtocol = exports.normaliseSessionScopeOrThrowError = exports.sendTokenTheftDetectedResponse = exports.sendInvalidClaimResponse = exports.sendUnauthorisedResponse = exports.sendTryRefreshTokenResponse = void 0; const cookieAndHeaders_1 = require("./cookieAndHeaders"); @@ -28,11 +26,7 @@ const utils_1 = require("../../utils"); const utils_2 = require("../../utils"); const logger_1 = require("../../logger"); async function sendTryRefreshTokenResponse(recipeInstance, _, __, response, ___) { - utils_2.sendNon200ResponseWithMessage( - response, - "try refresh token", - recipeInstance.config.sessionExpiredStatusCode - ); + utils_2.sendNon200ResponseWithMessage(response, "try refresh token", recipeInstance.config.sessionExpiredStatusCode); } exports.sendTryRefreshTokenResponse = sendTryRefreshTokenResponse; async function sendUnauthorisedResponse(recipeInstance, _, __, response, ___) { @@ -48,11 +42,7 @@ async function sendInvalidClaimResponse(recipeInstance, claimValidationErrors, _ exports.sendInvalidClaimResponse = sendInvalidClaimResponse; async function sendTokenTheftDetectedResponse(recipeInstance, sessionHandle, _, __, ___, response, userContext) { await recipeInstance.recipeInterfaceImpl.revokeSession({ sessionHandle, userContext }); - utils_2.sendNon200ResponseWithMessage( - response, - "token theft detected", - recipeInstance.config.sessionExpiredStatusCode - ); + utils_2.sendNon200ResponseWithMessage(response, "token theft detected", recipeInstance.config.sessionExpiredStatusCode); } exports.sendTokenTheftDetectedResponse = sendTokenTheftDetectedResponse; function normaliseSessionScopeOrThrowError(sessionScope) { @@ -69,7 +59,8 @@ function normaliseSessionScopeOrThrowError(sessionScope) { let urlObj = new URL(sessionScope); sessionScope = urlObj.hostname; return sessionScope; - } catch (err) { + } + catch (err) { throw new Error("Please provide a valid sessionScope"); } } @@ -90,30 +81,23 @@ function getURLProtocol(url) { exports.getURLProtocol = getURLProtocol; function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { var _a, _b, _c, _d; - let cookieDomain = - config === undefined || config.cookieDomain === undefined - ? undefined - : normaliseSessionScopeOrThrowError(config.cookieDomain); - let olderCookieDomain = - config === undefined || config.olderCookieDomain === undefined || config.olderCookieDomain === "" - ? config === null || config === void 0 - ? void 0 - : config.olderCookieDomain - : normaliseSessionScopeOrThrowError(config.olderCookieDomain); - let accessTokenPath = - config === undefined || config.accessTokenPath === undefined - ? new normalisedURLPath_1.default("/") - : new normalisedURLPath_1.default(config.accessTokenPath); + let cookieDomain = config === undefined || config.cookieDomain === undefined + ? undefined + : normaliseSessionScopeOrThrowError(config.cookieDomain); + let olderCookieDomain = config === undefined || config.olderCookieDomain === undefined || config.olderCookieDomain === "" + ? config === null || config === void 0 ? void 0 : config.olderCookieDomain + : normaliseSessionScopeOrThrowError(config.olderCookieDomain); + let accessTokenPath = config === undefined || config.accessTokenPath === undefined + ? new normalisedURLPath_1.default("/") + : new normalisedURLPath_1.default(config.accessTokenPath); let protocolOfAPIDomain = getURLProtocol(appInfo.apiDomain.getAsStringDangerous()); let cookieSameSite = (input) => { - let protocolOfWebsiteDomain = getURLProtocol( - appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() - ); + let protocolOfWebsiteDomain = getURLProtocol(appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous()); return appInfo.topLevelAPIDomain !== appInfo.getTopLevelWebsiteDomain(input) || protocolOfAPIDomain !== protocolOfWebsiteDomain ? "none" @@ -123,16 +107,11 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { let normalisedCookieSameSite = normaliseSameSiteOrThrowError(config.cookieSameSite); cookieSameSite = () => normalisedCookieSameSite; } - let cookieSecure = - config === undefined || config.cookieSecure === undefined - ? appInfo.apiDomain.getAsStringDangerous().startsWith("https") - : config.cookieSecure; - let sessionExpiredStatusCode = - config === undefined || config.sessionExpiredStatusCode === undefined ? 401 : config.sessionExpiredStatusCode; - const invalidClaimStatusCode = - (_a = config === null || config === void 0 ? void 0 : config.invalidClaimStatusCode) !== null && _a !== void 0 - ? _a - : 403; + let cookieSecure = config === undefined || config.cookieSecure === undefined + ? appInfo.apiDomain.getAsStringDangerous().startsWith("https") + : config.cookieSecure; + let sessionExpiredStatusCode = config === undefined || config.sessionExpiredStatusCode === undefined ? 401 : config.sessionExpiredStatusCode; + const invalidClaimStatusCode = (_a = config === null || config === void 0 ? void 0 : config.invalidClaimStatusCode) !== null && _a !== void 0 ? _a : 403; if (sessionExpiredStatusCode === invalidClaimStatusCode) { throw new Error("sessionExpiredStatusCode and sessionExpiredStatusCode must be different"); } @@ -141,7 +120,7 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { throw new Error("antiCsrf config must be one of 'NONE' or 'VIA_CUSTOM_HEADER' or 'VIA_TOKEN'"); } } - let antiCsrf = ({ request, userContext }) => { + let antiCsrf = ({ request, userContext, }) => { const sameSite = cookieSameSite({ request, userContext, @@ -156,15 +135,7 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { } let errorHandlers = { onTokenTheftDetected: async (sessionHandle, userId, recipeUserId, request, response, userContext) => { - return await sendTokenTheftDetectedResponse( - recipeInstance, - sessionHandle, - userId, - recipeUserId, - request, - response, - userContext - ); + return await sendTokenTheftDetectedResponse(recipeInstance, sessionHandle, userId, recipeUserId, request, response, userContext); }, onTryRefreshToken: async (message, request, response, userContext) => { return await sendTryRefreshTokenResponse(recipeInstance, message, request, response, userContext); @@ -196,31 +167,15 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { errorHandlers.onClearDuplicateSessionCookies = config.errorHandlers.onClearDuplicateSessionCookies; } } - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { - useDynamicAccessTokenSigningKey: - (_b = config === null || config === void 0 ? void 0 : config.useDynamicAccessTokenSigningKey) !== null && - _b !== void 0 - ? _b - : true, - exposeAccessTokenToFrontendInCookieBasedAuth: - (_c = - config === null || config === void 0 ? void 0 : config.exposeAccessTokenToFrontendInCookieBasedAuth) !== - null && _c !== void 0 - ? _c - : false, + useDynamicAccessTokenSigningKey: (_b = config === null || config === void 0 ? void 0 : config.useDynamicAccessTokenSigningKey) !== null && _b !== void 0 ? _b : true, + exposeAccessTokenToFrontendInCookieBasedAuth: (_c = config === null || config === void 0 ? void 0 : config.exposeAccessTokenToFrontendInCookieBasedAuth) !== null && _c !== void 0 ? _c : false, refreshTokenPath: appInfo.apiBasePath.appendPath(new normalisedURLPath_1.default(constants_1.REFRESH_API_PATH)), accessTokenPath, - getTokenTransferMethod: - (config === null || config === void 0 ? void 0 : config.getTokenTransferMethod) === undefined - ? defaultGetTokenTransferMethod - : config.getTokenTransferMethod, + getTokenTransferMethod: (config === null || config === void 0 ? void 0 : config.getTokenTransferMethod) === undefined + ? defaultGetTokenTransferMethod + : config.getTokenTransferMethod, cookieDomain, olderCookieDomain, getCookieSameSite: cookieSameSite, @@ -230,13 +185,8 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { antiCsrfFunctionOrString: antiCsrf, override, invalidClaimStatusCode, - overwriteSessionDuringSignInUp: - config === null || config === void 0 ? void 0 : config.overwriteSessionDuringSignInUp, - jwksRefreshIntervalSec: - (_d = config === null || config === void 0 ? void 0 : config.jwksRefreshIntervalSec) !== null && - _d !== void 0 - ? _d - : 3600 * 4, + overwriteSessionDuringSignInUp: config === null || config === void 0 ? void 0 : config.overwriteSessionDuringSignInUp, + jwksRefreshIntervalSec: (_d = config === null || config === void 0 ? void 0 : config.jwksRefreshIntervalSec) !== null && _d !== void 0 ? _d : 3600 * 4, }; } exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; @@ -251,51 +201,31 @@ function normaliseSameSiteOrThrowError(sameSite) { exports.normaliseSameSiteOrThrowError = normaliseSameSiteOrThrowError; function setAccessTokenInResponse(res, accessToken, frontToken, config, transferMethod, req, userContext) { cookieAndHeaders_1.setFrontTokenInHeaders(res, frontToken); - cookieAndHeaders_1.setToken( - config, - res, - "access", - accessToken, - // We set the expiration to 1 year, because we can't really access the expiration of the refresh token everywhere we are setting it. + cookieAndHeaders_1.setToken(config, res, "access", accessToken, + // We set the expiration to 1 year, because we can't really access the expiration of the refresh token everywhere we are setting it. + // This should be safe to do, since this is only the validity of the cookie (set here or on the frontend) but we check the expiration of the JWT anyway. + // Even if the token is expired the presence of the token indicates that the user could have a valid refresh token + // Some browsers now cap the maximum expiry at 400 days, so we set it to 1 year, which should suffice. + Date.now() + constants_1.oneYearInMs, transferMethod, req, userContext); + if (config.exposeAccessTokenToFrontendInCookieBasedAuth && transferMethod === "cookie") { + cookieAndHeaders_1.setToken(config, res, "access", accessToken, + // We set the expiration to 1 years, because we can't really access the expiration of the refresh token everywhere we are setting it. // This should be safe to do, since this is only the validity of the cookie (set here or on the frontend) but we check the expiration of the JWT anyway. // Even if the token is expired the presence of the token indicates that the user could have a valid refresh token // Some browsers now cap the maximum expiry at 400 days, so we set it to 1 year, which should suffice. - Date.now() + constants_1.oneYearInMs, - transferMethod, - req, - userContext - ); - if (config.exposeAccessTokenToFrontendInCookieBasedAuth && transferMethod === "cookie") { - cookieAndHeaders_1.setToken( - config, - res, - "access", - accessToken, - // We set the expiration to 1 years, because we can't really access the expiration of the refresh token everywhere we are setting it. - // This should be safe to do, since this is only the validity of the cookie (set here or on the frontend) but we check the expiration of the JWT anyway. - // Even if the token is expired the presence of the token indicates that the user could have a valid refresh token - // Some browsers now cap the maximum expiry at 400 days, so we set it to 1 year, which should suffice. - Date.now() + constants_1.oneYearInMs, - "header", - req, - userContext - ); + Date.now() + constants_1.oneYearInMs, "header", req, userContext); } } exports.setAccessTokenInResponse = setAccessTokenInResponse; async function getRequiredClaimValidators(session, overrideGlobalClaimValidators, userContext) { - const claimValidatorsAddedByOtherRecipes = recipe_1.default - .getInstanceOrThrowError() - .getClaimValidatorsAddedByOtherRecipes(); - const globalClaimValidators = await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.getGlobalClaimValidators({ - userId: session.getUserId(userContext), - recipeUserId: session.getRecipeUserId(userContext), - tenantId: session.getTenantId(userContext), - claimValidatorsAddedByOtherRecipes, - userContext, - }); + const claimValidatorsAddedByOtherRecipes = recipe_1.default.getInstanceOrThrowError().getClaimValidatorsAddedByOtherRecipes(); + const globalClaimValidators = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getGlobalClaimValidators({ + userId: session.getUserId(userContext), + recipeUserId: session.getRecipeUserId(userContext), + tenantId: session.getTenantId(userContext), + claimValidatorsAddedByOtherRecipes, + userContext, + }); return overrideGlobalClaimValidators !== undefined ? await overrideGlobalClaimValidators(globalClaimValidators, session, userContext) : globalClaimValidators; @@ -305,9 +235,7 @@ async function validateClaimsInPayload(claimValidators, newAccessTokenPayload, u const validationErrors = []; for (const validator of claimValidators) { const claimValidationResult = await validator.validate(newAccessTokenPayload, userContext); - logger_1.logDebugMessage( - "validateClaimsInPayload " + validator.id + " validation res " + JSON.stringify(claimValidationResult) - ); + logger_1.logDebugMessage("validateClaimsInPayload " + validator.id + " validation res " + JSON.stringify(claimValidationResult)); if (!claimValidationResult.isValid) { validationErrors.push({ id: validator.id, @@ -318,7 +246,7 @@ async function validateClaimsInPayload(claimValidators, newAccessTokenPayload, u return validationErrors; } exports.validateClaimsInPayload = validateClaimsInPayload; -function defaultGetTokenTransferMethod({ req, forCreateNewSession }) { +function defaultGetTokenTransferMethod({ req, forCreateNewSession, }) { // We allow fallback (checking headers then cookies) by default when validating if (!forCreateNewSession) { return "any"; diff --git a/lib/build/recipe/thirdparty/api/appleRedirect.d.ts b/lib/build/recipe/thirdparty/api/appleRedirect.d.ts index ae0e54770..e1011ab6d 100644 --- a/lib/build/recipe/thirdparty/api/appleRedirect.d.ts +++ b/lib/build/recipe/thirdparty/api/appleRedirect.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function appleRedirectHandler( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function appleRedirectHandler(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/thirdparty/api/authorisationUrl.d.ts b/lib/build/recipe/thirdparty/api/authorisationUrl.d.ts index dac53edea..95ae35f51 100644 --- a/lib/build/recipe/thirdparty/api/authorisationUrl.d.ts +++ b/lib/build/recipe/thirdparty/api/authorisationUrl.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function authorisationUrlAPI( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function authorisationUrlAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/thirdparty/api/authorisationUrl.js b/lib/build/recipe/thirdparty/api/authorisationUrl.js index b5f572f4f..4e8af7c1a 100644 --- a/lib/build/recipe/thirdparty/api/authorisationUrl.js +++ b/lib/build/recipe/thirdparty/api/authorisationUrl.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/thirdparty/api/implementation.js b/lib/build/recipe/thirdparty/api/implementation.js index 1fed7ca95..a2e76c254 100644 --- a/lib/build/recipe/thirdparty/api/implementation.js +++ b/lib/build/recipe/thirdparty/api/implementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const emailverification_1 = __importDefault(require("../../emailverification")); const recipe_1 = __importDefault(require("../../emailverification/recipe")); @@ -21,19 +19,13 @@ function getAPIInterface() { }, 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)", + 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)", + 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; @@ -43,9 +35,11 @@ function getAPIInterface() { redirectURIInfo: input.redirectURIInfo, userContext, }); - } else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { + } + else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { oAuthTokensToUse = input.oAuthTokens; - } else { + } + else { throw Error("should never come here"); } const userInfo = await provider.getUserInfo({ oAuthTokens: oAuthTokensToUse, userContext }); @@ -70,21 +64,19 @@ function getAPIInterface() { // We essentially did this above when calling exchangeAuthCodeForOAuthTokens return true; }; - const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( - { - accountInfo: { - thirdParty: { - userId: userInfo.thirdPartyUserId, - id: provider.id, - }, + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ + accountInfo: { + thirdParty: { + userId: userInfo.thirdPartyUserId, + id: provider.id, }, - recipeId, - userContext: input.userContext, - session: input.session, - tenantId, - checkCredentialsOnTenant, - } - ); + }, + 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 @@ -99,11 +91,7 @@ function getAPIInterface() { // So we just check that as well before calling isEmailChangeAllowed const recipeUserId = authenticatingUser.loginMethod.recipeUserId; if (!emailInfo.isVerified && recipe_1.default.getInstance() !== undefined) { - emailInfo.isVerified = await emailverification_1.default.isEmailVerified( - recipeUserId, - emailInfo.id, - userContext - ); + emailInfo.isVerified = await emailverification_1.default.isEmailVerified(recipeUserId, emailInfo.id, userContext); } } const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ @@ -115,8 +103,7 @@ function getAPIInterface() { id: provider.id, }, }, - authenticatingUser: - authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, factorIds: ["thirdparty"], isSignUp, isVerified: emailInfo.isVerified, @@ -132,16 +119,10 @@ function getAPIInterface() { shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { - logger_1.logDebugMessage( - "signInUpPOST: erroring out because preAuthChecks returned " + preAuthChecks.status - ); + 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" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthChecks, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); } let response = await options.recipeImplementation.signInUp({ thirdPartyId: provider.id, @@ -161,11 +142,7 @@ function getAPIInterface() { } if (response.status !== "OK") { logger_1.logDebugMessage("signInUpPOST: erroring out because signInUp returned " + response.status); - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - response, - errorCodeMap, - "SIGN_IN_UP_NOT_ALLOWED" - ); + 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 @@ -185,14 +162,8 @@ function getAPIInterface() { session: input.session, }); if (postAuthChecks.status !== "OK") { - logger_1.logDebugMessage( - "signInUpPOST: erroring out because postAuthChecks returned " + postAuthChecks.status - ); - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - postAuthChecks, - errorCodeMap, - "SIGN_IN_UP_NOT_ALLOWED" - ); + 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", diff --git a/lib/build/recipe/thirdparty/api/signinup.d.ts b/lib/build/recipe/thirdparty/api/signinup.d.ts index 06f84bb63..0415d8bdc 100644 --- a/lib/build/recipe/thirdparty/api/signinup.d.ts +++ b/lib/build/recipe/thirdparty/api/signinup.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function signInUpAPI( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signInUpAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/thirdparty/api/signinup.js b/lib/build/recipe/thirdparty/api/signinup.js index 772fd4bcd..fb75af068 100644 --- a/lib/build/recipe/thirdparty/api/signinup.js +++ b/lib/build/recipe/thirdparty/api/signinup.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../error")); const utils_1 = require("../../../utils"); @@ -45,9 +43,11 @@ async function signInUpAPI(apiImplementation, tenantId, options, userContext) { }); } redirectURIInfo = bodyParams.redirectURIInfo; - } else if (bodyParams.oAuthTokens !== undefined) { + } + else if (bodyParams.oAuthTokens !== undefined) { oAuthTokens = bodyParams.oAuthTokens; - } else { + } + else { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "Please provide one of redirectURIInfo or oAuthTokens in the request body", @@ -66,16 +66,8 @@ async function signInUpAPI(apiImplementation, tenantId, options, userContext) { }); } const provider = providerResponse; - const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( - options.req, - bodyParams - ); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( - options.req, - options.res, - shouldTryLinkingWithSessionUser, - userContext - ); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, bodyParams); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -90,14 +82,9 @@ async function signInUpAPI(apiImplementation, tenantId, options, userContext) { userContext, }); if (result.status === "OK") { - utils_1.send200Response( - options.res, - Object.assign( - { status: result.status }, - utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext) - ) - ); - } else { + utils_1.send200Response(options.res, Object.assign({ status: result.status }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); + } + else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/thirdparty/error.d.ts b/lib/build/recipe/thirdparty/error.d.ts index 89e4828d2..c65582222 100644 --- a/lib/build/recipe/thirdparty/error.d.ts +++ b/lib/build/recipe/thirdparty/error.d.ts @@ -1,8 +1,14 @@ // @ts-nocheck import STError from "../../error"; export default class ThirdPartyError extends STError { - constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); + constructor(options: { + type: "BAD_INPUT_ERROR"; + message: string; + }); } export declare class ClientTypeNotFoundError extends STError { - constructor(options: { type: "CLIENT_TYPE_NOT_FOUND_ERROR"; message: string }); + constructor(options: { + type: "CLIENT_TYPE_NOT_FOUND_ERROR"; + message: string; + }); } diff --git a/lib/build/recipe/thirdparty/error.js b/lib/build/recipe/thirdparty/error.js index 8e62fdaab..3624b9ffc 100644 --- a/lib/build/recipe/thirdparty/error.js +++ b/lib/build/recipe/thirdparty/error.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClientTypeNotFoundError = void 0; const error_1 = __importDefault(require("../../error")); diff --git a/lib/build/recipe/thirdparty/index.d.ts b/lib/build/recipe/thirdparty/index.d.ts index 8fdd56cac..3e318201b 100644 --- a/lib/build/recipe/thirdparty/index.d.ts +++ b/lib/build/recipe/thirdparty/index.d.ts @@ -8,68 +8,34 @@ import RecipeUserId from "../../recipeUserId"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static getProvider( - tenantId: string, - thirdPartyId: string, - clientType: string | undefined, - userContext?: Record - ): Promise; - static manuallyCreateOrUpdateUser( - tenantId: string, - thirdPartyId: string, - thirdPartyUserId: string, - email: string, - isVerified: boolean, - session?: undefined, - userContext?: Record - ): Promise< - | { - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } - | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } - >; - static manuallyCreateOrUpdateUser( - tenantId: string, - thirdPartyId: string, - thirdPartyUserId: string, - email: string, - isVerified: boolean, - session: SessionContainerInterface, - userContext?: Record - ): Promise< - | { - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } - | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } - | { - 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"; - } - >; + static getProvider(tenantId: string, thirdPartyId: string, clientType: string | undefined, userContext?: Record): Promise; + static manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record): Promise<{ + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + }>; + static manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainerInterface, userContext?: Record): Promise<{ + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } | { + 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"; + }>; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/thirdparty/index.js b/lib/build/recipe/thirdparty/index.js index 2ee060ce9..76abeadc6 100644 --- a/lib/build/recipe/thirdparty/index.js +++ b/lib/build/recipe/thirdparty/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.manuallyCreateOrUpdateUser = exports.getProvider = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -33,15 +31,7 @@ class Wrapper { userContext: utils_1.getUserContext(userContext), }); } - static async manuallyCreateOrUpdateUser( - tenantId, - thirdPartyId, - thirdPartyUserId, - email, - isVerified, - session, - userContext - ) { + static async manuallyCreateOrUpdateUser(tenantId, thirdPartyId, thirdPartyUserId, email, isVerified, session, userContext) { return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.manuallyCreateOrUpdateUser({ thirdPartyId, thirdPartyUserId, diff --git a/lib/build/recipe/thirdparty/providers/activeDirectory.js b/lib/build/recipe/thirdparty/providers/activeDirectory.js index bb48e32c7..2054e76ff 100644 --- a/lib/build/recipe/thirdparty/providers/activeDirectory.js +++ b/lib/build/recipe/thirdparty/providers/activeDirectory.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); const utils_1 = require("./utils"); @@ -35,9 +33,7 @@ function ActiveDirectory(input) { } if (config.oidcDiscoveryEndpoint !== undefined) { // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( - config.oidcDiscoveryEndpoint - ); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); } if (config.scope === undefined) { config.scope = ["openid", "email"]; diff --git a/lib/build/recipe/thirdparty/providers/apple.js b/lib/build/recipe/thirdparty/providers/apple.js index 9f86cb31a..815f94081 100644 --- a/lib/build/recipe/thirdparty/providers/apple.js +++ b/lib/build/recipe/thirdparty/providers/apple.js @@ -13,42 +13,25 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importStar(require("./custom")); const jose = __importStar(require("jose")); @@ -72,10 +55,7 @@ function Apple(input) { if (input.config.oidcDiscoveryEndpoint === undefined) { input.config.oidcDiscoveryEndpoint = "https://appleid.apple.com/.well-known/openid-configuration"; } - input.config.authorizationEndpointQueryParams = Object.assign( - { response_mode: "form_post" }, - input.config.authorizationEndpointQueryParams - ); + input.config.authorizationEndpointQueryParams = Object.assign({ response_mode: "form_post" }, input.config.authorizationEndpointQueryParams); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; @@ -85,27 +65,16 @@ function Apple(input) { config.scope = ["openid", "email"]; } if (config.clientSecret === undefined) { - if ( - config.additionalConfig === undefined || + if (config.additionalConfig === undefined || config.additionalConfig.keyId === undefined || config.additionalConfig.teamId === undefined || - config.additionalConfig.privateKey === undefined - ) { - throw new Error( - "Please ensure that keyId, teamId and privateKey are provided in the additionalConfig" - ); + config.additionalConfig.privateKey === undefined) { + throw new Error("Please ensure that keyId, teamId and privateKey are provided in the additionalConfig"); } - config.clientSecret = await getClientSecret( - config.clientId, - config.additionalConfig.keyId, - config.additionalConfig.teamId, - config.additionalConfig.privateKey - ); + config.clientSecret = await getClientSecret(config.clientId, config.additionalConfig.keyId, config.additionalConfig.teamId, config.additionalConfig.privateKey); } // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( - config.oidcDiscoveryEndpoint - ); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); return config; }; const oExchangeAuthCodeForOAuthTokens = originalImplementation.exchangeAuthCodeForOAuthTokens; @@ -115,7 +84,8 @@ function Apple(input) { if (user !== undefined) { if (typeof user === "string") { response.user = JSON.parse(user); - } else if (typeof user === "object") { + } + else if (typeof user === "object") { response.user = user; } } @@ -128,35 +98,10 @@ function Apple(input) { const user = input.oAuthTokens.user; if (user !== undefined) { if (typeof user === "string") { - response.rawUserInfoFromProvider = Object.assign( - Object.assign({}, response.rawUserInfoFromProvider), - { - fromIdTokenPayload: Object.assign( - Object.assign( - {}, - (_a = response.rawUserInfoFromProvider) === null || _a === void 0 - ? void 0 - : _a.fromIdTokenPayload - ), - { user: JSON.parse(user) } - ), - } - ); - } else if (typeof user === "object") { - response.rawUserInfoFromProvider = Object.assign( - Object.assign({}, response.rawUserInfoFromProvider), - { - fromIdTokenPayload: Object.assign( - Object.assign( - {}, - (_b = response.rawUserInfoFromProvider) === null || _b === void 0 - ? void 0 - : _b.fromIdTokenPayload - ), - { user } - ), - } - ); + response.rawUserInfoFromProvider = Object.assign(Object.assign({}, response.rawUserInfoFromProvider), { fromIdTokenPayload: Object.assign(Object.assign({}, (_a = response.rawUserInfoFromProvider) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload), { user: JSON.parse(user) }) }); + } + else if (typeof user === "object") { + response.rawUserInfoFromProvider = Object.assign(Object.assign({}, response.rawUserInfoFromProvider), { fromIdTokenPayload: Object.assign(Object.assign({}, (_b = response.rawUserInfoFromProvider) === null || _b === void 0 ? void 0 : _b.fromIdTokenPayload), { user }) }); } } return response; diff --git a/lib/build/recipe/thirdparty/providers/bitbucket.js b/lib/build/recipe/thirdparty/providers/bitbucket.js index 6c5a60d23..b659ce7d2 100644 --- a/lib/build/recipe/thirdparty/providers/bitbucket.js +++ b/lib/build/recipe/thirdparty/providers/bitbucket.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); const custom_1 = __importDefault(require("./custom")); @@ -59,32 +57,16 @@ function Bitbucket(input) { fromUserInfoAPI: {}, fromIdTokenPayload: {}, }; - const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( - "https://api.bitbucket.org/2.0/user", - undefined, - headers - ); + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest("https://api.bitbucket.org/2.0/user", undefined, headers); if (userInfoFromAccessToken.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` - ); - throw new Error( - `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); + throw new Error(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); } rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; - const userInfoFromEmail = await thirdpartyUtils_1.doGetRequest( - "https://api.bitbucket.org/2.0/user/emails", - undefined, - headers - ); + const userInfoFromEmail = await thirdpartyUtils_1.doGetRequest("https://api.bitbucket.org/2.0/user/emails", undefined, headers); if (userInfoFromEmail.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${userInfoFromEmail.status} and body ${userInfoFromEmail.stringResponse}` - ); - throw new Error( - `Received response with status ${userInfoFromEmail.status} and body ${userInfoFromEmail.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${userInfoFromEmail.status} and body ${userInfoFromEmail.stringResponse}`); + throw new Error(`Received response with status ${userInfoFromEmail.status} and body ${userInfoFromEmail.stringResponse}`); } rawUserInfoFromProvider.fromUserInfoAPI.email = userInfoFromEmail.jsonResponse; let email = undefined; @@ -97,13 +79,12 @@ function Bitbucket(input) { } return { thirdPartyUserId: rawUserInfoFromProvider.fromUserInfoAPI.uuid, - email: - email === undefined - ? undefined - : { - id: email, - isVerified, - }, + email: email === undefined + ? undefined + : { + id: email, + isVerified, + }, rawUserInfoFromProvider, }; }; diff --git a/lib/build/recipe/thirdparty/providers/boxySaml.js b/lib/build/recipe/thirdparty/providers/boxySaml.js index 3aed7692b..b42dbdc7c 100644 --- a/lib/build/recipe/thirdparty/providers/boxySaml.js +++ b/lib/build/recipe/thirdparty/providers/boxySaml.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); function BoxySAML(input) { @@ -25,12 +23,7 @@ function BoxySAML(input) { if (input.config.name === undefined) { input.config.name = "SAML"; } - input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { - fromUserInfoAPI: Object.assign( - { userId: "id", email: "email" }, - (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI - ), - }); + input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { fromUserInfoAPI: Object.assign({ userId: "id", email: "email" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) }); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; diff --git a/lib/build/recipe/thirdparty/providers/configUtils.d.ts b/lib/build/recipe/thirdparty/providers/configUtils.d.ts index c4e9bbe88..5ec42b1ab 100644 --- a/lib/build/recipe/thirdparty/providers/configUtils.d.ts +++ b/lib/build/recipe/thirdparty/providers/configUtils.d.ts @@ -1,25 +1,7 @@ // @ts-nocheck import { UserContext } from "../../../types"; -import { - ProviderClientConfig, - ProviderConfig, - ProviderConfigForClientType, - ProviderInput, - TypeProvider, -} from "../types"; -export declare function getProviderConfigForClient( - providerConfig: ProviderConfig, - clientConfig: ProviderClientConfig -): ProviderConfigForClientType; -export declare function findAndCreateProviderInstance( - providers: ProviderInput[], - thirdPartyId: string, - clientType: string | undefined, - userContext: UserContext -): Promise; +import { ProviderClientConfig, ProviderConfig, ProviderConfigForClientType, ProviderInput, TypeProvider } from "../types"; +export declare function getProviderConfigForClient(providerConfig: ProviderConfig, clientConfig: ProviderClientConfig): ProviderConfigForClientType; +export declare function findAndCreateProviderInstance(providers: ProviderInput[], thirdPartyId: string, clientType: string | undefined, userContext: UserContext): Promise; export declare function mergeConfig(staticConfig: ProviderConfig, coreConfig: ProviderConfig): ProviderConfig; -export declare function mergeProvidersFromCoreAndStatic( - providerConfigsFromCore: ProviderConfig[], - providerInputsFromStatic: ProviderInput[], - includeAllProviders: boolean -): ProviderInput[]; +export declare function mergeProvidersFromCoreAndStatic(providerConfigsFromCore: ProviderConfig[], providerInputsFromStatic: ProviderInput[], includeAllProviders: boolean): ProviderInput[]; diff --git a/lib/build/recipe/thirdparty/providers/configUtils.js b/lib/build/recipe/thirdparty/providers/configUtils.js index f0be656c4..b70052163 100644 --- a/lib/build/recipe/thirdparty/providers/configUtils.js +++ b/lib/build/recipe/thirdparty/providers/configUtils.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeProvidersFromCoreAndStatic = exports.mergeConfig = exports.findAndCreateProviderInstance = exports.getProviderConfigForClient = void 0; const _1 = require("."); @@ -36,29 +34,41 @@ function createProvider(input) { const clonedInput = Object.assign({}, input); if (clonedInput.config.thirdPartyId.startsWith("active-directory")) { return _1.ActiveDirectory(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("apple")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("apple")) { return _1.Apple(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("bitbucket")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("bitbucket")) { return _1.Bitbucket(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("discord")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("discord")) { return _1.Discord(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("facebook")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("facebook")) { return _1.Facebook(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("github")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("github")) { return _1.Github(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("gitlab")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("gitlab")) { return _1.Gitlab(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("google-workspaces")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("google-workspaces")) { return _1.GoogleWorkspaces(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("google")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("google")) { return _1.Google(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("okta")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("okta")) { return _1.Okta(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("linkedin")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("linkedin")) { return _1.Linkedin(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("twitter")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("twitter")) { return _1.Twitter(clonedInput); - } else if (clonedInput.config.thirdPartyId.startsWith("boxy-saml")) { + } + else if (clonedInput.config.thirdPartyId.startsWith("boxy-saml")) { return _1.BoxySAML(clonedInput); } return custom_1.default(clonedInput); @@ -76,31 +86,18 @@ async function findAndCreateProviderInstance(providers, thirdPartyId, clientType exports.findAndCreateProviderInstance = findAndCreateProviderInstance; function mergeConfig(staticConfig, coreConfig) { var _a, _b, _c, _d; - const result = Object.assign(Object.assign(Object.assign({}, staticConfig), coreConfig), { - userInfoMap: { - fromIdTokenPayload: Object.assign( - Object.assign( - {}, - (_a = staticConfig.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload - ), - (_b = coreConfig.userInfoMap) === null || _b === void 0 ? void 0 : _b.fromIdTokenPayload - ), - fromUserInfoAPI: Object.assign( - Object.assign( - {}, - (_c = staticConfig.userInfoMap) === null || _c === void 0 ? void 0 : _c.fromUserInfoAPI - ), - (_d = coreConfig.userInfoMap) === null || _d === void 0 ? void 0 : _d.fromUserInfoAPI - ), - }, - }); + const result = Object.assign(Object.assign(Object.assign({}, staticConfig), coreConfig), { userInfoMap: { + fromIdTokenPayload: Object.assign(Object.assign({}, (_a = staticConfig.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload), (_b = coreConfig.userInfoMap) === null || _b === void 0 ? void 0 : _b.fromIdTokenPayload), + fromUserInfoAPI: Object.assign(Object.assign({}, (_c = staticConfig.userInfoMap) === null || _c === void 0 ? void 0 : _c.fromUserInfoAPI), (_d = coreConfig.userInfoMap) === null || _d === void 0 ? void 0 : _d.fromUserInfoAPI), + } }); const mergedClients = staticConfig.clients === undefined ? [] : [...staticConfig.clients]; const coreConfigClients = coreConfig.clients === undefined ? [] : coreConfig.clients; for (const client of coreConfigClients) { const index = mergedClients.findIndex((c) => c.clientType === client.clientType); if (index === -1) { mergedClients.push(client); - } else { + } + else { mergedClients[index] = Object.assign({}, client); } } @@ -111,12 +108,11 @@ exports.mergeConfig = mergeConfig; function mergeProvidersFromCoreAndStatic(providerConfigsFromCore, providerInputsFromStatic, includeAllProviders) { const mergedProviders = []; if (providerConfigsFromCore.length === 0) { - for (const config of providerInputsFromStatic.filter( - (config) => config.includeInNonPublicTenantsByDefault === true || includeAllProviders === true - )) { + for (const config of providerInputsFromStatic.filter((config) => config.includeInNonPublicTenantsByDefault === true || includeAllProviders === true)) { mergedProviders.push(config); } - } else { + } + else { for (const providerConfigFromCore of providerConfigsFromCore) { let mergedProviderInput = { config: providerConfigFromCore, diff --git a/lib/build/recipe/thirdparty/providers/custom.js b/lib/build/recipe/thirdparty/providers/custom.js index ddd2b7a00..4f9eb9c52 100644 --- a/lib/build/recipe/thirdparty/providers/custom.js +++ b/lib/build/recipe/thirdparty/providers/custom.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getActualClientIdFromDevelopmentClientId = exports.isUsingDevelopmentClientId = exports.DEV_OAUTH_REDIRECT_URL = void 0; const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); @@ -46,27 +44,14 @@ function accessField(obj, key) { function getSupertokensUserInfoResultFromRawUserInfo(config, rawUserInfoResponse) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; let thirdPartyUserId = ""; - if ( - ((_b = (_a = config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) === null || - _b === void 0 - ? void 0 - : _b.userId) !== undefined - ) { + if (((_b = (_a = config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.userId) !== undefined) { const userId = accessField(rawUserInfoResponse.fromUserInfoAPI, config.userInfoMap.fromUserInfoAPI.userId); if (userId !== undefined) { thirdPartyUserId = userId; } } - if ( - ((_d = (_c = config.userInfoMap) === null || _c === void 0 ? void 0 : _c.fromIdTokenPayload) === null || - _d === void 0 - ? void 0 - : _d.userId) !== undefined - ) { - const userId = accessField( - rawUserInfoResponse.fromIdTokenPayload, - config.userInfoMap.fromIdTokenPayload.userId - ); + if (((_d = (_c = config.userInfoMap) === null || _c === void 0 ? void 0 : _c.fromIdTokenPayload) === null || _d === void 0 ? void 0 : _d.userId) !== undefined) { + const userId = accessField(rawUserInfoResponse.fromIdTokenPayload, config.userInfoMap.fromIdTokenPayload.userId); if (userId !== undefined) { thirdPartyUserId = userId; } @@ -78,27 +63,14 @@ function getSupertokensUserInfoResultFromRawUserInfo(config, rawUserInfoResponse thirdPartyUserId, }; let email = ""; - if ( - ((_f = (_e = config.userInfoMap) === null || _e === void 0 ? void 0 : _e.fromUserInfoAPI) === null || - _f === void 0 - ? void 0 - : _f.email) !== undefined - ) { + if (((_f = (_e = config.userInfoMap) === null || _e === void 0 ? void 0 : _e.fromUserInfoAPI) === null || _f === void 0 ? void 0 : _f.email) !== undefined) { const emailVal = accessField(rawUserInfoResponse.fromUserInfoAPI, config.userInfoMap.fromUserInfoAPI.email); if (emailVal !== undefined) { email = emailVal; } } - if ( - ((_h = (_g = config.userInfoMap) === null || _g === void 0 ? void 0 : _g.fromIdTokenPayload) === null || - _h === void 0 - ? void 0 - : _h.email) !== undefined - ) { - const emailVal = accessField( - rawUserInfoResponse.fromIdTokenPayload, - config.userInfoMap.fromIdTokenPayload.email - ); + if (((_h = (_g = config.userInfoMap) === null || _g === void 0 ? void 0 : _g.fromIdTokenPayload) === null || _h === void 0 ? void 0 : _h.email) !== undefined) { + const emailVal = accessField(rawUserInfoResponse.fromIdTokenPayload, config.userInfoMap.fromIdTokenPayload.email); if (emailVal !== undefined) { email = emailVal; } @@ -108,30 +80,14 @@ function getSupertokensUserInfoResultFromRawUserInfo(config, rawUserInfoResponse id: email, isVerified: false, }; - if ( - ((_k = (_j = config.userInfoMap) === null || _j === void 0 ? void 0 : _j.fromUserInfoAPI) === null || - _k === void 0 - ? void 0 - : _k.emailVerified) !== undefined - ) { - const emailVerifiedVal = accessField( - rawUserInfoResponse.fromUserInfoAPI, - config.userInfoMap.fromUserInfoAPI.emailVerified - ); + if (((_k = (_j = config.userInfoMap) === null || _j === void 0 ? void 0 : _j.fromUserInfoAPI) === null || _k === void 0 ? void 0 : _k.emailVerified) !== undefined) { + const emailVerifiedVal = accessField(rawUserInfoResponse.fromUserInfoAPI, config.userInfoMap.fromUserInfoAPI.emailVerified); result.email.isVerified = emailVerifiedVal === true || - (typeof emailVerifiedVal === "string" && emailVerifiedVal.toLowerCase() === "true"); + (typeof emailVerifiedVal === "string" && emailVerifiedVal.toLowerCase() === "true"); } - if ( - ((_m = (_l = config.userInfoMap) === null || _l === void 0 ? void 0 : _l.fromIdTokenPayload) === null || - _m === void 0 - ? void 0 - : _m.emailVerified) !== undefined - ) { - const emailVerifiedVal = accessField( - rawUserInfoResponse.fromIdTokenPayload, - config.userInfoMap.fromIdTokenPayload.emailVerified - ); + if (((_m = (_l = config.userInfoMap) === null || _l === void 0 ? void 0 : _l.fromIdTokenPayload) === null || _m === void 0 ? void 0 : _m.emailVerified) !== undefined) { + const emailVerifiedVal = accessField(rawUserInfoResponse.fromIdTokenPayload, config.userInfoMap.fromIdTokenPayload.emailVerified); result.email.isVerified = emailVerifiedVal === true || emailVerifiedVal === "true"; } } @@ -142,14 +98,8 @@ function NewProvider(input) { // These are safe defaults common to most providers. Each provider implementations override these // as necessary input.config.userInfoMap = { - fromIdTokenPayload: Object.assign( - { userId: "sub", email: "email", emailVerified: "email_verified" }, - (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload - ), - fromUserInfoAPI: Object.assign( - { userId: "sub", email: "email", emailVerified: "email_verified" }, - (_b = input.config.userInfoMap) === null || _b === void 0 ? void 0 : _b.fromUserInfoAPI - ), + fromIdTokenPayload: Object.assign({ userId: "sub", email: "email", emailVerified: "email_verified" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload), + fromUserInfoAPI: Object.assign({ userId: "sub", email: "email", emailVerified: "email_verified" }, (_b = input.config.userInfoMap) === null || _b === void 0 ? void 0 : _b.fromUserInfoAPI), }; if (input.config.generateFakeEmail === undefined) { input.config.generateFakeEmail = async function ({ thirdPartyUserId }) { @@ -196,7 +146,8 @@ function NewProvider(input) { for (const [key, value] of Object.entries(impl.config.authorizationEndpointQueryParams)) { if (value === null) { delete queryParams[key]; - } else { + } + else { queryParams[key] = value; } } @@ -241,7 +192,8 @@ function NewProvider(input) { for (const key in impl.config.tokenEndpointBodyParams) { if (impl.config.tokenEndpointBodyParams[key] === null) { delete accessTokenAPIParams[key]; - } else { + } + else { accessTokenAPIParams[key] = impl.config.tokenEndpointBodyParams[key]; } } @@ -253,12 +205,8 @@ function NewProvider(input) { /* Transformation needed for dev keys END */ const tokenResponse = await thirdpartyUtils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); if (tokenResponse.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` - ); - throw new Error( - `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); + throw new Error(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); } return tokenResponse.jsonResponse; }, @@ -273,13 +221,9 @@ function NewProvider(input) { if (jwks === undefined) { jwks = jose_1.createRemoteJWKSet(new URL(impl.config.jwksURI)); } - rawUserInfoFromProvider.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( - idToken, - jwks, - { - audience: getActualClientIdFromDevelopmentClientId(impl.config.clientId), - } - ); + rawUserInfoFromProvider.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload(idToken, jwks, { + audience: getActualClientIdFromDevelopmentClientId(impl.config.clientId), + }); if (impl.config.validateIdTokenPayload !== undefined) { await impl.config.validateIdTokenPayload({ idTokenPayload: rawUserInfoFromProvider.fromIdTokenPayload, @@ -304,7 +248,8 @@ function NewProvider(input) { for (const [key, value] of Object.entries(impl.config.userInfoEndpointHeaders)) { if (value === null) { delete headers[key]; - } else { + } + else { headers[key] = value; } } @@ -313,23 +258,16 @@ function NewProvider(input) { for (const [key, value] of Object.entries(impl.config.userInfoEndpointQueryParams)) { if (value === null) { delete queryParams[key]; - } else { + } + else { queryParams[key] = value; } } } - const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( - impl.config.userInfoEndpoint, - queryParams, - headers - ); + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest(impl.config.userInfoEndpoint, queryParams, headers); if (userInfoFromAccessToken.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` - ); - throw new Error( - `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); + throw new Error(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); } rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; } diff --git a/lib/build/recipe/thirdparty/providers/discord.js b/lib/build/recipe/thirdparty/providers/discord.js index cf161c55c..01ae77f9e 100644 --- a/lib/build/recipe/thirdparty/providers/discord.js +++ b/lib/build/recipe/thirdparty/providers/discord.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); function Discord(input) { @@ -20,12 +18,7 @@ function Discord(input) { if (input.config.userInfoEndpoint === undefined) { input.config.userInfoEndpoint = "https://discord.com/api/users/@me"; } - input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { - fromUserInfoAPI: Object.assign( - { userId: "id", email: "email", emailVerified: "verified" }, - (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI - ), - }); + input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { fromUserInfoAPI: Object.assign({ userId: "id", email: "email", emailVerified: "verified" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) }); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; diff --git a/lib/build/recipe/thirdparty/providers/facebook.js b/lib/build/recipe/thirdparty/providers/facebook.js index 0353cc704..bb9cf4f38 100644 --- a/lib/build/recipe/thirdparty/providers/facebook.js +++ b/lib/build/recipe/thirdparty/providers/facebook.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); function Facebook(input) { @@ -20,12 +18,7 @@ function Facebook(input) { if (input.config.userInfoEndpoint === undefined) { input.config.userInfoEndpoint = "https://graph.facebook.com/me"; } - input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { - fromUserInfoAPI: Object.assign( - { userId: "id" }, - (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI - ), - }); + input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { fromUserInfoAPI: Object.assign({ userId: "id" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) }); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; @@ -63,27 +56,9 @@ function Facebook(input) { user_age_range: ["age_range"], }; const scopeValues = originalImplementation.config.scope; - const fields = - (_a = - scopeValues === null || scopeValues === void 0 - ? void 0 - : scopeValues - .map((scopeValue) => { - var _a; - return (_a = fieldsPermissionMap[scopeValue]) !== null && _a !== void 0 ? _a : []; - }) - .flat() - .join(",")) !== null && _a !== void 0 - ? _a - : "id,email"; - originalImplementation.config.userInfoEndpointQueryParams = Object.assign( - { access_token: input.oAuthTokens.access_token, fields, format: "json" }, - originalImplementation.config.userInfoEndpointQueryParams - ); - originalImplementation.config.userInfoEndpointHeaders = Object.assign( - Object.assign({}, originalImplementation.config.userInfoEndpointHeaders), - { Authorization: null } - ); + const fields = (_a = scopeValues === null || scopeValues === void 0 ? void 0 : scopeValues.map((scopeValue) => { var _a; return (_a = fieldsPermissionMap[scopeValue]) !== null && _a !== void 0 ? _a : []; }).flat().join(",")) !== null && _a !== void 0 ? _a : "id,email"; + originalImplementation.config.userInfoEndpointQueryParams = Object.assign({ access_token: input.oAuthTokens.access_token, fields, format: "json" }, originalImplementation.config.userInfoEndpointQueryParams); + originalImplementation.config.userInfoEndpointHeaders = Object.assign(Object.assign({}, originalImplementation.config.userInfoEndpointHeaders), { Authorization: null }); return await oGetUserInfo(input); }; if (oOverride !== undefined) { diff --git a/lib/build/recipe/thirdparty/providers/github.js b/lib/build/recipe/thirdparty/providers/github.js index ced201b08..17763a34d 100644 --- a/lib/build/recipe/thirdparty/providers/github.js +++ b/lib/build/recipe/thirdparty/providers/github.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -56,26 +54,18 @@ function Github(input) { } if (input.config.validateAccessToken === undefined) { input.config.validateAccessToken = async ({ accessToken, clientConfig }) => { - const basicAuthToken = utils_1.encodeBase64( - `${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}` - ); - const applicationResponse = await thirdpartyUtils_1.doPostRequest( - `https://api.github.com/applications/${clientConfig.clientId}/token`, - { - access_token: accessToken, - }, - { - Authorization: `Basic ${basicAuthToken}`, - "Content-Type": "application/json", - } - ); + const basicAuthToken = utils_1.encodeBase64(`${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}`); + const applicationResponse = await thirdpartyUtils_1.doPostRequest(`https://api.github.com/applications/${clientConfig.clientId}/token`, { + access_token: accessToken, + }, { + Authorization: `Basic ${basicAuthToken}`, + "Content-Type": "application/json", + }); if (applicationResponse.status !== 200) { throw new Error("Invalid access token"); } - if ( - applicationResponse.jsonResponse.app === undefined || - applicationResponse.jsonResponse.app.client_id !== clientConfig.clientId - ) { + if (applicationResponse.jsonResponse.app === undefined || + applicationResponse.jsonResponse.app.client_id !== clientConfig.clientId) { throw new Error("Access token does not belong to your application"); } }; @@ -96,22 +86,12 @@ function Github(input) { Accept: "application/vnd.github.v3+json", }; const rawResponse = {}; - const emailInfoResp = await thirdpartyUtils_1.doGetRequest( - "https://api.github.com/user/emails", - undefined, - headers - ); + const emailInfoResp = await thirdpartyUtils_1.doGetRequest("https://api.github.com/user/emails", undefined, headers); if (emailInfoResp.status >= 400) { - throw new Error( - `Getting userInfo failed with ${emailInfoResp.status}: ${emailInfoResp.stringResponse}` - ); + throw new Error(`Getting userInfo failed with ${emailInfoResp.status}: ${emailInfoResp.stringResponse}`); } rawResponse.emails = emailInfoResp.jsonResponse; - const userInfoResp = await thirdpartyUtils_1.doGetRequest( - "https://api.github.com/user", - undefined, - headers - ); + const userInfoResp = await thirdpartyUtils_1.doGetRequest("https://api.github.com/user", undefined, headers); if (userInfoResp.status >= 400) { throw new Error(`Getting userInfo failed with ${userInfoResp.status}: ${userInfoResp.stringResponse}`); } diff --git a/lib/build/recipe/thirdparty/providers/gitlab.js b/lib/build/recipe/thirdparty/providers/gitlab.js index 2f5bd3967..9f6fc1394 100644 --- a/lib/build/recipe/thirdparty/providers/gitlab.js +++ b/lib/build/recipe/thirdparty/providers/gitlab.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLDomain_1 = __importDefault(require("../../../normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); @@ -40,13 +38,12 @@ function Gitlab(input) { const oidcDomain = new normalisedURLDomain_1.default(config.additionalConfig.gitlabBaseUrl); const oidcPath = new normalisedURLPath_1.default("/.well-known/openid-configuration"); config.oidcDiscoveryEndpoint = oidcDomain.getAsStringDangerous() + oidcPath.getAsStringDangerous(); - } else if (config.oidcDiscoveryEndpoint === undefined) { + } + else if (config.oidcDiscoveryEndpoint === undefined) { config.oidcDiscoveryEndpoint = "https://gitlab.com/.well-known/openid-configuration"; } // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( - config.oidcDiscoveryEndpoint - ); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); return config; }; if (oOverride !== undefined) { diff --git a/lib/build/recipe/thirdparty/providers/google.js b/lib/build/recipe/thirdparty/providers/google.js index 686604afe..5fa11ed12 100644 --- a/lib/build/recipe/thirdparty/providers/google.js +++ b/lib/build/recipe/thirdparty/providers/google.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); const utils_1 = require("./utils"); @@ -14,10 +12,7 @@ function Google(input) { if (input.config.oidcDiscoveryEndpoint === undefined) { input.config.oidcDiscoveryEndpoint = "https://accounts.google.com/.well-known/openid-configuration"; } - input.config.authorizationEndpointQueryParams = Object.assign( - { included_grant_scopes: "true", access_type: "offline" }, - input.config.authorizationEndpointQueryParams - ); + input.config.authorizationEndpointQueryParams = Object.assign({ included_grant_scopes: "true", access_type: "offline" }, input.config.authorizationEndpointQueryParams); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; @@ -27,9 +22,7 @@ function Google(input) { config.scope = ["openid", "email"]; } // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( - config.oidcDiscoveryEndpoint - ); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); return config; }; if (oOverride !== undefined) { diff --git a/lib/build/recipe/thirdparty/providers/googleWorkspaces.js b/lib/build/recipe/thirdparty/providers/googleWorkspaces.js index b7c9cc6fc..001dc1656 100644 --- a/lib/build/recipe/thirdparty/providers/googleWorkspaces.js +++ b/lib/build/recipe/thirdparty/providers/googleWorkspaces.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const google_1 = __importDefault(require("./google")); function GoogleWorkspaces(input) { @@ -13,35 +11,22 @@ function GoogleWorkspaces(input) { if (input.config.validateIdTokenPayload === undefined) { input.config.validateIdTokenPayload = async function (input) { var _a, _b, _c; - if ( - ((_a = input.clientConfig.additionalConfig) === null || _a === void 0 ? void 0 : _a.hd) !== undefined && - ((_b = input.clientConfig.additionalConfig) === null || _b === void 0 ? void 0 : _b.hd) !== "*" - ) { - if ( - ((_c = input.clientConfig.additionalConfig) === null || _c === void 0 ? void 0 : _c.hd) !== - input.idTokenPayload.hd - ) { - throw new Error( - "the value for hd claim in the id token does not match the value provided in the config" - ); + if (((_a = input.clientConfig.additionalConfig) === null || _a === void 0 ? void 0 : _a.hd) !== undefined && + ((_b = input.clientConfig.additionalConfig) === null || _b === void 0 ? void 0 : _b.hd) !== "*") { + if (((_c = input.clientConfig.additionalConfig) === null || _c === void 0 ? void 0 : _c.hd) !== input.idTokenPayload.hd) { + throw new Error("the value for hd claim in the id token does not match the value provided in the config"); } } }; } - input.config.authorizationEndpointQueryParams = Object.assign( - { included_grant_scopes: "true", access_type: "offline" }, - input.config.authorizationEndpointQueryParams - ); + input.config.authorizationEndpointQueryParams = Object.assign({ included_grant_scopes: "true", access_type: "offline" }, input.config.authorizationEndpointQueryParams); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; originalImplementation.getConfigForClientType = async function (input) { const config = await oGetConfig(input); config.additionalConfig = Object.assign({ hd: "*" }, config.additionalConfig); - config.authorizationEndpointQueryParams = Object.assign( - Object.assign({}, config.authorizationEndpointQueryParams), - { hd: config.additionalConfig.hd } - ); + config.authorizationEndpointQueryParams = Object.assign(Object.assign({}, config.authorizationEndpointQueryParams), { hd: config.additionalConfig.hd }); return config; }; if (oOverride !== undefined) { diff --git a/lib/build/recipe/thirdparty/providers/index.js b/lib/build/recipe/thirdparty/providers/index.js index 345d7f130..869c34251 100644 --- a/lib/build/recipe/thirdparty/providers/index.js +++ b/lib/build/recipe/thirdparty/providers/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.Twitter = exports.Gitlab = exports.Bitbucket = exports.Okta = exports.Linkedin = exports.GoogleWorkspaces = exports.Google = exports.Github = exports.Facebook = exports.Discord = exports.Apple = exports.BoxySAML = exports.ActiveDirectory = void 0; const activeDirectory_1 = __importDefault(require("./activeDirectory")); diff --git a/lib/build/recipe/thirdparty/providers/linkedin.js b/lib/build/recipe/thirdparty/providers/linkedin.js index defa0739c..199a745f6 100644 --- a/lib/build/recipe/thirdparty/providers/linkedin.js +++ b/lib/build/recipe/thirdparty/providers/linkedin.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -56,19 +54,11 @@ function Linkedin(input) { fromIdTokenPayload: {}, }; // https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2?context=linkedin%2Fconsumer%2Fcontext#sample-api-response - const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( - "https://api.linkedin.com/v2/userinfo", - undefined, - headers - ); + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest("https://api.linkedin.com/v2/userinfo", undefined, headers); rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; if (userInfoFromAccessToken.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` - ); - throw new Error( - `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); + throw new Error(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); } return { thirdPartyUserId: rawUserInfoFromProvider.fromUserInfoAPI.sub, diff --git a/lib/build/recipe/thirdparty/providers/okta.js b/lib/build/recipe/thirdparty/providers/okta.js index 2fdffd98b..6f58939ec 100644 --- a/lib/build/recipe/thirdparty/providers/okta.js +++ b/lib/build/recipe/thirdparty/providers/okta.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -39,9 +37,7 @@ function Okta(input) { } if (config.oidcDiscoveryEndpoint !== undefined) { // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( - config.oidcDiscoveryEndpoint - ); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); } if (config.scope === undefined) { config.scope = ["openid", "email"]; diff --git a/lib/build/recipe/thirdparty/providers/twitter.js b/lib/build/recipe/thirdparty/providers/twitter.js index ec82e5638..a77dd74ad 100644 --- a/lib/build/recipe/thirdparty/providers/twitter.js +++ b/lib/build/recipe/thirdparty/providers/twitter.js @@ -1,40 +1,23 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -71,15 +54,7 @@ function Twitter(input) { if (input.config.requireEmail === undefined) { input.config.requireEmail = false; } - input.config.userInfoMap = Object.assign( - { - fromUserInfoAPI: Object.assign( - { userId: "data.id" }, - (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI - ), - }, - input.config.userInfoMap - ); + input.config.userInfoMap = Object.assign({ fromUserInfoAPI: Object.assign({ userId: "data.id" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) }, input.config.userInfoMap); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; @@ -104,30 +79,13 @@ function Twitter(input) { } /* Transformation needed for dev keys END */ const basicAuthToken = utils_1.encodeBase64(`${clientId}:${originalImplementation.config.clientSecret}`); - const twitterOauthTokenParams = Object.assign( - { - grant_type: "authorization_code", - client_id: clientId, - code_verifier: input.redirectURIInfo.pkceCodeVerifier, - redirect_uri: redirectUri, - code: input.redirectURIInfo.redirectURIQueryParams.code, - }, - originalImplementation.config.tokenEndpointBodyParams - ); - const tokenResponse = await thirdpartyUtils_1.doPostRequest( - originalImplementation.config.tokenEndpoint, - twitterOauthTokenParams, - { - Authorization: `Basic ${basicAuthToken}`, - } - ); + const twitterOauthTokenParams = Object.assign({ grant_type: "authorization_code", client_id: clientId, code_verifier: input.redirectURIInfo.pkceCodeVerifier, redirect_uri: redirectUri, code: input.redirectURIInfo.redirectURIQueryParams.code }, originalImplementation.config.tokenEndpointBodyParams); + const tokenResponse = await thirdpartyUtils_1.doPostRequest(originalImplementation.config.tokenEndpoint, twitterOauthTokenParams, { + Authorization: `Basic ${basicAuthToken}`, + }); if (tokenResponse.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` - ); - throw new Error( - `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); + throw new Error(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); } return tokenResponse.jsonResponse; }; diff --git a/lib/build/recipe/thirdparty/providers/utils.d.ts b/lib/build/recipe/thirdparty/providers/utils.d.ts index e09a4d2bc..8121bfb8f 100644 --- a/lib/build/recipe/thirdparty/providers/utils.d.ts +++ b/lib/build/recipe/thirdparty/providers/utils.d.ts @@ -1,36 +1,24 @@ // @ts-nocheck import * as jose from "jose"; import { ProviderConfigForClientType } from "../types"; -export declare function doGetRequest( - url: string, - queryParams?: { - [key: string]: string; - }, - headers?: { - [key: string]: string; - } -): Promise<{ +export declare function doGetRequest(url: string, queryParams?: { + [key: string]: string; +}, headers?: { + [key: string]: string; +}): Promise<{ jsonResponse: Record | undefined; status: number; stringResponse: string; }>; -export declare function doPostRequest( - url: string, - params: { - [key: string]: any; - }, - headers?: { - [key: string]: string; - } -): Promise<{ +export declare function doPostRequest(url: string, params: { + [key: string]: any; +}, headers?: { + [key: string]: string; +}): Promise<{ jsonResponse: Record | undefined; status: number; stringResponse: string; }>; -export declare function verifyIdTokenFromJWKSEndpointAndGetPayload( - idToken: string, - jwks: jose.JWTVerifyGetKey, - otherOptions: jose.JWTVerifyOptions -): Promise; +export declare function verifyIdTokenFromJWKSEndpointAndGetPayload(idToken: string, jwks: jose.JWTVerifyGetKey, otherOptions: jose.JWTVerifyOptions): Promise; export declare function discoverOIDCEndpoints(config: ProviderConfigForClientType): Promise; export declare function normaliseOIDCEndpointToIncludeWellKnown(url: string): string; diff --git a/lib/build/recipe/thirdparty/providers/utils.js b/lib/build/recipe/thirdparty/providers/utils.js index bacd7b02f..6d3cb090b 100644 --- a/lib/build/recipe/thirdparty/providers/utils.js +++ b/lib/build/recipe/thirdparty/providers/utils.js @@ -1,45 +1,26 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.normaliseOIDCEndpointToIncludeWellKnown = exports.discoverOIDCEndpoints = exports.verifyIdTokenFromJWKSEndpointAndGetPayload = exports.doPostRequest = exports.doGetRequest = void 0; const jose = __importStar(require("jose")); @@ -49,9 +30,7 @@ const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath" const logger_1 = require("../../../logger"); const utils_1 = require("../../../utils"); async function doGetRequest(url, queryParams, headers) { - logger_1.logDebugMessage( - `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` - ); + logger_1.logDebugMessage(`GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}`); if ((headers === null || headers === void 0 ? void 0 : headers["Accept"]) === undefined) { headers = Object.assign(Object.assign({}, headers), { Accept: "application/json" }); } @@ -79,9 +58,7 @@ async function doPostRequest(url, params, headers) { } headers["Content-Type"] = "application/x-www-form-urlencoded"; headers["Accept"] = "application/json"; // few providers like github don't send back json response by default - logger_1.logDebugMessage( - `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` - ); + logger_1.logDebugMessage(`POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}`); const body = new URLSearchParams(params).toString(); let response = await utils_1.doFetch(url, { method: "POST", @@ -132,10 +109,8 @@ function normaliseOIDCEndpointToIncludeWellKnown(url) { const normalisedDomain = new normalisedURLDomain_1.default(url); const normalisedPath = new normalisedURLPath_1.default(url); const normalisedWellKnownPath = new normalisedURLPath_1.default("/.well-known/openid-configuration"); - return ( - normalisedDomain.getAsStringDangerous() + + return (normalisedDomain.getAsStringDangerous() + normalisedPath.getAsStringDangerous() + - normalisedWellKnownPath.getAsStringDangerous() - ); + normalisedWellKnownPath.getAsStringDangerous()); } exports.normaliseOIDCEndpointToIncludeWellKnown = normaliseOIDCEndpointToIncludeWellKnown; diff --git a/lib/build/recipe/thirdparty/recipe.d.ts b/lib/build/recipe/thirdparty/recipe.d.ts index cc79859cf..bd271c875 100644 --- a/lib/build/recipe/thirdparty/recipe.d.ts +++ b/lib/build/recipe/thirdparty/recipe.d.ts @@ -13,27 +13,12 @@ export default class Recipe extends RecipeModule { recipeInterfaceImpl: RecipeInterface; apiImpl: APIInterface; isInServerlessEnv: boolean; - constructor( - recipeId: string, - appInfo: NormalisedAppinfo, - isInServerlessEnv: boolean, - config: TypeInput | undefined, - _recipes: {}, - _ingredients: {} - ); + constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput | undefined, _recipes: {}, _ingredients: {}); static init(config?: TypeInput): RecipeListFunction; static getInstanceOrThrowError(): Recipe; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - _path: NormalisedURLPath, - _method: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/thirdparty/recipe.js b/lib/build/recipe/thirdparty/recipe.js index d9daed748..613764527 100644 --- a/lib/build/recipe/thirdparty/recipe.js +++ b/lib/build/recipe/thirdparty/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const utils_1 = require("./utils"); @@ -73,9 +71,11 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.SIGN_IN_UP_API) { return await signinup_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.AUTHORISATION_API) { + } + else if (id === constants_1.AUTHORISATION_API) { return await authorisationUrl_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.APPLE_REDIRECT_HANDLER) { + } + else if (id === constants_1.APPLE_REDIRECT_HANDLER) { return await appleRedirect_1.default(this.apiImpl, options, userContext); } return false; @@ -93,9 +93,7 @@ class Recipe extends recipeModule_1.default { this.isInServerlessEnv = isInServerlessEnv; this.providers = this.config.signInAndUpFeature.providers; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.providers) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.providers)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -113,18 +111,12 @@ class Recipe extends recipeModule_1.default { static init(config) { return (appInfo, isInServerlessEnv) => { if (Recipe.instance === undefined) { - Recipe.instance = new Recipe( - Recipe.RECIPE_ID, - appInfo, - isInServerlessEnv, - config, - {}, - { - emailDelivery: undefined, - } - ); + Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config, {}, { + emailDelivery: undefined, + }); return Recipe.instance; - } else { + } + else { throw new Error("ThirdParty recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/thirdparty/recipeImplementation.js b/lib/build/recipe/thirdparty/recipeImplementation.js index dce4ef1e6..624eae517 100644 --- a/lib/build/recipe/thirdparty/recipeImplementation.js +++ b/lib/build/recipe/thirdparty/recipeImplementation.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const configUtils_1 = require("./providers/configUtils"); @@ -16,23 +14,9 @@ const authUtils_1 = require("../../authUtils"); const constants_1 = require("../multitenancy/constants"); function getRecipeImplementation(querier, providers) { return { - manuallyCreateOrUpdateUser: async function ({ - thirdPartyId, - thirdPartyUserId, - email, - isVerified, - tenantId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }) { + manuallyCreateOrUpdateUser: async function ({ thirdPartyId, thirdPartyUserId, email, isVerified, tenantId, session, shouldTryLinkingWithSessionUser, userContext, }) { const accountLinking = recipe_1.default.getInstance(); - const users = await __1.listUsersByAccountInfo( - tenantId, - { thirdParty: { id: thirdPartyId, userId: thirdPartyUserId } }, - false, - userContext - ); + const users = await __1.listUsersByAccountInfo(tenantId, { thirdParty: { id: thirdPartyId, userId: thirdPartyUserId } }, false, userContext); const user = users[0]; if (user !== undefined) { const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ @@ -45,22 +29,17 @@ function getRecipeImplementation(querier, providers) { if (!isEmailChangeAllowed.allowed) { return { status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", - reason: - isEmailChangeAllowed.reason === "PRIMARY_USER_CONFLICT" - ? "Email already associated with another primary user." - : "New email cannot be applied to existing account because of account takeover risks.", + reason: isEmailChangeAllowed.reason === "PRIMARY_USER_CONFLICT" + ? "Email already associated with another primary user." + : "New email cannot be applied to existing account because of account takeover risks.", }; } } - let response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/${tenantId}/recipe/signinup`), - { - thirdPartyId, - thirdPartyUserId, - email: { id: email, isVerified }, - }, - userContext - ); + let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/signinup`), { + thirdPartyId, + thirdPartyUserId, + email: { id: email, isVerified }, + }, userContext); if (response.status !== "OK") { return response; } @@ -73,17 +52,15 @@ function getRecipeImplementation(querier, providers) { }); // 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 __1.getUser(response.recipeUserId.getAsString(), userContext); - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( - { - tenantId, - shouldTryLinkingWithSessionUser, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - userContext, - } - ); + response.user = (await __1.getUser(response.recipeUserId.getAsString(), userContext)); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + tenantId, + shouldTryLinkingWithSessionUser, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + userContext, + }); if (linkResult.status !== "OK") { return linkResult; } @@ -94,18 +71,7 @@ function getRecipeImplementation(querier, providers) { recipeUserId: response.recipeUserId, }; }, - signInUp: async function ({ - thirdPartyId, - thirdPartyUserId, - email, - isVerified, - tenantId, - userContext, - oAuthTokens, - session, - shouldTryLinkingWithSessionUser, - rawUserInfoFromProvider, - }) { + signInUp: async function ({ thirdPartyId, thirdPartyUserId, email, isVerified, tenantId, userContext, oAuthTokens, session, shouldTryLinkingWithSessionUser, rawUserInfoFromProvider, }) { let response = await this.manuallyCreateOrUpdateUser({ thirdPartyId, thirdPartyUserId, @@ -119,14 +85,14 @@ function getRecipeImplementation(querier, providers) { if (response.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { return { status: "SIGN_IN_UP_NOT_ALLOWED", - reason: - response.reason === "Email already associated with another primary user." - ? "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)" - : "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_024)", + reason: response.reason === "Email already associated with another primary user." + ? "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)" + : "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_024)", }; } if (response.status === "OK") { - return Object.assign(Object.assign({}, response), { oAuthTokens, rawUserInfoFromProvider }); + return Object.assign(Object.assign({}, response), { oAuthTokens, + rawUserInfoFromProvider }); } return response; }, @@ -136,17 +102,8 @@ function getRecipeImplementation(querier, providers) { if (tenantConfig === undefined) { throw new Error("Tenant not found"); } - const mergedProviders = configUtils_1.mergeProvidersFromCoreAndStatic( - tenantConfig.thirdParty.providers, - providers, - tenantId === constants_1.DEFAULT_TENANT_ID - ); - const provider = await configUtils_1.findAndCreateProviderInstance( - mergedProviders, - thirdPartyId, - clientType, - userContext - ); + const mergedProviders = configUtils_1.mergeProvidersFromCoreAndStatic(tenantConfig.thirdParty.providers, providers, tenantId === constants_1.DEFAULT_TENANT_ID); + const provider = await configUtils_1.findAndCreateProviderInstance(mergedProviders, thirdPartyId, clientType, userContext); return provider; }, }; diff --git a/lib/build/recipe/thirdparty/types.d.ts b/lib/build/recipe/thirdparty/types.d.ts index 8d0198e3b..022a34ad7 100644 --- a/lib/build/recipe/thirdparty/types.d.ts +++ b/lib/build/recipe/thirdparty/types.d.ts @@ -115,7 +115,10 @@ export declare type TypeProvider = { }; userContext: UserContext; }) => Promise; - getUserInfo: (input: { oAuthTokens: any; userContext: UserContext }) => Promise; + getUserInfo: (input: { + oAuthTokens: any; + userContext: UserContext; + }) => Promise; }; export declare type ProviderConfig = CommonProviderConfig & { clients?: ProviderClientConfig[]; @@ -134,20 +137,14 @@ export declare type TypeNormalisedInputSignInAndUp = { export declare type TypeInput = { signInAndUpFeature?: TypeInputSignInAndUp; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { signInAndUpFeature: TypeNormalisedInputSignInAndUp; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -178,37 +175,29 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - createdNewRecipeUser: boolean; - recipeUserId: RecipeUserId; - user: User; - oAuthTokens: { - [key: string]: any; - }; - rawUserInfoFromProvider: { - fromIdTokenPayload?: { - [key: string]: any; - }; - fromUserInfoAPI?: { - [key: string]: any; - }; - }; - } - | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } - | { - 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"; - } - >; + }): Promise<{ + status: "OK"; + createdNewRecipeUser: boolean; + recipeUserId: RecipeUserId; + user: User; + oAuthTokens: { + [key: string]: any; + }; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + } | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } | { + 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"; + }>; manuallyCreateOrUpdateUser(input: { thirdPartyId: string; thirdPartyUserId: string; @@ -218,30 +207,21 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } - | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } - | { - 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"; - } - >; + }): Promise<{ + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } | { + 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"; + }>; }; export declare type APIOptions = { recipeImplementation: RecipeInterface; @@ -254,81 +234,62 @@ export declare type APIOptions = { appInfo: NormalisedAppinfo; }; export declare type APIInterface = { - authorisationUrlGET: - | undefined - | ((input: { - provider: TypeProvider; - redirectURIOnProviderDashboard: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - urlWithQueryParams: string; - pkceCodeVerifier?: string; - } - | GeneralErrorResponse - >); - signInUpPOST: - | undefined - | (( - input: { - provider: TypeProvider; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - } & ( - | { - redirectURIInfo: { - redirectURIOnProviderDashboard: string; - redirectURIQueryParams: any; - pkceCodeVerifier?: string; - }; - } - | { - oAuthTokens: { - [key: string]: any; - }; - } - ) - ) => Promise< - | { - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - session: SessionContainerInterface; - oAuthTokens: { - [key: string]: any; - }; - rawUserInfoFromProvider: { - fromIdTokenPayload?: { - [key: string]: any; - }; - fromUserInfoAPI?: { - [key: string]: any; - }; - }; - } - | { - status: "NO_EMAIL_GIVEN_BY_PROVIDER"; - } - | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } - | GeneralErrorResponse - >); - appleRedirectHandlerPOST: - | undefined - | ((input: { - formPostInfoFromProvider: { - [key: string]: any; - }; - options: APIOptions; - userContext: UserContext; - }) => Promise); + authorisationUrlGET: undefined | ((input: { + provider: TypeProvider; + redirectURIOnProviderDashboard: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + urlWithQueryParams: string; + pkceCodeVerifier?: string; + } | GeneralErrorResponse>); + signInUpPOST: undefined | ((input: { + provider: TypeProvider; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + } & ({ + redirectURIInfo: { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string; + }; + } | { + oAuthTokens: { + [key: string]: any; + }; + })) => Promise<{ + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + session: SessionContainerInterface; + oAuthTokens: { + [key: string]: any; + }; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + } | { + status: "NO_EMAIL_GIVEN_BY_PROVIDER"; + } | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse>); + appleRedirectHandlerPOST: undefined | ((input: { + formPostInfoFromProvider: { + [key: string]: any; + }; + options: APIOptions; + userContext: UserContext; + }) => Promise); }; export {}; diff --git a/lib/build/recipe/thirdparty/utils.d.ts b/lib/build/recipe/thirdparty/utils.d.ts index e4db0684e..7e1bebd95 100644 --- a/lib/build/recipe/thirdparty/utils.d.ts +++ b/lib/build/recipe/thirdparty/utils.d.ts @@ -1,8 +1,5 @@ // @ts-nocheck import { NormalisedAppinfo } from "../../types"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput( - appInfo: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; export declare function isFakeEmail(email: string): boolean; diff --git a/lib/build/recipe/thirdparty/utils.js b/lib/build/recipe/thirdparty/utils.js index 8828f95a5..1e14d1ea9 100644 --- a/lib/build/recipe/thirdparty/utils.js +++ b/lib/build/recipe/thirdparty/utils.js @@ -16,17 +16,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.isFakeEmail = exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(appInfo, config) { - let signInAndUpFeature = validateAndNormaliseSignInAndUpConfig( - appInfo, - config === null || config === void 0 ? void 0 : config.signInAndUpFeature - ); - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let signInAndUpFeature = validateAndNormaliseSignInAndUpConfig(appInfo, config === null || config === void 0 ? void 0 : config.signInAndUpFeature); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { signInAndUpFeature, override, diff --git a/lib/build/recipe/totp/api/createDevice.d.ts b/lib/build/recipe/totp/api/createDevice.d.ts index aaa135298..9d703be7f 100644 --- a/lib/build/recipe/totp/api/createDevice.d.ts +++ b/lib/build/recipe/totp/api/createDevice.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function createDeviceAPI( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function createDeviceAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/totp/api/createDevice.js b/lib/build/recipe/totp/api/createDevice.js index c821f6e64..14d72b950 100644 --- a/lib/build/recipe/totp/api/createDevice.js +++ b/lib/build/recipe/totp/api/createDevice.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -25,12 +23,7 @@ async function createDeviceAPI(apiImplementation, options, userContext) { if (apiImplementation.createDevicePOST === undefined) { return false; } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: true }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); const bodyParams = await options.req.getJSONBody(); const deviceName = bodyParams.deviceName; if (deviceName !== undefined && typeof deviceName !== "string") { diff --git a/lib/build/recipe/totp/api/implementation.js b/lib/build/recipe/totp/api/implementation.js index 909546b34..87d0dedc8 100644 --- a/lib/build/recipe/totp/api/implementation.js +++ b/lib/build/recipe/totp/api/implementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const multifactorauth_1 = __importDefault(require("../../multifactorauth")); const recipe_1 = __importDefault(require("../../multifactorauth/recipe")); @@ -30,11 +28,7 @@ function getAPIInterface() { if (mfaInstance === undefined) { throw new Error("should never come here"); // If TOTP initialised, MFA is auto initialised. This should never happen. } - await multifactorauth_1.default.assertAllowedToSetupFactorElseThrowInvalidClaimError( - session, - "totp", - userContext - ); + await multifactorauth_1.default.assertAllowedToSetupFactorElseThrowInvalidClaimError(session, "totp", userContext); const createDeviceRes = await options.recipeImplementation.createDevice({ userId, deviceName: deviceName, @@ -45,7 +39,8 @@ function getAPIInterface() { type: error_1.default.UNAUTHORISED, message: "Session user not found", }); - } else { + } + else { return createDeviceRes; } }, @@ -71,11 +66,7 @@ function getAPIInterface() { if (mfaInstance === undefined) { throw new Error("should never come here"); // If TOTP initialised, MFA is auto initialised. This should never happen. } - await multifactorauth_1.default.assertAllowedToSetupFactorElseThrowInvalidClaimError( - session, - "totp", - userContext - ); + await multifactorauth_1.default.assertAllowedToSetupFactorElseThrowInvalidClaimError(session, "totp", userContext); const res = await options.recipeImplementation.verifyDevice({ tenantId, userId, diff --git a/lib/build/recipe/totp/api/listDevices.d.ts b/lib/build/recipe/totp/api/listDevices.d.ts index d6ba65d69..ffec14f56 100644 --- a/lib/build/recipe/totp/api/listDevices.d.ts +++ b/lib/build/recipe/totp/api/listDevices.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function listDevicesAPI( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function listDevicesAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/totp/api/listDevices.js b/lib/build/recipe/totp/api/listDevices.js index 39ba7d214..ea958ca65 100644 --- a/lib/build/recipe/totp/api/listDevices.js +++ b/lib/build/recipe/totp/api/listDevices.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -25,12 +23,7 @@ async function listDevicesAPI(apiImplementation, options, userContext) { if (apiImplementation.listDevicesGET === undefined) { return false; } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: true }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); let response = await apiImplementation.listDevicesGET({ options, session, diff --git a/lib/build/recipe/totp/api/removeDevice.d.ts b/lib/build/recipe/totp/api/removeDevice.d.ts index 047515bef..057e4d511 100644 --- a/lib/build/recipe/totp/api/removeDevice.d.ts +++ b/lib/build/recipe/totp/api/removeDevice.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function removeDeviceAPI( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function removeDeviceAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/totp/api/removeDevice.js b/lib/build/recipe/totp/api/removeDevice.js index 3893442a1..79fc750fa 100644 --- a/lib/build/recipe/totp/api/removeDevice.js +++ b/lib/build/recipe/totp/api/removeDevice.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -25,12 +23,7 @@ async function removeDeviceAPI(apiImplementation, options, userContext) { if (apiImplementation.removeDevicePOST === undefined) { return false; } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: true }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); const bodyParams = await options.req.getJSONBody(); const deviceName = bodyParams.deviceName; if (deviceName === undefined || typeof deviceName !== "string" || deviceName.length === 0) { diff --git a/lib/build/recipe/totp/api/verifyDevice.d.ts b/lib/build/recipe/totp/api/verifyDevice.d.ts index d4de4bcc9..27b92b41b 100644 --- a/lib/build/recipe/totp/api/verifyDevice.d.ts +++ b/lib/build/recipe/totp/api/verifyDevice.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function verifyDeviceAPI( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function verifyDeviceAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/totp/api/verifyDevice.js b/lib/build/recipe/totp/api/verifyDevice.js index 9499c0225..c4c78d106 100644 --- a/lib/build/recipe/totp/api/verifyDevice.js +++ b/lib/build/recipe/totp/api/verifyDevice.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -25,12 +23,7 @@ async function verifyDeviceAPI(apiImplementation, options, userContext) { if (apiImplementation.verifyDevicePOST === undefined) { return false; } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: true }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); const bodyParams = await options.req.getJSONBody(); const deviceName = bodyParams.deviceName; const totp = bodyParams.totp; diff --git a/lib/build/recipe/totp/api/verifyTOTP.d.ts b/lib/build/recipe/totp/api/verifyTOTP.d.ts index 925a9f40a..b9e24dc4a 100644 --- a/lib/build/recipe/totp/api/verifyTOTP.d.ts +++ b/lib/build/recipe/totp/api/verifyTOTP.d.ts @@ -1,8 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function verifyTOTPAPI( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; +export default function verifyTOTPAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/totp/api/verifyTOTP.js b/lib/build/recipe/totp/api/verifyTOTP.js index 1f8d08a05..299ed5e9a 100644 --- a/lib/build/recipe/totp/api/verifyTOTP.js +++ b/lib/build/recipe/totp/api/verifyTOTP.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -25,12 +23,7 @@ async function verifyTOTPAPI(apiImplementation, options, userContext) { if (apiImplementation.verifyTOTPPOST === undefined) { return false; } - const session = await session_1.default.getSession( - options.req, - options.res, - { overrideGlobalClaimValidators: () => [], sessionRequired: true }, - userContext - ); + const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); const bodyParams = await options.req.getJSONBody(); const totp = bodyParams.totp; if (totp === undefined || typeof totp !== "string") { diff --git a/lib/build/recipe/totp/index.d.ts b/lib/build/recipe/totp/index.d.ts index 9759cdc47..589bcf99a 100644 --- a/lib/build/recipe/totp/index.d.ts +++ b/lib/build/recipe/totp/index.d.ts @@ -3,39 +3,20 @@ import Recipe from "./recipe"; import { RecipeInterface, APIOptions, APIInterface } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static createDevice( - userId: string, - userIdentifierInfo?: string, - deviceName?: string, - skew?: number, - period?: number, - userContext?: Record - ): Promise< - | { - status: "OK"; - deviceName: string; - secret: string; - qrCodeString: string; - } - | { - status: "DEVICE_ALREADY_EXISTS_ERROR"; - } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } - >; - static updateDevice( - userId: string, - existingDeviceName: string, - newDeviceName: string, - userContext?: Record - ): Promise<{ + static createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record): Promise<{ + status: "OK"; + deviceName: string; + secret: string; + qrCodeString: string; + } | { + status: "DEVICE_ALREADY_EXISTS_ERROR"; + } | { + status: "UNKNOWN_USER_ID_ERROR"; + }>; + static updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record): Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR"; }>; - static listDevices( - userId: string, - userContext?: Record - ): Promise<{ + static listDevices(userId: string, userContext?: Record): Promise<{ status: "OK"; devices: { name: string; @@ -44,57 +25,33 @@ export default class Wrapper { verified: boolean; }[]; }>; - static removeDevice( - userId: string, - deviceName: string, - userContext?: Record - ): Promise<{ + static removeDevice(userId: string, deviceName: string, userContext?: Record): Promise<{ status: "OK"; didDeviceExist: boolean; }>; - static verifyDevice( - tenantId: string, - userId: string, - deviceName: string, - totp: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - wasAlreadyVerified: boolean; - } - | { - status: "UNKNOWN_DEVICE_ERROR"; - } - | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } - | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - } - >; - static verifyTOTP( - tenantId: string, - userId: string, - totp: string, - userContext?: Record - ): Promise< - | { - status: "OK" | "UNKNOWN_USER_ID_ERROR"; - } - | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } - | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - } - >; + static verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record): Promise<{ + status: "OK"; + wasAlreadyVerified: boolean; + } | { + status: "UNKNOWN_DEVICE_ERROR"; + } | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + }>; + static verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR"; + } | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + }>; } export declare let init: typeof Recipe.init; export declare let createDevice: typeof Wrapper.createDevice; diff --git a/lib/build/recipe/totp/index.js b/lib/build/recipe/totp/index.js index 579bb3332..c954e65d2 100644 --- a/lib/build/recipe/totp/index.js +++ b/lib/build/recipe/totp/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifyTOTP = exports.verifyDevice = exports.removeDevice = exports.updateDevice = exports.listDevices = exports.createDevice = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/totp/recipe.d.ts b/lib/build/recipe/totp/recipe.d.ts index 53309d471..28f023fec 100644 --- a/lib/build/recipe/totp/recipe.d.ts +++ b/lib/build/recipe/totp/recipe.d.ts @@ -18,15 +18,7 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - _tenantId: string, - req: BaseRequest, - res: BaseResponse, - _: NormalisedURLPath, - __: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, _tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/totp/recipe.js b/lib/build/recipe/totp/recipe.js index fb146942b..5a4619762 100644 --- a/lib/build/recipe/totp/recipe.js +++ b/lib/build/recipe/totp/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -85,13 +83,17 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.CREATE_TOTP_DEVICE) { return await createDevice_1.default(this.apiImpl, options, userContext); - } else if (id === constants_1.LIST_TOTP_DEVICES) { + } + else if (id === constants_1.LIST_TOTP_DEVICES) { return await listDevices_1.default(this.apiImpl, options, userContext); - } else if (id === constants_1.REMOVE_TOTP_DEVICE) { + } + else if (id === constants_1.REMOVE_TOTP_DEVICE) { return await removeDevice_1.default(this.apiImpl, options, userContext); - } else if (id === constants_1.VERIFY_TOTP_DEVICE) { + } + else if (id === constants_1.VERIFY_TOTP_DEVICE) { return await verifyDevice_1.default(this.apiImpl, options, userContext); - } else if (id === constants_1.VERIFY_TOTP) { + } + else if (id === constants_1.VERIFY_TOTP) { return await verifyTOTP_1.default(this.apiImpl, options, userContext); } throw new Error("should never come here"); @@ -108,9 +110,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -152,7 +152,8 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } else { + } + else { throw new Error("TOTP recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/totp/recipeImplementation.js b/lib/build/recipe/totp/recipeImplementation.js index 5ad44874b..fb86eb82b 100644 --- a/lib/build/recipe/totp/recipeImplementation.js +++ b/lib/build/recipe/totp/recipeImplementation.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const __1 = require("../.."); @@ -30,16 +28,15 @@ function getRecipeInterface(querier, config) { status: "UNKNOWN_USER_ID_ERROR", }; } - const primaryLoginMethod = user.loginMethods.find( - (method) => method.recipeUserId.getAsString() === user.id - ); + const primaryLoginMethod = user.loginMethods.find((method) => method.recipeUserId.getAsString() === user.id); if (primaryLoginMethod !== undefined) { if (primaryLoginMethod.email !== undefined) { return { info: primaryLoginMethod.email, status: "OK", }; - } else if (primaryLoginMethod.phoneNumber !== undefined) { + } + else if (primaryLoginMethod.phoneNumber !== undefined) { return { info: primaryLoginMethod.phoneNumber, status: "OK", @@ -48,7 +45,8 @@ function getRecipeInterface(querier, config) { } if (user.emails.length > 0) { return { info: user.emails[0], status: "OK" }; - } else if (user.phoneNumbers.length > 0) { + } + else if (user.phoneNumbers.length > 0) { return { info: user.phoneNumbers[0], status: "OK" }; } return { @@ -64,85 +62,55 @@ function getRecipeInterface(querier, config) { }); if (emailOrPhoneInfo.status === "OK") { input.userIdentifierInfo = emailOrPhoneInfo.info; - } else if (emailOrPhoneInfo.status === "UNKNOWN_USER_ID_ERROR") { + } + else if (emailOrPhoneInfo.status === "UNKNOWN_USER_ID_ERROR") { return { status: "UNKNOWN_USER_ID_ERROR", }; - } else { + } + else { // Ignore since UserIdentifierInfo is optional } } - const response = await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/totp/device"), - { - userId: input.userId, - deviceName: input.deviceName, - skew: (_a = input.skew) !== null && _a !== void 0 ? _a : config.defaultSkew, - period: (_b = input.period) !== null && _b !== void 0 ? _b : config.defaultPeriod, - }, - input.userContext - ); - return Object.assign(Object.assign({}, response), { - qrCodeString: - `otpauth://totp/${encodeURI(config.issuer)}${ - input.userIdentifierInfo !== undefined ? ":" + encodeURI(input.userIdentifierInfo) : "" - }` + - `?secret=${response.secret}&issuer=${encodeURI(config.issuer)}&digits=6&period=${ - (_c = input.period) !== null && _c !== void 0 ? _c : config.defaultPeriod - }`, - }); + const response = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/totp/device"), { + userId: input.userId, + deviceName: input.deviceName, + skew: (_a = input.skew) !== null && _a !== void 0 ? _a : config.defaultSkew, + period: (_b = input.period) !== null && _b !== void 0 ? _b : config.defaultPeriod, + }, input.userContext); + return Object.assign(Object.assign({}, response), { qrCodeString: `otpauth://totp/${encodeURI(config.issuer)}${input.userIdentifierInfo !== undefined ? ":" + encodeURI(input.userIdentifierInfo) : ""}` + + `?secret=${response.secret}&issuer=${encodeURI(config.issuer)}&digits=6&period=${(_c = input.period) !== null && _c !== void 0 ? _c : config.defaultPeriod}` }); }, updateDevice: (input) => { - return querier.sendPutRequest( - new normalisedURLPath_1.default("/recipe/totp/device"), - { - userId: input.userId, - existingDeviceName: input.existingDeviceName, - newDeviceName: input.newDeviceName, - }, - {}, - input.userContext - ); + return querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/totp/device"), { + userId: input.userId, + existingDeviceName: input.existingDeviceName, + newDeviceName: input.newDeviceName, + }, {}, input.userContext); }, listDevices: (input) => { - return querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/totp/device/list"), - { - userId: input.userId, - }, - input.userContext - ); + return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/totp/device/list"), { + userId: input.userId, + }, input.userContext); }, removeDevice: (input) => { - return querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/totp/device/remove"), - { - userId: input.userId, - deviceName: input.deviceName, - }, - input.userContext - ); + return querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/totp/device/remove"), { + userId: input.userId, + deviceName: input.deviceName, + }, input.userContext); }, verifyDevice: (input) => { - return querier.sendPostRequest( - new normalisedURLPath_1.default(`${input.tenantId}/recipe/totp/device/verify`), - { - userId: input.userId, - deviceName: input.deviceName, - totp: input.totp, - }, - input.userContext - ); + return querier.sendPostRequest(new normalisedURLPath_1.default(`${input.tenantId}/recipe/totp/device/verify`), { + userId: input.userId, + deviceName: input.deviceName, + totp: input.totp, + }, input.userContext); }, verifyTOTP: (input) => { - return querier.sendPostRequest( - new normalisedURLPath_1.default(`${input.tenantId}/recipe/totp/verify`), - { - userId: input.userId, - totp: input.totp, - }, - input.userContext - ); + return querier.sendPostRequest(new normalisedURLPath_1.default(`${input.tenantId}/recipe/totp/verify`), { + userId: input.userId, + totp: input.totp, + }, input.userContext); }, }; } diff --git a/lib/build/recipe/totp/types.d.ts b/lib/build/recipe/totp/types.d.ts index f4016b9e1..fc61b60ea 100644 --- a/lib/build/recipe/totp/types.d.ts +++ b/lib/build/recipe/totp/types.d.ts @@ -8,10 +8,7 @@ export declare type TypeInput = { defaultSkew?: number; defaultPeriod?: number; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -20,10 +17,7 @@ export declare type TypeNormalisedInput = { defaultSkew: number; defaultPeriod: number; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -31,15 +25,12 @@ export declare type RecipeInterface = { getUserIdentifierInfoForUserId: (input: { userId: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - info: string; - } - | { - status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR"; - } - >; + }) => Promise<{ + status: "OK"; + info: string; + } | { + status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR"; + }>; createDevice: (input: { userId: string; userIdentifierInfo?: string; @@ -47,20 +38,16 @@ export declare type RecipeInterface = { skew?: number; period?: number; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - deviceName: string; - secret: string; - qrCodeString: string; - } - | { - status: "DEVICE_ALREADY_EXISTS_ERROR"; - } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } - >; + }) => Promise<{ + status: "OK"; + deviceName: string; + secret: string; + qrCodeString: string; + } | { + status: "DEVICE_ALREADY_EXISTS_ERROR"; + } | { + status: "UNKNOWN_USER_ID_ERROR"; + }>; updateDevice: (input: { userId: string; existingDeviceName: string; @@ -95,43 +82,34 @@ export declare type RecipeInterface = { deviceName: string; totp: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - wasAlreadyVerified: boolean; - } - | { - status: "UNKNOWN_DEVICE_ERROR"; - } - | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } - | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - } - >; + }) => Promise<{ + status: "OK"; + wasAlreadyVerified: boolean; + } | { + status: "UNKNOWN_DEVICE_ERROR"; + } | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + }>; verifyTOTP: (input: { tenantId: string; userId: string; totp: string; userContext: UserContext; - }) => Promise< - | { - status: "OK" | "UNKNOWN_USER_ID_ERROR"; - } - | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } - | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - } - >; + }) => Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR"; + } | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + }>; }; export declare type APIOptions = { recipeImplementation: RecipeInterface; @@ -142,104 +120,73 @@ export declare type APIOptions = { res: BaseResponse; }; export declare type APIInterface = { - createDevicePOST: - | undefined - | ((input: { - deviceName?: string; - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - deviceName: string; - secret: string; - qrCodeString: string; - } - | { - status: "DEVICE_ALREADY_EXISTS_ERROR"; - } - | GeneralErrorResponse - >); - listDevicesGET: - | undefined - | ((input: { - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - devices: { - name: string; - period: number; - skew: number; - verified: boolean; - }[]; - } - | GeneralErrorResponse - >); - removeDevicePOST: - | undefined - | ((input: { - deviceName: string; - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - didDeviceExist: boolean; - } - | GeneralErrorResponse - >); - verifyDevicePOST: - | undefined - | ((input: { - deviceName: string; - totp: string; - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - wasAlreadyVerified: boolean; - } - | { - status: "UNKNOWN_DEVICE_ERROR"; - } - | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } - | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - } - | GeneralErrorResponse - >); - verifyTOTPPOST: - | undefined - | ((input: { - totp: string; - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise< - | { - status: "OK" | "UNKNOWN_USER_ID_ERROR"; - } - | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } - | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - } - | GeneralErrorResponse - >); + createDevicePOST: undefined | ((input: { + deviceName?: string; + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + deviceName: string; + secret: string; + qrCodeString: string; + } | { + status: "DEVICE_ALREADY_EXISTS_ERROR"; + } | GeneralErrorResponse>); + listDevicesGET: undefined | ((input: { + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + devices: { + name: string; + period: number; + skew: number; + verified: boolean; + }[]; + } | GeneralErrorResponse>); + removeDevicePOST: undefined | ((input: { + deviceName: string; + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + didDeviceExist: boolean; + } | GeneralErrorResponse>); + verifyDevicePOST: undefined | ((input: { + deviceName: string; + totp: string; + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + wasAlreadyVerified: boolean; + } | { + status: "UNKNOWN_DEVICE_ERROR"; + } | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + } | GeneralErrorResponse>); + verifyTOTPPOST: undefined | ((input: { + totp: string; + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR"; + } | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + } | GeneralErrorResponse>); }; diff --git a/lib/build/recipe/totp/utils.d.ts b/lib/build/recipe/totp/utils.d.ts index 6b5abd280..69bb66884 100644 --- a/lib/build/recipe/totp/utils.d.ts +++ b/lib/build/recipe/totp/utils.d.ts @@ -1,7 +1,4 @@ // @ts-nocheck import { NormalisedAppinfo } from "../../types"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput( - appInfo: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; diff --git a/lib/build/recipe/totp/utils.js b/lib/build/recipe/totp/utils.js index fbc423449..fec010fe6 100644 --- a/lib/build/recipe/totp/utils.js +++ b/lib/build/recipe/totp/utils.js @@ -17,26 +17,11 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(appInfo, config) { var _a, _b, _c; - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { - issuer: - (_a = config === null || config === void 0 ? void 0 : config.issuer) !== null && _a !== void 0 - ? _a - : appInfo.appName, - defaultSkew: - (_b = config === null || config === void 0 ? void 0 : config.defaultSkew) !== null && _b !== void 0 - ? _b - : 1, - defaultPeriod: - (_c = config === null || config === void 0 ? void 0 : config.defaultPeriod) !== null && _c !== void 0 - ? _c - : 30, + issuer: (_a = config === null || config === void 0 ? void 0 : config.issuer) !== null && _a !== void 0 ? _a : appInfo.appName, + defaultSkew: (_b = config === null || config === void 0 ? void 0 : config.defaultSkew) !== null && _b !== void 0 ? _b : 1, + defaultPeriod: (_c = config === null || config === void 0 ? void 0 : config.defaultPeriod) !== null && _c !== void 0 ? _c : 30, override, }; } diff --git a/lib/build/recipe/usermetadata/index.d.ts b/lib/build/recipe/usermetadata/index.d.ts index 17987da81..68b6918ca 100644 --- a/lib/build/recipe/usermetadata/index.d.ts +++ b/lib/build/recipe/usermetadata/index.d.ts @@ -4,25 +4,15 @@ import Recipe from "./recipe"; import { RecipeInterface } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static getUserMetadata( - userId: string, - userContext?: Record - ): Promise<{ + static getUserMetadata(userId: string, userContext?: Record): Promise<{ status: "OK"; metadata: any; }>; - static updateUserMetadata( - userId: string, - metadataUpdate: JSONObject, - userContext?: Record - ): Promise<{ + static updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record): Promise<{ status: "OK"; metadata: JSONObject; }>; - static clearUserMetadata( - userId: string, - userContext?: Record - ): Promise<{ + static clearUserMetadata(userId: string, userContext?: Record): Promise<{ status: "OK"; }>; } diff --git a/lib/build/recipe/usermetadata/index.js b/lib/build/recipe/usermetadata/index.js index b0884bab7..3502273ab 100644 --- a/lib/build/recipe/usermetadata/index.js +++ b/lib/build/recipe/usermetadata/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.clearUserMetadata = exports.updateUserMetadata = exports.getUserMetadata = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/usermetadata/recipe.d.ts b/lib/build/recipe/usermetadata/recipe.d.ts index 53d2c80ad..a1b5a2748 100644 --- a/lib/build/recipe/usermetadata/recipe.d.ts +++ b/lib/build/recipe/usermetadata/recipe.d.ts @@ -16,14 +16,7 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled(): APIHandled[]; - handleAPIRequest: ( - _: string, - _tenantId: string | undefined, - __: BaseRequest, - ___: BaseResponse, - ____: normalisedURLPath, - _____: HTTPMethod - ) => Promise; + handleAPIRequest: (_: string, _tenantId: string | undefined, __: BaseRequest, ___: BaseResponse, ____: normalisedURLPath, _____: HTTPMethod) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; diff --git a/lib/build/recipe/usermetadata/recipe.js b/lib/build/recipe/usermetadata/recipe.js index 600238705..1ada4d79e 100644 --- a/lib/build/recipe/usermetadata/recipe.js +++ b/lib/build/recipe/usermetadata/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); const querier_1 = require("../../querier"); @@ -36,9 +34,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_2.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId)) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId))); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } } @@ -47,16 +43,15 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance !== undefined) { return Recipe.instance; } - throw new Error( - "Initialisation not done. Did you forget to call the UserMetadata.init or UserMetadata.init function?" - ); + throw new Error("Initialisation not done. Did you forget to call the UserMetadata.init or UserMetadata.init function?"); } static init(config) { return (appInfo, isInServerlessEnv) => { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } else { + } + else { throw new Error("UserMetadata recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/usermetadata/recipeImplementation.js b/lib/build/recipe/usermetadata/recipeImplementation.js index 2fd78898b..660e2fce8 100644 --- a/lib/build/recipe/usermetadata/recipeImplementation.js +++ b/lib/build/recipe/usermetadata/recipeImplementation.js @@ -13,41 +13,26 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); function getRecipeInterface(querier) { return { getUserMetadata: function ({ userId, userContext }) { - return querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/user/metadata"), - { userId }, - userContext - ); + return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/user/metadata"), { userId }, userContext); }, updateUserMetadata: function ({ userId, metadataUpdate, userContext }) { - return querier.sendPutRequest( - new normalisedURLPath_1.default("/recipe/user/metadata"), - { - userId, - metadataUpdate, - }, - {}, - userContext - ); + return querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/user/metadata"), { + userId, + metadataUpdate, + }, {}, userContext); }, clearUserMetadata: function ({ userId, userContext }) { - return querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/user/metadata/remove"), - { - userId, - }, - userContext - ); + return querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/user/metadata/remove"), { + userId, + }, userContext); }, }; } diff --git a/lib/build/recipe/usermetadata/types.d.ts b/lib/build/recipe/usermetadata/types.d.ts index 8dd434ac7..2a192cfb0 100644 --- a/lib/build/recipe/usermetadata/types.d.ts +++ b/lib/build/recipe/usermetadata/types.d.ts @@ -3,19 +3,13 @@ import OverrideableBuilder from "supertokens-js-override"; import { JSONObject, UserContext } from "../../types"; export declare type TypeInput = { override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; diff --git a/lib/build/recipe/usermetadata/utils.d.ts b/lib/build/recipe/usermetadata/utils.d.ts index 4025b1b44..133d4840f 100644 --- a/lib/build/recipe/usermetadata/utils.d.ts +++ b/lib/build/recipe/usermetadata/utils.d.ts @@ -2,8 +2,4 @@ import { NormalisedAppinfo } from "../../types"; import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput( - _: Recipe, - __: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(_: Recipe, __: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; diff --git a/lib/build/recipe/usermetadata/utils.js b/lib/build/recipe/usermetadata/utils.js index 74993e81f..9bdd116e1 100644 --- a/lib/build/recipe/usermetadata/utils.js +++ b/lib/build/recipe/usermetadata/utils.js @@ -16,13 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(_, __, config) { - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { override, }; diff --git a/lib/build/recipe/userroles/index.d.ts b/lib/build/recipe/userroles/index.d.ts index 11a803d66..af8d359c2 100644 --- a/lib/build/recipe/userroles/index.d.ts +++ b/lib/build/recipe/userroles/index.d.ts @@ -5,99 +5,50 @@ export default class Wrapper { static init: typeof Recipe.init; static PermissionClaim: import("./permissionClaim").PermissionClaimClass; static UserRoleClaim: import("./userRoleClaim").UserRoleClaimClass; - static addRoleToUser( - tenantId: string, - userId: string, - role: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - didUserAlreadyHaveRole: boolean; - } - | { - status: "UNKNOWN_ROLE_ERROR"; - } - >; - static removeUserRole( - tenantId: string, - userId: string, - role: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - didUserHaveRole: boolean; - } - | { - status: "UNKNOWN_ROLE_ERROR"; - } - >; - static getRolesForUser( - tenantId: string, - userId: string, - userContext?: Record - ): Promise<{ + static addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record): Promise<{ + status: "OK"; + didUserAlreadyHaveRole: boolean; + } | { + status: "UNKNOWN_ROLE_ERROR"; + }>; + static removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record): Promise<{ + status: "OK"; + didUserHaveRole: boolean; + } | { + status: "UNKNOWN_ROLE_ERROR"; + }>; + static getRolesForUser(tenantId: string, userId: string, userContext?: Record): Promise<{ status: "OK"; roles: string[]; }>; - static getUsersThatHaveRole( - tenantId: string, - role: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - users: string[]; - } - | { - status: "UNKNOWN_ROLE_ERROR"; - } - >; - static createNewRoleOrAddPermissions( - role: string, - permissions: string[], - userContext?: Record - ): Promise<{ + static getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record): Promise<{ + status: "OK"; + users: string[]; + } | { + status: "UNKNOWN_ROLE_ERROR"; + }>; + static createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record): Promise<{ status: "OK"; createdNewRole: boolean; }>; - static getPermissionsForRole( - role: string, - userContext?: Record - ): Promise< - | { - status: "OK"; - permissions: string[]; - } - | { - status: "UNKNOWN_ROLE_ERROR"; - } - >; - static removePermissionsFromRole( - role: string, - permissions: string[], - userContext?: Record - ): Promise<{ + static getPermissionsForRole(role: string, userContext?: Record): Promise<{ + status: "OK"; + permissions: string[]; + } | { + status: "UNKNOWN_ROLE_ERROR"; + }>; + static removePermissionsFromRole(role: string, permissions: string[], userContext?: Record): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR"; }>; - static getRolesThatHavePermission( - permission: string, - userContext?: Record - ): Promise<{ + static getRolesThatHavePermission(permission: string, userContext?: Record): Promise<{ status: "OK"; roles: string[]; }>; - static deleteRole( - role: string, - userContext?: Record - ): Promise<{ + static deleteRole(role: string, userContext?: Record): Promise<{ status: "OK"; didRoleExist: boolean; }>; - static getAllRoles( - userContext?: Record - ): Promise<{ + static getAllRoles(userContext?: Record): Promise<{ status: "OK"; roles: string[]; }>; diff --git a/lib/build/recipe/userroles/index.js b/lib/build/recipe/userroles/index.js index 21e01ff10..fd98b7c01 100644 --- a/lib/build/recipe/userroles/index.js +++ b/lib/build/recipe/userroles/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.PermissionClaim = exports.UserRoleClaim = exports.getAllRoles = exports.deleteRole = exports.getRolesThatHavePermission = exports.removePermissionsFromRole = exports.getPermissionsForRole = exports.createNewRoleOrAddPermissions = exports.getUsersThatHaveRole = exports.getRolesForUser = exports.removeUserRole = exports.addRoleToUser = exports.init = void 0; const utils_1 = require("../../utils"); @@ -109,16 +107,6 @@ exports.getRolesThatHavePermission = Wrapper.getRolesThatHavePermission; exports.deleteRole = Wrapper.deleteRole; exports.getAllRoles = Wrapper.getAllRoles; var userRoleClaim_2 = require("./userRoleClaim"); -Object.defineProperty(exports, "UserRoleClaim", { - enumerable: true, - get: function () { - return userRoleClaim_2.UserRoleClaim; - }, -}); +Object.defineProperty(exports, "UserRoleClaim", { enumerable: true, get: function () { return userRoleClaim_2.UserRoleClaim; } }); var permissionClaim_2 = require("./permissionClaim"); -Object.defineProperty(exports, "PermissionClaim", { - enumerable: true, - get: function () { - return permissionClaim_2.PermissionClaim; - }, -}); +Object.defineProperty(exports, "PermissionClaim", { enumerable: true, get: function () { return permissionClaim_2.PermissionClaim; } }); diff --git a/lib/build/recipe/userroles/permissionClaim.js b/lib/build/recipe/userroles/permissionClaim.js index c022b4080..16a5cb348 100644 --- a/lib/build/recipe/userroles/permissionClaim.js +++ b/lib/build/recipe/userroles/permissionClaim.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.PermissionClaim = exports.PermissionClaimClass = void 0; const recipe_1 = __importDefault(require("./recipe")); diff --git a/lib/build/recipe/userroles/recipe.d.ts b/lib/build/recipe/userroles/recipe.d.ts index 53d2c80ad..a1b5a2748 100644 --- a/lib/build/recipe/userroles/recipe.d.ts +++ b/lib/build/recipe/userroles/recipe.d.ts @@ -16,14 +16,7 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled(): APIHandled[]; - handleAPIRequest: ( - _: string, - _tenantId: string | undefined, - __: BaseRequest, - ___: BaseResponse, - ____: normalisedURLPath, - _____: HTTPMethod - ) => Promise; + handleAPIRequest: (_: string, _tenantId: string | undefined, __: BaseRequest, ___: BaseResponse, ____: normalisedURLPath, _____: HTTPMethod) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; diff --git a/lib/build/recipe/userroles/recipe.js b/lib/build/recipe/userroles/recipe.js index 3f16cf7be..a4a3d7748 100644 --- a/lib/build/recipe/userroles/recipe.js +++ b/lib/build/recipe/userroles/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); const querier_1 = require("../../querier"); @@ -42,9 +40,7 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId)) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId))); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } postSuperTokensInitCallbacks_1.PostSuperTokensInitCallbacks.addPostInitCallback(() => { @@ -92,43 +88,41 @@ class Recipe extends recipeModule_1.default { }; recipe_2.default.getInstanceOrThrowError().addAccessTokenBuilderFromOtherRecipe(tokenPayloadBuilder); recipe_2.default.getInstanceOrThrowError().addIdTokenBuilderFromOtherRecipe(tokenPayloadBuilder); - recipe_2.default - .getInstanceOrThrowError() - .addUserInfoBuilderFromOtherRecipe(async (user, _accessTokenPayload, scopes, tenantId, userContext) => { - let userInfo = {}; - let userRoles = []; - if (scopes.includes("roles") || scopes.includes("permissions")) { - const res = await this.recipeInterfaceImpl.getRolesForUser({ - userId: user.id, - tenantId, + recipe_2.default.getInstanceOrThrowError().addUserInfoBuilderFromOtherRecipe(async (user, _accessTokenPayload, scopes, tenantId, userContext) => { + let userInfo = {}; + let userRoles = []; + if (scopes.includes("roles") || scopes.includes("permissions")) { + const res = await this.recipeInterfaceImpl.getRolesForUser({ + userId: user.id, + tenantId, + userContext, + }); + if (res.status !== "OK") { + throw new Error("Failed to fetch roles for the user"); + } + userRoles = res.roles; + } + if (scopes.includes("roles")) { + userInfo.roles = userRoles; + } + if (scopes.includes("permissions")) { + const userPermissions = new Set(); + for (const role of userRoles) { + const rolePermissions = await this.recipeInterfaceImpl.getPermissionsForRole({ + role, userContext, }); - if (res.status !== "OK") { - throw new Error("Failed to fetch roles for the user"); + if (rolePermissions.status !== "OK") { + throw new Error("Failed to fetch permissions for the role"); } - userRoles = res.roles; - } - if (scopes.includes("roles")) { - userInfo.roles = userRoles; - } - if (scopes.includes("permissions")) { - const userPermissions = new Set(); - for (const role of userRoles) { - const rolePermissions = await this.recipeInterfaceImpl.getPermissionsForRole({ - role, - userContext, - }); - if (rolePermissions.status !== "OK") { - throw new Error("Failed to fetch permissions for the role"); - } - for (const perm of rolePermissions.permissions) { - userPermissions.add(perm); - } + for (const perm of rolePermissions.permissions) { + userPermissions.add(perm); } - userInfo.permissions = Array.from(userPermissions); } - return userInfo; - }); + userInfo.permissions = Array.from(userPermissions); + } + return userInfo; + }); }); } /* Init functions */ @@ -136,16 +130,15 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance !== undefined) { return Recipe.instance; } - throw new Error( - "Initialisation not done. Did you forget to call the UserRoles.init or SuperTokens.init functions?" - ); + throw new Error("Initialisation not done. Did you forget to call the UserRoles.init or SuperTokens.init functions?"); } static init(config) { return (appInfo, isInServerlessEnv) => { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } else { + } + else { throw new Error("UserRoles recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/userroles/recipeImplementation.js b/lib/build/recipe/userroles/recipeImplementation.js index 47dbdc1ba..9e5c9a81d 100644 --- a/lib/build/recipe/userroles/recipeImplementation.js +++ b/lib/build/recipe/userroles/recipeImplementation.js @@ -13,91 +13,43 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const constants_1 = require("../multitenancy/constants"); function getRecipeInterface(querier) { return { addRoleToUser: function ({ userId, role, tenantId, userContext }) { - return querier.sendPutRequest( - new normalisedURLPath_1.default( - `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role` - ), - { userId, role }, - {}, - userContext - ); + return querier.sendPutRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role`), { userId, role }, {}, userContext); }, removeUserRole: function ({ userId, role, tenantId, userContext }) { - return querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role/remove` - ), - { userId, role }, - userContext - ); + return querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role/remove`), { userId, role }, userContext); }, getRolesForUser: function ({ userId, tenantId, userContext }) { - return querier.sendGetRequest( - new normalisedURLPath_1.default( - `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/roles` - ), - { userId }, - userContext - ); + return querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/roles`), { userId }, userContext); }, getUsersThatHaveRole: function ({ role, tenantId, userContext }) { - return querier.sendGetRequest( - new normalisedURLPath_1.default( - `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/role/users` - ), - { role }, - userContext - ); + return querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/role/users`), { role }, userContext); }, createNewRoleOrAddPermissions: function ({ role, permissions, userContext }) { - return querier.sendPutRequest( - new normalisedURLPath_1.default("/recipe/role"), - { role, permissions }, - {}, - userContext - ); + return querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/role"), { role, permissions }, {}, userContext); }, getPermissionsForRole: function ({ role, userContext }) { - return querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/role/permissions"), - { role }, - userContext - ); + return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/role/permissions"), { role }, userContext); }, removePermissionsFromRole: function ({ role, permissions, userContext }) { - return querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/role/permissions/remove"), - { - role, - permissions, - }, - userContext - ); + return querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/role/permissions/remove"), { + role, + permissions, + }, userContext); }, getRolesThatHavePermission: function ({ permission, userContext }) { - return querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/permission/roles"), - { permission }, - userContext - ); + return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/permission/roles"), { permission }, userContext); }, deleteRole: function ({ role, userContext }) { - return querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/role/remove"), - { role }, - userContext - ); + return querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/role/remove"), { role }, userContext); }, getAllRoles: function ({ userContext }) { return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/roles"), {}, userContext); diff --git a/lib/build/recipe/userroles/types.d.ts b/lib/build/recipe/userroles/types.d.ts index c79ca9b8d..b08d38c7c 100644 --- a/lib/build/recipe/userroles/types.d.ts +++ b/lib/build/recipe/userroles/types.d.ts @@ -5,10 +5,7 @@ export declare type TypeInput = { skipAddingRolesToAccessToken?: boolean; skipAddingPermissionsToAccessToken?: boolean; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -16,10 +13,7 @@ export declare type TypeNormalisedInput = { skipAddingRolesToAccessToken: boolean; skipAddingPermissionsToAccessToken: boolean; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -30,29 +24,23 @@ export declare type RecipeInterface = { role: string; tenantId: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - didUserAlreadyHaveRole: boolean; - } - | { - status: "UNKNOWN_ROLE_ERROR"; - } - >; + }) => Promise<{ + status: "OK"; + didUserAlreadyHaveRole: boolean; + } | { + status: "UNKNOWN_ROLE_ERROR"; + }>; removeUserRole: (input: { userId: string; role: string; tenantId: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - didUserHaveRole: boolean; - } - | { - status: "UNKNOWN_ROLE_ERROR"; - } - >; + }) => Promise<{ + status: "OK"; + didUserHaveRole: boolean; + } | { + status: "UNKNOWN_ROLE_ERROR"; + }>; getRolesForUser: (input: { userId: string; tenantId: string; @@ -65,15 +53,12 @@ export declare type RecipeInterface = { role: string; tenantId: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - users: string[]; - } - | { - status: "UNKNOWN_ROLE_ERROR"; - } - >; + }) => Promise<{ + status: "OK"; + users: string[]; + } | { + status: "UNKNOWN_ROLE_ERROR"; + }>; createNewRoleOrAddPermissions: (input: { role: string; permissions: string[]; @@ -85,15 +70,12 @@ export declare type RecipeInterface = { getPermissionsForRole: (input: { role: string; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - permissions: string[]; - } - | { - status: "UNKNOWN_ROLE_ERROR"; - } - >; + }) => Promise<{ + status: "OK"; + permissions: string[]; + } | { + status: "UNKNOWN_ROLE_ERROR"; + }>; removePermissionsFromRole: (input: { role: string; permissions: string[]; diff --git a/lib/build/recipe/userroles/userRoleClaim.js b/lib/build/recipe/userroles/userRoleClaim.js index ec8bdb305..20254bbd7 100644 --- a/lib/build/recipe/userroles/userRoleClaim.js +++ b/lib/build/recipe/userroles/userRoleClaim.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.UserRoleClaim = exports.UserRoleClaimClass = void 0; const recipe_1 = __importDefault(require("./recipe")); diff --git a/lib/build/recipe/userroles/utils.d.ts b/lib/build/recipe/userroles/utils.d.ts index 4025b1b44..133d4840f 100644 --- a/lib/build/recipe/userroles/utils.d.ts +++ b/lib/build/recipe/userroles/utils.d.ts @@ -2,8 +2,4 @@ import { NormalisedAppinfo } from "../../types"; import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput( - _: Recipe, - __: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput(_: Recipe, __: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; diff --git a/lib/build/recipe/userroles/utils.js b/lib/build/recipe/userroles/utils.js index 7023b84d5..f392db6ee 100644 --- a/lib/build/recipe/userroles/utils.js +++ b/lib/build/recipe/userroles/utils.js @@ -16,18 +16,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(_, __, config) { - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); return { - skipAddingRolesToAccessToken: - (config === null || config === void 0 ? void 0 : config.skipAddingRolesToAccessToken) === true, - skipAddingPermissionsToAccessToken: - (config === null || config === void 0 ? void 0 : config.skipAddingPermissionsToAccessToken) === true, + skipAddingRolesToAccessToken: (config === null || config === void 0 ? void 0 : config.skipAddingRolesToAccessToken) === true, + skipAddingPermissionsToAccessToken: (config === null || config === void 0 ? void 0 : config.skipAddingPermissionsToAccessToken) === true, override, }; } diff --git a/lib/build/recipe/webauthn/api/emailExists.d.ts b/lib/build/recipe/webauthn/api/emailExists.d.ts index 2f55b6d3b..478175dec 100644 --- a/lib/build/recipe/webauthn/api/emailExists.d.ts +++ b/lib/build/recipe/webauthn/api/emailExists.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function emailExists( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function emailExists(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/webauthn/api/emailExists.js b/lib/build/recipe/webauthn/api/emailExists.js index 878fa5c58..1c483fbe1 100644 --- a/lib/build/recipe/webauthn/api/emailExists.js +++ b/lib/build/recipe/webauthn/api/emailExists.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts index ca836c5b4..70044a514 100644 --- a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts +++ b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function generateRecoverAccountToken( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function generateRecoverAccountToken(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js index 0bec60d05..10dcd5788 100644 --- a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js +++ b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/webauthn/api/implementation.js b/lib/build/recipe/webauthn/api/implementation.js index 412737cf8..7c17a55f9 100644 --- a/lib/build/recipe/webauthn/api/implementation.js +++ b/lib/build/recipe/webauthn/api/implementation.js @@ -1,20 +1,18 @@ "use strict"; -var __rest = - (this && this.__rest) || - function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; - } - return t; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../accountlinking/recipe")); const recipe_2 = __importDefault(require("../../emailverification/recipe")); @@ -28,8 +26,7 @@ const __1 = require("../../.."); function getAPIImplementation() { return { registerOptionsPOST: async function (_a) { - var { tenantId, options, userContext } = _a, - props = __rest(_a, ["tenantId", "options", "userContext"]); + var { tenantId, options, userContext } = _a, props = __rest(_a, ["tenantId", "options", "userContext"]); const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, request: options.req, @@ -46,31 +43,27 @@ function getAPIImplementation() { }); const timeout = constants_1.DEFAULT_REGISTER_OPTIONS_TIMEOUT; const attestation = constants_1.DEFAULT_REGISTER_OPTIONS_ATTESTATION; - const requireResidentKey = constants_1.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY; const residentKey = constants_1.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY; const userVerification = constants_1.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION; const supportedAlgorithmIds = constants_1.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS; - let response = await options.recipeImplementation.registerOptions( - Object.assign(Object.assign({}, props), { - attestation, - requireResidentKey, - residentKey, - userVerification, - origin, - relyingPartyId, - relyingPartyName, - timeout, - tenantId, - userContext, - supportedAlgorithmIds, - }) - ); + let response = await options.recipeImplementation.registerOptions(Object.assign(Object.assign({}, props), { attestation, + residentKey, + userVerification, + origin, + relyingPartyId, + relyingPartyName, + timeout, + tenantId, + userContext, + supportedAlgorithmIds })); if (response.status !== "OK") { return response; } return { status: "OK", webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, + createdAt: response.createdAt, + expiresAt: response.expiresAt, challenge: response.challenge, timeout: response.timeout, attestation: response.attestation, @@ -81,7 +74,7 @@ function getAPIImplementation() { authenticatorSelection: response.authenticatorSelection, }; }, - signInOptionsPOST: async function ({ email, tenantId, options, userContext }) { + signInOptionsPOST: async function ({ email, tenantId, options, userContext, }) { const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, request: options.req, @@ -110,38 +103,26 @@ function getAPIImplementation() { return { status: "OK", webauthnGeneratedOptionsId: response.webauthnGeneratedOptionsId, + createdAt: response.createdAt, + expiresAt: response.expiresAt, challenge: response.challenge, timeout: response.timeout, userVerification: response.userVerification, }; }, - signUpPOST: async function ({ - webauthnGeneratedOptionsId, - credential, - tenantId, - session, - shouldTryLinkingWithSessionUser, - options, - userContext, - }) { + signUpPOST: async function ({ webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, options, userContext, }) { // TODO update error codes (ERR_CODE_XXX) after final implementation const errorCodeMap = { - SIGN_UP_NOT_ALLOWED: - "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", INVALID_AUTHENTICATOR_ERROR: { - // TODO: add more cases + // TODO: add more cases }, - INVALID_CREDENTIALS_ERROR: - "The sign up credentials are incorrect. Please use a different authenticator.", + INVALID_CREDENTIALS_ERROR: "The sign up credentials are incorrect. Please use a different authenticator.", LINKING_TO_SESSION_USER_FAILED: { - EMAIL_VERIFICATION_REQUIRED: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", - 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_016)", + EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", + 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_016)", }, }; const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ @@ -157,9 +138,7 @@ function getAPIImplementation() { // check for type is done in a parent function but they are kept // here to be on the safe side. if (!email) { - throw new Error( - "Should never come here since we already check that the email value is a string in validateEmailAddress" - ); + throw new Error("Should never come here since we already check that the email value is a string in validateEmailAddress"); } // todo familiarize with this method const preAuthCheckRes = await authUtils_1.AuthUtils.preAuthChecks({ @@ -179,32 +158,23 @@ function getAPIImplementation() { shouldTryLinkingWithSessionUser, }); if (preAuthCheckRes.status === "SIGN_UP_NOT_ALLOWED") { - const conflictingUsers = await recipe_1.default - .getInstance() - .recipeInterfaceImpl.listUsersByAccountInfo({ - tenantId, - accountInfo: { - email, - }, - doUnionOfAccountInfo: false, - userContext, - }); - if ( - conflictingUsers.some((u) => - u.loginMethods.some((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) - ) - ) { + const conflictingUsers = await recipe_1.default.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + // this isn't mandatory to + if (conflictingUsers.some((u) => u.loginMethods.some((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)))) { return { status: "EMAIL_ALREADY_EXISTS_ERROR", }; } } if (preAuthCheckRes.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - preAuthCheckRes, - errorCodeMap, - "SIGN_UP_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthCheckRes, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); } if (utils_1.isFakeEmail(email) && preAuthCheckRes.isFirstFactor) { // Fake emails cannot be used as a first factor @@ -225,11 +195,7 @@ function getAPIImplementation() { return signUpResponse; } if (signUpResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - signUpResponse, - errorCodeMap, - "SIGN_UP_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(signUpResponse, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); } // todo familiarize with this method // todo check if we need to remove webauthn credential ids from the type - it is not used atm. @@ -248,11 +214,7 @@ function getAPIImplementation() { // It should never actually come here, but we do it cause of consistency. // If it does come here (in case there is a bug), it would make this func throw // anyway, cause there is no SIGN_IN_NOT_ALLOWED in the errorCodeMap. - authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - postAuthChecks, - errorCodeMap, - "SIGN_UP_NOT_ALLOWED" - ); + authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); throw new Error("This should never happen"); } return { @@ -261,27 +223,14 @@ function getAPIImplementation() { user: postAuthChecks.user, }; }, - signInPOST: async function ({ - webauthnGeneratedOptionsId, - credential, - tenantId, - session, - shouldTryLinkingWithSessionUser, - options, - userContext, - }) { + signInPOST: async function ({ webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, options, userContext, }) { const errorCodeMap = { - SIGN_IN_NOT_ALLOWED: - "Cannot sign in due to security reasons. Please try recovering your account, use a different login method or contact support. (ERR_CODE_008)", + SIGN_IN_NOT_ALLOWED: "Cannot sign in due to security reasons. Please try recovering your account, use a different login method or contact support. (ERR_CODE_008)", LINKING_TO_SESSION_USER_FAILED: { - EMAIL_VERIFICATION_REQUIRED: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: - "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", - 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_012)", + EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", + 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_012)", }, }; const recipeId = "webauthn"; @@ -317,16 +266,14 @@ function getAPIImplementation() { // lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || // lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) // ); - const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( - { - accountInfo: { email }, - userContext, - recipeId, - session, - tenantId, - checkCredentialsOnTenant, - } - ); + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ + accountInfo: { email }, + userContext, + recipeId, + session, + tenantId, + checkCredentialsOnTenant, + }); const isVerified = authenticatingUser !== undefined && authenticatingUser.loginMethod.verified; // We check this before preAuthChecks, because that function assumes that if isSignUp is false, // then authenticatingUser is defined. While it wouldn't technically cause any problems with @@ -344,8 +291,7 @@ function getAPIImplementation() { }, factorIds: [recipeId], isSignUp: false, - authenticatingUser: - authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, isVerified, signInVerifiesLoginMethod: false, skipSessionUserUpdateInCore: false, @@ -358,11 +304,7 @@ function getAPIImplementation() { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); } if (preAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - preAuthChecks, - errorCodeMap, - "SIGN_IN_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); } if (utils_1.isFakeEmail(email) && preAuthChecks.isFirstFactor) { // Fake emails cannot be used as a first factor @@ -382,11 +324,7 @@ function getAPIImplementation() { return signInResponse; } if (signInResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - signInResponse, - errorCodeMap, - "SIGN_IN_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(signInResponse, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); } const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ authenticatedUser: signInResponse.user, @@ -400,11 +338,7 @@ function getAPIImplementation() { userContext, }); if (postAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( - postAuthChecks, - errorCodeMap, - "SIGN_IN_NOT_ALLOWED" - ); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); } return { status: "OK", @@ -412,7 +346,7 @@ function getAPIImplementation() { user: postAuthChecks.user, }; }, - emailExistsGET: async function ({ email, tenantId, userContext }) { + emailExistsGET: async function ({ email, tenantId, userContext, }) { // even if the above returns true, we still need to check if there // exists an webauthn user with the same email cause the function // above does not check for that. @@ -424,26 +358,21 @@ function getAPIImplementation() { doUnionOfAccountInfo: false, userContext, }); - let webauthnUserExists = - users.find((u) => { - return ( - u.loginMethods.find((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) !== - undefined - ); - }) !== undefined; + let webauthnUserExists = users.find((u) => { + return (u.loginMethods.find((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) !== + undefined); + }) !== undefined; return { status: "OK", exists: webauthnUserExists, }; }, - generateRecoverAccountTokenPOST: async function ({ email, tenantId, options, userContext }) { + generateRecoverAccountTokenPOST: async function ({ email, tenantId, options, userContext, }) { // NOTE: Check for email being a non-string value. This check will likely // never evaluate to `true` as there is an upper-level check for the type // in validation but kept here to be safe. if (typeof email !== "string") - throw new Error( - "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" - ); + throw new Error("Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"); // this function will be reused in different parts of the flow below.. async function generateAndSendRecoverAccountToken(primaryUserId, recipeUserId) { // the user ID here can be primary or recipe level. @@ -454,11 +383,7 @@ function getAPIImplementation() { userContext, }); if (response.status === "UNKNOWN_USER_ID_ERROR") { - logger_1.logDebugMessage( - `Recover account email not sent, unknown user id: ${ - recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() - }` - ); + logger_1.logDebugMessage(`Recover account email not sent, unknown user id: ${recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString()}`); return { status: "OK", }; @@ -501,9 +426,7 @@ function getAPIImplementation() { // for later use. let webauthnAccount = undefined; for (let i = 0; i < users.length; i++) { - let webauthnAccountTmp = users[i].loginMethods.find( - (l) => l.recipeId === "webauthn" && l.hasSameEmailAs(email) - ); + let webauthnAccountTmp = users[i].loginMethods.find((l) => l.recipeId === "webauthn" && l.hasSameEmailAs(email)); if (webauthnAccountTmp !== undefined) { webauthnAccount = webauthnAccountTmp; break; @@ -520,50 +443,36 @@ function getAPIImplementation() { status: "OK", }; } - return await generateAndSendRecoverAccountToken( - webauthnAccount.recipeUserId.getAsString(), - webauthnAccount.recipeUserId - ); + return await generateAndSendRecoverAccountToken(webauthnAccount.recipeUserId.getAsString(), webauthnAccount.recipeUserId); } // Next we check if there is any login method in which the input email is verified. // If that is the case, then it's proven that the user owns the email and we can // trust linking of the webauthn account. - let emailVerified = - primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - return lm.hasSameEmailAs(email) && lm.verified; - }) !== undefined; + let emailVerified = primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.hasSameEmailAs(email) && lm.verified; + }) !== undefined; // finally, we check if the primary user has any other email / phone number // associated with this account - and if it does, then it means that // there is a risk of account takeover, so we do not allow the token to be generated - let hasOtherEmailOrPhone = - primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - // we do the extra undefined check below cause - // hasSameEmailAs returns false if the lm.email is undefined, and - // we want to check that the email is different as opposed to email - // not existing in lm. - return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; - }) !== undefined; + let hasOtherEmailOrPhone = primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + }) !== undefined; if (!emailVerified && hasOtherEmailOrPhone) { return { status: "RECOVER_ACCOUNT_NOT_ALLOWED", - reason: - "Recover account link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + reason: "Recover account link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", }; } - let shouldDoAccountLinkingResponse = await recipe_1.default - .getInstance() - .config.shouldDoAutomaticAccountLinking( - webauthnAccount !== undefined - ? webauthnAccount - : { - recipeId: "webauthn", - email, - }, - primaryUserAssociatedWithEmail, - undefined, - tenantId, - userContext - ); + let shouldDoAccountLinkingResponse = await recipe_1.default.getInstance().config.shouldDoAutomaticAccountLinking(webauthnAccount !== undefined + ? webauthnAccount + : { + recipeId: "webauthn", + email, + }, primaryUserAssociatedWithEmail, undefined, tenantId, userContext); // Now we need to check that if there exists any webauthn user at all // for the input email. If not, then it implies that when the token is consumed, // then we will create a new user - so we should only generate the token if @@ -577,9 +486,7 @@ function getAPIImplementation() { // code consume cannot be linked to the primary user - therefore, we should // not generate a recover account reset token if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { - logger_1.logDebugMessage( - `Recover account email not sent, since webauthn user didn't exist, and account linking not enabled` - ); + logger_1.logDebugMessage(`Recover account email not sent, since webauthn user didn't exist, and account linking not enabled`); return { status: "OK", }; @@ -599,10 +506,9 @@ function getAPIImplementation() { // we will be creating a new webauthn account when the token // is consumed and linking it to this primary user. return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, undefined); - } else { - logger_1.logDebugMessage( - `Recover account email not sent, isSignUpAllowed returned false for email: ${email}` - ); + } + else { + logger_1.logDebugMessage(`Recover account email not sent, isSignUpAllowed returned false for email: ${email}`); return { status: "OK", }; @@ -612,15 +518,11 @@ function getAPIImplementation() { // and also some primary user ID exist. We now need to find out if they are linked // together or not. If they are linked together, then we can just generate the token // else we check for more security conditions (since we will be linking them post token generation) - let areTheTwoAccountsLinked = - primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - return lm.recipeUserId.getAsString() === webauthnAccount.recipeUserId.getAsString(); - }) !== undefined; + let areTheTwoAccountsLinked = primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.recipeUserId.getAsString() === webauthnAccount.recipeUserId.getAsString(); + }) !== undefined; if (areTheTwoAccountsLinked) { - return await generateAndSendRecoverAccountToken( - primaryUserAssociatedWithEmail.id, - webauthnAccount.recipeUserId - ); + return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, webauthnAccount.recipeUserId); } // Here we know that the two accounts are NOT linked. We now need to check for an // extra security measure here to make sure that the input email in the primary user @@ -643,43 +545,25 @@ function getAPIImplementation() { // here we will go ahead with the token generation cause // even when the token is consumed, we will not be linking the accounts // so no need to check for anything - return await generateAndSendRecoverAccountToken( - webauthnAccount.recipeUserId.getAsString(), - webauthnAccount.recipeUserId - ); + return await generateAndSendRecoverAccountToken(webauthnAccount.recipeUserId.getAsString(), webauthnAccount.recipeUserId); } if (!shouldDoAccountLinkingResponse.shouldRequireVerification) { // the checks below are related to email verification, and if the user // does not care about that, then we should just continue with token generation - return await generateAndSendRecoverAccountToken( - primaryUserAssociatedWithEmail.id, - webauthnAccount.recipeUserId - ); - } - return await generateAndSendRecoverAccountToken( - primaryUserAssociatedWithEmail.id, - webauthnAccount.recipeUserId - ); + return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, webauthnAccount.recipeUserId); + } + return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, webauthnAccount.recipeUserId); }, - recoverAccountPOST: async function ({ - webauthnGeneratedOptionsId, - credential, - token, - tenantId, - options, - userContext, - }) { + recoverAccountPOST: async function ({ webauthnGeneratedOptionsId, credential, token, tenantId, options, userContext, }) { async function markEmailAsVerified(recipeUserId, email) { const emailVerificationInstance = recipe_2.default.getInstance(); if (emailVerificationInstance) { - const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( - { - tenantId, - recipeUserId, - email, - userContext, - } - ); + const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken({ + tenantId, + recipeUserId, + email, + userContext, + }); if (tokenResponse.status === "OK") { await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ tenantId, @@ -706,11 +590,13 @@ function getAPIImplementation() { status: "INVALID_AUTHENTICATOR_ERROR", reason: updateResponse.reason, }; - } else if (updateResponse.status === "INVALID_CREDENTIALS_ERROR") { + } + else if (updateResponse.status === "INVALID_CREDENTIALS_ERROR") { return { status: "INVALID_CREDENTIALS_ERROR", }; - } else { + } + else { // status: "OK" // If the update was successful, we try to mark the email as verified. // We do this because we assume that the recover account token was delivered by email (and to the appropriate email address) @@ -722,10 +608,7 @@ function getAPIImplementation() { // If we verified (and linked) the existing user with the original credential, User M would get access to the current user and any linked users. await markEmailAsVerified(recipeUserId, emailForWhomTokenWasGenerated); // We refresh the user information here, because the verification status may be updated, which is used during linking. - const updatedUserAfterEmailVerification = await __1.getUser( - recipeUserId.getAsString(), - userContext - ); + const updatedUserAfterEmailVerification = await __1.getUser(recipeUserId.getAsString(), userContext); if (updatedUserAfterEmailVerification === undefined) { throw new Error("Should never happen - user deleted after during recover account"); } @@ -749,8 +632,7 @@ function getAPIImplementation() { session: undefined, userContext, }); - const userAfterWeTriedLinking = - linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; + const userAfterWeTriedLinking = linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; return { status: "OK", email: emailForWhomTokenWasGenerated, @@ -784,27 +666,23 @@ function getAPIImplementation() { if (existingUser.isPrimaryUser) { // If this user contains an webauthn account for whom the token was generated, // then we update that user's credential. - let webauthnUserIsLinkedToExistingUser = - existingUser.loginMethods.find((lm) => { - // we check based on user ID and not email because the only time - // the primary user ID is used for token generation is if the webauthn - // user did not exist - in which case the value of emailPasswordUserExists will - // resolve to false anyway, and that's what we want. - // there is an edge case where if the webauthn recipe user was created - // after the recover account token generation, and it was linked to the - // primary user id (userIdForWhomTokenWasGenerated), in this case, - // we still don't allow credntials update, cause the user should try again - // and the token should be regenerated for the right recipe user. - return ( - lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && - lm.recipeId === "webauthn" - ); - }) !== undefined; + let webauthnUserIsLinkedToExistingUser = existingUser.loginMethods.find((lm) => { + // we check based on user ID and not email because the only time + // the primary user ID is used for token generation is if the webauthn + // user did not exist - in which case the value of emailPasswordUserExists will + // resolve to false anyway, and that's what we want. + // there is an edge case where if the webauthn recipe user was created + // after the recover account token generation, and it was linked to the + // primary user id (userIdForWhomTokenWasGenerated), in this case, + // we still don't allow credntials update, cause the user should try again + // and the token should be regenerated for the right recipe user. + return (lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && + lm.recipeId === "webauthn"); + }) !== undefined; if (webauthnUserIsLinkedToExistingUser) { - return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( - new recipeUserId_1.default(userIdForWhomTokenWasGenerated) - ); - } else { + return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary(new recipeUserId_1.default(userIdForWhomTokenWasGenerated)); + } + else { // this means that the existingUser does not have an webauthn user associated // with it. It could now mean that no webauthn user exists, or it could mean that // the the webauthn user exists, but it's not linked to the current account. @@ -825,28 +703,25 @@ function getAPIImplementation() { credential, userContext, }); - if ( - createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || + if (createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || createUserResponse.status === "GENERATED_OPTIONS_NOT_FOUND_ERROR" || createUserResponse.status === "INVALID_GENERATED_OPTIONS_ERROR" || - createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR" - ) { + createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR") { return createUserResponse; - } else if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { + } + else if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { // this means that the user already existed and we can just return an invalid // token (see the above comment) return { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", }; - } else { + } + else { // we mark the email as verified because recover account also requires // access to the email to work.. This has a good side effect that // any other login method with the same email in existingAccount will also get marked // as verified. - await markEmailAsVerified( - createUserResponse.user.loginMethods[0].recipeUserId, - tokenConsumptionResponse.email - ); + await markEmailAsVerified(createUserResponse.user.loginMethods[0].recipeUserId, tokenConsumptionResponse.email); const updatedUser = await __1.getUser(createUserResponse.user.id, userContext); if (updatedUser === undefined) { throw new Error("Should never happen - user deleted after during recover account"); @@ -858,14 +733,12 @@ function getAPIImplementation() { // email is shared. // We do not take try linking by session here, since this is supposed to be called without a session // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking - const linkRes = await recipe_1.default - .getInstance() - .tryLinkingByAccountInfoOrCreatePrimaryUser({ - tenantId, - inputUser: createUserResponse.user, - session: undefined, - userContext, - }); + const linkRes = await recipe_1.default.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ + tenantId, + inputUser: createUserResponse.user, + session: undefined, + userContext, + }); const userAfterLinking = linkRes.status === "OK" ? linkRes.user : createUserResponse.user; if (linkRes.status === "OK" && linkRes.user.id !== existingUser.id) { // this means that the account we just linked to @@ -880,16 +753,53 @@ function getAPIImplementation() { }; } } - } else { + } + else { // This means that the existing user is not a primary account, which implies that // it must be a non linked webauthn account. In this case, we simply update the credential. // Linking to an existing account will be done after the user goes through the email // verification flow once they log in (if applicable). - return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( - new recipeUserId_1.default(userIdForWhomTokenWasGenerated) - ); + return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary(new recipeUserId_1.default(userIdForWhomTokenWasGenerated)); } }, + registerCredentialPOST: async function ({ webauthnGeneratedOptionsId, credential, tenantId, options, userContext, session, }) { + // TODO update error codes (ERR_CODE_XXX) after final implementation + const errorCodeMap = { + REGISTER_CREDENTIAL_NOT_ALLOWED: "Cannot register credential due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + INVALID_AUTHENTICATOR_ERROR: { + // TODO: add more cases + }, + INVALID_CREDENTIALS_ERROR: "The credentials are incorrect. Please use a different authenticator.", + }; + const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext, + }); + if (generatedOptions.status !== "OK") { + return generatedOptions; + } + const email = generatedOptions.email; + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (!email) { + throw new Error("Should never come here since we already check that the email value is a string in validateEmailAddress"); + } + // we are using the email from the register options + const registerCredentialResponse = await options.recipeImplementation.registerCredential({ + webauthnGeneratedOptionsId, + credential, + userContext, + recipeUserId: session.getRecipeUserId(), + }); + if (registerCredentialResponse.status !== "OK") { + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(registerCredentialResponse, errorCodeMap, "REGISTER_CREDENTIAL_NOT_ALLOWED"); + } + return { + status: "OK", + }; + }, }; } exports.default = getAPIImplementation; diff --git a/lib/build/recipe/webauthn/api/recoverAccount.d.ts b/lib/build/recipe/webauthn/api/recoverAccount.d.ts index a5038fad1..c0fd31373 100644 --- a/lib/build/recipe/webauthn/api/recoverAccount.d.ts +++ b/lib/build/recipe/webauthn/api/recoverAccount.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function recoverAccount( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function recoverAccount(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/webauthn/api/recoverAccount.js b/lib/build/recipe/webauthn/api/recoverAccount.js index acb883f39..305fb33df 100644 --- a/lib/build/recipe/webauthn/api/recoverAccount.js +++ b/lib/build/recipe/webauthn/api/recoverAccount.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -27,9 +25,7 @@ async function recoverAccount(apiImplementation, tenantId, options, userContext) return false; } const requestBody = await options.req.getJSONBody(); - let webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( - requestBody.webauthnGeneratedOptionsId - ); + let webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError(requestBody.webauthnGeneratedOptionsId); let credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); let token = requestBody.token; if (token === undefined) { @@ -52,14 +48,11 @@ async function recoverAccount(apiImplementation, tenantId, options, userContext) options, userContext, }); - utils_1.send200Response( - options.res, - result.status === "OK" - ? { - status: "OK", - } - : result - ); + utils_1.send200Response(options.res, result.status === "OK" + ? { + status: "OK", + } + : result); return true; } exports.default = recoverAccount; diff --git a/lib/build/recipe/webauthn/api/registerCredential.d.ts b/lib/build/recipe/webauthn/api/registerCredential.d.ts new file mode 100644 index 000000000..01bbfa0ae --- /dev/null +++ b/lib/build/recipe/webauthn/api/registerCredential.d.ts @@ -0,0 +1,4 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function registerCredentialAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/webauthn/api/registerCredential.js b/lib/build/recipe/webauthn/api/registerCredential.js new file mode 100644 index 000000000..69cc88689 --- /dev/null +++ b/lib/build/recipe/webauthn/api/registerCredential.js @@ -0,0 +1,59 @@ +"use strict"; +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const utils_1 = require("../../../utils"); +const utils_2 = require("./utils"); +const error_1 = __importDefault(require("../error")); +const authUtils_1 = require("../../../authUtils"); +async function registerCredentialAPI(apiImplementation, tenantId, options, userContext) { + if (apiImplementation.registerCredentialPOST === undefined) { + return false; + } + const requestBody = await options.req.getJSONBody(); + const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError(requestBody.webauthnGeneratedOptionsId); + const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, undefined, userContext); + if (session === undefined) { + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "A valid session is required to register a credential", + }); + } + let result = await apiImplementation.registerCredentialPOST({ + credential, + webauthnGeneratedOptionsId, + tenantId, + options, + userContext: userContext, + session, + }); + if (result.status === "OK") { + utils_1.send200Response(options.res, { + status: "OK", + }); + } + else if (result.status === "GENERAL_ERROR") { + utils_1.send200Response(options.res, result); + } + else { + utils_1.send200Response(options.res, result); + } + return true; +} +exports.default = registerCredentialAPI; diff --git a/lib/build/recipe/webauthn/api/registerOptions.d.ts b/lib/build/recipe/webauthn/api/registerOptions.d.ts index 6f9f603b6..ee285ec10 100644 --- a/lib/build/recipe/webauthn/api/registerOptions.d.ts +++ b/lib/build/recipe/webauthn/api/registerOptions.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function registerOptions( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function registerOptions(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/webauthn/api/registerOptions.js b/lib/build/recipe/webauthn/api/registerOptions.js index 9d6ed7036..91e1ee902 100644 --- a/lib/build/recipe/webauthn/api/registerOptions.js +++ b/lib/build/recipe/webauthn/api/registerOptions.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -29,10 +27,8 @@ async function registerOptions(apiImplementation, tenantId, options, userContext const requestBody = await options.req.getJSONBody(); let email = (_a = requestBody.email) === null || _a === void 0 ? void 0 : _a.trim(); let recoverAccountToken = requestBody.recoverAccountToken; - if ( - (email === undefined || typeof email !== "string") && - (recoverAccountToken === undefined || typeof recoverAccountToken !== "string") - ) { + if ((email === undefined || typeof email !== "string") && + (recoverAccountToken === undefined || typeof recoverAccountToken !== "string")) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "Please provide the email or the recover account token", diff --git a/lib/build/recipe/webauthn/api/signInOptions.d.ts b/lib/build/recipe/webauthn/api/signInOptions.d.ts index 1e3bc7b5f..17ae095ee 100644 --- a/lib/build/recipe/webauthn/api/signInOptions.d.ts +++ b/lib/build/recipe/webauthn/api/signInOptions.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function signInOptions( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signInOptions(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/webauthn/api/signInOptions.js b/lib/build/recipe/webauthn/api/signInOptions.js index 49f3445a5..cdc4c777f 100644 --- a/lib/build/recipe/webauthn/api/signInOptions.js +++ b/lib/build/recipe/webauthn/api/signInOptions.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/webauthn/api/signin.d.ts b/lib/build/recipe/webauthn/api/signin.d.ts index 72cd6e46b..079e9b8fb 100644 --- a/lib/build/recipe/webauthn/api/signin.d.ts +++ b/lib/build/recipe/webauthn/api/signin.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function signInAPI( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signInAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/webauthn/api/signin.js b/lib/build/recipe/webauthn/api/signin.js index 7a9c59de5..fa62f569a 100644 --- a/lib/build/recipe/webauthn/api/signin.js +++ b/lib/build/recipe/webauthn/api/signin.js @@ -22,20 +22,10 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { return false; } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( - requestBody.webauthnGeneratedOptionsId - ); + const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError(requestBody.webauthnGeneratedOptionsId); const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); - const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( - options.req, - requestBody - ); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( - options.req, - options.res, - shouldTryLinkingWithSessionUser, - userContext - ); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -49,11 +39,9 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { userContext, }); if (result.status === "OK") { - utils_1.send200Response( - options.res, - Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) - ); - } else { + utils_1.send200Response(options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); + } + else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/webauthn/api/signup.d.ts b/lib/build/recipe/webauthn/api/signup.d.ts index afc748051..6c7c81572 100644 --- a/lib/build/recipe/webauthn/api/signup.d.ts +++ b/lib/build/recipe/webauthn/api/signup.d.ts @@ -1,9 +1,4 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function signUpAPI( - apiImplementation: APIInterface, - tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; +export default function signUpAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; diff --git a/lib/build/recipe/webauthn/api/signup.js b/lib/build/recipe/webauthn/api/signup.js index c196907c7..f1921de3c 100644 --- a/lib/build/recipe/webauthn/api/signup.js +++ b/lib/build/recipe/webauthn/api/signup.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -28,20 +26,10 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { return false; } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( - requestBody.webauthnGeneratedOptionsId - ); + const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError(requestBody.webauthnGeneratedOptionsId); const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); - const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( - options.req, - requestBody - ); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( - options.req, - options.res, - shouldTryLinkingWithSessionUser, - userContext - ); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -55,18 +43,18 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { userContext: userContext, }); if (result.status === "OK") { - utils_1.send200Response( - options.res, - Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) - ); - } else if (result.status === "GENERAL_ERROR") { + utils_1.send200Response(options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); + } + else if (result.status === "GENERAL_ERROR") { utils_1.send200Response(options.res, result); - } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { + } + else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "This email already exists. Please sign in instead.", }); - } else { + } + else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/webauthn/api/utils.d.ts b/lib/build/recipe/webauthn/api/utils.d.ts index 881337429..66b31a0b0 100644 --- a/lib/build/recipe/webauthn/api/utils.d.ts +++ b/lib/build/recipe/webauthn/api/utils.d.ts @@ -1,5 +1,3 @@ // @ts-nocheck -export declare function validateWebauthnGeneratedOptionsIdOrThrowError( - webauthnGeneratedOptionsId: string -): Promise; +export declare function validateWebauthnGeneratedOptionsIdOrThrowError(webauthnGeneratedOptionsId: string): Promise; export declare function validateCredentialOrThrowError(credential: T): Promise; diff --git a/lib/build/recipe/webauthn/api/utils.js b/lib/build/recipe/webauthn/api/utils.js index ae0a5d6fd..da7c7df34 100644 --- a/lib/build/recipe/webauthn/api/utils.js +++ b/lib/build/recipe/webauthn/api/utils.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateCredentialOrThrowError = exports.validateWebauthnGeneratedOptionsIdOrThrowError = void 0; /* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. diff --git a/lib/build/recipe/webauthn/constants.d.ts b/lib/build/recipe/webauthn/constants.d.ts index d58df9d60..d6aac4507 100644 --- a/lib/build/recipe/webauthn/constants.d.ts +++ b/lib/build/recipe/webauthn/constants.d.ts @@ -7,7 +7,6 @@ export declare const GENERATE_RECOVER_ACCOUNT_TOKEN_API = "/user/webauthn/reset/ export declare const RECOVER_ACCOUNT_API = "/user/webauthn/reset"; export declare const SIGNUP_EMAIL_EXISTS_API = "/webauthn/email/exists"; export declare const DEFAULT_REGISTER_OPTIONS_ATTESTATION = "none"; -export declare const DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = false; export declare const DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = "required"; export declare const DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = "preferred"; export declare const DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS: number[]; diff --git a/lib/build/recipe/webauthn/constants.js b/lib/build/recipe/webauthn/constants.js index 2bb0063da..31ba4f538 100644 --- a/lib/build/recipe/webauthn/constants.js +++ b/lib/build/recipe/webauthn/constants.js @@ -14,7 +14,7 @@ * under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.DEFAULT_SIGNIN_OPTIONS_TIMEOUT = exports.DEFAULT_REGISTER_OPTIONS_TIMEOUT = exports.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION = exports.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS = exports.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = exports.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = exports.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = exports.DEFAULT_REGISTER_OPTIONS_ATTESTATION = exports.SIGNUP_EMAIL_EXISTS_API = exports.RECOVER_ACCOUNT_API = exports.GENERATE_RECOVER_ACCOUNT_TOKEN_API = exports.SIGN_IN_API = exports.SIGN_UP_API = exports.SIGNIN_OPTIONS_API = exports.REGISTER_OPTIONS_API = void 0; +exports.DEFAULT_SIGNIN_OPTIONS_TIMEOUT = exports.DEFAULT_REGISTER_OPTIONS_TIMEOUT = exports.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION = exports.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS = exports.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = exports.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = exports.DEFAULT_REGISTER_OPTIONS_ATTESTATION = exports.SIGNUP_EMAIL_EXISTS_API = exports.RECOVER_ACCOUNT_API = exports.GENERATE_RECOVER_ACCOUNT_TOKEN_API = exports.SIGN_IN_API = exports.SIGN_UP_API = exports.SIGNIN_OPTIONS_API = exports.REGISTER_OPTIONS_API = void 0; exports.REGISTER_OPTIONS_API = "/webauthn/options/register"; exports.SIGNIN_OPTIONS_API = "/webauthn/options/signin"; exports.SIGN_UP_API = "/webauthn/signup"; @@ -24,7 +24,6 @@ exports.RECOVER_ACCOUNT_API = "/user/webauthn/reset"; exports.SIGNUP_EMAIL_EXISTS_API = "/webauthn/email/exists"; // defaults that can be overridden by the developer exports.DEFAULT_REGISTER_OPTIONS_ATTESTATION = "none"; -exports.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY = false; exports.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY = "required"; exports.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION = "preferred"; exports.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS = [-8, -7, -257]; diff --git a/lib/build/recipe/webauthn/core-mock.d.ts b/lib/build/recipe/webauthn/core-mock.d.ts new file mode 100644 index 000000000..0bb5666c4 --- /dev/null +++ b/lib/build/recipe/webauthn/core-mock.d.ts @@ -0,0 +1,3 @@ +// @ts-nocheck +import { Querier } from "../../querier"; +export declare const getMockQuerier: (recipeId: string) => Querier; diff --git a/lib/build/recipe/webauthn/core-mock.js b/lib/build/recipe/webauthn/core-mock.js new file mode 100644 index 000000000..78d1ee44b --- /dev/null +++ b/lib/build/recipe/webauthn/core-mock.js @@ -0,0 +1,197 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getMockQuerier = void 0; +const querier_1 = require("../../querier"); +const server_1 = require("@simplewebauthn/server"); +const crypto_1 = __importDefault(require("crypto")); +const db = { + generatedOptions: {}, + credentials: {}, + users: {}, +}; +const writeDb = (table, key, value) => { + db[table][key] = value; +}; +const readDb = (table, key) => { + return db[table][key]; +}; +// const readDbBy = (table: keyof typeof db, func: (value: any) => boolean) => { +// return Object.values(db[table]).find(func); +// }; +const getMockQuerier = (recipeId) => { + const querier = querier_1.Querier.getNewInstanceOrThrowError(recipeId); + const sendPostRequest = async (path, body, _userContext) => { + var _a, _b; + if (path.getAsStringDangerous().includes("/recipe/webauthn/options/register")) { + const registrationOptions = await server_1.generateRegistrationOptions({ + rpID: body.relyingPartyId, + rpName: body.relyingPartyName, + userName: body.email, + timeout: body.timeout, + attestationType: body.attestation || "none", + authenticatorSelection: { + userVerification: body.userVerification || "preferred", + requireResidentKey: body.requireResidentKey || false, + residentKey: body.residentKey || "required", + }, + supportedAlgorithmIDs: body.supportedAlgorithmIDs || [-8, -7, -257], + userDisplayName: body.displayName || body.email, + }); + const id = crypto_1.default.randomUUID(); + const now = new Date(); + const createdAt = now.getTime(); + const expiresAt = createdAt + body.timeout * 1000; + writeDb("generatedOptions", id, Object.assign(Object.assign({}, registrationOptions), { id, origin: body.origin, tenantId: body.tenantId, email: body.email, rpId: registrationOptions.rp.id, createdAt, + expiresAt })); + // @ts-ignore + return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id, createdAt, + expiresAt }, registrationOptions); + } + else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + const signInOptions = await server_1.generateAuthenticationOptions({ + rpID: body.relyingPartyId, + timeout: body.timeout, + userVerification: body.userVerification || "preferred", + }); + const id = crypto_1.default.randomUUID(); + const now = new Date(); + const createdAt = now.getTime(); + const expiresAt = createdAt + body.timeout * 1000; + writeDb("generatedOptions", id, Object.assign(Object.assign({}, signInOptions), { id, origin: body.origin, tenantId: body.tenantId, email: body.email, createdAt, + expiresAt })); + // @ts-ignore + return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id, createdAt, + expiresAt }, signInOptions); + } + else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { + const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + const registrationVerification = await server_1.verifyRegistrationResponse({ + expectedChallenge: options.challenge, + expectedOrigin: options.origin, + expectedRPID: options.rpId, + response: body.credential, + }); + if (!registrationVerification.verified) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const credentialId = body.credential.id; + if (!credentialId) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const recipeUserId = crypto_1.default.randomUUID(); + const now = new Date(); + writeDb("credentials", credentialId, { + id: credentialId, + userId: recipeUserId, + counter: 0, + publicKey: (_a = registrationVerification.registrationInfo) === null || _a === void 0 ? void 0 : _a.credential.publicKey.toString(), + rpId: options.rpId, + transports: (_b = registrationVerification.registrationInfo) === null || _b === void 0 ? void 0 : _b.credential.transports, + createdAt: now.toISOString(), + }); + const user = { + id: recipeUserId, + timeJoined: now.getTime(), + isPrimaryUser: true, + tenantIds: [body.tenantId], + emails: [options.email], + phoneNumbers: [], + thirdParty: [], + webauthn: { + credentialIds: [credentialId], + }, + loginMethods: [ + { + recipeId: "webauthn", + recipeUserId, + tenantIds: [body.tenantId], + verified: true, + timeJoined: now.getTime(), + webauthn: { + credentialIds: [credentialId], + }, + email: options.email, + }, + ], + }; + writeDb("users", recipeUserId, user); + const response = { + status: "OK", + user: user, + recipeUserId, + }; + // @ts-ignore + return response; + } + else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const credentialId = body.credential.id; + const credential = readDb("credentials", credentialId); + if (!credential) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const authenticationVerification = await server_1.verifyAuthenticationResponse({ + expectedChallenge: options.challenge, + expectedOrigin: options.origin, + expectedRPID: options.rpId, + response: body.credential, + credential: { + publicKey: new Uint8Array(credential.publicKey.split(",").map((byte) => parseInt(byte))), + transports: credential.transports, + counter: credential.counter, + id: credential.id, + }, + }); + if (!authenticationVerification.verified) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + const user = readDb("users", credential.userId); + if (!user) { + // @ts-ignore + return { status: "INVALID_CREDENTIALS_ERROR" }; + } + // @ts-ignore + return { + status: "OK", + user, + recipeUserId: user.id, + }; + } + throw new Error(`Unmocked endpoint: ${path}`); + }; + const sendGetRequest = async (path, _body, _userContext) => { + if (path.getAsStringDangerous().includes("/recipe/webauthn/options")) { + const webauthnGeneratedOptionsId = path.getAsStringDangerous().split("/").pop(); + if (!webauthnGeneratedOptionsId) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + const options = readDb("generatedOptions", webauthnGeneratedOptionsId); + if (!options) { + // @ts-ignore + return { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; + } + return Object.assign({ status: "OK" }, options); + } + throw new Error(`Unmocked endpoint: ${path}`); + }; + querier.sendPostRequest = sendPostRequest; + querier.sendGetRequest = sendGetRequest; + return querier; +}; +exports.getMockQuerier = getMockQuerier; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts index b4ac552b5..0d74c5679 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts @@ -6,9 +6,7 @@ export default class BackwardCompatibilityService implements EmailDeliveryInterf private isInServerlessEnv; private appInfo; constructor(appInfo: NormalisedAppinfo, isInServerlessEnv: boolean); - sendEmail: ( - input: TypeWebauthnEmailDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: TypeWebauthnEmailDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js index 8c1477788..90e55746b 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js @@ -5,22 +5,17 @@ async function createAndSendEmailUsingSupertokensService(input) { if (utils_1.isTestEnv()) { return; } - const result = await utils_1.postWithFetch( - "https://api.supertokens.io/0/st/auth/webauthn/recover", - { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, - { - email: input.user.email, - appName: input.appInfo.appName, - recoverAccountURL: input.recoverAccountLink, - }, - { - successLog: `Email sent to ${input.user.email}`, - errorLogHeader: "Error sending webauthn recover account email", - } - ); + const result = await utils_1.postWithFetch("https://api.supertokens.io/0/st/auth/webauthn/recover", { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, { + email: input.user.email, + appName: input.appInfo.appName, + recoverAccountURL: input.recoverAccountLink, + }, { + successLog: `Email sent to ${input.user.email}`, + errorLogHeader: "Error sending webauthn recover account email", + }); if ("error" in result) { throw result.error; } @@ -31,7 +26,8 @@ async function createAndSendEmailUsingSupertokensService(input) { * will be of type `{err: string}` */ throw new Error(result.resp.body.err); - } else { + } + else { throw new Error(`Request failed with status code ${result.resp.status}`); } } @@ -48,8 +44,9 @@ class BackwardCompatibilityService { appInfo: this.appInfo, user: input.user, recoverAccountLink: input.recoverAccountLink, - }).catch((_) => {}); - } else { + }).catch((_) => { }); + } + else { // see https://github.com/supertokens/supertokens-node/pull/135 await createAndSendEmailUsingSupertokensService({ appInfo: this.appInfo, @@ -57,7 +54,8 @@ class BackwardCompatibilityService { recoverAccountLink: input.recoverAccountLink, }); } - } catch (_) {} + } + catch (_) { } }; this.isInServerlessEnv = isInServerlessEnv; this.appInfo = appInfo; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/index.js b/lib/build/recipe/webauthn/emaildelivery/services/index.js index 91700aeaf..d648973c6 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/index.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMTPService = void 0; const smtp_1 = __importDefault(require("./smtp")); diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts index d792d04cb..937bd644e 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts @@ -6,9 +6,7 @@ import { UserContext } from "../../../../../types"; export default class SMTPService implements EmailDeliveryInterface { serviceImpl: ServiceInterface; constructor(config: TypeInput); - sendEmail: ( - input: TypeWebauthnEmailDeliveryInput & { - userContext: UserContext; - } - ) => Promise; + sendEmail: (input: TypeWebauthnEmailDeliveryInput & { + userContext: UserContext; + }) => Promise; } diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js index dedcbe33f..da1ee9c8a 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const nodemailer_1 = require("nodemailer"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); @@ -12,9 +10,7 @@ class SMTPService { constructor(config) { this.sendEmail = async (input) => { let content = await this.serviceImpl.getContent(input); - await this.serviceImpl.sendRawEmail( - Object.assign(Object.assign({}, content), { userContext: input.userContext }) - ); + await this.serviceImpl.sendRawEmail(Object.assign(Object.assign({}, content), { userContext: input.userContext })); }; const transporter = nodemailer_1.createTransport({ host: config.smtpSettings.host, @@ -25,9 +21,7 @@ class SMTPService { }, secure: config.smtpSettings.secure, }); - let builder = new supertokens_js_override_1.default( - serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) - ); + let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from)); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts index 3f85c452a..9538c7ea0 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts @@ -1,7 +1,5 @@ // @ts-nocheck import { TypeWebauthnRecoverAccountEmailDeliveryInput } from "../../../types"; import { GetContentResult } from "../../../../../ingredients/emaildelivery/services/smtp"; -export default function getRecoverAccountEmailContent( - input: TypeWebauthnRecoverAccountEmailDeliveryInput -): GetContentResult; +export default function getRecoverAccountEmailContent(input: TypeWebauthnRecoverAccountEmailDeliveryInput): GetContentResult; export declare function getRecoverAccountEmailHTML(appName: string, email: string, resetLink: string): string; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js index 39185d372..dc2fde1d6 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRecoverAccountEmailHTML = void 0; const supertokens_1 = __importDefault(require("../../../../../supertokens")); diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts index 5eec27f1c..c5c631103 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts @@ -2,10 +2,7 @@ import { TypeWebauthnEmailDeliveryInput } from "../../../../types"; import { Transporter } from "nodemailer"; import { ServiceInterface } from "../../../../../../ingredients/emaildelivery/services/smtp"; -export declare function getServiceImplementation( - transporter: Transporter, - from: { - name: string; - email: string; - } -): ServiceInterface; +export declare function getServiceImplementation(transporter: Transporter, from: { + name: string; + email: string; +}): ServiceInterface; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js index f097d8647..2ded64273 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const recoverAccount_1 = __importDefault(require("../recoverAccount")); @@ -31,7 +29,8 @@ function getServiceImplementation(transporter, from) { subject: input.subject, html: input.body, }); - } else { + } + else { await transporter.sendMail({ from: `${from.name} <${from.email}>`, to: input.toEmail, diff --git a/lib/build/recipe/webauthn/error.d.ts b/lib/build/recipe/webauthn/error.d.ts index 486758b61..d6412505c 100644 --- a/lib/build/recipe/webauthn/error.d.ts +++ b/lib/build/recipe/webauthn/error.d.ts @@ -1,5 +1,8 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); + constructor(options: { + type: "BAD_INPUT_ERROR"; + message: string; + }); } diff --git a/lib/build/recipe/webauthn/error.js b/lib/build/recipe/webauthn/error.js index 7de05e126..31debe4fe 100644 --- a/lib/build/recipe/webauthn/error.js +++ b/lib/build/recipe/webauthn/error.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts index 154a2c439..4d23e7582 100644 --- a/lib/build/recipe/webauthn/index.d.ts +++ b/lib/build/recipe/webauthn/index.d.ts @@ -1,16 +1,7 @@ // @ts-nocheck import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { - RecipeInterface, - APIInterface, - APIOptions, - TypeWebauthnEmailDeliveryInput, - CredentialPayload, - UserVerification, - ResidentKey, - Attestation, -} from "./types"; +import { RecipeInterface, APIInterface, APIOptions, TypeWebauthnEmailDeliveryInput, CredentialPayload, UserVerification, ResidentKey, Attestation, AuthenticationPayload } from "./types"; import RecipeUserId from "../../recipeUserId"; import { SessionContainerInterface } from "../session/types"; import { User } from "../../types"; @@ -18,18 +9,7 @@ import { BaseRequest } from "../../framework"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static registerOptions({ - requireResidentKey, - residentKey, - userVerification, - attestation, - supportedAlgorithmIds, - timeout, - tenantId, - userContext, - ...rest - }: { - requireResidentKey?: boolean; + static registerOptions({ residentKey, userVerification, attestation, supportedAlgorithmIds, timeout, tenantId, userContext, ...rest }: { residentKey?: ResidentKey; userVerification?: UserVerification; attestation?: Attestation; @@ -37,216 +17,144 @@ export default class Wrapper { timeout?: number; tenantId?: string; userContext?: Record; - } & ( - | { - relyingPartyId: string; - relyingPartyName: string; - origin: string; - } - | { - request: BaseRequest; - relyingPartyId?: string; - relyingPartyName?: string; - origin?: string; - } - ) & - ( - | { - email: string; - } - | { - recoverAccountToken: string; - } - )): Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: "public-key"; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: "public-key"; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: ResidentKey; - userVerification: UserVerification; - }; - } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - | { - status: "INVALID_EMAIL_ERROR"; - err: string; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - >; - static signInOptions({ - email, - tenantId, - userVerification, - timeout, - userContext, - ...rest - }: { + } & ({ + relyingPartyId: string; + relyingPartyName: string; + origin: string; + } | { + request: BaseRequest; + relyingPartyId?: string; + relyingPartyName?: string; + origin?: string; + }) & ({ + email: string; + } | { + recoverAccountToken: string; + })): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: ResidentKey; + userVerification: UserVerification; + }; + } | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + }>; + static signInOptions({ email, tenantId, userVerification, timeout, userContext, ...rest }: { email: string; timeout?: number; userVerification?: UserVerification; tenantId?: string; userContext?: Record; - } & ( - | { - relyingPartyId: string; - origin: string; - } - | { - request: BaseRequest; - relyingPartyId?: string; - origin?: string; - } - )): Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: UserVerification; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - >; - static getGeneratedOptions({ - webauthnGeneratedOptionsId, - tenantId, - userContext, - }: { + } & ({ + relyingPartyId: string; + origin: string; + } | { + request: BaseRequest; + relyingPartyId?: string; + origin?: string; + })): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: UserVerification; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + }>; + static getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId, userContext, }: { webauthnGeneratedOptionsId: string; tenantId?: string; userContext?: Record; - }): Promise< - | { - status: "OK"; - id: string; - relyingPartyId: string; - origin: string; - email: string; - timeout: string; - challenge: string; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - >; - static signUp({ - tenantId, - webauthnGeneratedOptionsId, - credential, - session, - userContext, - }: { + }): Promise<{ + status: "OK"; + id: string; + relyingPartyId: string; + origin: string; + email: string; + timeout: string; + challenge: string; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + }>; + static signUp({ tenantId, webauthnGeneratedOptionsId, credential, session, userContext, }: { tenantId?: string; webauthnGeneratedOptionsId: string; credential: CredentialPayload; userContext?: Record; session?: SessionContainerInterface; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } - | { - 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"; - } - >; - static signIn({ - tenantId, - webauthnGeneratedOptionsId, - credential, - session, - userContext, - }: { + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } | { + 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"; + }>; + static signIn({ tenantId, webauthnGeneratedOptionsId, credential, session, userContext, }: { tenantId?: string; webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; session?: SessionContainerInterface; userContext?: Record; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - 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"; - } - >; - static verifyCredentials({ - tenantId, - webauthnGeneratedOptionsId, - credential, - userContext, - }: { + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + 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"; + }>; + static verifyCredentials({ tenantId, webauthnGeneratedOptionsId, credential, userContext, }: { tenantId?: string; webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; userContext?: Record; - }): Promise< - | { - status: "OK"; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + }>; /** * We do not make email optional here cause we want to * allow passing in primaryUserId. If we make email optional, @@ -258,129 +166,77 @@ export default class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static generateRecoverAccountToken({ - tenantId, - userId, - email, - userContext, - }: { + static generateRecoverAccountToken({ tenantId, userId, email, userContext, }: { tenantId?: string; userId: string; email: string; userContext?: Record; - }): Promise< - | { - status: "OK"; - token: string; - } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } - >; - static recoverAccount({ - tenantId, - webauthnGeneratedOptionsId, - token, - credential, - userContext, - }: { + }): Promise<{ + status: "OK"; + token: string; + } | { + status: "UNKNOWN_USER_ID_ERROR"; + }>; + static recoverAccount({ tenantId, webauthnGeneratedOptionsId, token, credential, userContext, }: { tenantId?: string; webauthnGeneratedOptionsId: string; token: string; credential: CredentialPayload; userContext?: Record; - }): Promise< - | { - status: "OK"; - } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - | { - status: "INVALID_AUTHENTICATOR_ERROR"; - failureReason: string; - } - >; - static consumeRecoverAccountToken({ - tenantId, - token, - userContext, - }: { + }): Promise<{ + status: "OK"; + } | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + failureReason: string; + }>; + static consumeRecoverAccountToken({ tenantId, token, userContext, }: { tenantId?: string; token: string; userContext?: Record; - }): Promise< - | { - status: "OK"; - email: string; - userId: string; - } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - >; - static registerCredential({ - recipeUserId, - webauthnGeneratedOptionsId, - credential, - userContext, - }: { + }): Promise<{ + status: "OK"; + email: string; + userId: string; + } | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + }>; + static registerCredential({ recipeUserId, webauthnGeneratedOptionsId, credential, userContext, }: { recipeUserId: RecipeUserId; webauthnGeneratedOptionsId: string; credential: CredentialPayload; userContext?: Record; - }): Promise< - | { - status: "OK"; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } - >; - static createRecoverAccountLink({ - tenantId, - userId, - email, - userContext, - }: { + }): Promise<{ + status: "OK"; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }>; + static createRecoverAccountLink({ tenantId, userId, email, userContext, }: { tenantId?: string; userId: string; email: string; userContext?: Record; - }): Promise< - | { - status: "OK"; - link: string; - } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } - >; - static sendRecoverAccountEmail({ - tenantId, - userId, - email, - userContext, - }: { + }): Promise<{ + status: "OK"; + link: string; + } | { + status: "UNKNOWN_USER_ID_ERROR"; + }>; + static sendRecoverAccountEmail({ tenantId, userId, email, userContext, }: { tenantId?: string; userId: string; email: string; @@ -388,11 +244,9 @@ export default class Wrapper { }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR"; }>; - static sendEmail( - input: TypeWebauthnEmailDeliveryInput & { - userContext?: Record; - } - ): Promise; + static sendEmail(input: TypeWebauthnEmailDeliveryInput & { + userContext?: Record; + }): Promise; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/webauthn/index.js b/lib/build/recipe/webauthn/index.js index cd54ae5f2..d23707847 100644 --- a/lib/build/recipe/webauthn/index.js +++ b/lib/build/recipe/webauthn/index.js @@ -13,22 +13,20 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __rest = - (this && this.__rest) || - function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; - } - return t; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getGeneratedOptions = exports.sendEmail = exports.sendRecoverAccountEmail = exports.createRecoverAccountLink = exports.registerCredential = exports.consumeRecoverAccountToken = exports.recoverAccount = exports.generateRecoverAccountToken = exports.verifyCredentials = exports.signIn = exports.signInOptions = exports.registerOptions = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -41,34 +39,17 @@ const utils_2 = require("../../utils"); const constants_2 = require("./constants"); class Wrapper { static async registerOptions(_a) { - var { - requireResidentKey = constants_2.DEFAULT_REGISTER_OPTIONS_REQUIRE_RESIDENT_KEY, - residentKey = constants_2.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, - userVerification = constants_2.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, - attestation = constants_2.DEFAULT_REGISTER_OPTIONS_ATTESTATION, - supportedAlgorithmIds = constants_2.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, - timeout = constants_2.DEFAULT_REGISTER_OPTIONS_TIMEOUT, - tenantId = constants_1.DEFAULT_TENANT_ID, - userContext, - } = _a, - rest = __rest(_a, [ - "requireResidentKey", - "residentKey", - "userVerification", - "attestation", - "supportedAlgorithmIds", - "timeout", - "tenantId", - "userContext", - ]); + var { residentKey = constants_2.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, userVerification = constants_2.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, attestation = constants_2.DEFAULT_REGISTER_OPTIONS_ATTESTATION, supportedAlgorithmIds = constants_2.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, timeout = constants_2.DEFAULT_REGISTER_OPTIONS_TIMEOUT, tenantId = constants_1.DEFAULT_TENANT_ID, userContext } = _a, rest = __rest(_a, ["residentKey", "userVerification", "attestation", "supportedAlgorithmIds", "timeout", "tenantId", "userContext"]); let emailOrRecoverAccountToken; if ("email" in rest || "recoverAccountToken" in rest) { if ("email" in rest) { emailOrRecoverAccountToken = { email: rest.email }; - } else { + } + else { emailOrRecoverAccountToken = { recoverAccountToken: rest.recoverAccountToken }; } - } else { + } + else { return { status: "INVALID_EMAIL_ERROR", err: "Email is missing" }; } let relyingPartyId; @@ -77,25 +58,26 @@ class Wrapper { if ("request" in rest) { origin = rest.origin || - (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ - request: rest.request, - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); + (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); relyingPartyId = rest.relyingPartyId || - (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ - request: rest.request, - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); relyingPartyName = rest.relyingPartyName || - (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyName({ - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); - } else { + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyName({ + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); + } + else { if (!rest.origin) { throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "Origin missing from the input" }); } @@ -103,58 +85,43 @@ class Wrapper { throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); } if (!rest.relyingPartyName) { - throw new exports.Error({ - type: "BAD_INPUT_ERROR", - message: "RelyingPartyName missing from the input", - }); + throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyName missing from the input" }); } origin = rest.origin; relyingPartyId = rest.relyingPartyId; relyingPartyName = rest.relyingPartyName; } - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions( - Object.assign(Object.assign({}, emailOrRecoverAccountToken), { - requireResidentKey, - residentKey, - userVerification, - supportedAlgorithmIds, - relyingPartyId, - relyingPartyName, - origin, - timeout, - attestation, - tenantId, - userContext: utils_2.getUserContext(userContext), - }) - ); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions(Object.assign(Object.assign({}, emailOrRecoverAccountToken), { residentKey, + userVerification, + supportedAlgorithmIds, + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation, + tenantId, userContext: utils_2.getUserContext(userContext) })); } static async signInOptions(_a) { - var { - email, - tenantId = constants_1.DEFAULT_TENANT_ID, - userVerification = constants_2.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, - timeout = constants_2.DEFAULT_SIGNIN_OPTIONS_TIMEOUT, - userContext, - } = _a, - rest = __rest(_a, ["email", "tenantId", "userVerification", "timeout", "userContext"]); + var { email, tenantId = constants_1.DEFAULT_TENANT_ID, userVerification = constants_2.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, timeout = constants_2.DEFAULT_SIGNIN_OPTIONS_TIMEOUT, userContext } = _a, rest = __rest(_a, ["email", "tenantId", "userVerification", "timeout", "userContext"]); let origin; let relyingPartyId; if ("request" in rest) { relyingPartyId = rest.relyingPartyId || - (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ - request: rest.request, - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); origin = rest.origin || - (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ - request: rest.request, - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); - } else { + (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); + } + else { if (!rest.relyingPartyId) { throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); } @@ -174,20 +141,14 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId = constants_1.DEFAULT_TENANT_ID, userContext }) { + static getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId = constants_1.DEFAULT_TENANT_ID, userContext, }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId, userContext: utils_2.getUserContext(userContext), }); } - static signUp({ - tenantId = constants_1.DEFAULT_TENANT_ID, - webauthnGeneratedOptionsId, - credential, - session, - userContext, - }) { + static signUp({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, credential, session, userContext, }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signUp({ webauthnGeneratedOptionsId, credential, @@ -197,13 +158,7 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static signIn({ - tenantId = constants_1.DEFAULT_TENANT_ID, - webauthnGeneratedOptionsId, - credential, - session, - userContext, - }) { + static signIn({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, credential, session, userContext, }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signIn({ webauthnGeneratedOptionsId, credential, @@ -213,12 +168,7 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static async verifyCredentials({ - tenantId = constants_1.DEFAULT_TENANT_ID, - webauthnGeneratedOptionsId, - credential, - userContext, - }) { + static async verifyCredentials({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, credential, userContext, }) { const resp = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ webauthnGeneratedOptionsId, credential, @@ -241,7 +191,7 @@ class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static generateRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { + static generateRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext, }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.generateRecoverAccountToken({ userId, email, @@ -249,13 +199,7 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static async recoverAccount({ - tenantId = constants_1.DEFAULT_TENANT_ID, - webauthnGeneratedOptionsId, - token, - credential, - userContext, - }) { + static async recoverAccount({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, token, credential, userContext, }) { const consumeResp = await Wrapper.consumeRecoverAccountToken({ tenantId, token, userContext }); if (consumeResp.status !== "OK") { return consumeResp; @@ -276,14 +220,14 @@ class Wrapper { status: result.status, }; } - static consumeRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, token, userContext }) { + static consumeRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, token, userContext, }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeRecoverAccountToken({ token, tenantId, userContext: utils_2.getUserContext(userContext), }); } - static registerCredential({ recipeUserId, webauthnGeneratedOptionsId, credential, userContext }) { + static registerCredential({ recipeUserId, webauthnGeneratedOptionsId, credential, userContext, }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerCredential({ recipeUserId, webauthnGeneratedOptionsId, @@ -291,7 +235,7 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static async createRecoverAccountLink({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { + static async createRecoverAccountLink({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext, }) { let token = await this.generateRecoverAccountToken({ tenantId, userId, email, userContext }); if (token.status === "UNKNOWN_USER_ID_ERROR") { return token; @@ -309,7 +253,7 @@ class Wrapper { }), }; } - static async sendRecoverAccountEmail({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { + static async sendRecoverAccountEmail({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext, }) { const user = await __1.getUser(userId, userContext); if (!user) { return { status: "UNKNOWN_USER_ID_ERROR" }; @@ -339,12 +283,7 @@ class Wrapper { } static async sendEmail(input) { let recipeInstance = recipe_1.default.getInstanceOrThrowError(); - return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail( - Object.assign(Object.assign({}, input), { - tenantId: input.tenantId || constants_1.DEFAULT_TENANT_ID, - userContext: utils_2.getUserContext(input.userContext), - }) - ); + return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail(Object.assign(Object.assign({}, input), { tenantId: input.tenantId || constants_1.DEFAULT_TENANT_ID, userContext: utils_2.getUserContext(input.userContext) })); } } exports.default = Wrapper; diff --git a/lib/build/recipe/webauthn/recipe.d.ts b/lib/build/recipe/webauthn/recipe.d.ts index 1eecfffbe..b82fe1e38 100644 --- a/lib/build/recipe/webauthn/recipe.d.ts +++ b/lib/build/recipe/webauthn/recipe.d.ts @@ -15,28 +15,14 @@ export default class Recipe extends RecipeModule { apiImpl: APIInterface; isInServerlessEnv: boolean; emailDelivery: EmailDeliveryIngredient; - constructor( - recipeId: string, - appInfo: NormalisedAppinfo, - isInServerlessEnv: boolean, - config: TypeInput | undefined, - ingredients: { - emailDelivery: EmailDeliveryIngredient | undefined; - } - ); + constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput | undefined, ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + }); static getInstanceOrThrowError(): Recipe; static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: ( - id: string, - tenantId: string, - req: BaseRequest, - res: BaseResponse, - _path: NormalisedURLPath, - _method: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/webauthn/recipe.js b/lib/build/recipe/webauthn/recipe.js index 1f6b7689d..8d3ccc339 100644 --- a/lib/build/recipe/webauthn/recipe.js +++ b/lib/build/recipe/webauthn/recipe.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -41,7 +39,7 @@ const recipe_1 = __importDefault(require("../multifactorauth/recipe")); const recipe_2 = __importDefault(require("../multitenancy/recipe")); const utils_3 = require("../thirdparty/utils"); const multifactorauth_1 = require("../multifactorauth"); -const core_mock_1 = require("../../core-mock"); +const core_mock_1 = require("./core-mock"); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config, ingredients) { super(recipeId, appInfo); @@ -74,9 +72,7 @@ class Recipe extends recipeModule_1.default { }, { method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default( - constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API - ), + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API), id: constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API, disabled: this.apiImpl.generateRecoverAccountTokenPOST === undefined, }, @@ -107,24 +103,33 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.REGISTER_OPTIONS_API) { return await registerOptions_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.SIGNIN_OPTIONS_API) { + } + else if (id === constants_1.SIGNIN_OPTIONS_API) { return await signInOptions_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.SIGN_UP_API) { + } + else if (id === constants_1.SIGN_UP_API) { return await signup_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.SIGN_IN_API) { + } + else if (id === constants_1.SIGN_IN_API) { return await signin_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API) { + } + else if (id === constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API) { return await generateRecoverAccountToken_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.RECOVER_ACCOUNT_API) { + } + else if (id === constants_1.RECOVER_ACCOUNT_API) { return await recoverAccount_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API) { + } + else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API) { return await emailExists_1.default(this.apiImpl, tenantId, options, userContext); - } else return false; + } + else + return false; }; this.handleError = async (err, _request, _response) => { if (err.fromRecipe === Recipe.RECIPE_ID) { throw err; - } else { + } + else { throw err; } }; @@ -140,9 +145,7 @@ class Recipe extends recipeModule_1.default { const getWebauthnConfig = () => this.config; // const querier = Querier.getNewInstanceOrThrowError(recipeId); const querier = core_mock_1.getMockQuerier(recipeId); - let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(querier, getWebauthnConfig) - ); + let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier, getWebauthnConfig)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -194,9 +197,7 @@ class Recipe extends recipeModule_1.default { return a.timeJoined - b.timeJoined; }); // Then we take the ones that belong to this recipe - const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter( - (lm) => lm.recipeId === Recipe.RECIPE_ID - ); + const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter((lm) => lm.recipeId === Recipe.RECIPE_ID); let result; if (recipeLoginMethodsOrderedByTimeJoinedOldestFirst.length !== 0) { // If there are login methods belonging to this recipe, the factor is set up @@ -223,18 +224,16 @@ class Recipe extends recipeModule_1.default { .map((lm) => lm.email), ]; // We handle moving the session email to the top of the list later - } else { + } + else { // This factor hasn't been set up, we list all emails belonging to the user - if ( - orderedLoginMethodsByTimeJoinedOldestFirst.some( - (lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email) - ) - ) { + if (orderedLoginMethodsByTimeJoinedOldestFirst.some((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email))) { // If there is at least one real email address linked to the user, we only suggest real addresses result = orderedLoginMethodsByTimeJoinedOldestFirst .filter((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email)) .map((lm) => lm.email); - } else { + } + else { // Else we use the fake ones result = orderedLoginMethodsByTimeJoinedOldestFirst .filter((lm) => lm.email !== undefined && utils_3.isFakeEmail(lm.email)) @@ -288,7 +287,8 @@ class Recipe extends recipeModule_1.default { emailDelivery: undefined, }); return Recipe.instance; - } else { + } + else { throw new Error("Webauthn recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/webauthn/recipeImplementation.d.ts b/lib/build/recipe/webauthn/recipeImplementation.d.ts index b6421a667..d1514956f 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.d.ts +++ b/lib/build/recipe/webauthn/recipeImplementation.d.ts @@ -1,7 +1,4 @@ // @ts-nocheck import { RecipeInterface, TypeNormalisedInput } from "./types"; import { Querier } from "../../querier"; -export default function getRecipeInterface( - querier: Querier, - getWebauthnConfig: () => TypeNormalisedInput -): RecipeInterface; +export default function getRecipeInterface(querier: Querier, getWebauthnConfig: () => TypeNormalisedInput): RecipeInterface; diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js index 089433503..3847372d7 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.js +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -1,56 +1,37 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; -var __rest = - (this && this.__rest) || - function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; - } - return t; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../accountlinking/recipe")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -64,37 +45,20 @@ const utils_1 = require("../thirdparty/utils"); function getRecipeInterface(querier, getWebauthnConfig) { return { registerOptions: async function (_a) { - var { - relyingPartyId, - relyingPartyName, - origin, - timeout, - attestation = "none", - tenantId, - userContext, - supportedAlgorithmIds, - } = _a, - rest = __rest(_a, [ - "relyingPartyId", - "relyingPartyName", - "origin", - "timeout", - "attestation", - "tenantId", - "userContext", - "supportedAlgorithmIds", - ]); + var { relyingPartyId, relyingPartyName, origin, timeout, attestation = "none", tenantId, userContext, supportedAlgorithmIds, userVerification, residentKey } = _a, rest = __rest(_a, ["relyingPartyId", "relyingPartyName", "origin", "timeout", "attestation", "tenantId", "userContext", "supportedAlgorithmIds", "userVerification", "residentKey"]); const emailInput = "email" in rest ? rest.email : undefined; const recoverAccountTokenInput = "recoverAccountToken" in rest ? rest.recoverAccountToken : undefined; let email; if (emailInput !== undefined) { email = emailInput; - } else if (recoverAccountTokenInput !== undefined) { + } + else if (recoverAccountTokenInput !== undefined) { // the actual validation of the token will be done during consumeRecoverAccountToken let decoded; try { decoded = await jose.decodeJwt(recoverAccountTokenInput); - } catch (e) { + } + catch (e) { console.error(e); return { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", @@ -120,57 +84,38 @@ function getRecipeInterface(querier, getWebauthnConfig) { let displayName; if (rest.displayName) { displayName = rest.displayName; - } else { + } + else { if (utils_1.isFakeEmail(email)) { displayName = email.split("@")[0]; - } else { + } + else { displayName = email; } } - return await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/options/register` - ), - { - email, - displayName, - relyingPartyName, - relyingPartyId, - origin, - timeout, - attestation, - supportedAlgorithmIds, - }, - userContext - ); + return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/register`), { + email, + displayName, + relyingPartyName, + relyingPartyId, + origin, + timeout, + attestation, + supportedAlgorithmIds, + userVerification, + residentKey, + }, userContext); }, signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext, email }) { // the input user ID can be a recipe or a primary user ID. - return await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/options/signin` - ), - { - email, - relyingPartyId, - origin, - timeout, - }, - userContext - ); + return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/signin`), { + email, + relyingPartyId, + origin, + timeout, + }, userContext); }, - signUp: async function ({ - webauthnGeneratedOptionsId, - credential, - tenantId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }) { + signUp: async function ({ webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) { const response = await this.createNewRecipeUser({ credential, webauthnGeneratedOptionsId, @@ -181,16 +126,14 @@ function getRecipeInterface(querier, getWebauthnConfig) { return response; } let updatedUser = response.user; - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( - { - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - } - ); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }); if (linkResult.status != "OK") { return linkResult; } @@ -201,14 +144,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { recipeUserId: response.recipeUserId, }; }, - signIn: async function ({ - credential, - webauthnGeneratedOptionsId, - tenantId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }) { + signIn: async function ({ credential, webauthnGeneratedOptionsId, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) { const response = await this.verifyCredentials({ credential, webauthnGeneratedOptionsId, @@ -218,9 +154,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { if (response.status !== "OK") { return response; } - const loginMethod = response.user.loginMethods.find( - (lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString() - ); + const loginMethod = response.user.loginMethods.find((lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString()); if (!loginMethod.verified) { await recipe_1.default.getInstance().verifyEmailForRecipeUserIfLinkedAccountsAreVerified({ user: response.user, @@ -238,18 +172,16 @@ function getRecipeInterface(querier, getWebauthnConfig) { // 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 __1.getUser(response.recipeUserId.getAsString(), userContext); + response.user = (await __1.getUser(response.recipeUserId.getAsString(), userContext)); } - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( - { - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - } - ); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }); if (linkResult.status === "LINKING_TO_SESSION_USER_FAILED") { return linkResult; } @@ -257,16 +189,10 @@ function getRecipeInterface(querier, getWebauthnConfig) { return response; }, verifyCredentials: async function ({ credential, webauthnGeneratedOptionsId, tenantId, userContext }) { - const response = await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/signin` - ), - { - credential, - webauthnGeneratedOptionsId, - }, - userContext - ); + const response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/signin`), { + credential, + webauthnGeneratedOptionsId, + }, userContext); if (response.status === "OK") { return { status: "OK", @@ -279,18 +205,10 @@ function getRecipeInterface(querier, getWebauthnConfig) { }; }, createNewRecipeUser: async function (input) { - const resp = await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId - }/recipe/webauthn/signup` - ), - { - webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId, - credential: input.credential, - }, - input.userContext - ); + const resp = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId}/recipe/webauthn/signup`), { + webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId, + credential: input.credential, + }, input.userContext); if (resp.status === "OK") { return { status: "OK", @@ -302,50 +220,26 @@ function getRecipeInterface(querier, getWebauthnConfig) { }, generateRecoverAccountToken: async function ({ userId, email, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. - return await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/user/recover/token` - ), - { - userId, - email, - }, - userContext - ); + return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token`), { + userId, + email, + }, userContext); }, consumeRecoverAccountToken: async function ({ token, tenantId, userContext }) { - return await querier.sendPostRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/user/recover/token/consume` - ), - { - token, - }, - userContext - ); + return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token/consume`), { + token, + }, userContext); }, registerCredential: async function ({ webauthnGeneratedOptionsId, credential, userContext, recipeUserId }) { - return await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/register`), - { - webauthnGeneratedOptionsId, - credential, - }, - userContext - ); + return await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/register`), { + webauthnGeneratedOptionsId, + credential, + }, userContext); }, decodeCredential: async function ({ credential, userContext }) { - const response = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/webauthn/credential/decode`), - { - credential, - }, - userContext - ); + const response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/webauthn/credential/decode`), { + credential, + }, userContext); if (response.status === "OK") { return response; } @@ -354,64 +248,22 @@ function getRecipeInterface(querier, getWebauthnConfig) { }; }, getUserFromRecoverAccountToken: async function ({ token, tenantId, userContext }) { - return await querier.sendGetRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/user/recover/token/${token}` - ), - {}, - userContext - ); + return await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token/${token}`), {}, userContext); }, removeCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { - return await querier.sendDeleteRequest( - new normalisedURLPath_1.default( - `/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}` - ), - {}, - {}, - userContext - ); + return await querier.sendDeleteRequest(new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}`), {}, {}, userContext); }, getCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { - return await querier.sendGetRequest( - new normalisedURLPath_1.default( - `/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}` - ), - {}, - userContext - ); + return await querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}`), {}, userContext); }, listCredentials: async function ({ recipeUserId, userContext }) { - return await querier.sendGetRequest( - new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/list`), - {}, - userContext - ); + return await querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/list`), {}, userContext); }, removeGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { - return await querier.sendDeleteRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` - ), - {}, - {}, - userContext - ); + return await querier.sendDeleteRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/${webauthnGeneratedOptionsId}`), {}, {}, userContext); }, getGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { - return await querier.sendGetRequest( - new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` - ), - {}, - userContext - ); + return await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/${webauthnGeneratedOptionsId}`), {}, userContext); }, }; } diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts index 86bde2d7a..af4631de5 100644 --- a/lib/build/recipe/webauthn/types.d.ts +++ b/lib/build/recipe/webauthn/types.d.ts @@ -2,10 +2,7 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; import { SessionContainerInterface } from "../session/types"; -import { - TypeInput as EmailDeliveryTypeInput, - TypeInputWithService as EmailDeliveryTypeInputWithService, -} from "../../ingredients/emaildelivery/types"; +import { TypeInput as EmailDeliveryTypeInput, TypeInputWithService as EmailDeliveryTypeInputWithService } from "../../ingredients/emaildelivery/types"; import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; @@ -13,15 +10,10 @@ export declare type TypeNormalisedInput = { getRelyingPartyId: TypeNormalisedInputRelyingPartyId; getRelyingPartyName: TypeNormalisedInputRelyingPartyName; getOrigin: TypeNormalisedInputGetOrigin; - getEmailDeliveryConfig: ( - isInServerlessEnv: boolean - ) => EmailDeliveryTypeInputWithService; + getEmailDeliveryConfig: (isInServerlessEnv: boolean) => EmailDeliveryTypeInputWithService; validateEmailAddress: TypeNormalisedInputValidateEmailAddress; override: { - functions: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -39,10 +31,7 @@ export declare type TypeNormalisedInputGetOrigin = (input: { request: BaseRequest; userContext: UserContext; }) => Promise; -export declare type TypeNormalisedInputValidateEmailAddress = ( - email: string, - tenantId: string -) => Promise | string | undefined; +export declare type TypeNormalisedInputValidateEmailAddress = (email: string, tenantId: string) => Promise | string | undefined; export declare type TypeInput = { emailDelivery?: EmailDeliveryTypeInput; getRelyingPartyId?: TypeInputRelyingPartyId; @@ -50,97 +39,85 @@ export declare type TypeInput = { validateEmailAddress?: TypeInputValidateEmailAddress; getOrigin?: TypeInputGetOrigin; override?: { - functions?: ( - originalImplementation: RecipeInterface, - builder?: OverrideableBuilder - ) => RecipeInterface; + functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; -export declare type TypeInputRelyingPartyId = - | string - | ((input: { tenantId: string; request: BaseRequest | undefined; userContext: UserContext }) => Promise); -export declare type TypeInputRelyingPartyName = - | string - | ((input: { tenantId: string; userContext: UserContext }) => Promise); +export declare type TypeInputRelyingPartyId = string | ((input: { + tenantId: string; + request: BaseRequest | undefined; + userContext: UserContext; +}) => Promise); +export declare type TypeInputRelyingPartyName = string | ((input: { + tenantId: string; + userContext: UserContext; +}) => Promise); export declare type TypeInputGetOrigin = (input: { tenantId: string; request: BaseRequest; userContext: UserContext; }) => Promise; -export declare type TypeInputValidateEmailAddress = ( - email: string, - tenantId: string -) => Promise | string | undefined; +export declare type TypeInputValidateEmailAddress = (email: string, tenantId: string) => Promise | string | undefined; declare type Base64URLString = string; export declare type ResidentKey = "required" | "preferred" | "discouraged"; export declare type UserVerification = "required" | "preferred" | "discouraged"; export declare type Attestation = "none" | "indirect" | "direct" | "enterprise"; export declare type RecipeInterface = { - registerOptions( - input: { - relyingPartyId: string; - relyingPartyName: string; - displayName?: string; - origin: string; - requireResidentKey: boolean | undefined; - residentKey: ResidentKey | undefined; - userVerification: UserVerification | undefined; - attestation: Attestation | undefined; - supportedAlgorithmIds: number[] | undefined; - timeout: number | undefined; - tenantId: string; - userContext: UserContext; - } & ( - | { - recoverAccountToken: string; - } - | { - email: string; - } - ) - ): Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: "public-key"; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: Attestation; - pubKeyCredParams: { - alg: number; - type: "public-key"; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: ResidentKey; - userVerification: UserVerification; - }; - } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - | { - status: "INVALID_EMAIL_ERROR"; - err: string; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - >; + registerOptions(input: { + relyingPartyId: string; + relyingPartyName: string; + displayName?: string; + origin: string; + residentKey: ResidentKey | undefined; + userVerification: UserVerification | undefined; + attestation: Attestation | undefined; + supportedAlgorithmIds: number[] | undefined; + timeout: number | undefined; + tenantId: string; + userContext: UserContext; + } & ({ + recoverAccountToken: string; + } | { + email: string; + })): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: Attestation; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: ResidentKey; + userVerification: UserVerification; + }; + } | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + }>; signInOptions(input: { email: string; relyingPartyId: string; @@ -149,124 +126,93 @@ export declare type RecipeInterface = { timeout: number | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: UserVerification; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; + challenge: string; + timeout: number; + userVerification: UserVerification; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + }>; signUp(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } - | { - 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"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } | { + 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"; + }>; signIn(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; session: SessionContainerInterface | undefined; shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - 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"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + 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"; + }>; verifyCredentials(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: AuthenticationPayload; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + }>; createNewRecipeUser(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + }>; /** * We pass in the email as well to this function cause the input userId * may not be associated with an webauthn account. In this case, we @@ -277,156 +223,132 @@ export declare type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - token: string; - } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + token: string; + } | { + status: "UNKNOWN_USER_ID_ERROR"; + }>; consumeRecoverAccountToken(input: { token: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - email: string; - userId: string; - } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + email: string; + userId: string; + } | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + }>; registerCredential(input: { webauthnGeneratedOptionsId: string; - credential: CredentialPayload; + credential: RegistrationPayload; userContext: UserContext; recipeUserId: RecipeUserId; - }): Promise< - | { - status: "OK"; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } - >; + }): Promise<{ + status: "OK"; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }>; decodeCredential(input: { credential: CredentialPayload; userContext: UserContext; - }): Promise< - | { - status: "OK"; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: { - type: string; - challenge: string; - origin: string; - crossOrigin?: boolean; - tokenBinding?: { - id?: string; - status: "present" | "supported" | "not-supported"; - }; - }; - attestationObject: { - fmt: "packed" | "tpm" | "android-key" | "android-safetynet" | "fido-u2f" | "none"; - authData: { - rpIdHash: string; - flags: { - up: boolean; - uv: boolean; - be: boolean; - bs: boolean; - at: boolean; - ed: boolean; - flagsInt: number; - }; - counter: number; - aaguid?: string; - credentialID?: string; - credentialPublicKey?: string; - extensionsData?: unknown; - }; - attStmt: { - sig?: Base64URLString; - x5c?: Base64URLString[]; - response?: Base64URLString; - alg?: number; - ver?: string; - certInfo?: Base64URLString; - pubArea?: Base64URLString; - size: number; - }; - }; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: string; - }; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: { + type: string; + challenge: string; + origin: string; + crossOrigin?: boolean; + tokenBinding?: { + id?: string; + status: "present" | "supported" | "not-supported"; + }; + }; + attestationObject: { + fmt: "packed" | "tpm" | "android-key" | "android-safetynet" | "fido-u2f" | "none"; + authData: { + rpIdHash: string; + flags: { + up: boolean; + uv: boolean; + be: boolean; + bs: boolean; + at: boolean; + ed: boolean; + flagsInt: number; + }; + counter: number; + aaguid?: string; + credentialID?: string; + credentialPublicKey?: string; + extensionsData?: unknown; + }; + attStmt: { + sig?: Base64URLString; + x5c?: Base64URLString[]; + response?: Base64URLString; + alg?: number; + ver?: string; + certInfo?: Base64URLString; + pubArea?: Base64URLString; + size: number; + }; + }; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: string; + }; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + }>; getUserFromRecoverAccountToken(input: { token: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + }>; removeCredential(input: { webauthnCredentialId: string; recipeUserId: RecipeUserId; userContext: UserContext; - }): Promise< - | { - status: "OK"; - } - | { - status: "CREDENTIAL_NOT_FOUND_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + } | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + }>; getCredential(input: { webauthnCredentialId: string; recipeUserId: RecipeUserId; userContext: UserContext; - }): Promise< - | { - status: "OK"; - id: string; - relyingPartyId: string; - recipeUserId: RecipeUserId; - createdAt: number; - } - | { - status: "CREDENTIAL_NOT_FOUND_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + id: string; + relyingPartyId: string; + recipeUserId: RecipeUserId; + createdAt: number; + } | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + }>; listCredentials(input: { recipeUserId: RecipeUserId; userContext: UserContext; @@ -442,32 +364,26 @@ export declare type RecipeInterface = { webauthnGeneratedOptionsId: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + }>; getGeneratedOptions(input: { webauthnGeneratedOptionsId: string; tenantId: string; userContext: UserContext; - }): Promise< - | { - status: "OK"; - id: string; - relyingPartyId: string; - origin: string; - email: string; - timeout: string; - challenge: string; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - >; + }): Promise<{ + status: "OK"; + id: string; + relyingPartyId: string; + origin: string; + email: string; + timeout: string; + challenge: string; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + }>; }; export declare type APIOptions = { recipeImplementation: RecipeInterface; @@ -480,211 +396,179 @@ export declare type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; export declare type APIInterface = { - registerOptionsPOST: - | undefined - | (( - input: { - tenantId: string; - options: APIOptions; - userContext: UserContext; - } & ( - | { - email: string; - } - | { - recoverAccountToken: string; - } - ) - ) => Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: "public-key"; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: string; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: ResidentKey; - userVerification: UserVerification; - }; - } - | GeneralErrorResponse - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - | { - status: "INVALID_EMAIL_ERROR"; - err: string; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - >); - signInOptionsPOST: - | undefined - | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: UserVerification; - } - | GeneralErrorResponse - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - >); - signUpPOST: - | undefined - | ((input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - user: User; - session: SessionContainerInterface; - } - | GeneralErrorResponse - | { - status: "SIGN_UP_NOT_ALLOWED"; - reason: string; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - >); - signInPOST: - | undefined - | ((input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - user: User; - session: SessionContainerInterface; - } - | GeneralErrorResponse - | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - >); - generateRecoverAccountTokenPOST: - | undefined - | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - } - | GeneralErrorResponse - | { - status: "RECOVER_ACCOUNT_NOT_ALLOWED"; - reason: string; - } - >); - recoverAccountPOST: - | undefined - | ((input: { - token: string; - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - user: User; - email: string; - } - | GeneralErrorResponse - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } - | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } - >); - emailExistsGET: - | undefined - | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - exists: boolean; - } - | GeneralErrorResponse - >); + registerOptionsPOST: undefined | ((input: { + tenantId: string; + options: APIOptions; + userContext: UserContext; + } & ({ + email: string; + } | { + recoverAccountToken: string; + })) => Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: ResidentKey; + userVerification: UserVerification; + }; + } | GeneralErrorResponse | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + }>); + signInOptionsPOST: undefined | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; + challenge: string; + timeout: number; + userVerification: UserVerification; + } | GeneralErrorResponse | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + }>); + signUpPOST: undefined | ((input: { + webauthnGeneratedOptionsId: string; + credential: RegistrationPayload; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + user: User; + session: SessionContainerInterface; + } | GeneralErrorResponse | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + }>); + signInPOST: undefined | ((input: { + webauthnGeneratedOptionsId: string; + credential: AuthenticationPayload; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + user: User; + session: SessionContainerInterface; + } | GeneralErrorResponse | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + }>); + generateRecoverAccountTokenPOST: undefined | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + } | GeneralErrorResponse | { + status: "RECOVER_ACCOUNT_NOT_ALLOWED"; + reason: string; + }>); + recoverAccountPOST: undefined | ((input: { + token: string; + webauthnGeneratedOptionsId: string; + credential: RegistrationPayload; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + user: User; + email: string; + } | GeneralErrorResponse | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }>); + registerCredentialPOST: undefined | ((input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + } | GeneralErrorResponse | { + status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; + reason: string; + } | { + status: "INVALID_CREDENTIALS_ERROR"; + } | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }>); + emailExistsGET: undefined | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ + status: "OK"; + exists: boolean; + } | GeneralErrorResponse>); }; export declare type TypeWebauthnRecoverAccountEmailDeliveryInput = { type: "RECOVER_ACCOUNT"; @@ -697,17 +581,39 @@ export declare type TypeWebauthnRecoverAccountEmailDeliveryInput = { tenantId: string; }; export declare type TypeWebauthnEmailDeliveryInput = TypeWebauthnRecoverAccountEmailDeliveryInput; -export declare type CredentialPayload = { +export declare type CredentialPayloadBase = { id: string; rawId: string; + authenticatorAttachment?: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: "public-key"; +}; +export declare type AuthenticatorAssertionResponseJSON = { + clientDataJSON: Base64URLString; + authenticatorData: Base64URLString; + signature: Base64URLString; + userHandle?: Base64URLString; +}; +export declare type AuthenticatorAttestationResponseJSON = { + clientDataJSON: Base64URLString; + attestationObject: Base64URLString; + authenticatorData?: Base64URLString; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + publicKeyAlgorithm?: COSEAlgorithmIdentifier; + publicKey?: Base64URLString; +}; +export declare type AuthenticationPayload = CredentialPayloadBase & { + response: AuthenticatorAssertionResponseJSON; +}; +export declare type RegistrationPayload = CredentialPayloadBase & { + response: AuthenticatorAttestationResponseJSON; +}; +export declare type CredentialPayload = CredentialPayloadBase & { response: { clientDataJSON: string; attestationObject: string; transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; userHandle: string; }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: "public-key"; }; export {}; diff --git a/lib/build/recipe/webauthn/utils.d.ts b/lib/build/recipe/webauthn/utils.d.ts index b37ca89ff..06e8b66db 100644 --- a/lib/build/recipe/webauthn/utils.d.ts +++ b/lib/build/recipe/webauthn/utils.d.ts @@ -3,14 +3,8 @@ import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; import { NormalisedAppinfo, UserContext } from "../../types"; import { BaseRequest } from "../../framework"; -export declare function validateAndNormaliseUserInput( - _: Recipe, - appInfo: NormalisedAppinfo, - config?: TypeInput -): TypeNormalisedInput; -export declare function defaultEmailValidator( - value: any -): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; +export declare function validateAndNormaliseUserInput(_: Recipe, appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function defaultEmailValidator(value: any): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; export declare function getRecoverAccountLink(input: { appInfo: NormalisedAppinfo; token: string; diff --git a/lib/build/recipe/webauthn/utils.js b/lib/build/recipe/webauthn/utils.js index a6ef86349..afa11eca4 100644 --- a/lib/build/recipe/webauthn/utils.js +++ b/lib/build/recipe/webauthn/utils.js @@ -13,43 +13,21 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRecoverAccountLink = exports.defaultEmailValidator = exports.validateAndNormaliseUserInput = void 0; const backwardCompatibility_1 = __importDefault(require("./emaildelivery/services/backwardCompatibility")); function validateAndNormaliseUserInput(_, appInfo, config) { - let getRelyingPartyId = validateAndNormaliseRelyingPartyIdConfig( - appInfo, - config === null || config === void 0 ? void 0 : config.getRelyingPartyId - ); - let getRelyingPartyName = validateAndNormaliseRelyingPartyNameConfig( - appInfo, - config === null || config === void 0 ? void 0 : config.getRelyingPartyName - ); - let getOrigin = validateAndNormaliseGetOriginConfig( - appInfo, - config === null || config === void 0 ? void 0 : config.getOrigin - ); - let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig( - config === null || config === void 0 ? void 0 : config.validateEmailAddress - ); - let override = Object.assign( - { - functions: (originalImplementation) => originalImplementation, - apis: (originalImplementation) => originalImplementation, - }, - config === null || config === void 0 ? void 0 : config.override - ); + let getRelyingPartyId = validateAndNormaliseRelyingPartyIdConfig(appInfo, config === null || config === void 0 ? void 0 : config.getRelyingPartyId); + let getRelyingPartyName = validateAndNormaliseRelyingPartyNameConfig(appInfo, config === null || config === void 0 ? void 0 : config.getRelyingPartyName); + let getOrigin = validateAndNormaliseGetOriginConfig(appInfo, config === null || config === void 0 ? void 0 : config.getOrigin); + let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig(config === null || config === void 0 ? void 0 : config.validateEmailAddress); + let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); function getEmailDeliveryConfig(isInServerlessEnv) { var _a; - let emailService = - (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 - ? void 0 - : _a.service; + let emailService = (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; /** * If the user has not passed even that config, we use the default * createAndSendCustomEmail implementation which calls our supertokens API @@ -57,7 +35,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { if (emailService === undefined) { emailService = new backwardCompatibility_1.default(appInfo, isInServerlessEnv); } - return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { + return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { /** * if we do * let emailDelivery = { @@ -70,8 +48,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { * set service at the end */ // todo implemenet this - service: emailService, - }); + service: emailService }); } return { override, @@ -87,12 +64,12 @@ function validateAndNormaliseRelyingPartyIdConfig(normalisedAppinfo, relyingPart return (props) => { if (typeof relyingPartyIdConfig === "string") { return Promise.resolve(relyingPartyIdConfig); - } else if (typeof relyingPartyIdConfig === "function") { + } + else if (typeof relyingPartyIdConfig === "function") { return relyingPartyIdConfig(props); - } else { - const urlString = normalisedAppinfo - .getOrigin({ request: props.request, userContext: props.userContext }) - .getAsStringDangerous(); + } + else { + const urlString = normalisedAppinfo.apiDomain.getAsStringDangerous(); // should let this throw if the url is invalid const url = new URL(urlString); const hostname = url.hostname; @@ -104,9 +81,11 @@ function validateAndNormaliseRelyingPartyNameConfig(normalisedAppInfo, relyingPa return (props) => { if (typeof relyingPartyNameConfig === "string") { return Promise.resolve(relyingPartyNameConfig); - } else if (typeof relyingPartyNameConfig === "function") { + } + else if (typeof relyingPartyNameConfig === "function") { return relyingPartyNameConfig(props); - } else { + } + else { return Promise.resolve(normalisedAppInfo.appName); } }; @@ -115,12 +94,11 @@ function validateAndNormaliseGetOriginConfig(normalisedAppinfo, getOriginConfig) return (props) => { if (typeof getOriginConfig === "function") { return getOriginConfig(props); - } else { - return Promise.resolve( - normalisedAppinfo - .getOrigin({ request: props.request, userContext: props.userContext }) - .getAsStringDangerous() - ); + } + else { + return Promise.resolve(normalisedAppinfo + .getOrigin({ request: props.request, userContext: props.userContext }) + .getAsStringDangerous()); } }; } @@ -128,7 +106,8 @@ function validateAndNormaliseValidateEmailAddressConfig(validateEmailAddressConf return (email, tenantId) => { if (typeof validateEmailAddressConfig === "function") { return validateEmailAddressConfig(email, tenantId); - } else { + } + else { return defaultEmailValidator(email); } }; @@ -140,29 +119,23 @@ async function defaultEmailValidator(value) { if (typeof value !== "string") { return "Development bug: Please make sure the email field yields a string"; } - if ( - value.match( - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - ) === null - ) { + if (value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) === null) { return "Email is invalid"; } return undefined; } exports.defaultEmailValidator = defaultEmailValidator; function getRecoverAccountLink(input) { - return ( - input.appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() + + return (input.appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + input.appInfo.websiteBasePath.getAsStringDangerous() + "/recover-account?token=" + input.token + "&tenantId=" + - input.tenantId - ); + input.tenantId); } exports.getRecoverAccountLink = getRecoverAccountLink; diff --git a/lib/build/recipeModule.d.ts b/lib/build/recipeModule.d.ts index e277192a9..eea95cbb4 100644 --- a/lib/build/recipeModule.d.ts +++ b/lib/build/recipeModule.d.ts @@ -9,34 +9,14 @@ export default abstract class RecipeModule { constructor(recipeId: string, appInfo: NormalisedAppinfo); getRecipeId: () => string; getAppInfo: () => NormalisedAppinfo; - returnAPIIdIfCanHandleRequest: ( - path: NormalisedURLPath, - method: HTTPMethod, - userContext: UserContext - ) => Promise< - | { - id: string; - tenantId: string; - exactMatch: boolean; - } - | undefined - >; + returnAPIIdIfCanHandleRequest: (path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext) => Promise<{ + id: string; + tenantId: string; + exactMatch: boolean; + } | undefined>; abstract getAPIsHandled(): APIHandled[]; - abstract handleAPIRequest( - id: string, - tenantId: string, - req: BaseRequest, - response: BaseResponse, - path: NormalisedURLPath, - method: HTTPMethod, - userContext: UserContext - ): Promise; - abstract handleError( - error: STError, - request: BaseRequest, - response: BaseResponse, - userContext: UserContext - ): Promise; + abstract handleAPIRequest(id: string, tenantId: string, req: BaseRequest, response: BaseResponse, path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext): Promise; + abstract handleError(error: STError, request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; abstract getAllCORSHeaders(): string[]; abstract isErrorFromThisRecipe(err: any): err is STError; } diff --git a/lib/build/recipeModule.js b/lib/build/recipeModule.js index 75c3219ae..1f3dfb145 100644 --- a/lib/build/recipeModule.js +++ b/lib/build/recipeModule.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); const constants_1 = require("./recipe/multitenancy/constants"); @@ -55,12 +53,11 @@ class RecipeModule { userContext, }); return { id: currAPI.id, tenantId: finalTenantId, exactMatch: true }; - } else if ( - remainingPath !== undefined && + } + else if (remainingPath !== undefined && this.appInfo.apiBasePath .appendPath(currAPI.pathWithoutApiBasePath) - .equals(this.appInfo.apiBasePath.appendPath(remainingPath)) - ) { + .equals(this.appInfo.apiBasePath.appendPath(remainingPath))) { const finalTenantId = await mtRecipe.recipeInterfaceImpl.getTenantId({ tenantIdFromFrontend: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, userContext, diff --git a/lib/build/supertokens.d.ts b/lib/build/supertokens.d.ts index e4f7f6777..e4dfeb78b 100644 --- a/lib/build/supertokens.d.ts +++ b/lib/build/supertokens.d.ts @@ -16,53 +16,34 @@ export default class SuperTokens { static init(config: TypeInput): void; static reset(): void; static getInstanceOrThrowError(): SuperTokens; - handleAPI: ( - matchedRecipe: RecipeModule, - id: string, - tenantId: string, - request: BaseRequest, - response: BaseResponse, - path: NormalisedURLPath, - method: HTTPMethod, - userContext: UserContext - ) => Promise; + handleAPI: (matchedRecipe: RecipeModule, id: string, tenantId: string, request: BaseRequest, response: BaseResponse, path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext) => Promise; getAllCORSHeaders: () => string[]; - getUserCount: ( - includeRecipeIds: string[] | undefined, - tenantId: string | undefined, - userContext: UserContext - ) => Promise; + getUserCount: (includeRecipeIds: string[] | undefined, tenantId: string | undefined, userContext: UserContext) => Promise; createUserIdMapping: (input: { superTokensUserId: string; externalUserId: string; externalUserIdInfo?: string; force?: boolean; userContext: UserContext; - }) => Promise< - | { - status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"; - } - | { - status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR"; - doesSuperTokensUserIdExist: boolean; - doesExternalUserIdExist: boolean; - } - >; + }) => Promise<{ + status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"; + } | { + status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR"; + doesSuperTokensUserIdExist: boolean; + doesExternalUserIdExist: boolean; + }>; getUserIdMapping: (input: { userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - superTokensUserId: string; - externalUserId: string; - externalUserIdInfo: string | undefined; - } - | { - status: "UNKNOWN_MAPPING_ERROR"; - } - >; + }) => Promise<{ + status: "OK"; + superTokensUserId: string; + externalUserId: string; + externalUserIdInfo: string | undefined; + } | { + status: "UNKNOWN_MAPPING_ERROR"; + }>; deleteUserIdMapping: (input: { userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"; diff --git a/lib/build/supertokens.js b/lib/build/supertokens.js index db7ceb9ef..8a5101416 100644 --- a/lib/build/supertokens.js +++ b/lib/build/supertokens.js @@ -13,11 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("./utils"); const querier_1 = require("./querier"); @@ -50,24 +48,16 @@ class SuperTokens { let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); let apiVersion = await querier.getAPIVersion(userContext); if (utils_1.maxVersion(apiVersion, "2.7") === "2.7") { - throw new Error( - "Please use core version >= 3.5 to call this function. Otherwise, you can call .getUserCount() instead (for example, EmailPassword.getUserCount())" - ); + throw new Error("Please use core version >= 3.5 to call this function. Otherwise, you can call .getUserCount() instead (for example, EmailPassword.getUserCount())"); } let includeRecipeIdsStr = undefined; if (includeRecipeIds !== undefined) { includeRecipeIdsStr = includeRecipeIds.join(","); } - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default( - `/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/users/count` - ), - { - includeRecipeIds: includeRecipeIdsStr, - includeAllTenants: tenantId === undefined, - }, - userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/users/count`), { + includeRecipeIds: includeRecipeIdsStr, + includeAllTenants: tenantId === undefined, + }, userContext); return Number(response.count); }; this.createUserIdMapping = async function (input) { @@ -75,17 +65,14 @@ class SuperTokens { let cdiVersion = await querier.getAPIVersion(input.userContext); if (utils_1.maxVersion("2.15", cdiVersion) === cdiVersion) { // create userId mapping is only available >= CDI 2.15 - return await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/userid/map"), - { - superTokensUserId: input.superTokensUserId, - externalUserId: input.externalUserId, - externalUserIdInfo: input.externalUserIdInfo, - force: input.force, - }, - input.userContext - ); - } else { + return await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/userid/map"), { + superTokensUserId: input.superTokensUserId, + externalUserId: input.externalUserId, + externalUserIdInfo: input.externalUserIdInfo, + force: input.force, + }, input.userContext); + } + else { throw new global.Error("Please upgrade the SuperTokens core to >= 3.15.0"); } }; @@ -94,16 +81,13 @@ class SuperTokens { let cdiVersion = await querier.getAPIVersion(input.userContext); if (utils_1.maxVersion("2.15", cdiVersion) === cdiVersion) { // create userId mapping is only available >= CDI 2.15 - let response = await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/userid/map"), - { - userId: input.userId, - userIdType: input.userIdType, - }, - input.userContext - ); + let response = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/userid/map"), { + userId: input.userId, + userIdType: input.userIdType, + }, input.userContext); return response; - } else { + } + else { throw new global.Error("Please upgrade the SuperTokens core to >= 3.15.0"); } }; @@ -111,16 +95,13 @@ class SuperTokens { let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); let cdiVersion = await querier.getAPIVersion(input.userContext); if (utils_1.maxVersion("2.15", cdiVersion) === cdiVersion) { - return await querier.sendPostRequest( - new normalisedURLPath_1.default("/recipe/userid/map/remove"), - { - userId: input.userId, - userIdType: input.userIdType, - force: input.force, - }, - input.userContext - ); - } else { + return await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/userid/map/remove"), { + userId: input.userId, + userIdType: input.userIdType, + force: input.force, + }, input.userContext); + } + else { throw new global.Error("Please upgrade the SuperTokens core to >= 3.15.0"); } }; @@ -128,32 +109,24 @@ class SuperTokens { let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); let cdiVersion = await querier.getAPIVersion(input.userContext); if (utils_1.maxVersion("2.15", cdiVersion) === cdiVersion) { - return await querier.sendPutRequest( - new normalisedURLPath_1.default("/recipe/userid/external-user-id-info"), - { - userId: input.userId, - userIdType: input.userIdType, - externalUserIdInfo: input.externalUserIdInfo, - }, - {}, - input.userContext - ); - } else { + return await querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/userid/external-user-id-info"), { + userId: input.userId, + userIdType: input.userIdType, + externalUserIdInfo: input.externalUserIdInfo, + }, {}, input.userContext); + } + else { throw new global.Error("Please upgrade the SuperTokens core to >= 3.15.0"); } }; this.middleware = async (request, response, userContext) => { logger_1.logDebugMessage("middleware: Started"); - let path = this.appInfo.apiGatewayPath.appendPath( - new normalisedURLPath_1.default(request.getOriginalURL()) - ); + let path = this.appInfo.apiGatewayPath.appendPath(new normalisedURLPath_1.default(request.getOriginalURL())); let method = utils_1.normaliseHttpMethod(request.getMethod()); // if the prefix of the URL doesn't match the base path, we skip if (!path.startsWith(this.appInfo.apiBasePath)) { - logger_1.logDebugMessage( - "middleware: Not handling because request path did not start with config path. Request path: " + - path.getAsStringDangerous() - ); + logger_1.logDebugMessage("middleware: Not handling because request path did not start with config path. Request path: " + + path.getAsStringDangerous()); return false; } let requestRID = utils_1.getRidFromHeader(request); @@ -165,14 +138,12 @@ class SuperTokens { async function handleWithoutRid(recipeModules) { let bestMatch = undefined; for (let i = 0; i < recipeModules.length; i++) { - logger_1.logDebugMessage( - "middleware: Checking recipe ID for match: " + - recipeModules[i].getRecipeId() + - " with path: " + - path.getAsStringDangerous() + - " and method: " + - method - ); + logger_1.logDebugMessage("middleware: Checking recipe ID for match: " + + recipeModules[i].getRecipeId() + + " with path: " + + path.getAsStringDangerous() + + " and method: " + + method); let idResult = await recipeModules[i].returnAPIIdIfCanHandleRequest(path, method, userContext); if (idResult !== undefined) { // The request path may or may not include the tenantId. `returnAPIIdIfCanHandleRequest` handles both cases. @@ -191,19 +162,9 @@ class SuperTokens { if (bestMatch !== undefined) { const { idResult, recipeModule } = bestMatch; logger_1.logDebugMessage("middleware: Request being handled by recipe. ID is: " + idResult.id); - let requestHandled = await recipeModule.handleAPIRequest( - idResult.id, - idResult.tenantId, - request, - response, - path, - method, - userContext - ); + let requestHandled = await recipeModule.handleAPIRequest(idResult.id, idResult.tenantId, request, response, path, method, userContext); if (!requestHandled) { - logger_1.logDebugMessage( - "middleware: Not handled because API returned requestHandled as false" - ); + logger_1.logDebugMessage("middleware: Not handled because API returned requestHandled as false"); return false; } logger_1.logDebugMessage("middleware: Ended"); @@ -219,23 +180,19 @@ class SuperTokens { let matchedRecipe = []; // we loop through all recipe modules to find the one with the matching rId for (let i = 0; i < this.recipeModules.length; i++) { - logger_1.logDebugMessage( - "middleware: Checking recipe ID for match: " + this.recipeModules[i].getRecipeId() - ); + logger_1.logDebugMessage("middleware: Checking recipe ID for match: " + this.recipeModules[i].getRecipeId()); if (this.recipeModules[i].getRecipeId() === requestRID) { matchedRecipe.push(this.recipeModules[i]); - } else if (requestRID === "thirdpartyemailpassword") { - if ( - this.recipeModules[i].getRecipeId() === "thirdparty" || - this.recipeModules[i].getRecipeId() === "emailpassword" - ) { + } + else if (requestRID === "thirdpartyemailpassword") { + if (this.recipeModules[i].getRecipeId() === "thirdparty" || + this.recipeModules[i].getRecipeId() === "emailpassword") { matchedRecipe.push(this.recipeModules[i]); } - } else if (requestRID === "thirdpartypasswordless") { - if ( - this.recipeModules[i].getRecipeId() === "thirdparty" || - this.recipeModules[i].getRecipeId() === "passwordless" - ) { + } + else if (requestRID === "thirdpartypasswordless") { + if (this.recipeModules[i].getRecipeId() === "thirdparty" || + this.recipeModules[i].getRecipeId() === "passwordless") { matchedRecipe.push(this.recipeModules[i]); } } @@ -254,18 +211,15 @@ class SuperTokens { // the path and methods of the APIs exposed via those recipes is unique. let currIdResult = await matchedRecipe[i].returnAPIIdIfCanHandleRequest(path, method, userContext); if (currIdResult !== undefined) { - if ( - idResult === undefined || + if (idResult === undefined || // The request path may or may not include the tenantId. `returnAPIIdIfCanHandleRequest` handles both cases. // If one recipe matches with tenantId and another matches exactly, we prefer the exact match. - (currIdResult.exactMatch === true && idResult.exactMatch === false) - ) { + (currIdResult.exactMatch === true && idResult.exactMatch === false)) { finalMatchedRecipe = matchedRecipe[i]; idResult = currIdResult; - } else { - throw new Error( - "Two recipes have matched the same API path and method! This is a bug in the SDK. Please contact support." - ); + } + else { + throw new Error("Two recipes have matched the same API path and method! This is a bug in the SDK. Please contact support."); } } } @@ -274,22 +228,15 @@ class SuperTokens { } logger_1.logDebugMessage("middleware: Request being handled by recipe. ID is: " + idResult.id); // give task to the matched recipe - let requestHandled = await finalMatchedRecipe.handleAPIRequest( - idResult.id, - idResult.tenantId, - request, - response, - path, - method, - userContext - ); + let requestHandled = await finalMatchedRecipe.handleAPIRequest(idResult.id, idResult.tenantId, request, response, path, method, userContext); if (!requestHandled) { logger_1.logDebugMessage("middleware: Not handled because API returned requestHandled as false"); return false; } logger_1.logDebugMessage("middleware: Ended"); return true; - } else { + } + else { return handleWithoutRid(this.recipeModules); } }; @@ -302,13 +249,9 @@ class SuperTokens { return utils_1.sendNon200ResponseWithMessage(response, err.message, 400); } for (let i = 0; i < this.recipeModules.length; i++) { - logger_1.logDebugMessage( - "errorHandler: Checking recipe for match: " + this.recipeModules[i].getRecipeId() - ); + logger_1.logDebugMessage("errorHandler: Checking recipe for match: " + this.recipeModules[i].getRecipeId()); if (this.recipeModules[i].isErrorFromThisRecipe(err)) { - logger_1.logDebugMessage( - "errorHandler: Matched with recipeID: " + this.recipeModules[i].getRecipeId() - ); + logger_1.logDebugMessage("errorHandler: Matched with recipeID: " + this.recipeModules[i].getRecipeId()); return await this.recipeModules[i].handleError(err, request, response, userContext); } } @@ -334,35 +277,23 @@ class SuperTokens { logger_1.enableDebugLogs(); } logger_1.logDebugMessage("Started SuperTokens with debug logging (supertokens.init called)"); - const originToPrint = - config.appInfo.origin === undefined - ? undefined - : typeof config.appInfo.origin === "string" + const originToPrint = config.appInfo.origin === undefined + ? undefined + : typeof config.appInfo.origin === "string" ? config.appInfo.origin : "function"; - logger_1.logDebugMessage( - "appInfo: " + JSON.stringify(Object.assign(Object.assign({}, config.appInfo), { origin: originToPrint })) - ); + logger_1.logDebugMessage("appInfo: " + + JSON.stringify(Object.assign(Object.assign({}, config.appInfo), { origin: originToPrint }))); this.framework = config.framework !== undefined ? config.framework : "express"; logger_1.logDebugMessage("framework: " + this.framework); this.appInfo = utils_1.normaliseInputAppInfoOrThrowError(config.appInfo); this.supertokens = config.supertokens; - querier_1.Querier.init( - (_a = config.supertokens) === null || _a === void 0 - ? void 0 - : _a.connectionURI - .split(";") - .filter((h) => h !== "") - .map((h) => { - return { - domain: new normalisedURLDomain_1.default(h.trim()), - basePath: new normalisedURLPath_1.default(h.trim()), - }; - }), - (_b = config.supertokens) === null || _b === void 0 ? void 0 : _b.apiKey, - (_c = config.supertokens) === null || _c === void 0 ? void 0 : _c.networkInterceptor, - (_d = config.supertokens) === null || _d === void 0 ? void 0 : _d.disableCoreCallCache - ); + querier_1.Querier.init((_a = config.supertokens) === null || _a === void 0 ? void 0 : _a.connectionURI.split(";").filter((h) => h !== "").map((h) => { + return { + domain: new normalisedURLDomain_1.default(h.trim()), + basePath: new normalisedURLPath_1.default(h.trim()), + }; + }), (_b = config.supertokens) === null || _b === void 0 ? void 0 : _b.apiKey, (_c = config.supertokens) === null || _c === void 0 ? void 0 : _c.networkInterceptor, (_d = config.supertokens) === null || _d === void 0 ? void 0 : _d.disableCoreCallCache); if (config.recipeList === undefined || config.recipeList.length === 0) { throw new Error("Please provide at least one recipe to the supertokens.init function call"); } @@ -393,17 +324,23 @@ class SuperTokens { const recipeModule = func(this.appInfo, this.isInServerlessEnv); if (recipeModule.getRecipeId() === MultitenancyRecipe.RECIPE_ID) { multitenancyFound = true; - } else if (recipeModule.getRecipeId() === UserMetadataRecipe.RECIPE_ID) { + } + else if (recipeModule.getRecipeId() === UserMetadataRecipe.RECIPE_ID) { userMetadataFound = true; - } else if (recipeModule.getRecipeId() === MultiFactorAuthRecipe.RECIPE_ID) { + } + else if (recipeModule.getRecipeId() === MultiFactorAuthRecipe.RECIPE_ID) { multiFactorAuthFound = true; - } else if (recipeModule.getRecipeId() === TotpRecipe.RECIPE_ID) { + } + else if (recipeModule.getRecipeId() === TotpRecipe.RECIPE_ID) { totpFound = true; - } else if (recipeModule.getRecipeId() === OAuth2ProviderRecipe.RECIPE_ID) { + } + else if (recipeModule.getRecipeId() === OAuth2ProviderRecipe.RECIPE_ID) { oauth2Found = true; - } else if (recipeModule.getRecipeId() === OpenIdRecipe.RECIPE_ID) { + } + else if (recipeModule.getRecipeId() === OpenIdRecipe.RECIPE_ID) { openIdFound = true; - } else if (recipeModule.getRecipeId() === jwtRecipe.RECIPE_ID) { + } + else if (recipeModule.getRecipeId() === jwtRecipe.RECIPE_ID) { jwtFound = true; } return recipeModule; diff --git a/lib/build/thirdpartyUtils.d.ts b/lib/build/thirdpartyUtils.d.ts index 84517d5e2..8db4b25a7 100644 --- a/lib/build/thirdpartyUtils.d.ts +++ b/lib/build/thirdpartyUtils.d.ts @@ -1,34 +1,22 @@ // @ts-nocheck import * as jose from "jose"; -export declare function doGetRequest( - url: string, - queryParams?: { - [key: string]: string; - }, - headers?: { - [key: string]: string; - } -): Promise<{ +export declare function doGetRequest(url: string, queryParams?: { + [key: string]: string; +}, headers?: { + [key: string]: string; +}): Promise<{ jsonResponse: Record | undefined; status: number; stringResponse: string; }>; -export declare function doPostRequest( - url: string, - params: { - [key: string]: any; - }, - headers?: { - [key: string]: string; - } -): Promise<{ +export declare function doPostRequest(url: string, params: { + [key: string]: any; +}, headers?: { + [key: string]: string; +}): Promise<{ jsonResponse: Record | undefined; status: number; stringResponse: string; }>; -export declare function verifyIdTokenFromJWKSEndpointAndGetPayload( - idToken: string, - jwks: jose.JWTVerifyGetKey, - otherOptions: jose.JWTVerifyOptions -): Promise; +export declare function verifyIdTokenFromJWKSEndpointAndGetPayload(idToken: string, jwks: jose.JWTVerifyGetKey, otherOptions: jose.JWTVerifyOptions): Promise; export declare function getOIDCDiscoveryInfo(issuer: string): Promise; diff --git a/lib/build/thirdpartyUtils.js b/lib/build/thirdpartyUtils.js index 83cc3d405..da6ca5c0c 100644 --- a/lib/build/thirdpartyUtils.js +++ b/lib/build/thirdpartyUtils.js @@ -1,45 +1,26 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.getOIDCDiscoveryInfo = exports.verifyIdTokenFromJWKSEndpointAndGetPayload = exports.doPostRequest = exports.doGetRequest = void 0; const jose = __importStar(require("jose")); @@ -48,9 +29,7 @@ const utils_1 = require("./utils"); const normalisedURLDomain_1 = __importDefault(require("./normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); async function doGetRequest(url, queryParams, headers) { - logger_1.logDebugMessage( - `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` - ); + logger_1.logDebugMessage(`GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}`); if ((headers === null || headers === void 0 ? void 0 : headers["Accept"]) === undefined) { headers = Object.assign(Object.assign({}, headers), { Accept: "application/json" }); } @@ -78,9 +57,7 @@ async function doPostRequest(url, params, headers) { } headers["Content-Type"] = "application/x-www-form-urlencoded"; headers["Accept"] = "application/json"; - logger_1.logDebugMessage( - `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` - ); + logger_1.logDebugMessage(`POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}`); const body = new URLSearchParams(params).toString(); let response = await utils_1.doFetch(url, { method: "POST", @@ -113,13 +90,9 @@ async function getOIDCDiscoveryInfo(issuer) { if (oidcInfoMap[issuer] !== undefined) { return oidcInfoMap[issuer]; } - const oidcInfo = await doGetRequest( - normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous() - ); + const oidcInfo = await doGetRequest(normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous()); if (oidcInfo.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}` - ); + logger_1.logDebugMessage(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); throw new Error(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); } oidcInfoMap[issuer] = oidcInfo.jsonResponse; diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index c1abe0c67..138add514 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -17,7 +17,10 @@ export declare type UserContext = Branded, "UserContext">; export declare type AppInfo = { appName: string; websiteDomain?: string; - origin?: string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); + origin?: string | ((input: { + request: BaseRequest | undefined; + userContext: UserContext; + }) => string); websiteBasePath?: string; apiDomain: string; apiBasePath?: string; @@ -25,10 +28,16 @@ export declare type AppInfo = { }; export declare type NormalisedAppinfo = { appName: string; - getOrigin: (input: { request: BaseRequest | undefined; userContext: UserContext }) => NormalisedURLDomain; + getOrigin: (input: { + request: BaseRequest | undefined; + userContext: UserContext; + }) => NormalisedURLDomain; apiDomain: NormalisedURLDomain; topLevelAPIDomain: string; - getTopLevelWebsiteDomain: (input: { request: BaseRequest | undefined; userContext: UserContext }) => string; + getTopLevelWebsiteDomain: (input: { + request: BaseRequest | undefined; + userContext: UserContext; + }) => string; apiBasePath: NormalisedURLPath; apiGatewayPath: NormalisedURLPath; websiteBasePath: NormalisedURLPath; @@ -94,7 +103,10 @@ export declare type User = { verified: boolean; hasSameEmailAs: (email: string | undefined) => boolean; hasSamePhoneNumberAs: (phoneNumber: string | undefined) => boolean; - hasSameThirdPartyInfoAs: (thirdParty?: { id: string; userId: string }) => boolean; + hasSameThirdPartyInfoAs: (thirdParty?: { + id: string; + userId: string; + }) => boolean; toJson: () => any; })[]; toJson: () => any; diff --git a/lib/build/user.d.ts b/lib/build/user.d.ts index 9bc4835a5..176114d1d 100644 --- a/lib/build/user.d.ts +++ b/lib/build/user.d.ts @@ -15,7 +15,10 @@ export declare class LoginMethod implements RecipeLevelUser { constructor(loginMethod: UserWithoutHelperFunctions["loginMethods"][number]); hasSameEmailAs(email: string | undefined): boolean; hasSamePhoneNumberAs(phoneNumber: string | undefined): boolean; - hasSameThirdPartyInfoAs(thirdParty?: { id: string; userId: string }): boolean; + hasSameThirdPartyInfoAs(thirdParty?: { + id: string; + userId: string; + }): boolean; toJson(): JSONObject; } export declare class User implements UserType { diff --git a/lib/build/user.js b/lib/build/user.js index fe045dde2..57e0ef149 100644 --- a/lib/build/user.js +++ b/lib/build/user.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.User = exports.LoginMethod = void 0; const recipeUserId_1 = __importDefault(require("./recipeUserId")); @@ -37,7 +35,8 @@ class LoginMethod { // this means that the phone number is not valid according to the E.164 standard. // but we still just trim it. phoneNumber = phoneNumber.trim(); - } else { + } + else { phoneNumber = parsedPhoneNumber.format("E.164"); } return this.phoneNumber !== undefined && this.phoneNumber === phoneNumber; @@ -48,11 +47,9 @@ class LoginMethod { } thirdParty.id = thirdParty.id.trim(); thirdParty.userId = thirdParty.userId.trim(); - return ( - this.thirdParty !== undefined && + return (this.thirdParty !== undefined && this.thirdParty.id === thirdParty.id && - this.thirdParty.userId === thirdParty.userId - ); + this.thirdParty.userId === thirdParty.userId); } toJson() { return { diff --git a/lib/build/utils.d.ts b/lib/build/utils.d.ts index b681642e1..fafd36454 100644 --- a/lib/build/utils.d.ts +++ b/lib/build/utils.d.ts @@ -13,15 +13,11 @@ export declare function sendNon200Response(res: BaseResponse, statusCode: number export declare function send200Response(res: BaseResponse, responseJson: any): void; export declare function isAnIpAddress(ipaddress: string): boolean; export declare function getNormalisedShouldTryLinkingWithSessionUserFlag(req: BaseRequest, body: any): any; -export declare function getBackwardsCompatibleUserInfo( - req: BaseRequest, - result: { - user: User; - session: SessionContainer; - createdNewRecipeUser?: boolean; - }, - userContext: UserContext -): JSONObject; +export declare function getBackwardsCompatibleUserInfo(req: BaseRequest, result: { + user: User; + session: SessionContainer; + createdNewRecipeUser?: boolean; +}, userContext: UserContext): JSONObject; export declare function getLatestFDIVersionFromFDIList(fdiHeaderValue: string): string; export declare function hasGreaterThanEqualToFDI(req: BaseRequest, version: string): boolean; export declare function getRidFromHeader(req: BaseRequest): string | undefined; @@ -29,43 +25,26 @@ export declare function frontendHasInterceptor(req: BaseRequest): boolean; export declare function humaniseMilliseconds(ms: number): string; export declare function makeDefaultUserContextFromAPI(request: BaseRequest): UserContext; export declare function getUserContext(inputUserContext?: Record): UserContext; -export declare function setRequestInUserContextIfNotDefined( - userContext: UserContext | undefined, - request: BaseRequest -): UserContext; +export declare function setRequestInUserContextIfNotDefined(userContext: UserContext | undefined, request: BaseRequest): UserContext; export declare function getTopLevelDomainForSameSiteResolution(url: string): string; export declare function getFromObjectCaseInsensitive(key: string, object: Record): T | undefined; -export declare function postWithFetch( - url: string, - headers: Record, - body: any, - { - successLog, - errorLogHeader, - }: { - successLog: string; - errorLogHeader: string; - } -): Promise< - | { - resp: { - status: number; - body: any; - }; - } - | { - error: any; - } ->; +export declare function postWithFetch(url: string, headers: Record, body: any, { successLog, errorLogHeader }: { + successLog: string; + errorLogHeader: string; +}): Promise<{ + resp: { + status: number; + body: any; + }; +} | { + error: any; +}>; export declare function normaliseEmail(email: string): string; export declare function toCamelCase(str: string): string; export declare function toSnakeCase(str: string): string; -export declare function transformObjectKeys( - obj: { - [key: string]: any; - }, - caseType: "snake-case" | "camelCase" -): T; +export declare function transformObjectKeys(obj: { + [key: string]: any; +}, caseType: "snake-case" | "camelCase"): T; export declare const getProcess: () => any; export declare const getBuffer: () => any; export declare const isTestEnv: () => boolean; diff --git a/lib/build/utils.js b/lib/build/utils.js index 0f0ddadde..29b751f98 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -1,9 +1,7 @@ "use strict"; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.isBuffer = exports.decodeBase64 = exports.encodeBase64 = exports.isTestEnv = exports.getBuffer = exports.getProcess = exports.transformObjectKeys = exports.toSnakeCase = exports.toCamelCase = exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.getUserContext = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.hasGreaterThanEqualToFDI = exports.getLatestFDIVersionFromFDIList = exports.getBackwardsCompatibleUserInfo = exports.getNormalisedShouldTryLinkingWithSessionUserFlag = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; const tldts_1 = require("tldts"); @@ -17,18 +15,15 @@ const doFetch = async (input, init) => { // frameworks like nextJS cache fetch GET requests (https://nextjs.org/docs/app/building-your-application/caching#data-cache) // we don't want that because it may lead to weird behaviour when querying the core. if (init === undefined) { - processState_1.ProcessState.getInstance().addState( - processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH - ); + processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH); init = { cache: "no-cache", redirect: "manual", }; - } else { + } + else { if (init.cache === undefined) { - processState_1.ProcessState.getInstance().addState( - processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH - ); + processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH); init.cache = "no-cache"; init.redirect = "manual"; } @@ -36,17 +31,18 @@ const doFetch = async (input, init) => { const fetchFunction = typeof fetch !== "undefined" ? fetch : cross_fetch_1.default; try { return await fetchFunction(input, init); - } catch (e) { + } + catch (e) { // Cloudflare Workers don't support the 'cache' field in RequestInit. // To work around this, we delete the 'cache' field and retry the fetch if the error is due to the missing 'cache' field. // Remove this workaround once the 'cache' field is supported. // More info: https://github.com/cloudflare/workerd/issues/698 - const unimplementedCacheError = - e && + const unimplementedCacheError = e && typeof e === "object" && "message" in e && e.message === "The 'cache' field on 'RequestInitializerDict' is not implemented."; - if (!unimplementedCacheError) throw e; + if (!unimplementedCacheError) + throw e; const newOpts = Object.assign({}, init); delete newOpts.cache; return await fetchFunction(input, newOpts); @@ -74,7 +70,8 @@ function maxVersion(version1, version2) { let v2 = Number(splittedv2[i]); if (v1 > v2) { return version1; - } else if (v2 > v1) { + } + else if (v2 > v1) { return version2; } } @@ -94,14 +91,11 @@ function normaliseInputAppInfoOrThrowError(appInfo) { if (appInfo.appName === undefined) { throw new Error("Please provide your appName inside the appInfo object when calling supertokens.init"); } - let apiGatewayPath = - appInfo.apiGatewayPath !== undefined - ? new normalisedURLPath_1.default(appInfo.apiGatewayPath) - : new normalisedURLPath_1.default(""); + let apiGatewayPath = appInfo.apiGatewayPath !== undefined + ? new normalisedURLPath_1.default(appInfo.apiGatewayPath) + : new normalisedURLPath_1.default(""); if (appInfo.origin === undefined && appInfo.websiteDomain === undefined) { - throw new Error( - "Please provide either origin or websiteDomain inside the appInfo object when calling supertokens.init" - ); + throw new Error("Please provide either origin or websiteDomain inside the appInfo object when calling supertokens.init"); } let websiteDomainFunction = (input) => { let origin = appInfo.origin; @@ -126,15 +120,12 @@ function normaliseInputAppInfoOrThrowError(appInfo) { appName: appInfo.appName, getOrigin: websiteDomainFunction, apiDomain, - apiBasePath: apiGatewayPath.appendPath( - appInfo.apiBasePath === undefined - ? new normalisedURLPath_1.default("/auth") - : new normalisedURLPath_1.default(appInfo.apiBasePath) - ), - websiteBasePath: - appInfo.websiteBasePath === undefined - ? new normalisedURLPath_1.default("/auth") - : new normalisedURLPath_1.default(appInfo.websiteBasePath), + apiBasePath: apiGatewayPath.appendPath(appInfo.apiBasePath === undefined + ? new normalisedURLPath_1.default("/auth") + : new normalisedURLPath_1.default(appInfo.apiBasePath)), + websiteBasePath: appInfo.websiteBasePath === undefined + ? new normalisedURLPath_1.default("/auth") + : new normalisedURLPath_1.default(appInfo.websiteBasePath), apiGatewayPath, topLevelAPIDomain, getTopLevelWebsiteDomain: topLevelWebsiteDomain, @@ -174,18 +165,18 @@ function deepTransform(obj) { let val = obj[key]; if (val && typeof val === "object" && val["toJson"] !== undefined && typeof val["toJson"] === "function") { out[key] = val.toJson(); - } else if (val && typeof val === "object") { + } + else if (val && typeof val === "object") { out[key] = deepTransform(val); - } else { + } + else { out[key] = val; } } return out; } function isAnIpAddress(ipaddress) { - return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( - ipaddress - ); + return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress); } exports.isAnIpAddress = isAnIpAddress; function getNormalisedShouldTryLinkingWithSessionUserFlag(req, body) { @@ -200,10 +191,8 @@ function getBackwardsCompatibleUserInfo(req, result, userContext) { let resp; // (>= 1.18 && < 2.0) || >= 3.0: This is because before 1.18, and between 2 and 3, FDI does not // support account linking. - if ( - (hasGreaterThanEqualToFDI(req, "1.18") && !hasGreaterThanEqualToFDI(req, "2.0")) || - hasGreaterThanEqualToFDI(req, "3.0") - ) { + if ((hasGreaterThanEqualToFDI(req, "1.18") && !hasGreaterThanEqualToFDI(req, "2.0")) || + hasGreaterThanEqualToFDI(req, "3.0")) { resp = { user: result.user.toJson(), }; @@ -211,10 +200,9 @@ function getBackwardsCompatibleUserInfo(req, result, userContext) { resp.createdNewRecipeUser = result.createdNewRecipeUser; } return resp; - } else { - let loginMethod = result.user.loginMethods.find( - (lm) => lm.recipeUserId.getAsString() === result.session.getRecipeUserId(userContext).getAsString() - ); + } + else { + let loginMethod = result.user.loginMethods.find((lm) => lm.recipeUserId.getAsString() === result.session.getRecipeUserId(userContext).getAsString()); if (loginMethod === undefined) { // we pick the oldest login method here for the user. // this can happen in case the user is implementing something like @@ -222,7 +210,8 @@ function getBackwardsCompatibleUserInfo(req, result, userContext) { for (let i = 0; i < result.user.loginMethods.length; i++) { if (loginMethod === undefined) { loginMethod = result.user.loginMethods[i]; - } else if (loginMethod.timeJoined > result.user.loginMethods[i].timeJoined) { + } + else if (loginMethod.timeJoined > result.user.loginMethods[i].timeJoined) { loginMethod = result.user.loginMethods[i]; } } @@ -287,15 +276,20 @@ function humaniseMilliseconds(ms) { let t = Math.floor(ms / 1000); let suffix = ""; if (t < 60) { - if (t > 1) suffix = "s"; + if (t > 1) + suffix = "s"; return `${t} second${suffix}`; - } else if (t < 3600) { + } + else if (t < 3600) { const m = Math.floor(t / 60); - if (m > 1) suffix = "s"; + if (m > 1) + suffix = "s"; return `${m} minute${suffix}`; - } else { + } + else { const h = Math.floor(t / 360) / 10; - if (h > 1) suffix = "s"; + if (h > 1) + suffix = "s"; return `${h} hour${suffix}`; } } @@ -305,7 +299,7 @@ function makeDefaultUserContextFromAPI(request) { } exports.makeDefaultUserContextFromAPI = makeDefaultUserContextFromAPI; function getUserContext(inputUserContext) { - return inputUserContext !== null && inputUserContext !== void 0 ? inputUserContext : {}; + return (inputUserContext !== null && inputUserContext !== void 0 ? inputUserContext : {}); } exports.getUserContext = getUserContext; function setRequestInUserContextIfNotDefined(userContext, request) { @@ -382,12 +376,14 @@ async function postWithFetch(url, headers, body, { successLog, errorLogHeader }) logger_1.logDebugMessage(errorLogHeader); logger_1.logDebugMessage(`Error status: ${fetchResp.status}`); logger_1.logDebugMessage(`Error response: ${respText}`); - } catch (caught) { + } + catch (caught) { error = caught; logger_1.logDebugMessage(errorLogHeader); if (error instanceof Error) { logger_1.logDebugMessage(`Error: ${error.message}`); - } else { + } + else { logger_1.logDebugMessage(`Error: ${JSON.stringify(error)}`); } } @@ -435,7 +431,8 @@ const getProcess = () => { * to one that is compatible where process may not be available * (like `edge` runtime). */ - if (typeof process !== "undefined") return process; + if (typeof process !== "undefined") + return process; const ponyFilledProcess = require("process"); return ponyFilledProcess; }; @@ -446,7 +443,8 @@ const getBuffer = () => { * to one that is compatible where it may not be available * (like `edge` runtime). */ - if (typeof Buffer !== "undefined") return Buffer; + if (typeof Buffer !== "undefined") + return Buffer; const ponyFilledBuffer = require("buffer").Buffer; return ponyFilledBuffer; }; diff --git a/lib/ts/authUtils.ts b/lib/ts/authUtils.ts index ae278847c..91a0a33a7 100644 --- a/lib/ts/authUtils.ts +++ b/lib/ts/authUtils.ts @@ -326,9 +326,20 @@ export const AuthUtils = { }: { recipeId: string; accountInfo: - | { email: string; thirdParty?: undefined; phoneNumber?: undefined } - | { email?: undefined; thirdParty?: undefined; phoneNumber: string } - | { email?: undefined; thirdParty: { id: string; userId: string }; phoneNumber?: undefined }; + | { 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; diff --git a/lib/ts/index.ts b/lib/ts/index.ts index a6b3d323e..fb08f95c3 100644 --- a/lib/ts/index.ts +++ b/lib/ts/index.ts @@ -17,7 +17,7 @@ import SuperTokens from "./supertokens"; import SuperTokensError from "./error"; import { UserContext, User as UserType } from "./types"; import AccountLinking from "./recipe/accountlinking/recipe"; -import { AccountInfo } from "./recipe/accountlinking/types"; +import { AccountInfoInput } from "./recipe/accountlinking/types"; import RecipeUserId from "./recipeUserId"; import { User } from "./user"; import { getUserContext } from "./utils"; @@ -135,7 +135,7 @@ export default class SuperTokensWrapper { static async listUsersByAccountInfo( tenantId: string, - accountInfo: AccountInfo, + accountInfo: AccountInfoInput, doUnionOfAccountInfo: boolean = false, userContext?: Record ) { diff --git a/lib/ts/recipe/accountlinking/recipe.ts b/lib/ts/recipe/accountlinking/recipe.ts index c494a21fa..e03366053 100644 --- a/lib/ts/recipe/accountlinking/recipe.ts +++ b/lib/ts/recipe/accountlinking/recipe.ts @@ -146,13 +146,17 @@ export default class Recipe extends RecipeModule { return user; } - // then, we try and find a primary user based on the email / phone number / third party ID. + // then, we try and find a primary user based on the email / phone number / third party ID / credentialId. let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo: user.loginMethods[0], // todo we might need to omit the credental id here - + accountInfo: { + ...user.loginMethods[0], + // we don't need to list by (webauthn) credentialId because we are looking for + // a user to link to the current recipe user, but any search using the credentialId + // of the current user "will identify the same user" which is the current one. + webauthn: undefined, + }, doUnionOfAccountInfo: true, - userContext, }); @@ -205,7 +209,13 @@ export default class Recipe extends RecipeModule { // then, we try and find matching users based on the email / phone number / third party ID. let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo: user.loginMethods[0], + accountInfo: { + ...user.loginMethods[0], + // we don't need to list by (webauthn) credentialId because we are looking for + // a user to link to the current recipe user, but any search using the credentialId + // of the current user "will identify the same user" which is the current one. + webauthn: undefined, + }, doUnionOfAccountInfo: true, userContext, }); @@ -322,9 +332,16 @@ export default class Recipe extends RecipeModule { // we do not pass in third party info, or both email or phone // cause we want to guarantee that the output array contains just one // primary user. + let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo, + accountInfo: { + ...accountInfo, + // we don't need to list by (webauthn) credentialId because we are looking for + // a user to link to the current recipe user, but any search using the credentialId + // of the current user "will identify the same user" which is the current one. + webauthn: undefined, + }, doUnionOfAccountInfo: true, userContext, }); diff --git a/lib/ts/recipe/accountlinking/recipeImplementation.ts b/lib/ts/recipe/accountlinking/recipeImplementation.ts index d7e1b13f4..598e4baae 100644 --- a/lib/ts/recipe/accountlinking/recipeImplementation.ts +++ b/lib/ts/recipe/accountlinking/recipeImplementation.ts @@ -13,7 +13,7 @@ * under the License. */ -import { AccountInfo, RecipeInterface, TypeNormalisedInput } from "./types"; +import { AccountInfoInput, RecipeInterface, TypeNormalisedInput } from "./types"; import { Querier } from "../../querier"; import NormalisedURLPath from "../../normalisedURLPath"; import RecipeUserId from "../../recipeUserId"; @@ -308,7 +308,7 @@ export default function getRecipeImplementation( userContext, }: { tenantId: string; - accountInfo: AccountInfo; + accountInfo: AccountInfoInput; doUnionOfAccountInfo: boolean; userContext: UserContext; } diff --git a/lib/ts/recipe/accountlinking/types.ts b/lib/ts/recipe/accountlinking/types.ts index 11fede4c3..d543f83ba 100644 --- a/lib/ts/recipe/accountlinking/types.ts +++ b/lib/ts/recipe/accountlinking/types.ts @@ -174,7 +174,7 @@ export type RecipeInterface = { getUser: (input: { userId: string; userContext: UserContext }) => Promise; listUsersByAccountInfo: (input: { tenantId: string; - accountInfo: AccountInfo; + accountInfo: AccountInfoInput; doUnionOfAccountInfo: boolean; userContext: UserContext; }) => Promise; @@ -197,6 +197,12 @@ export type AccountInfo = { }; }; +export type AccountInfoInput = Omit & { + webauthn?: { + credentialId: string; + }; +}; + export type AccountInfoWithRecipeId = { recipeId: "emailpassword" | "thirdparty" | "passwordless" | "webauthn"; } & AccountInfo; diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 824efea16..36e6d46e1 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -19,7 +19,7 @@ import { getRecoverAccountLink } from "../utils"; import { logDebugMessage } from "../../../logger"; import { RecipeLevelUser } from "../../accountlinking/types"; import { getUser } from "../../.."; -import { AuthenticationPayload, RegistrationPayload, ResidentKey, UserVerification } from "../types"; +import { AuthenticationPayload, CredentialPayload, RegistrationPayload, ResidentKey, UserVerification } from "../types"; export default function getAPIImplementation(): APIInterface { return { diff --git a/lib/ts/recipe/webauthn/api/registerCredential.ts b/lib/ts/recipe/webauthn/api/registerCredential.ts index 4aacd8bd9..546417d40 100644 --- a/lib/ts/recipe/webauthn/api/registerCredential.ts +++ b/lib/ts/recipe/webauthn/api/registerCredential.ts @@ -13,11 +13,7 @@ * under the License. */ -import { - getBackwardsCompatibleUserInfo, - getNormalisedShouldTryLinkingWithSessionUserFlag, - send200Response, -} from "../../../utils"; +import { send200Response } from "../../../utils"; import { validateWebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils"; import { APIInterface, APIOptions } from ".."; import STError from "../error"; From fd879845fcd17e765bf51a72e3d992637804c166 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Tue, 21 Jan 2025 12:42:15 +0200 Subject: [PATCH 32/36] removed email support for sign in --- lib/ts/recipe/webauthn/api/implementation.ts | 17 ++++++++++++----- lib/ts/recipe/webauthn/api/signInOptions.ts | 12 ------------ lib/ts/recipe/webauthn/core-mock.ts | 1 - lib/ts/recipe/webauthn/recipeImplementation.ts | 3 +-- lib/ts/recipe/webauthn/types.ts | 2 -- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 36e6d46e1..41eb38c1f 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -127,12 +127,10 @@ export default function getAPIImplementation(): APIInterface { }, signInOptionsPOST: async function ({ - email, tenantId, options, userContext, }: { - email: string; tenantId: string; options: APIOptions; userContext: UserContext; @@ -166,7 +164,6 @@ export default function getAPIImplementation(): APIInterface { const userVerification = DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION; let response = await options.recipeImplementation.signInOptions({ - email, userVerification, origin, relyingPartyId, @@ -427,7 +424,6 @@ export default function getAPIImplementation(): APIInterface { status: "INVALID_CREDENTIALS_ERROR", }; } - let email = generatedOptions.email; const checkCredentialsOnTenant = async () => { return true; @@ -442,8 +438,10 @@ export default function getAPIImplementation(): APIInterface { // lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || // lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) // ); + + const accountInfo = { webauthn: { credentialId: credential.id } }; const authenticatingUser = await AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ - accountInfo: { email }, + accountInfo, userContext, recipeId, session, @@ -461,6 +459,15 @@ export default function getAPIImplementation(): APIInterface { status: "INVALID_CREDENTIALS_ERROR", }; } + + // we find the email of the user that has the same credentialId as the one we are verifying + const email = authenticatingUser.user.loginMethods.find( + (lm) => lm.recipeId === "webauthn" && lm.webauthn?.credentialIds.includes(credential.id) + )?.email; + if (email === undefined) { + throw new Error("This should never happen: webauthn user has no email"); + } + const preAuthChecks = await AuthUtils.preAuthChecks({ authenticatingAccountInfo: { recipeId, diff --git a/lib/ts/recipe/webauthn/api/signInOptions.ts b/lib/ts/recipe/webauthn/api/signInOptions.ts index 4e0802657..f81fde810 100644 --- a/lib/ts/recipe/webauthn/api/signInOptions.ts +++ b/lib/ts/recipe/webauthn/api/signInOptions.ts @@ -16,7 +16,6 @@ import { send200Response } from "../../../utils"; import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -import STError from "../error"; export default async function signInOptions( apiImplementation: APIInterface, @@ -27,19 +26,8 @@ export default async function signInOptions( if (apiImplementation.signInOptionsPOST === undefined) { return false; } - const requestBody = await options.req.getJSONBody(); - - let email = requestBody.email?.trim(); - - if (email === undefined || typeof email !== "string") { - throw new STError({ - type: STError.BAD_INPUT_ERROR, - message: "Please provide the email", - }); - } let result = await apiImplementation.signInOptionsPOST({ - email, tenantId, options, userContext, diff --git a/lib/ts/recipe/webauthn/core-mock.ts b/lib/ts/recipe/webauthn/core-mock.ts index daf4a6eb8..f7e27f496 100644 --- a/lib/ts/recipe/webauthn/core-mock.ts +++ b/lib/ts/recipe/webauthn/core-mock.ts @@ -89,7 +89,6 @@ export const getMockQuerier = (recipeId: string) => { id, origin: body.origin, tenantId: body.tenantId, - email: body.email, createdAt, expiresAt, }); diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 2cb7e6d5e..c2456e85f 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -98,14 +98,13 @@ export default function getRecipeInterface( ); }, - signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext, email }) { + signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest( new NormalisedURLPath( `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/signin` ), { - email, relyingPartyId, origin, timeout, diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 1c070194d..08216ae18 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -218,7 +218,6 @@ export type RecipeInterface = { >; signInOptions(input: { - email: string; relyingPartyId: string; origin: string; userVerification: UserVerification | undefined; // see register options @@ -630,7 +629,6 @@ export type APIInterface = { signInOptionsPOST: | undefined | ((input: { - email: string; tenantId: string; options: APIOptions; userContext: UserContext; From 8e68827e7c1742660237f27e7aca875cf09a2c47 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 27 Jan 2025 12:52:31 +0530 Subject: [PATCH 33/36] Add fix for not requiring email in webauthn sign in --- lib/ts/recipe/webauthn/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 75b0abbe8..d93979448 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -177,14 +177,12 @@ export default class Wrapper { } static async signInOptions({ - email, tenantId = DEFAULT_TENANT_ID, userVerification = DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, timeout = DEFAULT_SIGNIN_OPTIONS_TIMEOUT, userContext, ...rest }: { - email: string; timeout?: number; userVerification?: UserVerification; tenantId?: string; @@ -232,7 +230,6 @@ export default class Wrapper { } return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ - email, relyingPartyId, origin, timeout, From f3fd0dddf2deb15f1a6111d74710033ffa0ba913 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 27 Jan 2025 12:57:01 +0530 Subject: [PATCH 34/36] Add build files for changes related to email not being required --- lib/build/authUtils.d.ts | 311 +++-- lib/build/authUtils.js | 595 +++++++--- lib/build/combinedRemoteJWKSet.d.ts | 5 +- lib/build/combinedRemoteJWKSet.js | 17 +- lib/build/core-mock.js | 54 +- lib/build/customFramework.d.ts | 34 +- lib/build/customFramework.js | 39 +- lib/build/error.d.ts | 22 +- lib/build/framework/awsLambda/framework.d.ts | 23 +- lib/build/framework/awsLambda/framework.js | 81 +- lib/build/framework/awsLambda/index.d.ts | 4 +- lib/build/framework/constants.d.ts | 3 +- lib/build/framework/constants.js | 3 +- lib/build/framework/custom/framework.d.ts | 66 +- lib/build/framework/custom/framework.js | 26 +- lib/build/framework/custom/index.d.ts | 31 +- lib/build/framework/custom/index.js | 14 +- lib/build/framework/custom/nodeHeaders.js | 101 +- lib/build/framework/express/framework.d.ts | 11 +- lib/build/framework/express/framework.js | 32 +- lib/build/framework/express/index.d.ts | 13 +- lib/build/framework/fastify/framework.d.ts | 11 +- lib/build/framework/fastify/framework.js | 37 +- lib/build/framework/fastify/index.d.ts | 12 +- lib/build/framework/fastify/types.d.ts | 12 +- lib/build/framework/hapi/framework.d.ts | 18 +- lib/build/framework/hapi/framework.js | 29 +- lib/build/framework/index.js | 69 +- lib/build/framework/koa/framework.d.ts | 11 +- lib/build/framework/koa/framework.js | 20 +- lib/build/framework/loopback/framework.d.ts | 11 +- lib/build/framework/loopback/framework.js | 23 +- lib/build/framework/request.js | 12 +- lib/build/framework/response.d.ts | 11 +- lib/build/framework/utils.d.ts | 41 +- lib/build/framework/utils.js | 172 +-- lib/build/index.d.ts | 59 +- lib/build/index.js | 62 +- lib/build/ingredients/emaildelivery/index.js | 8 +- .../emaildelivery/services/smtp.d.ts | 8 +- .../ingredients/emaildelivery/types.d.ts | 20 +- lib/build/ingredients/smsdelivery/index.js | 8 +- .../smsdelivery/services/twilio.d.ts | 45 +- .../smsdelivery/services/twilio.js | 9 +- lib/build/ingredients/smsdelivery/types.d.ts | 20 +- lib/build/logger.js | 14 +- lib/build/nextjs.d.ts | 28 +- lib/build/nextjs.js | 8 +- lib/build/normalisedURLDomain.js | 18 +- lib/build/normalisedURLPath.js | 18 +- lib/build/processState.d.ts | 2 +- lib/build/processState.js | 27 +- lib/build/querier.d.ts | 40 +- lib/build/querier.js | 621 +++++----- lib/build/recipe/accountlinking/index.d.ts | 172 ++- lib/build/recipe/accountlinking/index.js | 8 +- lib/build/recipe/accountlinking/recipe.d.ts | 100 +- lib/build/recipe/accountlinking/recipe.js | 473 +++++--- .../accountlinking/recipeImplementation.d.ts | 6 +- .../accountlinking/recipeImplementation.js | 154 ++- lib/build/recipe/accountlinking/types.d.ts | 182 +-- lib/build/recipe/accountlinking/utils.js | 14 +- lib/build/recipe/dashboard/api/analytics.d.ts | 7 +- lib/build/recipe/dashboard/api/analytics.js | 18 +- .../recipe/dashboard/api/apiKeyProtector.d.ts | 8 +- .../recipe/dashboard/api/apiKeyProtector.js | 11 +- lib/build/recipe/dashboard/api/dashboard.d.ts | 6 +- .../recipe/dashboard/api/implementation.js | 27 +- .../createOrUpdateThirdPartyConfig.d.ts | 28 +- .../createOrUpdateThirdPartyConfig.js | 58 +- .../api/multitenancy/createTenant.d.ts | 28 +- .../api/multitenancy/createTenant.js | 36 +- .../api/multitenancy/deleteTenant.d.ts | 21 +- .../api/multitenancy/deleteTenant.js | 11 +- .../multitenancy/deleteThirdPartyConfig.d.ts | 21 +- .../multitenancy/deleteThirdPartyConfig.js | 36 +- .../api/multitenancy/getTenantInfo.d.ts | 45 +- .../api/multitenancy/getTenantInfo.js | 96 +- .../api/multitenancy/getThirdPartyConfig.d.ts | 29 +- .../api/multitenancy/getThirdPartyConfig.js | 157 ++- .../listAllTenantsWithLoginMethods.d.ts | 7 +- .../listAllTenantsWithLoginMethods.js | 8 +- .../multitenancy/updateTenantCoreConfig.d.ts | 26 +- .../multitenancy/updateTenantCoreConfig.js | 11 +- .../multitenancy/updateTenantFirstFactor.d.ts | 26 +- .../multitenancy/updateTenantFirstFactor.js | 35 +- .../updateTenantSecondaryFactor.d.ts | 33 +- .../updateTenantSecondaryFactor.js | 35 +- .../dashboard/api/multitenancy/utils.d.ts | 8 +- .../dashboard/api/multitenancy/utils.js | 46 +- .../recipe/dashboard/api/search/tagsGet.d.ts | 7 +- .../recipe/dashboard/api/search/tagsGet.js | 14 +- lib/build/recipe/dashboard/api/signIn.js | 20 +- lib/build/recipe/dashboard/api/signOut.d.ts | 7 +- lib/build/recipe/dashboard/api/signOut.js | 21 +- .../api/user/create/emailpasswordUser.d.ts | 37 +- .../api/user/create/emailpasswordUser.js | 18 +- .../api/user/create/passwordlessUser.d.ts | 39 +- .../api/user/create/passwordlessUser.js | 30 +- .../dashboard/api/userdetails/userDelete.js | 11 +- .../api/userdetails/userEmailVerifyGet.js | 17 +- .../api/userdetails/userEmailVerifyPut.d.ts | 7 +- .../api/userdetails/userEmailVerifyPut.js | 31 +- .../userdetails/userEmailVerifyTokenPost.d.ts | 7 +- .../userdetails/userEmailVerifyTokenPost.js | 16 +- .../dashboard/api/userdetails/userGet.js | 21 +- .../api/userdetails/userMetadataGet.js | 11 +- .../api/userdetails/userMetadataPut.d.ts | 7 +- .../api/userdetails/userMetadataPut.js | 11 +- .../api/userdetails/userPasswordPut.d.ts | 21 +- .../api/userdetails/userPasswordPut.js | 17 +- .../dashboard/api/userdetails/userPut.d.ts | 52 +- .../dashboard/api/userdetails/userPut.js | 54 +- .../api/userdetails/userSessionsGet.js | 35 +- .../api/userdetails/userSessionsPost.d.ts | 7 +- .../api/userdetails/userSessionsPost.js | 8 +- .../api/userdetails/userUnlinkGet.d.ts | 7 +- .../api/userdetails/userUnlinkGet.js | 8 +- .../api/userroles/addRoleToUser.d.ts | 20 +- .../dashboard/api/userroles/addRoleToUser.js | 11 +- .../api/userroles/getRolesForUser.d.ts | 20 +- .../api/userroles/getRolesForUser.js | 11 +- .../permissions/getPermissionsForRole.d.ts | 20 +- .../permissions/getPermissionsForRole.js | 11 +- .../permissions/removePermissions.d.ts | 7 +- .../permissions/removePermissions.js | 11 +- .../api/userroles/removeUserRole.d.ts | 20 +- .../dashboard/api/userroles/removeUserRole.js | 11 +- .../roles/createRoleOrAddPermissions.d.ts | 20 +- .../roles/createRoleOrAddPermissions.js | 11 +- .../api/userroles/roles/deleteRole.d.ts | 20 +- .../api/userroles/roles/deleteRole.js | 11 +- .../api/userroles/roles/getAllRoles.js | 11 +- .../recipe/dashboard/api/usersCountGet.d.ts | 7 +- .../recipe/dashboard/api/usersCountGet.js | 8 +- lib/build/recipe/dashboard/api/usersGet.d.ts | 4 +- lib/build/recipe/dashboard/api/usersGet.js | 67 +- lib/build/recipe/dashboard/api/validateKey.js | 3 +- lib/build/recipe/dashboard/error.js | 8 +- lib/build/recipe/dashboard/index.js | 11 +- lib/build/recipe/dashboard/recipe.d.ts | 10 +- lib/build/recipe/dashboard/recipe.js | 255 +++-- .../recipe/dashboard/recipeImplementation.js | 35 +- lib/build/recipe/dashboard/types.d.ts | 26 +- lib/build/recipe/dashboard/utils.d.ts | 6 +- lib/build/recipe/dashboard/utils.js | 49 +- .../recipe/emailpassword/api/emailExists.d.ts | 7 +- .../recipe/emailpassword/api/emailExists.js | 8 +- .../api/generatePasswordResetToken.d.ts | 7 +- .../api/generatePasswordResetToken.js | 7 +- .../emailpassword/api/implementation.js | 389 +++++-- .../emailpassword/api/passwordReset.d.ts | 7 +- .../recipe/emailpassword/api/passwordReset.js | 28 +- .../recipe/emailpassword/api/signin.d.ts | 7 +- lib/build/recipe/emailpassword/api/signin.js | 22 +- .../recipe/emailpassword/api/signup.d.ts | 7 +- lib/build/recipe/emailpassword/api/signup.js | 41 +- lib/build/recipe/emailpassword/api/utils.d.ts | 15 +- lib/build/recipe/emailpassword/api/utils.js | 15 +- .../services/backwardCompatibility/index.d.ts | 11 +- .../services/backwardCompatibility/index.js | 16 +- .../emaildelivery/services/index.js | 8 +- .../emaildelivery/services/smtp/index.d.ts | 8 +- .../emaildelivery/services/smtp/index.js | 16 +- .../services/smtp/passwordReset.d.ts | 4 +- .../services/smtp/passwordReset.js | 8 +- .../smtp/serviceImplementation/index.d.ts | 11 +- .../smtp/serviceImplementation/index.js | 11 +- lib/build/recipe/emailpassword/error.d.ts | 26 +- lib/build/recipe/emailpassword/error.js | 8 +- lib/build/recipe/emailpassword/index.d.ts | 239 ++-- lib/build/recipe/emailpassword/index.js | 25 +- .../emailpassword/passwordResetFunctions.d.ts | 12 +- .../emailpassword/passwordResetFunctions.js | 27 +- lib/build/recipe/emailpassword/recipe.d.ts | 22 +- lib/build/recipe/emailpassword/recipe.js | 56 +- .../emailpassword/recipeImplementation.d.ts | 5 +- .../emailpassword/recipeImplementation.js | 144 ++- lib/build/recipe/emailpassword/types.d.ts | 359 +++--- lib/build/recipe/emailpassword/utils.d.ts | 21 +- lib/build/recipe/emailpassword/utils.js | 103 +- .../emailverification/api/emailVerify.d.ts | 7 +- .../emailverification/api/emailVerify.js | 28 +- .../api/generateEmailVerifyToken.d.ts | 6 +- .../api/generateEmailVerifyToken.js | 15 +- .../emailverification/api/implementation.js | 115 +- .../emailVerificationClaim.js | 51 +- .../emailVerificationFunctions.d.ts | 6 +- .../emailVerificationFunctions.js | 27 +- .../services/backwardCompatibility/index.d.ts | 11 +- .../services/backwardCompatibility/index.js | 16 +- .../emaildelivery/services/index.js | 8 +- .../services/smtp/emailVerify.js | 8 +- .../emaildelivery/services/smtp/index.d.ts | 8 +- .../emaildelivery/services/smtp/index.js | 16 +- .../services/smtp/serviceImplementation.d.ts | 11 +- .../services/smtp/serviceImplementation.js | 11 +- lib/build/recipe/emailverification/error.d.ts | 5 +- lib/build/recipe/emailverification/error.js | 8 +- lib/build/recipe/emailverification/index.d.ts | 114 +- lib/build/recipe/emailverification/index.js | 49 +- .../recipe/emailverification/recipe.d.ts | 22 +- lib/build/recipe/emailverification/recipe.js | 103 +- .../recipeImplementation.d.ts | 5 +- .../emailverification/recipeImplementation.js | 80 +- lib/build/recipe/emailverification/types.d.ts | 191 ++-- lib/build/recipe/emailverification/utils.d.ts | 6 +- lib/build/recipe/emailverification/utils.js | 37 +- lib/build/recipe/jwt/api/getJWKS.d.ts | 6 +- lib/build/recipe/jwt/api/getJWKS.js | 3 +- lib/build/recipe/jwt/api/implementation.js | 2 +- lib/build/recipe/jwt/index.d.ts | 24 +- lib/build/recipe/jwt/index.js | 8 +- lib/build/recipe/jwt/recipe.d.ts | 10 +- lib/build/recipe/jwt/recipe.js | 19 +- .../recipe/jwt/recipeImplementation.d.ts | 6 +- lib/build/recipe/jwt/recipeImplementation.js | 38 +- lib/build/recipe/jwt/types.d.ts | 42 +- lib/build/recipe/jwt/utils.d.ts | 6 +- lib/build/recipe/jwt/utils.js | 13 +- .../multifactorauth/api/implementation.js | 42 +- .../api/resyncSessionAndFetchMFAInfo.d.ts | 6 +- .../api/resyncSessionAndFetchMFAInfo.js | 15 +- lib/build/recipe/multifactorauth/index.d.ts | 29 +- lib/build/recipe/multifactorauth/index.js | 49 +- .../multifactorauth/multiFactorAuthClaim.d.ts | 46 +- .../multifactorauth/multiFactorAuthClaim.js | 48 +- lib/build/recipe/multifactorauth/recipe.d.ts | 59 +- lib/build/recipe/multifactorauth/recipe.js | 41 +- .../multifactorauth/recipeImplementation.js | 88 +- lib/build/recipe/multifactorauth/types.d.ts | 106 +- lib/build/recipe/multifactorauth/utils.d.ts | 25 +- lib/build/recipe/multifactorauth/utils.js | 153 +-- .../multitenancy/allowedDomainsClaim.js | 8 +- .../recipe/multitenancy/api/implementation.js | 25 +- .../recipe/multitenancy/api/loginMethods.d.ts | 7 +- lib/build/recipe/multitenancy/error.d.ts | 5 +- lib/build/recipe/multitenancy/error.js | 8 +- lib/build/recipe/multitenancy/index.d.ts | 88 +- lib/build/recipe/multitenancy/index.js | 15 +- lib/build/recipe/multitenancy/recipe.d.ts | 10 +- lib/build/recipe/multitenancy/recipe.js | 22 +- .../multitenancy/recipeImplementation.js | 100 +- lib/build/recipe/multitenancy/types.d.ts | 87 +- lib/build/recipe/multitenancy/utils.d.ts | 28 +- lib/build/recipe/multitenancy/utils.js | 74 +- .../recipe/oauth2client/api/implementation.js | 28 +- lib/build/recipe/oauth2client/api/signin.d.ts | 7 +- lib/build/recipe/oauth2client/api/signin.js | 25 +- lib/build/recipe/oauth2client/index.d.ts | 19 +- lib/build/recipe/oauth2client/index.js | 8 +- lib/build/recipe/oauth2client/recipe.d.ts | 18 +- lib/build/recipe/oauth2client/recipe.js | 15 +- .../oauth2client/recipeImplementation.js | 56 +- lib/build/recipe/oauth2client/types.d.ts | 85 +- lib/build/recipe/oauth2client/utils.d.ts | 5 +- lib/build/recipe/oauth2client/utils.js | 12 +- .../recipe/oauth2provider/OAuth2Client.d.ts | 29 +- .../recipe/oauth2provider/OAuth2Client.js | 29 +- lib/build/recipe/oauth2provider/api/auth.d.ts | 6 +- lib/build/recipe/oauth2provider/api/auth.js | 31 +- .../recipe/oauth2provider/api/endSession.d.ts | 12 +- .../recipe/oauth2provider/api/endSession.js | 27 +- .../oauth2provider/api/implementation.js | 6 +- .../oauth2provider/api/introspectToken.d.ts | 6 +- .../recipe/oauth2provider/api/login.d.ts | 6 +- lib/build/recipe/oauth2provider/api/login.js | 36 +- .../recipe/oauth2provider/api/loginInfo.d.ts | 6 +- .../recipe/oauth2provider/api/loginInfo.js | 13 +- .../recipe/oauth2provider/api/logout.d.ts | 6 +- lib/build/recipe/oauth2provider/api/logout.js | 17 +- .../oauth2provider/api/revokeToken.d.ts | 6 +- .../recipe/oauth2provider/api/revokeToken.js | 9 +- .../recipe/oauth2provider/api/token.d.ts | 6 +- lib/build/recipe/oauth2provider/api/token.js | 3 +- .../recipe/oauth2provider/api/userInfo.d.ts | 7 +- .../recipe/oauth2provider/api/userInfo.js | 27 +- .../recipe/oauth2provider/api/utils.d.ts | 68 +- lib/build/recipe/oauth2provider/api/utils.js | 71 +- lib/build/recipe/oauth2provider/index.d.ts | 173 ++- lib/build/recipe/oauth2provider/index.js | 35 +- lib/build/recipe/oauth2provider/recipe.d.ts | 42 +- lib/build/recipe/oauth2provider/recipe.js | 51 +- .../oauth2provider/recipeImplementation.d.ts | 9 +- .../oauth2provider/recipeImplementation.js | 449 +++++--- lib/build/recipe/oauth2provider/types.d.ts | 500 ++++---- lib/build/recipe/oauth2provider/utils.d.ts | 6 +- lib/build/recipe/oauth2provider/utils.js | 8 +- .../api/getOpenIdDiscoveryConfiguration.d.ts | 6 +- .../api/getOpenIdDiscoveryConfiguration.js | 3 +- lib/build/recipe/openid/index.d.ts | 4 +- lib/build/recipe/openid/index.js | 8 +- lib/build/recipe/openid/recipe.d.ts | 10 +- lib/build/recipe/openid/recipe.js | 18 +- .../recipe/openid/recipeImplementation.js | 17 +- lib/build/recipe/openid/types.d.ts | 64 +- lib/build/recipe/openid/utils.js | 8 +- .../recipe/passwordless/api/consumeCode.d.ts | 7 +- .../recipe/passwordless/api/consumeCode.js | 65 +- .../recipe/passwordless/api/createCode.d.ts | 7 +- .../recipe/passwordless/api/createCode.js | 38 +- .../recipe/passwordless/api/emailExists.d.ts | 7 +- .../recipe/passwordless/api/emailExists.js | 8 +- .../recipe/passwordless/api/implementation.js | 468 +++++--- .../passwordless/api/phoneNumberExists.d.ts | 7 +- .../passwordless/api/phoneNumberExists.js | 8 +- .../recipe/passwordless/api/resendCode.d.ts | 7 +- .../recipe/passwordless/api/resendCode.js | 15 +- .../services/backwardCompatibility/index.d.ts | 11 +- .../services/backwardCompatibility/index.js | 36 +- .../emaildelivery/services/index.js | 8 +- .../emaildelivery/services/smtp/index.d.ts | 8 +- .../emaildelivery/services/smtp/index.js | 16 +- .../services/smtp/passwordlessLogin.d.ts | 8 +- .../services/smtp/passwordlessLogin.js | 31 +- .../services/smtp/serviceImplementation.d.ts | 11 +- .../services/smtp/serviceImplementation.js | 11 +- lib/build/recipe/passwordless/error.d.ts | 5 +- lib/build/recipe/passwordless/error.js | 8 +- lib/build/recipe/passwordless/index.d.ts | 398 ++++--- lib/build/recipe/passwordless/index.js | 103 +- lib/build/recipe/passwordless/recipe.d.ts | 80 +- lib/build/recipe/passwordless/recipe.js | 229 ++-- .../passwordless/recipeImplementation.js | 107 +- .../services/backwardCompatibility/index.d.ts | 8 +- .../services/backwardCompatibility/index.js | 55 +- .../smsdelivery/services/index.js | 8 +- .../smsdelivery/services/supertokens/index.js | 47 +- .../smsdelivery/services/twilio/index.d.ts | 8 +- .../smsdelivery/services/twilio/index.js | 35 +- .../services/twilio/passwordlessLogin.js | 14 +- .../twilio/serviceImplementation.d.ts | 4 +- .../services/twilio/serviceImplementation.js | 11 +- lib/build/recipe/passwordless/types.d.ts | 517 +++++---- lib/build/recipe/passwordless/utils.d.ts | 6 +- lib/build/recipe/passwordless/utils.js | 87 +- lib/build/recipe/session/accessToken.d.ts | 6 +- lib/build/recipe/session/accessToken.js | 191 ++-- .../recipe/session/api/implementation.js | 17 +- lib/build/recipe/session/api/refresh.d.ts | 6 +- lib/build/recipe/session/api/signout.d.ts | 6 +- .../session/claimBaseClasses/booleanClaim.js | 5 +- .../claimBaseClasses/primitiveArrayClaim.d.ts | 14 +- .../claimBaseClasses/primitiveArrayClaim.js | 59 +- .../claimBaseClasses/primitiveClaim.d.ts | 14 +- .../claimBaseClasses/primitiveClaim.js | 9 +- lib/build/recipe/session/claims.js | 28 +- .../recipe/session/cookieAndHeaders.d.ts | 50 +- lib/build/recipe/session/cookieAndHeaders.js | 75 +- lib/build/recipe/session/error.d.ts | 57 +- lib/build/recipe/session/error.js | 21 +- .../recipe/session/framework/awsLambda.js | 11 +- .../recipe/session/framework/custom.d.ts | 8 +- lib/build/recipe/session/framework/custom.js | 14 +- .../recipe/session/framework/express.d.ts | 4 +- lib/build/recipe/session/framework/express.js | 14 +- .../recipe/session/framework/fastify.d.ts | 4 +- lib/build/recipe/session/framework/fastify.js | 11 +- lib/build/recipe/session/framework/hapi.d.ts | 4 +- lib/build/recipe/session/framework/hapi.js | 14 +- lib/build/recipe/session/framework/index.js | 55 +- lib/build/recipe/session/framework/koa.d.ts | 4 +- lib/build/recipe/session/framework/koa.js | 14 +- .../recipe/session/framework/loopback.js | 14 +- lib/build/recipe/session/index.d.ts | 226 +++- lib/build/recipe/session/index.js | 56 +- lib/build/recipe/session/jwt.js | 31 +- lib/build/recipe/session/recipe.d.ts | 34 +- lib/build/recipe/session/recipe.js | 108 +- .../recipe/session/recipeImplementation.d.ts | 7 +- .../recipe/session/recipeImplementation.js | 306 +++-- lib/build/recipe/session/sessionClass.d.ts | 15 +- lib/build/recipe/session/sessionClass.js | 186 ++- .../recipe/session/sessionFunctions.d.ts | 79 +- lib/build/recipe/session/sessionFunctions.js | 233 ++-- .../session/sessionRequestFunctions.d.ts | 44 +- .../recipe/session/sessionRequestFunctions.js | 221 ++-- lib/build/recipe/session/types.d.ts | 182 +-- lib/build/recipe/session/utils.d.ts | 80 +- lib/build/recipe/session/utils.js | 186 ++- .../recipe/thirdparty/api/appleRedirect.d.ts | 6 +- .../thirdparty/api/authorisationUrl.d.ts | 7 +- .../recipe/thirdparty/api/authorisationUrl.js | 8 +- .../recipe/thirdparty/api/implementation.js | 93 +- lib/build/recipe/thirdparty/api/signinup.d.ts | 7 +- lib/build/recipe/thirdparty/api/signinup.js | 37 +- lib/build/recipe/thirdparty/error.d.ts | 10 +- lib/build/recipe/thirdparty/error.js | 8 +- lib/build/recipe/thirdparty/index.d.ts | 90 +- lib/build/recipe/thirdparty/index.js | 18 +- .../thirdparty/providers/activeDirectory.js | 12 +- .../recipe/thirdparty/providers/apple.js | 117 +- .../recipe/thirdparty/providers/bitbucket.js | 49 +- .../recipe/thirdparty/providers/boxySaml.js | 15 +- .../thirdparty/providers/configUtils.d.ts | 26 +- .../thirdparty/providers/configUtils.js | 76 +- .../recipe/thirdparty/providers/custom.js | 126 ++- .../recipe/thirdparty/providers/discord.js | 15 +- .../recipe/thirdparty/providers/facebook.js | 39 +- .../recipe/thirdparty/providers/github.js | 50 +- .../recipe/thirdparty/providers/gitlab.js | 15 +- .../recipe/thirdparty/providers/google.js | 17 +- .../thirdparty/providers/googleWorkspaces.js | 33 +- .../recipe/thirdparty/providers/index.js | 8 +- .../recipe/thirdparty/providers/linkedin.js | 22 +- lib/build/recipe/thirdparty/providers/okta.js | 12 +- .../recipe/thirdparty/providers/twitter.js | 94 +- .../recipe/thirdparty/providers/utils.d.ts | 34 +- .../recipe/thirdparty/providers/utils.js | 77 +- lib/build/recipe/thirdparty/recipe.d.ts | 19 +- lib/build/recipe/thirdparty/recipe.js | 34 +- .../recipe/thirdparty/recipeImplementation.js | 103 +- lib/build/recipe/thirdparty/types.d.ts | 241 ++-- lib/build/recipe/thirdparty/utils.d.ts | 5 +- lib/build/recipe/thirdparty/utils.js | 13 +- lib/build/recipe/totp/api/createDevice.d.ts | 6 +- lib/build/recipe/totp/api/createDevice.js | 15 +- lib/build/recipe/totp/api/implementation.js | 23 +- lib/build/recipe/totp/api/listDevices.d.ts | 6 +- lib/build/recipe/totp/api/listDevices.js | 15 +- lib/build/recipe/totp/api/removeDevice.d.ts | 6 +- lib/build/recipe/totp/api/removeDevice.js | 15 +- lib/build/recipe/totp/api/verifyDevice.d.ts | 6 +- lib/build/recipe/totp/api/verifyDevice.js | 15 +- lib/build/recipe/totp/api/verifyTOTP.d.ts | 6 +- lib/build/recipe/totp/api/verifyTOTP.js | 15 +- lib/build/recipe/totp/index.d.ts | 115 +- lib/build/recipe/totp/index.js | 8 +- lib/build/recipe/totp/recipe.d.ts | 10 +- lib/build/recipe/totp/recipe.js | 27 +- lib/build/recipe/totp/recipeImplementation.js | 114 +- lib/build/recipe/totp/types.d.ts | 273 +++-- lib/build/recipe/totp/utils.d.ts | 5 +- lib/build/recipe/totp/utils.js | 23 +- lib/build/recipe/usermetadata/index.d.ts | 16 +- lib/build/recipe/usermetadata/index.js | 8 +- lib/build/recipe/usermetadata/recipe.d.ts | 9 +- lib/build/recipe/usermetadata/recipe.js | 19 +- .../usermetadata/recipeImplementation.js | 37 +- lib/build/recipe/usermetadata/types.d.ts | 10 +- lib/build/recipe/usermetadata/utils.d.ts | 6 +- lib/build/recipe/usermetadata/utils.js | 8 +- lib/build/recipe/userroles/index.d.ts | 109 +- lib/build/recipe/userroles/index.js | 22 +- lib/build/recipe/userroles/permissionClaim.js | 8 +- lib/build/recipe/userroles/recipe.d.ts | 9 +- lib/build/recipe/userroles/recipe.js | 81 +- .../recipe/userroles/recipeImplementation.js | 78 +- lib/build/recipe/userroles/types.d.ts | 70 +- lib/build/recipe/userroles/userRoleClaim.js | 8 +- lib/build/recipe/userroles/utils.d.ts | 6 +- lib/build/recipe/userroles/utils.js | 14 +- .../recipe/webauthn/api/emailExists.d.ts | 7 +- lib/build/recipe/webauthn/api/emailExists.js | 8 +- .../api/generateRecoverAccountToken.d.ts | 7 +- .../api/generateRecoverAccountToken.js | 8 +- .../recipe/webauthn/api/implementation.js | 473 +++++--- .../recipe/webauthn/api/recoverAccount.d.ts | 7 +- .../recipe/webauthn/api/recoverAccount.js | 25 +- .../webauthn/api/registerCredential.d.ts | 7 +- .../recipe/webauthn/api/registerCredential.js | 25 +- .../recipe/webauthn/api/registerOptions.d.ts | 7 +- .../recipe/webauthn/api/registerOptions.js | 14 +- .../recipe/webauthn/api/signInOptions.d.ts | 7 +- .../recipe/webauthn/api/signInOptions.js | 14 - lib/build/recipe/webauthn/api/signin.d.ts | 7 +- lib/build/recipe/webauthn/api/signin.js | 24 +- lib/build/recipe/webauthn/api/signup.d.ts | 7 +- lib/build/recipe/webauthn/api/signup.js | 38 +- lib/build/recipe/webauthn/api/utils.d.ts | 4 +- lib/build/recipe/webauthn/api/utils.js | 8 +- lib/build/recipe/webauthn/core-mock.js | 64 +- .../services/backwardCompatibility/index.d.ts | 8 +- .../services/backwardCompatibility/index.js | 38 +- .../webauthn/emaildelivery/services/index.js | 8 +- .../emaildelivery/services/smtp/index.d.ts | 8 +- .../emaildelivery/services/smtp/index.js | 16 +- .../services/smtp/recoverAccount.d.ts | 4 +- .../services/smtp/recoverAccount.js | 8 +- .../smtp/serviceImplementation/index.d.ts | 11 +- .../smtp/serviceImplementation/index.js | 11 +- lib/build/recipe/webauthn/error.d.ts | 5 +- lib/build/recipe/webauthn/error.js | 8 +- lib/build/recipe/webauthn/index.d.ts | 489 +++++--- lib/build/recipe/webauthn/index.js | 196 ++-- lib/build/recipe/webauthn/recipe.d.ts | 22 +- lib/build/recipe/webauthn/recipe.js | 60 +- .../recipe/webauthn/recipeImplementation.d.ts | 5 +- .../recipe/webauthn/recipeImplementation.js | 371 ++++-- lib/build/recipe/webauthn/types.d.ts | 1003 ++++++++++------- lib/build/recipe/webauthn/utils.d.ts | 10 +- lib/build/recipe/webauthn/utils.js | 93 +- lib/build/recipeModule.d.ts | 34 +- lib/build/recipeModule.js | 15 +- lib/build/supertokens.d.ts | 53 +- lib/build/supertokens.js | 241 ++-- lib/build/thirdpartyUtils.d.ts | 34 +- lib/build/thirdpartyUtils.js | 79 +- lib/build/types.d.ts | 20 +- lib/build/user.d.ts | 5 +- lib/build/user.js | 17 +- lib/build/utils.d.ts | 61 +- lib/build/utils.js | 112 +- 503 files changed, 15978 insertions(+), 8177 deletions(-) diff --git a/lib/build/authUtils.d.ts b/lib/build/authUtils.d.ts index 339c35930..ccbaac498 100644 --- a/lib/build/authUtils.d.ts +++ b/lib/build/authUtils.d.ts @@ -20,10 +20,14 @@ export declare const AuthUtils: { * } * ``` */ - getErrorStatusResponseWithReason(resp: { - status: string; - reason?: string; - }, errorCodeMap: Record | string | undefined>, errorStatus: T): { + getErrorStatusResponseWithReason( + resp: { + status: string; + reason?: string; + }, + errorCodeMap: Record | string | undefined>, + errorStatus: T + ): { status: T; reason: string; }; @@ -40,7 +44,19 @@ export declare const AuthUtils: { * - 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: ({ authenticatingAccountInfo, tenantId, isSignUp, isVerified, signInVerifiesLoginMethod, authenticatingUser, factorIds, skipSessionUserUpdateInCore, session, shouldTryLinkingWithSessionUser, userContext, }: { + preAuthChecks: ({ + authenticatingAccountInfo, + tenantId, + isSignUp, + isVerified, + signInVerifiesLoginMethod, + authenticatingUser, + factorIds, + skipSessionUserUpdateInCore, + session, + shouldTryLinkingWithSessionUser, + userContext, + }: { authenticatingAccountInfo: AccountInfoWithRecipeId; authenticatingUser: User | undefined; tenantId: string; @@ -52,18 +68,23 @@ export declare const AuthUtils: { session?: SessionContainerInterface | undefined; 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"; - }>; + }) => 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 @@ -81,7 +102,17 @@ export declare const AuthUtils: { * - 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: ({ authenticatedUser, recipeUserId, isSignUp, factorId, session, req, res, tenantId, userContext, }: { + postAuthChecks: ({ + authenticatedUser, + recipeUserId, + isSignUp, + factorId, + session, + req, + res, + tenantId, + userContext, + }: { authenticatedUser: User; recipeUserId: RecipeUserId; tenantId: string; @@ -91,13 +122,16 @@ export declare const AuthUtils: { userContext: UserContext; req: BaseRequest; res: BaseResponse; - }) => Promise<{ - status: "OK"; - session: SessionContainerInterface; - user: User; - } | { - status: "SIGN_IN_NOT_ALLOWED"; - }>; + }) => 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 @@ -108,42 +142,56 @@ export declare const AuthUtils: { * 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: ({ recipeId, accountInfo, checkCredentialsOnTenant, tenantId, session, userContext, }: { + getAuthenticatingUserAndAddToCurrentTenantIfRequired: ({ + recipeId, + accountInfo, + checkCredentialsOnTenant, + tenantId, + session, + userContext, + }: { 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; - }; - }; + 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; userContext: UserContext; - }) => Promise<{ - user: User; - loginMethod: LoginMethod; - } | undefined>; + }) => 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): @@ -157,24 +205,36 @@ export declare const AuthUtils: { * - 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: (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"; - }>; + checkAuthTypeAndLinkingStatus: ( + 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 @@ -189,20 +249,34 @@ export declare const AuthUtils: { * - 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: ({ tenantId, inputUser, recipeUserId, session, shouldTryLinkingWithSessionUser, userContext, }: { + linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo: ({ + tenantId, + inputUser, + recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }: { 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"; - }>; + }) => 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: @@ -213,14 +287,22 @@ export declare const AuthUtils: { * * It throws INVALID_CLAIM_ERROR if shouldDoAutomaticAccountLinking returned `{ shouldAutomaticallyLink: false }` but the email verification status was wrong */ - tryAndMakeSessionUserIntoAPrimaryUser: (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"; - }>; + tryAndMakeSessionUserIntoAPrimaryUser: ( + 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 * @@ -234,22 +316,45 @@ export declare const AuthUtils: { * - 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: ({ linkingToSessionUserRequiresVerification, authLoginMethod, authenticatedUser, sessionUser, userContext, }: { + tryLinkingBySession: ({ + linkingToSessionUserRequiresVerification, + authLoginMethod, + authenticatedUser, + sessionUser, + userContext, + }: { 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: (factorIds: string[], tenantId: string, hasSession: boolean, userContext: UserContext) => Promise; - loadSessionInAuthAPIIfNeeded: (req: BaseRequest, res: BaseResponse, shouldTryLinkingWithSessionUser: boolean | undefined, userContext: UserContext) => Promise; + }) => 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: ( + factorIds: string[], + tenantId: string, + hasSession: boolean, + userContext: UserContext + ) => Promise; + loadSessionInAuthAPIIfNeeded: ( + req: BaseRequest, + res: BaseResponse, + shouldTryLinkingWithSessionUser: boolean | undefined, + userContext: UserContext + ) => Promise; }; diff --git a/lib/build/authUtils.js b/lib/build/authUtils.js index 84a29bb06..b03e8cb94 100644 --- a/lib/build/authUtils.js +++ b/lib/build/authUtils.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthUtils = void 0; const recipe_1 = __importDefault(require("./recipe/accountlinking/recipe")); @@ -41,8 +43,7 @@ exports.AuthUtils = { status: errorStatus, reason: reasons, }; - } - else if (typeof reasons === "object" && resp.reason !== undefined) { + } else if (typeof reasons === "object" && resp.reason !== undefined) { if (reasons[resp.reason]) { return { status: errorStatus, @@ -67,7 +68,19 @@ exports.AuthUtils = { * - 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: async function ({ authenticatingAccountInfo, tenantId, isSignUp, isVerified, signInVerifiesLoginMethod, authenticatingUser, factorIds, skipSessionUserUpdateInCore, session, shouldTryLinkingWithSessionUser, userContext, }) { + preAuthChecks: async function ({ + authenticatingAccountInfo, + tenantId, + isSignUp, + isVerified, + signInVerifiesLoginMethod, + authenticatingUser, + factorIds, + skipSessionUserUpdateInCore, + session, + shouldTryLinkingWithSessionUser, + userContext, + }) { let validFactorIds; // This would be an implementation error on our part, we only check it because TS doesn't if (factorIds.length === 0) { @@ -77,7 +90,14 @@ exports.AuthUtils = { // First we check if the app intends to link the inputUser or not, // to decide if this is a first factor auth or not and if it'll link to the session user // We also load the session user here if it is available. - const authTypeInfo = await exports.AuthUtils.checkAuthTypeAndLinkingStatus(session, shouldTryLinkingWithSessionUser, authenticatingAccountInfo, authenticatingUser, skipSessionUserUpdateInCore, userContext); + const authTypeInfo = await exports.AuthUtils.checkAuthTypeAndLinkingStatus( + session, + shouldTryLinkingWithSessionUser, + authenticatingAccountInfo, + authenticatingUser, + skipSessionUserUpdateInCore, + userContext + ); if (authTypeInfo.status !== "OK") { logger_1.logDebugMessage(`preAuthChecks returning ${authTypeInfo.status} from checkAuthType results`); return authTypeInfo; @@ -92,47 +112,66 @@ exports.AuthUtils = { // The filtered list can be used to select email templates. As an example: // If the flowType for passwordless is USER_INPUT_CODE_AND_MAGIC_LINK and firstFactors for the tenant we only have otp-email // then we do not want to include a link in the email. - const validFirstFactors = await exports.AuthUtils.filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid(factorIds, tenantId, session !== undefined, userContext); + const validFirstFactors = await exports.AuthUtils.filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid( + factorIds, + tenantId, + session !== undefined, + userContext + ); validFactorIds = validFirstFactors; - } - else { + } else { logger_1.logDebugMessage("preAuthChecks getting valid secondary factors"); // In this case the app will try to link the session user and the authenticating user after auth, // so we need to check if this is allowed by the MFA recipe (if initialized). - validFactorIds = await filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid(factorIds, authTypeInfo.inputUserAlreadyLinkedToSessionUser, authTypeInfo.sessionUser, session, userContext); + validFactorIds = await filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid( + factorIds, + authTypeInfo.inputUserAlreadyLinkedToSessionUser, + authTypeInfo.sessionUser, + session, + userContext + ); } if (!isSignUp && authenticatingUser === undefined) { - throw new Error("This should never happen: preAuthChecks called with isSignUp: false, authenticatingUser: undefined"); + throw new Error( + "This should never happen: preAuthChecks called with isSignUp: false, authenticatingUser: undefined" + ); } // If this is a sign up we check that the sign up is allowed if (isSignUp) { // We need this check in case the session user has verified an email address and now tries to add a password for it. - let verifiedInSessionUser = !authTypeInfo.isFirstFactor && - authTypeInfo.sessionUser.loginMethods.some((lm) => lm.verified && - (lm.hasSameEmailAs(authenticatingAccountInfo.email) || - lm.hasSamePhoneNumberAs(authenticatingAccountInfo.phoneNumber))); + let verifiedInSessionUser = + !authTypeInfo.isFirstFactor && + authTypeInfo.sessionUser.loginMethods.some( + (lm) => + lm.verified && + (lm.hasSameEmailAs(authenticatingAccountInfo.email) || + lm.hasSamePhoneNumberAs(authenticatingAccountInfo.phoneNumber)) + ); logger_1.logDebugMessage("preAuthChecks checking if the user is allowed to sign up"); - if (!(await recipe_1.default.getInstance().isSignUpAllowed({ - newUser: authenticatingAccountInfo, - isVerified: isVerified || signInVerifiesLoginMethod || verifiedInSessionUser, - tenantId, - session, - userContext, - }))) { + if ( + !(await recipe_1.default.getInstance().isSignUpAllowed({ + newUser: authenticatingAccountInfo, + isVerified: isVerified || signInVerifiesLoginMethod || verifiedInSessionUser, + tenantId, + session, + userContext, + })) + ) { return { status: "SIGN_UP_NOT_ALLOWED" }; } - } - else if (authenticatingUser !== undefined) { + } else if (authenticatingUser !== undefined) { // for sign ins, this is checked after the credentials have been verified logger_1.logDebugMessage("preAuthChecks checking if the user is allowed to sign in"); - if (!(await recipe_1.default.getInstance().isSignInAllowed({ - user: authenticatingUser, - accountInfo: authenticatingAccountInfo, - signInVerifiesLoginMethod, - tenantId, - session, - userContext, - }))) { + if ( + !(await recipe_1.default.getInstance().isSignInAllowed({ + user: authenticatingUser, + accountInfo: authenticatingAccountInfo, + signInVerifiesLoginMethod, + tenantId, + session, + userContext, + })) + ) { return { status: "SIGN_IN_NOT_ALLOWED" }; } } @@ -161,12 +200,28 @@ exports.AuthUtils = { * - 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: async function ({ authenticatedUser, recipeUserId, isSignUp, factorId, session, req, res, tenantId, userContext, }) { - logger_1.logDebugMessage(`postAuthChecks called ${session !== undefined ? "with" : "without"} a session to ${isSignUp ? "sign in" : "sign up"} with ${factorId}`); + postAuthChecks: async function ({ + authenticatedUser, + recipeUserId, + isSignUp, + factorId, + session, + req, + res, + tenantId, + userContext, + }) { + logger_1.logDebugMessage( + `postAuthChecks called ${session !== undefined ? "with" : "without"} a session to ${ + isSignUp ? "sign in" : "sign up" + } with ${factorId}` + ); const mfaInstance = recipe_2.default.getInstance(); let respSession = session; if (session !== undefined) { - const authenticatedUserLinkedToSessionUser = authenticatedUser.loginMethods.some((lm) => lm.recipeUserId.getAsString() === session.getRecipeUserId(userContext).getAsString()); + const authenticatedUserLinkedToSessionUser = authenticatedUser.loginMethods.some( + (lm) => lm.recipeUserId.getAsString() === session.getRecipeUserId(userContext).getAsString() + ); if (authenticatedUserLinkedToSessionUser) { logger_1.logDebugMessage(`postAuthChecks session and input user got linked`); if (mfaInstance !== undefined) { @@ -175,28 +230,48 @@ exports.AuthUtils = { // we mark it as completed in the session. await multifactorauth_1.default.markFactorAsCompleteInSession(respSession, factorId, userContext); } - } - else { + } else { logger_1.logDebugMessage(`postAuthChecks checking overwriteSessionDuringSignInUp`); // If the new user wasn't linked to the current one, we check the config and overwrite the session if required // Note: we could also get here if MFA is enabled, but the app didn't want to link the user to the session user. // This is intentional, since the MFA and overwriteSessionDuringSignInUp configs should work independently. - let overwriteSessionDuringSignInUp = recipe_3.default.getInstanceOrThrowError().getNormalisedOverwriteSessionDuringSignInUp(req); + let overwriteSessionDuringSignInUp = recipe_3.default + .getInstanceOrThrowError() + .getNormalisedOverwriteSessionDuringSignInUp(req); if (overwriteSessionDuringSignInUp) { - respSession = await session_1.default.createNewSession(req, res, tenantId, recipeUserId, {}, {}, userContext); + respSession = await session_1.default.createNewSession( + req, + res, + tenantId, + recipeUserId, + {}, + {}, + userContext + ); if (mfaInstance !== undefined) { - await multifactorauth_1.default.markFactorAsCompleteInSession(respSession, factorId, userContext); + await multifactorauth_1.default.markFactorAsCompleteInSession( + respSession, + factorId, + userContext + ); } } } - } - else { + } else { // We do not have to care about overwriting the session here, since we either: // - have overwriteSessionDuringSignInUp true and we didn't even try to load the session because we ignore it anyway // - have overwriteSessionDuringSignInUp false and we checked in the api imlp that there is no session logger_1.logDebugMessage(`postAuthChecks creating session for first factor sign in/up`); // If there is no input session, we do not need to do anything other checks and create a new session - respSession = await session_1.default.createNewSession(req, res, tenantId, recipeUserId, {}, {}, userContext); + respSession = await session_1.default.createNewSession( + req, + res, + tenantId, + recipeUserId, + {}, + {}, + userContext + ); // Here we can always mark the factor as completed, since we just created the session if (mfaInstance !== undefined) { await multifactorauth_1.default.markFactorAsCompleteInSession(respSession, factorId, userContext); @@ -214,29 +289,47 @@ exports.AuthUtils = { * 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: async ({ recipeId, accountInfo, checkCredentialsOnTenant, tenantId, session, userContext, }) => { + getAuthenticatingUserAndAddToCurrentTenantIfRequired: async ({ + recipeId, + accountInfo, + checkCredentialsOnTenant, + tenantId, + session, + userContext, + }) => { let i = 0; while (i++ < 300) { - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired called with ${JSON.stringify(accountInfo)}`); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired called with ${JSON.stringify(accountInfo)}` + ); const existingUsers = await recipe_1.default.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, accountInfo, doUnionOfAccountInfo: true, userContext: userContext, }); - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired got ${existingUsers.length} users from the core resp`); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired got ${existingUsers.length} users from the core resp` + ); const usersWithMatchingLoginMethods = existingUsers .map((user) => ({ - user, - loginMethod: user.loginMethods.find((lm) => lm.recipeId === recipeId && - ((accountInfo.email !== undefined && lm.hasSameEmailAs(accountInfo.email)) || - lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || - lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty))), - })) + user, + loginMethod: user.loginMethods.find( + (lm) => + lm.recipeId === recipeId && + ((accountInfo.email !== undefined && lm.hasSameEmailAs(accountInfo.email)) || + lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || + lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) + ), + })) .filter(({ loginMethod }) => loginMethod !== undefined); - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired got ${usersWithMatchingLoginMethods.length} users with matching login methods`); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired got ${usersWithMatchingLoginMethods.length} users with matching login methods` + ); if (usersWithMatchingLoginMethods.length > 1) { - throw new Error("You have found a bug. Please report it on https://github.com/supertokens/supertokens-node/issues"); + throw new Error( + "You have found a bug. Please report it on https://github.com/supertokens/supertokens-node/issues" + ); } let authenticatingUser = usersWithMatchingLoginMethods[0]; if (authenticatingUser === undefined && session !== undefined) { @@ -252,16 +345,25 @@ exports.AuthUtils = { // and we know that that login method is associated with the current tenant. // This means that the user has no loginMethods we need to check (that only belong to other tenantIds) if (!sessionUser.isPrimaryUser) { - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired session user is non-primary so returning early without checking other tenants`); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired session user is non-primary so returning early without checking other tenants` + ); return undefined; } - const matchingLoginMethodsFromSessionUser = sessionUser.loginMethods.filter((lm) => lm.recipeId === recipeId && - (lm.hasSameEmailAs(accountInfo.email) || - lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || - lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty))); - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired session has ${matchingLoginMethodsFromSessionUser.length} matching login methods`); + const matchingLoginMethodsFromSessionUser = sessionUser.loginMethods.filter( + (lm) => + lm.recipeId === recipeId && + (lm.hasSameEmailAs(accountInfo.email) || + lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || + lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) + ); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired session has ${matchingLoginMethodsFromSessionUser.length} matching login methods` + ); if (matchingLoginMethodsFromSessionUser.some((lm) => lm.tenantIds.includes(tenantId))) { - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired session has ${matchingLoginMethodsFromSessionUser.length} matching login methods`); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired session has ${matchingLoginMethodsFromSessionUser.length} matching login methods` + ); // This can happen in a race condition where a user was created and linked with the session user // between listing the existing users and loading the session user // We can return early, this only means that someone did the same sharing this function was aiming to do @@ -273,21 +375,33 @@ exports.AuthUtils = { } let goToRetry = false; for (const lm of matchingLoginMethodsFromSessionUser) { - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired session checking credentials on ${lm.tenantIds[0]}`); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired session checking credentials on ${lm.tenantIds[0]}` + ); if (await checkCredentialsOnTenant(lm.tenantIds[0])) { - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired associating user from ${lm.tenantIds[0]} with current tenant`); - const associateRes = await multitenancy_1.default.associateUserToTenant(tenantId, lm.recipeUserId, userContext); - logger_1.logDebugMessage(`getAuthenticatingUserAndAddToCurrentTenantIfRequired associating returned ${associateRes.status}`); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired associating user from ${lm.tenantIds[0]} with current tenant` + ); + const associateRes = await multitenancy_1.default.associateUserToTenant( + tenantId, + lm.recipeUserId, + userContext + ); + logger_1.logDebugMessage( + `getAuthenticatingUserAndAddToCurrentTenantIfRequired associating returned ${associateRes.status}` + ); if (associateRes.status === "OK") { // We know that this is what happens lm.tenantIds.push(tenantId); return { user: sessionUser, loginMethod: lm }; } - if (associateRes.status === "UNKNOWN_USER_ID_ERROR" || // This means that the recipe user was deleted + if ( + associateRes.status === "UNKNOWN_USER_ID_ERROR" || // This means that the recipe user was deleted // All below conditions mean that both the account list and the session user we loaded is outdated associateRes.status === "EMAIL_ALREADY_EXISTS_ERROR" || associateRes.status === "PHONE_NUMBER_ALREADY_EXISTS_ERROR" || - associateRes.status === "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR") { + associateRes.status === "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" + ) { // In these cases we retry, because we know some info we are using is outdated // while some of these cases we could handle locally, it's cleaner to restart the process. goToRetry = true; @@ -312,7 +426,9 @@ exports.AuthUtils = { } return authenticatingUser; } - throw new Error("This should never happen: ran out of retries for getAuthenticatingUserAndAddToCurrentTenantIfRequired"); + throw new Error( + "This should never happen: ran out of retries for getAuthenticatingUserAndAddToCurrentTenantIfRequired" + ); }, /** * This function checks if the current authentication attempt should be considered a first factor or not. @@ -327,7 +443,14 @@ exports.AuthUtils = { * - 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: async function (session, shouldTryLinkingWithSessionUser, accountInfo, inputUser, skipSessionUserUpdateInCore, userContext) { + checkAuthTypeAndLinkingStatus: async function ( + session, + shouldTryLinkingWithSessionUser, + accountInfo, + inputUser, + skipSessionUserUpdateInCore, + userContext + ) { logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus called`); let sessionUser = undefined; if (session === undefined) { @@ -337,28 +460,35 @@ exports.AuthUtils = { message: "Session not found but shouldTryLinkingWithSessionUser is true", }); } - logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus returning first factor because there is no session`); + logger_1.logDebugMessage( + `checkAuthTypeAndLinkingStatus returning first factor because there is no session` + ); // If there is no active session we have nothing to link to - so this has to be a first factor sign in return { status: "OK", isFirstFactor: true }; - } - else { + } else { if (shouldTryLinkingWithSessionUser === false) { - logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus returning first factor because shouldTryLinkingWithSessionUser is false`); + logger_1.logDebugMessage( + `checkAuthTypeAndLinkingStatus returning first factor because shouldTryLinkingWithSessionUser is false` + ); // In our normal flows this should never happen - but some user overrides might do this. // Anyway, since shouldTryLinkingWithSessionUser explicitly set to false, it's safe to consider this a firstFactor return { status: "OK", isFirstFactor: true }; } if (!utils_3.recipeInitDefinedShouldDoAutomaticAccountLinking(recipe_1.default.getInstance().config)) { if (shouldTryLinkingWithSessionUser === true) { - throw new Error("Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA"); - } - else { + throw new Error( + "Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA" + ); + } else { // This is the legacy case where shouldTryLinkingWithSessionUser is undefined if (recipe_2.default.getInstance() !== undefined) { - throw new Error("Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA"); - } - else { - logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus (legacy behaviour) returning first factor because MFA is not initialised and shouldDoAutomaticAccountLinking is not defined`); + throw new Error( + "Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA" + ); + } else { + logger_1.logDebugMessage( + `checkAuthTypeAndLinkingStatus (legacy behaviour) returning first factor because MFA is not initialised and shouldDoAutomaticAccountLinking is not defined` + ); return { status: "OK", isFirstFactor: true }; } } @@ -370,7 +500,9 @@ exports.AuthUtils = { // - MFA may or may not be initialized // If the input and the session user are the same if (inputUser !== undefined && inputUser.id === session.getUserId()) { - logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus returning secondary factor, session and input user are the same`); + logger_1.logDebugMessage( + `checkAuthTypeAndLinkingStatus returning secondary factor, session and input user are the same` + ); // Then this is basically a user logging in with an already linked secondary account // Which is basically a factor completion in MFA terms. // Since the sessionUser and the inputUser are the same in this case, we can just return early @@ -381,14 +513,23 @@ exports.AuthUtils = { sessionUser: inputUser, }; } - logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus loading session user, ${inputUser === null || inputUser === void 0 ? void 0 : inputUser.id} === ${session.getUserId()}`); + logger_1.logDebugMessage( + `checkAuthTypeAndLinkingStatus loading session user, ${ + inputUser === null || inputUser === void 0 ? void 0 : inputUser.id + } === ${session.getUserId()}` + ); // We have to load the session user in order to get the account linking info - const sessionUserResult = await exports.AuthUtils.tryAndMakeSessionUserIntoAPrimaryUser(session, skipSessionUserUpdateInCore, userContext); + const sessionUserResult = await exports.AuthUtils.tryAndMakeSessionUserIntoAPrimaryUser( + session, + skipSessionUserUpdateInCore, + userContext + ); if (sessionUserResult.status === "SHOULD_AUTOMATICALLY_LINK_FALSE") { if (shouldTryLinkingWithSessionUser === true) { // tryAndMakeSessionUserIntoAPrimaryUser throws if it is an email verification iss throw new _1.Error({ - message: "shouldDoAutomaticAccountLinking returned false when making the session user primary but shouldTryLinkingWithSessionUser is true", + message: + "shouldDoAutomaticAccountLinking returned false when making the session user primary but shouldTryLinkingWithSessionUser is true", type: "BAD_INPUT_ERROR", }); } @@ -396,8 +537,9 @@ exports.AuthUtils = { status: "OK", isFirstFactor: true, }; - } - else if (sessionUserResult.status === "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { + } else if ( + sessionUserResult.status === "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + ) { return { status: "LINKING_TO_SESSION_USER_FAILED", reason: "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", @@ -406,18 +548,30 @@ exports.AuthUtils = { sessionUser = sessionUserResult.sessionUser; // We check if the app intends to link these two accounts // Note: in some cases if the accountInfo already belongs to a primary user - const shouldLink = await recipe_1.default.getInstance().config.shouldDoAutomaticAccountLinking(accountInfo, sessionUser, session, session.getTenantId(), userContext); - logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus session user <-> input user shouldDoAutomaticAccountLinking returned ${JSON.stringify(shouldLink)}`); + const shouldLink = await recipe_1.default + .getInstance() + .config.shouldDoAutomaticAccountLinking( + accountInfo, + sessionUser, + session, + session.getTenantId(), + userContext + ); + logger_1.logDebugMessage( + `checkAuthTypeAndLinkingStatus session user <-> input user shouldDoAutomaticAccountLinking returned ${JSON.stringify( + shouldLink + )}` + ); if (shouldLink.shouldAutomaticallyLink === false) { if (shouldTryLinkingWithSessionUser === true) { throw new _1.Error({ - message: "shouldDoAutomaticAccountLinking returned false when making the session user primary but shouldTryLinkingWithSessionUser is true", + message: + "shouldDoAutomaticAccountLinking returned false when making the session user primary but shouldTryLinkingWithSessionUser is true", type: "BAD_INPUT_ERROR", }); } return { status: "OK", isFirstFactor: true }; - } - else { + } else { return { status: "OK", isFirstFactor: false, @@ -442,7 +596,14 @@ exports.AuthUtils = { * - 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: async function ({ tenantId, inputUser, recipeUserId, session, shouldTryLinkingWithSessionUser, userContext, }) { + linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo: async function ({ + tenantId, + inputUser, + recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }) { logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo called"); const retry = () => { logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo retrying...."); @@ -457,20 +618,35 @@ exports.AuthUtils = { }; // If we got here, we have a session and a primary session user // We can not assume the inputUser is non-primary, since we'll only check that seeing if the app wants to link to the session user or not. - const authLoginMethod = inputUser.loginMethods.find((lm) => lm.recipeUserId.getAsString() === recipeUserId.getAsString()); + const authLoginMethod = inputUser.loginMethods.find( + (lm) => lm.recipeUserId.getAsString() === recipeUserId.getAsString() + ); if (authLoginMethod === undefined) { - throw new Error("This should never happen: the recipeUserId and user is inconsistent in createPrimaryUserIdOrLinkByAccountInfo params"); + throw new Error( + "This should never happen: the recipeUserId and user is inconsistent in createPrimaryUserIdOrLinkByAccountInfo params" + ); } - const authTypeRes = await exports.AuthUtils.checkAuthTypeAndLinkingStatus(session, shouldTryLinkingWithSessionUser, authLoginMethod, inputUser, false, userContext); + const authTypeRes = await exports.AuthUtils.checkAuthTypeAndLinkingStatus( + session, + shouldTryLinkingWithSessionUser, + authLoginMethod, + inputUser, + false, + userContext + ); if (authTypeRes.status !== "OK") { return authTypeRes; } if (authTypeRes.isFirstFactor) { if (!utils_3.recipeInitDefinedShouldDoAutomaticAccountLinking(recipe_1.default.getInstance().config)) { - logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo skipping link by account info because this is a first factor auth and the app hasn't defined shouldDoAutomaticAccountLinking"); + logger_1.logDebugMessage( + "linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo skipping link by account info because this is a first factor auth and the app hasn't defined shouldDoAutomaticAccountLinking" + ); return { status: "OK", user: inputUser }; } - logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo trying to link by account info because this is a first factor auth"); + logger_1.logDebugMessage( + "linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo trying to link by account info because this is a first factor auth" + ); // We try and list all users that can be linked to the input user based on the account info // later we can use these when trying to link or when checking if linking to the session user is possible. const linkRes = await recipe_1.default.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ @@ -493,7 +669,9 @@ exports.AuthUtils = { user: authTypeRes.sessionUser, }; } - logger_1.logDebugMessage("linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo trying to link by session info"); + logger_1.logDebugMessage( + "linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo trying to link by session info" + ); const sessionLinkingRes = await exports.AuthUtils.tryLinkingBySession({ sessionUser: authTypeRes.sessionUser, authenticatedUser: inputUser, @@ -506,12 +684,10 @@ exports.AuthUtils = { // This means that although we made the session user primary above, some race condition undid that (e.g.: calling unlink concurrently with this func) // We can retry in this case, since we start by trying to make it into a primary user and throwing if we can't return retry(); - } - else { + } else { return sessionLinkingRes; } - } - else { + } else { // If we get here the status is OK, so we can just return it return sessionLinkingRes; } @@ -539,38 +715,62 @@ exports.AuthUtils = { logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser session user already primary`); // if the session user was already primary we can just return it return { status: "OK", sessionUser }; - } - else { + } else { // if the session user is not primary we try and make it one logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser not primary user yet`); // We could check here if the session user can even become a primary user, but that'd only mean one extra core call // without any added benefits, since the core already checks all pre-conditions // We do this check here instead of using the shouldBecomePrimaryUser util, because // here we handle the shouldRequireVerification case differently - const shouldDoAccountLinking = await recipe_1.default.getInstance().config.shouldDoAutomaticAccountLinking(sessionUser.loginMethods[0], undefined, session, session.getTenantId(userContext), userContext); - logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser shouldDoAccountLinking: ${JSON.stringify(shouldDoAccountLinking)}`); + const shouldDoAccountLinking = await recipe_1.default + .getInstance() + .config.shouldDoAutomaticAccountLinking( + sessionUser.loginMethods[0], + undefined, + session, + session.getTenantId(userContext), + userContext + ); + logger_1.logDebugMessage( + `tryAndMakeSessionUserIntoAPrimaryUser shouldDoAccountLinking: ${JSON.stringify( + shouldDoAccountLinking + )}` + ); if (shouldDoAccountLinking.shouldAutomaticallyLink) { if (skipSessionUserUpdateInCore) { return { status: "OK", sessionUser: sessionUser }; } if (shouldDoAccountLinking.shouldRequireVerification && !sessionUser.loginMethods[0].verified) { // We force-update the claim value if it is not set or different from what we just fetched from the DB - if ((await session.getClaimValue(emailverification_1.EmailVerificationClaim, userContext)) !== false) { - logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser updating emailverification status in session`); + if ( + (await session.getClaimValue(emailverification_1.EmailVerificationClaim, userContext)) !== false + ) { + logger_1.logDebugMessage( + `tryAndMakeSessionUserIntoAPrimaryUser updating emailverification status in session` + ); // This will let the frontend know if the value has been updated in the background await session.setClaimValue(emailverification_1.EmailVerificationClaim, false, userContext); } logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser throwing validation error`); // Then run the validation expecting it to fail. We run assertClaims instead of throwing the error locally // to make sure the error shape in the response will match what we'd return normally - await session.assertClaims([emailverification_1.EmailVerificationClaim.validators.isVerified()], userContext); - throw new Error("This should never happen: email verification claim validator passed after setting value to false"); + await session.assertClaims( + [emailverification_1.EmailVerificationClaim.validators.isVerified()], + userContext + ); + throw new Error( + "This should never happen: email verification claim validator passed after setting value to false" + ); } - const createPrimaryUserRes = await recipe_1.default.getInstance().recipeInterfaceImpl.createPrimaryUser({ - recipeUserId: sessionUser.loginMethods[0].recipeUserId, - userContext, - }); - logger_1.logDebugMessage(`tryAndMakeSessionUserIntoAPrimaryUser createPrimaryUser returned ${createPrimaryUserRes.status}`); + const createPrimaryUserRes = await recipe_1.default + .getInstance() + .recipeInterfaceImpl.createPrimaryUser({ + recipeUserId: sessionUser.loginMethods[0].recipeUserId, + userContext, + }); + logger_1.logDebugMessage( + `tryAndMakeSessionUserIntoAPrimaryUser createPrimaryUser returned ${createPrimaryUserRes.status}` + ); if (createPrimaryUserRes.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR") { // This means that the session user got primary since we loaded the session user info above // but this status means that the user id has also changed, so the session should be invalid @@ -578,17 +778,14 @@ exports.AuthUtils = { type: error_1.default.UNAUTHORISED, message: "Session user not found", }); - } - else if (createPrimaryUserRes.status === "OK") { + } else if (createPrimaryUserRes.status === "OK") { return { status: "OK", sessionUser: createPrimaryUserRes.user }; - } - else { + } else { // All other statuses signify that we can't make the session user primary // Which means we can't continue return createPrimaryUserRes; } - } - else { + } else { // This means that the app doesn't want to make the session user primary return { status: "SHOULD_AUTOMATICALLY_LINK_FALSE" }; } @@ -607,7 +804,13 @@ exports.AuthUtils = { * - 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: async function ({ linkingToSessionUserRequiresVerification, authLoginMethod, authenticatedUser, sessionUser, userContext, }) { + tryLinkingBySession: async function ({ + linkingToSessionUserRequiresVerification, + authLoginMethod, + authenticatedUser, + sessionUser, + userContext, + }) { logger_1.logDebugMessage("tryLinkingBySession called"); // If the input user has another user (and it's not the session user) it could be linked to based on account info then we can't link it to the session user. // However, we do not need to check this as the linkAccounts check will fail anyway and we do not want the extra core call in case it succeeds @@ -615,9 +818,13 @@ exports.AuthUtils = { // then we don't want to ask them to verify it again. // This is different from linking based on account info, but the presence of a session shows that the user has access to both accounts, // and intends to link these two accounts. - const sessionUserHasVerifiedAccountInfo = sessionUser.loginMethods.some((lm) => (lm.hasSameEmailAs(authLoginMethod.email) || lm.hasSamePhoneNumberAs(authLoginMethod.phoneNumber)) && - lm.verified); - const canLinkBasedOnVerification = !linkingToSessionUserRequiresVerification || authLoginMethod.verified || sessionUserHasVerifiedAccountInfo; + const sessionUserHasVerifiedAccountInfo = sessionUser.loginMethods.some( + (lm) => + (lm.hasSameEmailAs(authLoginMethod.email) || lm.hasSamePhoneNumberAs(authLoginMethod.phoneNumber)) && + lm.verified + ); + const canLinkBasedOnVerification = + !linkingToSessionUserRequiresVerification || authLoginMethod.verified || sessionUserHasVerifiedAccountInfo; if (!canLinkBasedOnVerification) { return { status: "LINKING_TO_SESSION_USER_FAILED", reason: "EMAIL_VERIFICATION_REQUIRED" }; } @@ -632,22 +839,25 @@ exports.AuthUtils = { if (linkAccountsResult.status === "OK") { logger_1.logDebugMessage("tryLinkingBySession successfully linked input user to session user"); return { status: "OK", user: linkAccountsResult.user }; - } - else if (linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { + } else if (linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { // this can happen because of a race condition wherein the recipe user ID get's linked to // some other primary user whilst the linking is going on. - logger_1.logDebugMessage("tryLinkingBySession linking to session user failed because of a race condition - input user linked to another user"); + logger_1.logDebugMessage( + "tryLinkingBySession linking to session user failed because of a race condition - input user linked to another user" + ); return { status: "LINKING_TO_SESSION_USER_FAILED", reason: linkAccountsResult.status }; - } - else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { - logger_1.logDebugMessage("tryLinkingBySession linking to session user failed because of a race condition - INPUT_USER_IS_NOT_A_PRIMARY_USER, should retry"); + } else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { + logger_1.logDebugMessage( + "tryLinkingBySession linking to session user failed because of a race condition - INPUT_USER_IS_NOT_A_PRIMARY_USER, should retry" + ); // This can be possible during a race condition wherein the primary user we created above // is somehow no more a primary user. This can happen if the unlink function was called in parallel // on that user. We can just retry, as that will try and make it a primary user again. return { status: "LINKING_TO_SESSION_USER_FAILED", reason: linkAccountsResult.status }; - } - else { - logger_1.logDebugMessage("tryLinkingBySession linking to session user failed because of a race condition - input user has another primary user it can be linked to"); + } else { + logger_1.logDebugMessage( + "tryLinkingBySession linking to session user failed because of a race condition - input user has another primary user it can be linked to" + ); // Status can only be "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" // It can come here if the recipe user ID can't be linked to the primary user ID because the email / phone number is associated with // some other primary user ID. @@ -667,12 +877,10 @@ exports.AuthUtils = { type: error_1.default.UNAUTHORISED, message: "Tenant not found", }); - } - else { + } else { throw new Error("Tenant not found error."); } - } - else if (validRes.status === "OK") { + } else if (validRes.status === "OK") { validFactorIds.push(id); } } @@ -682,44 +890,72 @@ exports.AuthUtils = { type: error_1.default.UNAUTHORISED, message: "A valid session is required to authenticate with secondary factors", }); - } - else { + } else { throw new error_2.default({ type: error_2.default.BAD_INPUT_ERROR, - message: "First factor sign in/up called for a non-first factor with an active session. This might indicate that you are trying to use this as a secondary factor, but disabled account linking.", + message: + "First factor sign in/up called for a non-first factor with an active session. This might indicate that you are trying to use this as a secondary factor, but disabled account linking.", }); } } return validFactorIds; }, loadSessionInAuthAPIIfNeeded: async function (req, res, shouldTryLinkingWithSessionUser, userContext) { - const overwriteSessionDuringSignInUp = recipe_3.default.getInstanceOrThrowError().getNormalisedOverwriteSessionDuringSignInUp(req); + const overwriteSessionDuringSignInUp = recipe_3.default + .getInstanceOrThrowError() + .getNormalisedOverwriteSessionDuringSignInUp(req); if (shouldTryLinkingWithSessionUser !== false) { - logger_1.logDebugMessage("loadSessionInAuthAPIIfNeeded: loading session because shouldTryLinkingWithSessionUser is not set to false so we may want to link later"); - return await session_1.default.getSession(req, res, { - // This is optional only if shouldTryLinkingWithSessionUser is undefined - // in the (old) 3.0 FDI, this flag didn't exist and we linking was based on the session presence and shouldDoAutomaticAccountLinking - sessionRequired: shouldTryLinkingWithSessionUser === true, - overrideGlobalClaimValidators: () => [], - }, userContext); + logger_1.logDebugMessage( + "loadSessionInAuthAPIIfNeeded: loading session because shouldTryLinkingWithSessionUser is not set to false so we may want to link later" + ); + return await session_1.default.getSession( + req, + res, + { + // This is optional only if shouldTryLinkingWithSessionUser is undefined + // in the (old) 3.0 FDI, this flag didn't exist and we linking was based on the session presence and shouldDoAutomaticAccountLinking + sessionRequired: shouldTryLinkingWithSessionUser === true, + overrideGlobalClaimValidators: () => [], + }, + userContext + ); } if (overwriteSessionDuringSignInUp === false) { - logger_1.logDebugMessage("loadSessionInAuthAPIIfNeeded: loading session in optional mode because overwriteSessionDuringSignInUp is false so if it is not found we will skip session creation"); - return await session_1.default.getSession(req, res, { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, userContext); + logger_1.logDebugMessage( + "loadSessionInAuthAPIIfNeeded: loading session in optional mode because overwriteSessionDuringSignInUp is false so if it is not found we will skip session creation" + ); + return await session_1.default.getSession( + req, + res, + { + sessionRequired: false, + overrideGlobalClaimValidators: () => [], + }, + userContext + ); } - logger_1.logDebugMessage("loadSessionInAuthAPIIfNeeded: skipping session loading because we are not linking and we would overwrite it anyway"); + logger_1.logDebugMessage( + "loadSessionInAuthAPIIfNeeded: skipping session loading because we are not linking and we would overwrite it anyway" + ); return undefined; }, }; -async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid(factorIds, inputUserAlreadyLinkedToSessionUser, sessionUser, session, userContext) { +async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid( + factorIds, + inputUserAlreadyLinkedToSessionUser, + sessionUser, + session, + userContext +) { if (session === undefined) { - throw new Error("This should never happen: filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called without a session"); + throw new Error( + "This should never happen: filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called without a session" + ); } if (sessionUser === undefined) { - throw new Error("This should never happen: filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called without a sessionUser"); + throw new Error( + "This should never happen: filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called without a sessionUser" + ); } logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid called for ${factorIds.join(", ")}`); const mfaInstance = recipe_2.default.getInstance(); @@ -754,7 +990,9 @@ async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid(factorIds, in }; // If we are linking the input user to the session user, then we need to check if MFA allows it // From an MFA perspective this is a factor setup - logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid checking if linking is allowed by the mfa recipe`); + logger_1.logDebugMessage( + `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid checking if linking is allowed by the mfa recipe` + ); let caughtSetupFactorError; const validFactorIds = []; // In all apis besides passwordless createCode, we know exactly which factor we are signing into, so in those cases, @@ -765,35 +1003,46 @@ async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid(factorIds, in // If the flowType for passwordless is USER_INPUT_CODE_AND_MAGIC_LINK and but only the otp-email factor is allowed to be set up // then we do not want to include a link in the email. for (const id of factorIds) { - logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid checking assertAllowedToSetupFactorElseThrowInvalidClaimError`); + logger_1.logDebugMessage( + `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid checking assertAllowedToSetupFactorElseThrowInvalidClaimError` + ); try { memoizedAllowedToSetupFactorInput.factorId = id; - await mfaInstance.recipeInterfaceImpl.assertAllowedToSetupFactorElseThrowInvalidClaimError(memoizedAllowedToSetupFactorInput); - logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid ${id} valid because assertAllowedToSetupFactorElseThrowInvalidClaimError passed`); + await mfaInstance.recipeInterfaceImpl.assertAllowedToSetupFactorElseThrowInvalidClaimError( + memoizedAllowedToSetupFactorInput + ); + logger_1.logDebugMessage( + `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid ${id} valid because assertAllowedToSetupFactorElseThrowInvalidClaimError passed` + ); // we add it to the valid factor ids list since it is either already set up or allowed to be set up validFactorIds.push(id); - } - catch (err) { - logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid assertAllowedToSetupFactorElseThrowInvalidClaimError failed for ${id}`); + } catch (err) { + logger_1.logDebugMessage( + `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid assertAllowedToSetupFactorElseThrowInvalidClaimError failed for ${id}` + ); caughtSetupFactorError = err; } } if (validFactorIds.length === 0) { - logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid rethrowing error from assertAllowedToSetupFactorElseThrowInvalidClaimError because we found no valid factors`); + logger_1.logDebugMessage( + `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid rethrowing error from assertAllowedToSetupFactorElseThrowInvalidClaimError because we found no valid factors` + ); // we can safely re-throw this since this should be an InvalidClaimError // if it's anything else, we do not really have a way of handling it anyway. throw caughtSetupFactorError; } return validFactorIds; - } - else { + } else { // If signing in will not change the user (no linking), then we can let the sign-in/up happen (if allowed by account linking checks) - logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid allowing all factors because it'll not create new link`); + logger_1.logDebugMessage( + `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid allowing all factors because it'll not create new link` + ); return factorIds; } - } - else { - logger_1.logDebugMessage(`filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid allowing all factors because MFA is not enabled`); + } else { + logger_1.logDebugMessage( + `filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid allowing all factors because MFA is not enabled` + ); // If MFA is not enabled, we allow the user to connect any secondary account to the session user. return factorIds; } diff --git a/lib/build/combinedRemoteJWKSet.d.ts b/lib/build/combinedRemoteJWKSet.d.ts index 539a6d328..d36ad16ae 100644 --- a/lib/build/combinedRemoteJWKSet.d.ts +++ b/lib/build/combinedRemoteJWKSet.d.ts @@ -15,4 +15,7 @@ export declare function resetCombinedJWKS(): void; */ export declare function getCombinedJWKS(config: { jwksRefreshIntervalSec: number; -}): (protectedHeader?: import("jose").JWSHeaderParameters | undefined, token?: import("jose").FlattenedJWSInput | undefined) => Promise; +}): ( + protectedHeader?: import("jose").JWSHeaderParameters | undefined, + token?: import("jose").FlattenedJWSInput | undefined +) => Promise; diff --git a/lib/build/combinedRemoteJWKSet.js b/lib/build/combinedRemoteJWKSet.js index 0e5d73d2f..86d75b126 100644 --- a/lib/build/combinedRemoteJWKSet.js +++ b/lib/build/combinedRemoteJWKSet.js @@ -26,21 +26,24 @@ function getCombinedJWKS(config) { if (combinedJWKS === undefined) { const JWKS = querier_1.Querier.getNewInstanceOrThrowError(undefined) .getAllCoreUrlsForPath("/.well-known/jwks.json") - .map((url) => jose_1.createRemoteJWKSet(new URL(url), { - cacheMaxAge: config.jwksRefreshIntervalSec * 1000, - cooldownDuration: constants_1.JWKCacheCooldownInMs, - })); + .map((url) => + jose_1.createRemoteJWKSet(new URL(url), { + cacheMaxAge: config.jwksRefreshIntervalSec * 1000, + cooldownDuration: constants_1.JWKCacheCooldownInMs, + }) + ); combinedJWKS = async (...args) => { let lastError = undefined; if (JWKS.length === 0) { - throw Error("No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using."); + throw Error( + "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." + ); } for (const jwks of JWKS) { try { // We await before returning to make sure we catch the error return await jwks(...args); - } - catch (ex) { + } catch (ex) { lastError = ex; } } diff --git a/lib/build/core-mock.js b/lib/build/core-mock.js index 0d727271c..9a511d842 100644 --- a/lib/build/core-mock.js +++ b/lib/build/core-mock.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMockQuerier = void 0; const querier_1 = require("./querier"); @@ -42,11 +44,22 @@ const getMockQuerier = (recipeId) => { }); const id = crypto_1.default.randomUUID(); const now = new Date(); - writeDb("generatedOptions", id, Object.assign(Object.assign({}, registrationOptions), { id, origin: body.origin, tenantId: body.tenantId, email: body.email, rpId: registrationOptions.rp.id, createdAt: now.getTime(), expiresAt: now.getTime() + body.timeout * 1000 })); + writeDb( + "generatedOptions", + id, + Object.assign(Object.assign({}, registrationOptions), { + id, + origin: body.origin, + tenantId: body.tenantId, + email: body.email, + rpId: registrationOptions.rp.id, + createdAt: now.getTime(), + expiresAt: now.getTime() + body.timeout * 1000, + }) + ); // @ts-ignore return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, registrationOptions); - } - else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { const signInOptions = await server_1.generateAuthenticationOptions({ rpID: body.relyingPartyId, timeout: body.timeout, @@ -54,11 +67,21 @@ const getMockQuerier = (recipeId) => { }); const id = crypto_1.default.randomUUID(); const now = new Date(); - writeDb("generatedOptions", id, Object.assign(Object.assign({}, signInOptions), { id, origin: body.origin, tenantId: body.tenantId, email: body.email, createdAt: now.getTime(), expiresAt: now.getTime() + body.timeout * 1000 })); + writeDb( + "generatedOptions", + id, + Object.assign(Object.assign({}, signInOptions), { + id, + origin: body.origin, + tenantId: body.tenantId, + email: body.email, + createdAt: now.getTime(), + expiresAt: now.getTime() + body.timeout * 1000, + }) + ); // @ts-ignore return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id }, signInOptions); - } - else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); if (!options) { // @ts-ignore @@ -85,9 +108,15 @@ const getMockQuerier = (recipeId) => { id: credentialId, userId: recipeUserId, counter: 0, - publicKey: (_a = registrationVerification.registrationInfo) === null || _a === void 0 ? void 0 : _a.credential.publicKey.toString(), + publicKey: + (_a = registrationVerification.registrationInfo) === null || _a === void 0 + ? void 0 + : _a.credential.publicKey.toString(), rpId: options.rpId, - transports: (_b = registrationVerification.registrationInfo) === null || _b === void 0 ? void 0 : _b.credential.transports, + transports: + (_b = registrationVerification.registrationInfo) === null || _b === void 0 + ? void 0 + : _b.credential.transports, createdAt: now.toISOString(), }); const user = { @@ -123,8 +152,7 @@ const getMockQuerier = (recipeId) => { }; // @ts-ignore return response; - } - else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); if (!options) { // @ts-ignore diff --git a/lib/build/customFramework.d.ts b/lib/build/customFramework.d.ts index 9b944d2cb..d7abeb160 100644 --- a/lib/build/customFramework.d.ts +++ b/lib/build/customFramework.d.ts @@ -14,8 +14,12 @@ export interface ParsableRequest { formData: () => Promise; json: () => Promise; } -export declare function getCookieFromRequest(request: RequestType): Record; -export declare function getQueryFromRequest(request: RequestType): Record; +export declare function getCookieFromRequest( + request: RequestType +): Record; +export declare function getQueryFromRequest( + request: RequestType +): Record; export declare function handleAuthAPIRequest(): (req: Request) => Promise; /** * A helper function to retrieve session details on the server side. @@ -24,15 +28,33 @@ export declare function handleAuthAPIRequest(): (req: Request) => Promise(request: RequestType): Promise<{ +export declare function getSessionForSSR( + request: RequestType +): Promise<{ accessTokenPayload: JWTPayload | undefined; hasToken: boolean; error: Error | undefined; }>; -export declare function getSessionForSSRUsingAccessToken(accessToken: string | undefined): Promise<{ +export declare function getSessionForSSRUsingAccessToken( + accessToken: string | undefined +): Promise<{ accessTokenPayload: JWTPayload | undefined; hasToken: boolean; error: Error | undefined; }>; -export declare function withSession(request: RequestType, handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, options?: VerifySessionOptions, userContext?: Record): Promise; -export declare function withPreParsedRequestResponse(req: RequestType, handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise): Promise; +export declare function withSession< + RequestType extends ParsableRequest = Request, + ResponseType extends Response = Response +>( + request: RequestType, + handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, + options?: VerifySessionOptions, + userContext?: Record +): Promise; +export declare function withPreParsedRequestResponse< + RequestType extends ParsableRequest = Request, + ResponseType extends Response = Response +>( + req: RequestType, + handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise +): Promise; diff --git a/lib/build/customFramework.js b/lib/build/customFramework.js index 482dd3d08..25064135d 100644 --- a/lib/build/customFramework.js +++ b/lib/build/customFramework.js @@ -4,9 +4,11 @@ * that can be used to easily integrate the SDK with most * frameworks if they are not directly supported. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.withPreParsedRequestResponse = exports.withSession = exports.getSessionForSSRUsingAccessToken = exports.getSessionForSSR = exports.handleAuthAPIRequest = exports.getQueryFromRequest = exports.getCookieFromRequest = void 0; const cookie_1 = require("cookie"); @@ -121,12 +123,10 @@ async function getSessionForSSRUsingAccessToken(accessToken) { return { accessTokenPayload: decoded.userData, hasToken, error: undefined }; } return { accessTokenPayload: undefined, hasToken, error: undefined }; - } - catch (error) { + } catch (error) { return { accessTokenPayload: undefined, hasToken, error: undefined }; } - } - catch (error) { + } catch (error) { return { accessTokenPayload: undefined, hasToken, error: error }; } } @@ -137,8 +137,7 @@ async function withSession(request, handler, options, userContext) { const session = await session_1.default.getSession(baseRequest, baseResponse, options, userContext); return handler(undefined, session); }); - } - catch (error) { + } catch (error) { return await handler(error, undefined); } } @@ -149,8 +148,7 @@ async function withPreParsedRequestResponse(req, handler) { let userResponse; try { userResponse = await handler(baseRequest, baseResponse); - } - catch (err) { + } catch (err) { userResponse = await handleError(err, baseRequest, baseResponse); } return addCookies(baseResponse, userResponse); @@ -164,14 +162,17 @@ function addCookies(baseResponse, userResponse) { let didAddHeaders = false; for (const respCookie of baseResponse.cookies) { didAddCookies = true; - userResponse.headers.append("Set-Cookie", cookie_1.serialize(respCookie.key, respCookie.value, { - domain: respCookie.domain, - expires: new Date(respCookie.expires), - httpOnly: respCookie.httpOnly, - path: respCookie.path, - sameSite: respCookie.sameSite, - secure: respCookie.secure, - })); + userResponse.headers.append( + "Set-Cookie", + cookie_1.serialize(respCookie.key, respCookie.value, { + domain: respCookie.domain, + expires: new Date(respCookie.expires), + httpOnly: respCookie.httpOnly, + path: respCookie.path, + sameSite: respCookie.sameSite, + secure: respCookie.secure, + }) + ); } baseResponse.headers.forEach((value, key) => { didAddHeaders = true; diff --git a/lib/build/error.d.ts b/lib/build/error.d.ts index 354527199..41083aef0 100644 --- a/lib/build/error.d.ts +++ b/lib/build/error.d.ts @@ -6,14 +6,18 @@ export default class SuperTokensError extends Error { payload: any; fromRecipe: string | undefined; private errMagic; - constructor(options: { - message: string; - payload?: any; - type: string; - } | { - message: string; - type: "BAD_INPUT_ERROR"; - payload: undefined; - }); + constructor( + options: + | { + message: string; + payload?: any; + type: string; + } + | { + message: string; + type: "BAD_INPUT_ERROR"; + payload: undefined; + } + ); static isErrorFromSuperTokens(obj: any): obj is SuperTokensError; } diff --git a/lib/build/framework/awsLambda/framework.d.ts b/lib/build/framework/awsLambda/framework.d.ts index ae555f8f7..c5945ac37 100644 --- a/lib/build/framework/awsLambda/framework.d.ts +++ b/lib/build/framework/awsLambda/framework.d.ts @@ -1,5 +1,11 @@ // @ts-nocheck -import type { APIGatewayProxyEventV2, APIGatewayProxyEvent, APIGatewayProxyResult, APIGatewayProxyStructuredResultV2, Handler } from "aws-lambda"; +import type { + APIGatewayProxyEventV2, + APIGatewayProxyEvent, + APIGatewayProxyResult, + APIGatewayProxyStructuredResultV2, + Handler, +} from "aws-lambda"; import { HTTPMethod } from "../../types"; import { BaseRequest } from "../request"; import { BaseResponse } from "../response"; @@ -50,13 +56,24 @@ export declare class AWSResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; /** * @param {number} statusCode */ setStatusCode: (statusCode: number) => void; sendJSONResponse: (content: any) => void; - sendResponse: (response?: APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 | undefined) => APIGatewayProxyResult | APIGatewayProxyStructuredResultV2; + sendResponse: ( + response?: APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 | undefined + ) => APIGatewayProxyResult | APIGatewayProxyStructuredResultV2; } export interface SessionEventV2 extends SupertokensLambdaEventV2 { session?: SessionContainerInterface; diff --git a/lib/build/framework/awsLambda/framework.js b/lib/build/framework/awsLambda/framework.js index db261c799..9ad56d91b 100644 --- a/lib/build/framework/awsLambda/framework.js +++ b/lib/build/framework/awsLambda/framework.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AWSWrapper = exports.middleware = exports.AWSResponse = exports.AWSRequest = void 0; const utils_1 = require("../../utils"); @@ -17,13 +19,11 @@ class AWSRequest extends request_1.BaseRequest { this.getFormDataFromRequestBody = async () => { if (this.event.body === null || this.event.body === undefined) { return {}; - } - else { + } else { try { const parsedUrlEncodedFormData = Object.fromEntries(new URLSearchParams(this.event.body).entries()); return parsedUrlEncodedFormData === undefined ? {} : parsedUrlEncodedFormData; - } - catch (err) { + } catch (err) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "API input error: Please make sure to pass valid url encoded form in the request body", @@ -34,8 +34,7 @@ class AWSRequest extends request_1.BaseRequest { this.getJSONFromRequestBody = async () => { if (this.event.body === null || this.event.body === undefined) { return {}; - } - else { + } else { const parsedJSONBody = JSON.parse(this.event.body); return parsedJSONBody === undefined ? {} : parsedJSONBody; } @@ -59,15 +58,20 @@ class AWSRequest extends request_1.BaseRequest { }; this.getCookieValue = (key) => { let cookies = this.event.cookies; - if ((this.event.headers === undefined || this.event.headers === null) && - (cookies === undefined || cookies === null)) { + if ( + (this.event.headers === undefined || this.event.headers === null) && + (cookies === undefined || cookies === null) + ) { return undefined; } let value = utils_2.getCookieValueFromHeaders(this.event.headers, key); if (value === undefined && cookies !== undefined && cookies !== null) { - value = utils_2.getCookieValueFromHeaders({ - cookie: cookies.join(";"), - }, key); + value = utils_2.getCookieValueFromHeaders( + { + cookie: cookies.join(";"), + }, + key + ); } return value; }; @@ -118,10 +122,21 @@ class AWSResponse extends response_1.BaseResponse { }); }; this.removeHeader = (key) => { - this.event.supertokens.response.headers = this.event.supertokens.response.headers.filter((header) => header.key.toLowerCase() !== key.toLowerCase()); + this.event.supertokens.response.headers = this.event.supertokens.response.headers.filter( + (header) => header.key.toLowerCase() !== key.toLowerCase() + ); }; this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { - let serialisedCookie = utils_2.serializeCookieValue(key, value, domain, secure, httpOnly, expires, path, sameSite); + let serialisedCookie = utils_2.serializeCookieValue( + key, + value, + domain, + secure, + httpOnly, + expires, + path, + sameSite + ); this.event.supertokens.response.cookies = [ ...this.event.supertokens.response.cookies.filter((c) => !c.startsWith(key + "=")), serialisedCookie, @@ -175,13 +190,14 @@ class AWSResponse extends response_1.BaseResponse { For example if the caller is trying to add front token to the access control exposed headers property we do not want to append if something else had already added it */ - if (typeof currentValue !== "string" || - !currentValue.includes(supertokensHeaders[i].value.toString())) { + if ( + typeof currentValue !== "string" || + !currentValue.includes(supertokensHeaders[i].value.toString()) + ) { let newValue = `${currentValue}, ${supertokensHeaders[i].value}`; headers[supertokensHeaders[i].key] = newValue; } - } - else { + } else { headers[supertokensHeaders[i].key] = supertokensHeaders[i].value; } } @@ -191,26 +207,28 @@ class AWSResponse extends response_1.BaseResponse { cookies = []; } cookies.push(...supertokensCookies); - let result = Object.assign(Object.assign({}, response), { cookies, - body, - statusCode, - headers }); + let result = Object.assign(Object.assign({}, response), { cookies, body, statusCode, headers }); return result; - } - else { + } else { let multiValueHeaders = response.multiValueHeaders; if (multiValueHeaders === undefined) { multiValueHeaders = {}; } let headsersInMultiValueHeaders = Object.keys(multiValueHeaders); - let cookieHeader = headsersInMultiValueHeaders.find((h) => h.toLowerCase() === constants_1.COOKIE_HEADER.toLowerCase()); + let cookieHeader = headsersInMultiValueHeaders.find( + (h) => h.toLowerCase() === constants_1.COOKIE_HEADER.toLowerCase() + ); if (cookieHeader === undefined) { multiValueHeaders[constants_1.COOKIE_HEADER] = supertokensCookies; - } - else { + } else { multiValueHeaders[cookieHeader].push(...supertokensCookies); } - let result = Object.assign(Object.assign({}, response), { multiValueHeaders, body: body, statusCode: statusCode, headers }); + let result = Object.assign(Object.assign({}, response), { + multiValueHeaders, + body: body, + statusCode: statusCode, + headers, + }); return result; } }; @@ -253,8 +271,7 @@ const middleware = (handler) => { error: `The middleware couldn't serve the API path ${request.getOriginalURL()}, method: ${request.getMethod()}. If this is an unexpected behaviour, please create an issue here: https://github.com/supertokens/supertokens-node/issues`, }); return response.sendResponse(); - } - catch (err) { + } catch (err) { await supertokens.errorHandler(err, request, response, userContext); if (response.responseSet) { return response.sendResponse(); diff --git a/lib/build/framework/awsLambda/index.d.ts b/lib/build/framework/awsLambda/index.d.ts index 4ec1183d8..1028174f0 100644 --- a/lib/build/framework/awsLambda/index.d.ts +++ b/lib/build/framework/awsLambda/index.d.ts @@ -1,5 +1,7 @@ // @ts-nocheck export type { SessionEvent, SessionEventV2 } from "./framework"; -export declare const middleware: (handler?: import("aws-lambda").Handler | undefined) => import("aws-lambda").Handler; +export declare const middleware: ( + handler?: import("aws-lambda").Handler | undefined +) => import("aws-lambda").Handler; export declare const wrapRequest: (unwrapped: any) => import("..").BaseRequest; export declare const wrapResponse: (unwrapped: any) => import("..").BaseResponse; diff --git a/lib/build/framework/constants.d.ts b/lib/build/framework/constants.d.ts index e9b796f53..79db4b944 100644 --- a/lib/build/framework/constants.d.ts +++ b/lib/build/framework/constants.d.ts @@ -1,3 +1,4 @@ // @ts-nocheck export declare const COOKIE_HEADER = "Set-Cookie"; -export declare const BROTLI_DECOMPRESSION_ERROR_MESSAGE = "Brotli decompression not implement, Please add a middleware that handles decompression before the SuperTokens middleware."; +export declare const BROTLI_DECOMPRESSION_ERROR_MESSAGE = + "Brotli decompression not implement, Please add a middleware that handles decompression before the SuperTokens middleware."; diff --git a/lib/build/framework/constants.js b/lib/build/framework/constants.js index ae0e26999..1038b6933 100644 --- a/lib/build/framework/constants.js +++ b/lib/build/framework/constants.js @@ -17,4 +17,5 @@ exports.BROTLI_DECOMPRESSION_ERROR_MESSAGE = exports.COOKIE_HEADER = void 0; */ exports.COOKIE_HEADER = "Set-Cookie"; // Define error message for brotli decompression not being supported -exports.BROTLI_DECOMPRESSION_ERROR_MESSAGE = "Brotli decompression not implement, Please add a middleware that handles decompression before the SuperTokens middleware."; +exports.BROTLI_DECOMPRESSION_ERROR_MESSAGE = + "Brotli decompression not implement, Please add a middleware that handles decompression before the SuperTokens middleware."; diff --git a/lib/build/framework/custom/framework.d.ts b/lib/build/framework/custom/framework.d.ts index 0505a06d1..6b313019f 100644 --- a/lib/build/framework/custom/framework.d.ts +++ b/lib/build/framework/custom/framework.d.ts @@ -46,7 +46,16 @@ export declare class CollectingResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; /** * @param {number} statusCode */ @@ -54,22 +63,47 @@ export declare class CollectingResponse extends BaseResponse { sendJSONResponse: (content: any) => void; } export declare type NextFunction = (err?: any) => void; -export declare const middleware: (wrapRequest?: (req: OrigReqType) => BaseRequest, wrapResponse?: (req: OrigRespType) => BaseResponse) => (request: OrigReqType, response: OrigRespType, next?: NextFunction | undefined) => Promise<{ - handled: boolean; - error?: undefined; -} | { - error: any; - handled?: undefined; -}>; -export declare const errorHandler: () => (err: any, request: BaseRequest, response: BaseResponse, next: NextFunction) => Promise; +export declare const middleware: ( + wrapRequest?: (req: OrigReqType) => BaseRequest, + wrapResponse?: (req: OrigRespType) => BaseResponse +) => ( + request: OrigReqType, + response: OrigRespType, + next?: NextFunction | undefined +) => Promise< + | { + handled: boolean; + error?: undefined; + } + | { + error: any; + handled?: undefined; + } +>; +export declare const errorHandler: () => ( + err: any, + request: BaseRequest, + response: BaseResponse, + next: NextFunction +) => Promise; export declare const CustomFrameworkWrapper: { - middleware: (wrapRequest?: (req: OrigReqType) => BaseRequest, wrapResponse?: (req: OrigRespType) => BaseResponse) => (request: OrigReqType, response: OrigRespType, next?: NextFunction | undefined) => Promise<{ - handled: boolean; - error?: undefined; - } | { - error: any; - handled?: undefined; - }>; + middleware: ( + wrapRequest?: (req: OrigReqType) => BaseRequest, + wrapResponse?: (req: OrigRespType) => BaseResponse + ) => ( + request: OrigReqType, + response: OrigRespType, + next?: NextFunction | undefined + ) => Promise< + | { + handled: boolean; + error?: undefined; + } + | { + error: any; + handled?: undefined; + } + >; errorHandler: () => (err: any, request: BaseRequest, response: BaseResponse, next: NextFunction) => Promise; }; export {}; diff --git a/lib/build/framework/custom/framework.js b/lib/build/framework/custom/framework.js index 2bb6b38a1..a321c2ffb 100644 --- a/lib/build/framework/custom/framework.js +++ b/lib/build/framework/custom/framework.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CustomFrameworkWrapper = exports.errorHandler = exports.middleware = exports.CollectingResponse = exports.PreParsedRequest = void 0; const utils_1 = require("../../utils"); @@ -87,8 +89,7 @@ class CollectingResponse extends response_1.BaseResponse { if (!((_a = this.headers.get(key)) === null || _a === void 0 ? void 0 : _a.includes(value))) { this.headers.append(key, value); } - } - else { + } else { this.headers.set(key, value); } }; @@ -112,8 +113,7 @@ class CollectingResponse extends response_1.BaseResponse { // Still, if available we are using the built-in (node 18+) if (typeof Headers === "undefined") { this.headers = new nodeHeaders_1.default(null); - } - else { + } else { this.headers = new Headers(); } this.statusCode = 200; @@ -138,21 +138,18 @@ const middleware = (wrapRequest = identity, wrapResponse = identity) => { return { handled: false }; } return { handled: true }; - } - catch (err) { + } catch (err) { if (supertokens) { try { await supertokens.errorHandler(err, wrappedReq, wrappedResp, userContext); return { handled: true }; - } - catch (_a) { + } catch (_a) { if (next) { next(err); } return { error: err }; } - } - else { + } else { if (next) { next(err); } @@ -169,8 +166,7 @@ const errorHandler = () => { try { await supertokens.errorHandler(err, request, response, userContext); return next(); - } - catch (err) { + } catch (err) { return next(err); } }; diff --git a/lib/build/framework/custom/index.d.ts b/lib/build/framework/custom/index.d.ts index 490f51f3a..7c3b1f1fa 100644 --- a/lib/build/framework/custom/index.d.ts +++ b/lib/build/framework/custom/index.d.ts @@ -1,10 +1,25 @@ // @ts-nocheck export { PreParsedRequest, CollectingResponse } from "./framework"; -export declare const middleware: (wrapRequest?: (req: OrigReqType) => import("..").BaseRequest, wrapResponse?: (req: OrigRespType) => import("..").BaseResponse) => (request: OrigReqType, response: OrigRespType, next?: import("./framework").NextFunction | undefined) => Promise<{ - handled: boolean; - error?: undefined; -} | { - error: any; - handled?: undefined; -}>; -export declare const errorHandler: () => (err: any, request: import("..").BaseRequest, response: import("..").BaseResponse, next: import("./framework").NextFunction) => Promise; +export declare const middleware: ( + wrapRequest?: (req: OrigReqType) => import("..").BaseRequest, + wrapResponse?: (req: OrigRespType) => import("..").BaseResponse +) => ( + request: OrigReqType, + response: OrigRespType, + next?: import("./framework").NextFunction | undefined +) => Promise< + | { + handled: boolean; + error?: undefined; + } + | { + error: any; + handled?: undefined; + } +>; +export declare const errorHandler: () => ( + err: any, + request: import("..").BaseRequest, + response: import("..").BaseResponse, + next: import("./framework").NextFunction +) => Promise; diff --git a/lib/build/framework/custom/index.js b/lib/build/framework/custom/index.js index acd4ab523..31170e351 100644 --- a/lib/build/framework/custom/index.js +++ b/lib/build/framework/custom/index.js @@ -17,7 +17,17 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.errorHandler = exports.middleware = exports.CollectingResponse = exports.PreParsedRequest = void 0; const framework_1 = require("./framework"); var framework_2 = require("./framework"); -Object.defineProperty(exports, "PreParsedRequest", { enumerable: true, get: function () { return framework_2.PreParsedRequest; } }); -Object.defineProperty(exports, "CollectingResponse", { enumerable: true, get: function () { return framework_2.CollectingResponse; } }); +Object.defineProperty(exports, "PreParsedRequest", { + enumerable: true, + get: function () { + return framework_2.PreParsedRequest; + }, +}); +Object.defineProperty(exports, "CollectingResponse", { + enumerable: true, + get: function () { + return framework_2.CollectingResponse; + }, +}); exports.middleware = framework_1.CustomFrameworkWrapper.middleware; exports.errorHandler = framework_1.CustomFrameworkWrapper.errorHandler; diff --git a/lib/build/framework/custom/nodeHeaders.js b/lib/build/framework/custom/nodeHeaders.js index 47c5664cf..88f417cd1 100644 --- a/lib/build/framework/custom/nodeHeaders.js +++ b/lib/build/framework/custom/nodeHeaders.js @@ -57,19 +57,16 @@ class Headers extends URLSearchParams { for (const [name, values] of Object.entries(raw)) { result.push(...values.map((value) => [name, value])); } - } - else if (init == null) { + } else if (init == null) { // eslint-disable-line no-eq-null, eqeqeq // No op - } - else if (typeof init === "object" && !utils_1.isBoxedPrimitive(init)) { + } else if (typeof init === "object" && !utils_1.isBoxedPrimitive(init)) { const method = init[Symbol.iterator]; // eslint-disable-next-line no-eq-null, eqeqeq if (method == null) { // Record result.push(...Object.entries(init)); - } - else { + } else { if (typeof method !== "function") { throw new TypeError("Header pairs must be iterable"); } @@ -77,30 +74,31 @@ class Headers extends URLSearchParams { // Note: per spec we have to first exhaust the lists then process them result = [...init] .map((pair) => { - if (typeof pair !== "object" || utils_1.isBoxedPrimitive(pair)) { - throw new TypeError("Each header pair must be an iterable object"); - } - return [...pair]; - }) + if (typeof pair !== "object" || utils_1.isBoxedPrimitive(pair)) { + throw new TypeError("Each header pair must be an iterable object"); + } + return [...pair]; + }) .map((pair) => { - if (pair.length !== 2) { - throw new TypeError("Each header pair must be a name/value tuple"); - } - return [...pair]; - }); + if (pair.length !== 2) { + throw new TypeError("Each header pair must be a name/value tuple"); + } + return [...pair]; + }); } - } - else { - throw new TypeError("Failed to construct 'Headers': The provided value is not of type '(sequence> or record)"); + } else { + throw new TypeError( + "Failed to construct 'Headers': The provided value is not of type '(sequence> or record)" + ); } // Validate and lowercase result = result.length > 0 ? result.map(([name, value]) => { - validateHeaderName(name); - validateHeaderValue(name, String(value)); - return [String(name).toLowerCase(), String(value)]; - }) + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return [String(name).toLowerCase(), String(value)]; + }) : undefined; super(result); // Returning a Proxy that will lowercase key names, validate parameters and sort keys @@ -113,7 +111,11 @@ class Headers extends URLSearchParams { return (name, value) => { validateHeaderName(name); validateHeaderValue(name, String(value)); - return URLSearchParams.prototype[p].call(receiver, String(name).toLowerCase(), String(value)); + return URLSearchParams.prototype[p].call( + receiver, + String(name).toLowerCase(), + String(value) + ); }; case "delete": case "has": @@ -193,8 +195,7 @@ class Headers extends URLSearchParams { // This hack makes specifying custom Host header possible. if (key === "host") { result[key] = values[0]; - } - else { + } else { result[key] = values.length > 1 ? values : values[0]; } return result; @@ -206,33 +207,37 @@ exports.default = Headers; * Re-shaping object for Web IDL tests * Only need to do it for overridden methods */ -Object.defineProperties(Headers.prototype, ["get", "entries", "forEach", "values"].reduce((result, property) => { - result[property] = { enumerable: true }; - return result; -}, {})); +Object.defineProperties( + Headers.prototype, + ["get", "entries", "forEach", "values"].reduce((result, property) => { + result[property] = { enumerable: true }; + return result; + }, {}) +); /** * Create a Headers object from an http.IncomingMessage.rawHeaders, ignoring those that do * not conform to HTTP grammar productions. * @param {import('http').IncomingMessage['rawHeaders']} headers */ function fromRawHeaders(headers = []) { - return new Headers(headers - // Split into pairs - .reduce((result, value, index, array) => { - if (index % 2 === 0) { - result.push(array.slice(index, index + 2)); - } - return result; - }, []) - .filter(([name, value]) => { - try { - validateHeaderName(name); - validateHeaderValue(name, String(value)); - return true; - } - catch (_a) { - return false; - } - })); + return new Headers( + headers + // Split into pairs + .reduce((result, value, index, array) => { + if (index % 2 === 0) { + result.push(array.slice(index, index + 2)); + } + return result; + }, []) + .filter(([name, value]) => { + try { + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return true; + } catch (_a) { + return false; + } + }) + ); } exports.fromRawHeaders = fromRawHeaders; diff --git a/lib/build/framework/express/framework.d.ts b/lib/build/framework/express/framework.d.ts index b98f2fbe6..40cbcad7b 100644 --- a/lib/build/framework/express/framework.d.ts +++ b/lib/build/framework/express/framework.d.ts @@ -23,7 +23,16 @@ export declare class ExpressResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; /** * @param {number} statusCode */ diff --git a/lib/build/framework/express/framework.js b/lib/build/framework/express/framework.js index 09cf076ba..d8a58729b 100644 --- a/lib/build/framework/express/framework.js +++ b/lib/build/framework/express/framework.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExpressWrapper = exports.errorHandler = exports.middleware = exports.ExpressResponse = exports.ExpressRequest = void 0; const utils_1 = require("../../utils"); @@ -84,7 +86,17 @@ class ExpressResponse extends response_1.BaseResponse { this.response.removeHeader(key); }; this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { - utils_2.setCookieForServerResponse(this.response, key, value, domain, secure, httpOnly, expires, path, sameSite); + utils_2.setCookieForServerResponse( + this.response, + key, + value, + domain, + secure, + httpOnly, + expires, + path, + sameSite + ); }; /** * @param {number} statusCode @@ -117,17 +129,14 @@ const middleware = () => { if (!result) { return next(); } - } - catch (err) { + } catch (err) { if (supertokens) { try { await supertokens.errorHandler(err, request, response, userContext); - } - catch (_a) { + } catch (_a) { next(err); } - } - else { + } else { next(err); } } @@ -142,8 +151,7 @@ const errorHandler = () => { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { await supertokens.errorHandler(err, request, response, userContext); - } - catch (err) { + } catch (err) { return next(err); } }; diff --git a/lib/build/framework/express/index.d.ts b/lib/build/framework/express/index.d.ts index 8ff22112c..9bf873aa2 100644 --- a/lib/build/framework/express/index.d.ts +++ b/lib/build/framework/express/index.d.ts @@ -1,6 +1,15 @@ // @ts-nocheck export type { SessionRequest } from "./framework"; -export declare const middleware: () => (req: import("express").Request, res: import("express").Response, next: import("express").NextFunction) => Promise; -export declare const errorHandler: () => (err: any, req: import("express").Request, res: import("express").Response, next: import("express").NextFunction) => Promise; +export declare const middleware: () => ( + req: import("express").Request, + res: import("express").Response, + next: import("express").NextFunction +) => Promise; +export declare const errorHandler: () => ( + err: any, + req: import("express").Request, + res: import("express").Response, + next: import("express").NextFunction +) => Promise; export declare const wrapRequest: (unwrapped: any) => import("..").BaseRequest; export declare const wrapResponse: (unwrapped: any) => import("..").BaseResponse; diff --git a/lib/build/framework/fastify/framework.d.ts b/lib/build/framework/fastify/framework.d.ts index d299d1196..074d5ebe3 100644 --- a/lib/build/framework/fastify/framework.d.ts +++ b/lib/build/framework/fastify/framework.d.ts @@ -23,7 +23,16 @@ export declare class FastifyResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; /** * @param {number} statusCode */ diff --git a/lib/build/framework/fastify/framework.js b/lib/build/framework/fastify/framework.js index 2bffba3c6..5bce10130 100644 --- a/lib/build/framework/fastify/framework.js +++ b/lib/build/framework/fastify/framework.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FastifyWrapper = exports.errorHandler = exports.FastifyResponse = exports.FastifyRequest = void 0; const utils_1 = require("../../utils"); @@ -76,8 +78,7 @@ class FastifyResponse extends response_1.BaseResponse { // we have the this.response.header for compatibility with nextJS if (existingValue === undefined) { this.response.header(key, value); - } - else if (allowDuplicateKey) { + } else if (allowDuplicateKey) { /** We only want to append if it does not already exist For example if the caller is trying to add front token to the access control exposed headers property @@ -86,13 +87,11 @@ class FastifyResponse extends response_1.BaseResponse { if (typeof existingValue !== "string" || !existingValue.includes(value)) { this.response.header(key, existingValue + ", " + value); } - } - else { + } else { // we overwrite the current one with the new one this.response.header(key, value); } - } - catch (err) { + } catch (err) { throw new Error("Error while setting header with key: " + key + " and value: " + value); } }; @@ -100,12 +99,19 @@ class FastifyResponse extends response_1.BaseResponse { this.response.removeHeader(key); }; this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { - let serialisedCookie = utils_2.serializeCookieValue(key, value, domain, secure, httpOnly, expires, path, sameSite); + let serialisedCookie = utils_2.serializeCookieValue( + key, + value, + domain, + secure, + httpOnly, + expires, + path, + sameSite + ); let oldHeaders = this.response.getHeader(constants_1.COOKIE_HEADER); - if (oldHeaders === undefined) - oldHeaders = []; - else if (!(oldHeaders instanceof Array)) - oldHeaders = [oldHeaders.toString()]; + if (oldHeaders === undefined) oldHeaders = []; + else if (!(oldHeaders instanceof Array)) oldHeaders = [oldHeaders.toString()]; this.response.removeHeader(constants_1.COOKIE_HEADER); this.response.header(constants_1.COOKIE_HEADER, [ ...oldHeaders.filter((h) => !h.startsWith(key + "=")), @@ -143,8 +149,7 @@ function plugin(fastify, _, done) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { await supertokens.middleware(request, response, userContext); - } - catch (err) { + } catch (err) { await supertokens.errorHandler(err, request, response, userContext); } }); diff --git a/lib/build/framework/fastify/index.d.ts b/lib/build/framework/fastify/index.d.ts index d1d0e9664..878d1ec23 100644 --- a/lib/build/framework/fastify/index.d.ts +++ b/lib/build/framework/fastify/index.d.ts @@ -1,6 +1,14 @@ // @ts-nocheck export type { SessionRequest } from "./framework"; -export declare const plugin: import("./types").FastifyPluginCallback>; -export declare const errorHandler: () => (err: any, req: import("./types").FastifyRequest, res: import("./types").FastifyReply) => Promise; +export declare const plugin: import("./types").FastifyPluginCallback>; +export declare const errorHandler: () => ( + err: any, + req: import("./types").FastifyRequest, + res: import("./types").FastifyReply +) => Promise; export declare const wrapRequest: (unwrapped: any) => import("..").BaseRequest; export declare const wrapResponse: (unwrapped: any) => import("..").BaseResponse; diff --git a/lib/build/framework/fastify/types.d.ts b/lib/build/framework/fastify/types.d.ts index 08a02b14d..86c117df5 100644 --- a/lib/build/framework/fastify/types.d.ts +++ b/lib/build/framework/fastify/types.d.ts @@ -19,7 +19,15 @@ export interface FastifyReply { getHeader(key: any): number | string | string[] | undefined; type(contentType: string): FastifyReply; } -export interface FastifyInstance { +export interface FastifyInstance< + Instance = unknown, + Request extends FastifyRequest = FastifyRequest, + Reply extends FastifyReply = FastifyReply +> { addHook(this: Instance, name: string, hook: (req: Request, reply: Reply) => void): Instance; } -export declare type FastifyPluginCallback = (instance: Instance, opts: unknown, done: (err?: Error) => void) => void; +export declare type FastifyPluginCallback = ( + instance: Instance, + opts: unknown, + done: (err?: Error) => void +) => void; diff --git a/lib/build/framework/hapi/framework.d.ts b/lib/build/framework/hapi/framework.d.ts index 9cbdd0324..c7dafc2da 100644 --- a/lib/build/framework/hapi/framework.d.ts +++ b/lib/build/framework/hapi/framework.d.ts @@ -17,7 +17,12 @@ export declare class HapiRequest extends BaseRequest { getOriginalURL: () => string; } export interface ExtendedResponseToolkit extends ResponseToolkit { - lazyHeaderBindings: (h: ResponseToolkit, key: string, value: string | undefined, allowDuplicateKey: boolean) => void; + lazyHeaderBindings: ( + h: ResponseToolkit, + key: string, + value: string | undefined, + allowDuplicateKey: boolean + ) => void; } export declare class HapiResponse extends BaseResponse { private response; @@ -29,7 +34,16 @@ export declare class HapiResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; /** * @param {number} statusCode */ diff --git a/lib/build/framework/hapi/framework.js b/lib/build/framework/hapi/framework.js index 8c0936f90..7be6275ce 100644 --- a/lib/build/framework/hapi/framework.js +++ b/lib/build/framework/hapi/framework.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HapiWrapper = exports.HapiResponse = exports.HapiRequest = void 0; const utils_1 = require("../../utils"); @@ -73,8 +75,7 @@ class HapiResponse extends response_1.BaseResponse { this.setHeader = (key, value, allowDuplicateKey) => { try { this.response.lazyHeaderBindings(this.response, key, value, allowDuplicateKey); - } - catch (err) { + } catch (err) { throw new Error("Error while setting header with key: " + key + " and value: " + value); } }; @@ -89,12 +90,11 @@ class HapiResponse extends response_1.BaseResponse { path: path, domain, ttl: expires - now, - isSameSite: (sameSite === "lax" ? "Lax" : sameSite === "none" ? "None" : "Strict"), + isSameSite: sameSite === "lax" ? "Lax" : sameSite === "none" ? "None" : "Strict", }; if (expires > now) { this.response.state(key, value, cookieOptions); - } - else { + } else { this.response.unstate(key, cookieOptions); } }; @@ -149,8 +149,7 @@ const plugin = { (request.app.lazyHeaders || []).forEach(({ key, value, allowDuplicateKey }) => { if (request.response.isBoom) { request.response.output.headers[key] = value; - } - else { + } else { request.response.header(key, value, { append: allowDuplicateKey }); } }); @@ -170,8 +169,7 @@ const plugin = { return resObj.takeover(); } return h.continue; - } - catch (e) { + } catch (e) { return h.continue; } } @@ -182,9 +180,10 @@ const plugin = { const anyApp = h.request.app; anyApp.lazyHeaders = anyApp.lazyHeaders || []; if (value === undefined) { - anyApp.lazyHeaders = anyApp.lazyHeaders.filter((header) => header.key.toLowerCase() !== key.toLowerCase()); - } - else { + anyApp.lazyHeaders = anyApp.lazyHeaders.filter( + (header) => header.key.toLowerCase() !== key.toLowerCase() + ); + } else { anyApp.lazyHeaders.push({ key, value, allowDuplicateKey }); } }); diff --git a/lib/build/framework/index.js b/lib/build/framework/index.js index f9017266b..28dc9790d 100644 --- a/lib/build/framework/index.js +++ b/lib/build/framework/index.js @@ -1,23 +1,40 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.awsLambda = exports.koa = exports.loopback = exports.hapi = exports.fastify = exports.express = exports.BaseResponse = exports.BaseRequest = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -35,9 +52,19 @@ exports.awsLambda = exports.koa = exports.loopback = exports.hapi = exports.fast * under the License. */ var request_1 = require("./request"); -Object.defineProperty(exports, "BaseRequest", { enumerable: true, get: function () { return request_1.BaseRequest; } }); +Object.defineProperty(exports, "BaseRequest", { + enumerable: true, + get: function () { + return request_1.BaseRequest; + }, +}); var response_1 = require("./response"); -Object.defineProperty(exports, "BaseResponse", { enumerable: true, get: function () { return response_1.BaseResponse; } }); +Object.defineProperty(exports, "BaseResponse", { + enumerable: true, + get: function () { + return response_1.BaseResponse; + }, +}); const expressFramework = __importStar(require("./express")); const fastifyFramework = __importStar(require("./fastify")); const hapiFramework = __importStar(require("./hapi")); diff --git a/lib/build/framework/koa/framework.d.ts b/lib/build/framework/koa/framework.d.ts index 1df307ab7..a9dff8f43 100644 --- a/lib/build/framework/koa/framework.d.ts +++ b/lib/build/framework/koa/framework.d.ts @@ -24,7 +24,16 @@ export declare class KoaResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; /** * @param {number} statusCode */ diff --git a/lib/build/framework/koa/framework.js b/lib/build/framework/koa/framework.js index 5b4157368..c108eb50d 100644 --- a/lib/build/framework/koa/framework.js +++ b/lib/build/framework/koa/framework.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.KoaWrapper = exports.middleware = exports.KoaResponse = exports.KoaRequest = void 0; const utils_1 = require("../../utils"); @@ -78,8 +80,7 @@ class KoaResponse extends response_1.BaseResponse { let existingValue = existingHeaders[key.toLowerCase()]; if (existingValue === undefined) { this.ctx.set(key, value); - } - else if (allowDuplicateKey) { + } else if (allowDuplicateKey) { /** We only want to append if it does not already exist For example if the caller is trying to add front token to the access control exposed headers property @@ -88,13 +89,11 @@ class KoaResponse extends response_1.BaseResponse { if (typeof existingValue !== "string" || !existingValue.includes(value)) { this.ctx.set(key, existingValue + ", " + value); } - } - else { + } else { // we overwrite the current one with the new one this.ctx.set(key, value); } - } - catch (err) { + } catch (err) { throw new Error("Error while setting header with key: " + key + " and value: " + value); } }; @@ -143,8 +142,7 @@ const middleware = () => { if (!result) { return await next(); } - } - catch (err) { + } catch (err) { return await supertokens.errorHandler(err, request, response, userContext); } }; diff --git a/lib/build/framework/loopback/framework.d.ts b/lib/build/framework/loopback/framework.d.ts index 1ffe0dba4..12eb9c293 100644 --- a/lib/build/framework/loopback/framework.d.ts +++ b/lib/build/framework/loopback/framework.d.ts @@ -23,7 +23,16 @@ export declare class LoopbackResponse extends BaseResponse { sendHTMLResponse: (html: string) => void; setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; removeHeader: (key: string) => void; - setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; setStatusCode: (statusCode: number) => void; sendJSONResponse: (content: any) => void; } diff --git a/lib/build/framework/loopback/framework.js b/lib/build/framework/loopback/framework.js index 986b51a4a..fc4097b3f 100644 --- a/lib/build/framework/loopback/framework.js +++ b/lib/build/framework/loopback/framework.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LoopbackWrapper = exports.middleware = exports.LoopbackResponse = exports.LoopbackRequest = void 0; const utils_1 = require("../../utils"); @@ -77,7 +79,17 @@ class LoopbackResponse extends response_1.BaseResponse { this.response.removeHeader(key); }; this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { - utils_2.setCookieForServerResponse(this.response, key, value, domain, secure, httpOnly, expires, path, sameSite); + utils_2.setCookieForServerResponse( + this.response, + key, + value, + domain, + secure, + httpOnly, + expires, + path, + sameSite + ); }; this.setStatusCode = (statusCode) => { if (!this.response.writableEnded) { @@ -106,8 +118,7 @@ const middleware = async (ctx, next) => { return await next(); } return; - } - catch (err) { + } catch (err) { return await supertokens.errorHandler(err, request, response, userContext); } }; diff --git a/lib/build/framework/request.js b/lib/build/framework/request.js index c99c91879..edaa6e780 100644 --- a/lib/build/framework/request.js +++ b/lib/build/framework/request.js @@ -46,20 +46,16 @@ class BaseRequest { if (contentType) { if (contentType.startsWith("application/json")) { return await this.getJSONBody(); - } - else if (contentType.startsWith("application/x-www-form-urlencoded")) { + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { return await this.getFormData(); } - } - else { + } else { try { return await this.getJSONBody(); - } - catch (_a) { + } catch (_a) { try { return await this.getFormData(); - } - catch (_b) { + } catch (_b) { throw new Error("Unable to parse body as JSON or Form Data."); } } diff --git a/lib/build/framework/response.d.ts b/lib/build/framework/response.d.ts index 328749bf0..8cf7a67d3 100644 --- a/lib/build/framework/response.d.ts +++ b/lib/build/framework/response.d.ts @@ -5,7 +5,16 @@ export declare abstract class BaseResponse { constructor(); abstract setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; abstract removeHeader: (key: string) => void; - abstract setCookie: (key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void; + abstract setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; abstract setStatusCode: (statusCode: number) => void; abstract sendJSONResponse: (content: any) => void; abstract sendHTMLResponse: (html: string) => void; diff --git a/lib/build/framework/utils.d.ts b/lib/build/framework/utils.d.ts index f64b37767..4ef5fc0f5 100644 --- a/lib/build/framework/utils.d.ts +++ b/lib/build/framework/utils.d.ts @@ -10,9 +10,17 @@ export declare function getHeaderValueFromIncomingMessage(request: IncomingMessa export declare function normalizeHeaderValue(value: string | string[] | undefined): string | undefined; export declare function parseJSONBodyFromRequest(req: IncomingMessage): Promise; export declare function parseURLEncodedFormData(req: IncomingMessage): Promise; -export declare function assertThatBodyParserHasBeenUsedForExpressLikeRequest(method: HTTPMethod, request: Request): Promise; +export declare function assertThatBodyParserHasBeenUsedForExpressLikeRequest( + method: HTTPMethod, + request: Request +): Promise; export declare function assertFormDataBodyParserHasBeenUsedForExpressLikeRequest(request: Request): Promise; -export declare function setHeaderForExpressLikeResponse(res: Response, key: string, value: string, allowDuplicateKey: boolean): void; +export declare function setHeaderForExpressLikeResponse( + res: Response, + key: string, + value: string, + allowDuplicateKey: boolean +): void; /** * * @param res @@ -24,7 +32,30 @@ export declare function setHeaderForExpressLikeResponse(res: Response, key: stri * @param expires * @param path */ -export declare function setCookieForServerResponse(res: ServerResponse, key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): ServerResponse; -export declare function getCookieValueToSetInHeader(prev: string | string[] | undefined, val: string | string[], key: string): string | string[]; -export declare function serializeCookieValue(key: string, value: string, domain: string | undefined, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): string; +export declare function setCookieForServerResponse( + res: ServerResponse, + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" +): ServerResponse; +export declare function getCookieValueToSetInHeader( + prev: string | string[] | undefined, + val: string | string[], + key: string +): string | string[]; +export declare function serializeCookieValue( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" +): string; export declare function isBoxedPrimitive(value: any): boolean; diff --git a/lib/build/framework/utils.js b/lib/build/framework/utils.js index ed30fa0ec..eb78c07df 100644 --- a/lib/build/framework/utils.js +++ b/lib/build/framework/utils.js @@ -13,16 +13,43 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __asyncValues = (this && this.__asyncValues) || function (o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __asyncValues = + (this && this.__asyncValues) || + function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], + i; + return m + ? m.call(o) + : ((o = typeof __values === "function" ? __values(o) : o[Symbol.iterator]()), + (i = {}), + verb("next"), + verb("throw"), + verb("return"), + (i[Symbol.asyncIterator] = function () { + return this; + }), + i); + function verb(n) { + i[n] = + o[n] && + function (v) { + return new Promise(function (resolve, reject) { + (v = o[n](v)), settle(resolve, reject, v.done, v.value); + }); + }; + } + function settle(resolve, reject, d, v) { + Promise.resolve(v).then(function (v) { + resolve({ value: v, done: d }); + }, reject); + } + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isBoxedPrimitive = exports.serializeCookieValue = exports.getCookieValueToSetInHeader = exports.setCookieForServerResponse = exports.setHeaderForExpressLikeResponse = exports.assertFormDataBodyParserHasBeenUsedForExpressLikeRequest = exports.assertThatBodyParserHasBeenUsedForExpressLikeRequest = exports.parseURLEncodedFormData = exports.parseJSONBodyFromRequest = exports.normalizeHeaderValue = exports.getHeaderValueFromIncomingMessage = exports.getCookieValueFromIncomingMessage = exports.getCookieValueFromHeaders = void 0; const cookie_1 = require("cookie"); @@ -41,45 +68,52 @@ async function inflate(stream) { if (encoding === "gzip" || encoding === "deflate") { const inflator = new pako_1.default.Inflate(); try { - for (var stream_1 = __asyncValues(stream), stream_1_1; stream_1_1 = await stream_1.next(), !stream_1_1.done;) { + for ( + var stream_1 = __asyncValues(stream), stream_1_1; + (stream_1_1 = await stream_1.next()), !stream_1_1.done; + + ) { const chunk = stream_1_1.value; inflator.push(chunk, false); } - } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { + } catch (e_1_1) { + e_1 = { error: e_1_1 }; + } finally { try { if (stream_1_1 && !stream_1_1.done && (_a = stream_1.return)) await _a.call(stream_1); + } finally { + if (e_1) throw e_1.error; } - finally { if (e_1) throw e_1.error; } } if (inflator.err) { throw new Error(`Decompression error: ${inflator.msg}`); } decompressedData = inflator.result; - } - else if (encoding === "br") { + } else if (encoding === "br") { throw new Error(constants_1.BROTLI_DECOMPRESSION_ERROR_MESSAGE); - } - else { + } else { // Handle identity or unsupported encoding decompressedData = utils_1.getBuffer().concat([]); try { - for (var stream_2 = __asyncValues(stream), stream_2_1; stream_2_1 = await stream_2.next(), !stream_2_1.done;) { + for ( + var stream_2 = __asyncValues(stream), stream_2_1; + (stream_2_1 = await stream_2.next()), !stream_2_1.done; + + ) { const chunk = stream_2_1.value; decompressedData = utils_1.getBuffer().concat([decompressedData, chunk]); } - } - catch (e_2_1) { e_2 = { error: e_2_1 }; } - finally { + } catch (e_2_1) { + e_2 = { error: e_2_1 }; + } finally { try { if (stream_2_1 && !stream_2_1.done && (_b = stream_2.return)) await _b.call(stream_2); + } finally { + if (e_2) throw e_2.error; } - finally { if (e_2) throw e_2.error; } } } - if (typeof decompressedData === "string") - return decompressedData; + if (typeof decompressedData === "string") return decompressedData; return new TextDecoder().decode(decompressedData); } function getCookieValueFromHeaders(headers, key) { @@ -130,8 +164,7 @@ function JSONCookie(str) { } try { return JSON.parse(str.slice(2)); - } - catch (err) { + } catch (err) { return undefined; } } @@ -158,8 +191,7 @@ function JSONCookies(obj) { function getCharset(req) { try { return (content_type_1.default.parse(req).parameters.charset || "").toLowerCase(); - } - catch (e) { + } catch (e) { return undefined; } } @@ -186,12 +218,10 @@ async function parseURLEncodedFormData(req) { if (key in body) { if (body[key] instanceof Array) { body[key].push(val); - } - else { + } else { body[key] = [body[key], val]; } - } - else { + } else { body[key] = val; } } @@ -204,27 +234,25 @@ async function assertThatBodyParserHasBeenUsedForExpressLikeRequest(method, requ if (typeof request.body === "string") { try { request.body = JSON.parse(request.body); - } - catch (err) { + } catch (err) { if (request.body === "") { request.body = {}; - } - else { + } else { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "API input error: Please make sure to pass a valid JSON input in the request body", }); } } - } - else if (request.body === undefined || + } else if ( + request.body === undefined || utils_1.isBuffer(request.body) || - (Object.keys(request.body).length === 0 && request.readable)) { + (Object.keys(request.body).length === 0 && request.readable) + ) { try { // parsing it again to make sure that the request is parsed atleast once by a json parser request.body = await parseJSONBodyFromRequest(request); - } - catch (err) { + } catch (err) { // If the error message matches the brotli decompression // related error, then throw that error. if (err.message === constants_1.BROTLI_DECOMPRESSION_ERROR_MESSAGE) { @@ -246,27 +274,25 @@ async function assertFormDataBodyParserHasBeenUsedForExpressLikeRequest(request) if (typeof request.body === "string") { try { request.body = Object.fromEntries(new URLSearchParams(request.body).entries()); - } - catch (err) { + } catch (err) { if (request.body === "") { request.body = {}; - } - else { + } else { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "API input error: Please make sure to pass valid url encoded form in the request body", }); } } - } - else if (request.body === undefined || + } else if ( + request.body === undefined || utils_1.isBuffer(request.body) || - (Object.keys(request.body).length === 0 && request.readable)) { + (Object.keys(request.body).length === 0 && request.readable) + ) { try { // parsing it again to make sure that the request is parsed atleast once by a form data parser request.body = await parseURLEncodedFormData(request); - } - catch (_a) { + } catch (_a) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "API input error: Please make sure to pass valid url encoded form in the request body", @@ -284,12 +310,10 @@ function setHeaderForExpressLikeResponse(res, key, value, allowDuplicateKey) { if (existingValue === undefined) { if (res.header !== undefined) { res.header(key, value); - } - else { + } else { res.setHeader(key, value); } - } - else if (allowDuplicateKey) { + } else if (allowDuplicateKey) { /** We only want to append if it does not already exist For example if the caller is trying to add front token to the access control exposed headers property @@ -298,29 +322,27 @@ function setHeaderForExpressLikeResponse(res, key, value, allowDuplicateKey) { if (typeof existingValue !== "string" || !existingValue.includes(value)) { if (res.header !== undefined) { res.header(key, existingValue + ", " + value); - } - else { + } else { res.setHeader(key, existingValue + ", " + value); } } - } - else { + } else { // we overwrite the current one with the new one if (res.header !== undefined) { res.header(key, value); - } - else { + } else { res.setHeader(key, value); } } - } - catch (err) { - throw new Error("Error while setting header with key: " + - key + - " and value: " + - value + - "\nError: " + - ((_a = err.message) !== null && _a !== void 0 ? _a : err)); + } catch (err) { + throw new Error( + "Error while setting header with key: " + + key + + " and value: " + + value + + "\nError: " + + ((_a = err.message) !== null && _a !== void 0 ? _a : err) + ); } } exports.setHeaderForExpressLikeResponse = setHeaderForExpressLikeResponse; @@ -336,7 +358,12 @@ exports.setHeaderForExpressLikeResponse = setHeaderForExpressLikeResponse; * @param path */ function setCookieForServerResponse(res, key, value, domain, secure, httpOnly, expires, path, sameSite) { - return appendToServerResponse(res, constants_1.COOKIE_HEADER, serializeCookieValue(key, value, domain, secure, httpOnly, expires, path, sameSite), key); + return appendToServerResponse( + res, + constants_1.COOKIE_HEADER, + serializeCookieValue(key, value, domain, secure, httpOnly, expires, path, sameSite), + key + ); } exports.setCookieForServerResponse = setCookieForServerResponse; /** @@ -368,8 +395,7 @@ function getCookieValueToSetInHeader(prev, val, key) { } } prev = removedDuplicate; - } - else { + } else { if (prev.startsWith(key)) { prev = undefined; } diff --git a/lib/build/index.d.ts b/lib/build/index.d.ts index 15330fcd6..2fc94c6a0 100644 --- a/lib/build/index.d.ts +++ b/lib/build/index.d.ts @@ -11,7 +11,11 @@ export default class SuperTokensWrapper { static RecipeUserId: typeof RecipeUserId; static User: typeof User; static getAllCORSHeaders(): string[]; - static getUserCount(includeRecipeIds?: string[], tenantId?: string, userContext?: Record): Promise; + static getUserCount( + includeRecipeIds?: string[], + tenantId?: string, + userContext?: Record + ): Promise; static getUsersOldestFirst(input: { tenantId: string; limit?: number; @@ -44,25 +48,31 @@ export default class SuperTokensWrapper { externalUserIdInfo?: string; force?: boolean; userContext?: Record; - }): Promise<{ - status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"; - } | { - status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR"; - doesSuperTokensUserIdExist: boolean; - doesExternalUserIdExist: boolean; - }>; + }): Promise< + | { + status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"; + } + | { + status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR"; + doesSuperTokensUserIdExist: boolean; + doesExternalUserIdExist: boolean; + } + >; static getUserIdMapping(input: { userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"; userContext?: Record; - }): Promise<{ - status: "OK"; - superTokensUserId: string; - externalUserId: string; - externalUserIdInfo: string | undefined; - } | { - status: "UNKNOWN_MAPPING_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + superTokensUserId: string; + externalUserId: string; + externalUserIdInfo: string | undefined; + } + | { + status: "UNKNOWN_MAPPING_ERROR"; + } + >; static deleteUserIdMapping(input: { userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"; @@ -81,12 +91,23 @@ export default class SuperTokensWrapper { status: "OK" | "UNKNOWN_MAPPING_ERROR"; }>; static getUser(userId: string, userContext?: Record): Promise; - static listUsersByAccountInfo(tenantId: string, accountInfo: AccountInfoInput, doUnionOfAccountInfo?: boolean, userContext?: Record): Promise; - static deleteUser(userId: string, removeAllLinkedAccounts?: boolean, userContext?: Record): Promise<{ + static listUsersByAccountInfo( + tenantId: string, + accountInfo: AccountInfoInput, + doUnionOfAccountInfo?: boolean, + userContext?: Record + ): Promise; + static deleteUser( + userId: string, + removeAllLinkedAccounts?: boolean, + userContext?: Record + ): Promise<{ status: "OK"; }>; static convertToRecipeUserId(recipeUserId: string): RecipeUserId; - static getRequestFromUserContext(userContext: UserContext | undefined): import("./framework").BaseRequest | undefined; + static getRequestFromUserContext( + userContext: UserContext | undefined + ): import("./framework").BaseRequest | undefined; } export declare let init: typeof SuperTokens.init; export declare let getAllCORSHeaders: typeof SuperTokensWrapper.getAllCORSHeaders; diff --git a/lib/build/index.js b/lib/build/index.js index 1f8bfe36f..ecdd1b8b6 100644 --- a/lib/build/index.js +++ b/lib/build/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.User = exports.RecipeUserId = exports.Error = exports.getRequestFromUserContext = exports.convertToRecipeUserId = exports.listUsersByAccountInfo = exports.getUser = exports.updateOrDeleteUserIdMappingInfo = exports.deleteUserIdMapping = exports.getUserIdMapping = exports.createUserIdMapping = exports.deleteUser = exports.getUsersNewestFirst = exports.getUsersOldestFirst = exports.getUserCount = exports.getAllCORSHeaders = exports.init = void 0; const supertokens_1 = __importDefault(require("./supertokens")); @@ -30,25 +32,51 @@ class SuperTokensWrapper { return supertokens_1.default.getInstanceOrThrowError().getAllCORSHeaders(); } static getUserCount(includeRecipeIds, tenantId, userContext) { - return supertokens_1.default.getInstanceOrThrowError().getUserCount(includeRecipeIds, tenantId, utils_1.getUserContext(userContext)); + return supertokens_1.default + .getInstanceOrThrowError() + .getUserCount(includeRecipeIds, tenantId, utils_1.getUserContext(userContext)); } static getUsersOldestFirst(input) { - return recipe_1.default.getInstance().recipeInterfaceImpl.getUsers(Object.assign(Object.assign({ timeJoinedOrder: "ASC" }, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default.getInstance().recipeInterfaceImpl.getUsers( + Object.assign(Object.assign({ timeJoinedOrder: "ASC" }, input), { + userContext: utils_1.getUserContext(input.userContext), + }) + ); } static getUsersNewestFirst(input) { - return recipe_1.default.getInstance().recipeInterfaceImpl.getUsers(Object.assign(Object.assign({ timeJoinedOrder: "DESC" }, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default.getInstance().recipeInterfaceImpl.getUsers( + Object.assign(Object.assign({ timeJoinedOrder: "DESC" }, input), { + userContext: utils_1.getUserContext(input.userContext), + }) + ); } static createUserIdMapping(input) { - return supertokens_1.default.getInstanceOrThrowError().createUserIdMapping(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return supertokens_1.default + .getInstanceOrThrowError() + .createUserIdMapping( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static getUserIdMapping(input) { - return supertokens_1.default.getInstanceOrThrowError().getUserIdMapping(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return supertokens_1.default + .getInstanceOrThrowError() + .getUserIdMapping( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static deleteUserIdMapping(input) { - return supertokens_1.default.getInstanceOrThrowError().deleteUserIdMapping(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return supertokens_1.default + .getInstanceOrThrowError() + .deleteUserIdMapping( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static updateOrDeleteUserIdMappingInfo(input) { - return supertokens_1.default.getInstanceOrThrowError().updateOrDeleteUserIdMappingInfo(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return supertokens_1.default + .getInstanceOrThrowError() + .updateOrDeleteUserIdMappingInfo( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static async getUser(userId, userContext) { return await recipe_1.default.getInstance().recipeInterfaceImpl.getUser({ @@ -99,6 +127,16 @@ exports.convertToRecipeUserId = SuperTokensWrapper.convertToRecipeUserId; exports.getRequestFromUserContext = SuperTokensWrapper.getRequestFromUserContext; exports.Error = SuperTokensWrapper.Error; var recipeUserId_2 = require("./recipeUserId"); -Object.defineProperty(exports, "RecipeUserId", { enumerable: true, get: function () { return __importDefault(recipeUserId_2).default; } }); +Object.defineProperty(exports, "RecipeUserId", { + enumerable: true, + get: function () { + return __importDefault(recipeUserId_2).default; + }, +}); var user_2 = require("./user"); -Object.defineProperty(exports, "User", { enumerable: true, get: function () { return user_2.User; } }); +Object.defineProperty(exports, "User", { + enumerable: true, + get: function () { + return user_2.User; + }, +}); diff --git a/lib/build/ingredients/emaildelivery/index.js b/lib/build/ingredients/emaildelivery/index.js index e728e628a..958cd35a2 100644 --- a/lib/build/ingredients/emaildelivery/index.js +++ b/lib/build/ingredients/emaildelivery/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); class EmailDelivery { diff --git a/lib/build/ingredients/emaildelivery/services/smtp.d.ts b/lib/build/ingredients/emaildelivery/services/smtp.d.ts index a87b5fecb..a74be5624 100644 --- a/lib/build/ingredients/emaildelivery/services/smtp.d.ts +++ b/lib/build/ingredients/emaildelivery/services/smtp.d.ts @@ -23,9 +23,11 @@ export declare type TypeInputSendRawEmail = GetContentResult & { }; export declare type ServiceInterface = { sendRawEmail: (input: TypeInputSendRawEmail) => Promise; - getContent: (input: T & { - userContext: UserContext; - }) => Promise; + getContent: ( + input: T & { + userContext: UserContext; + } + ) => Promise; }; export declare type TypeInput = { smtpSettings: SMTPServiceConfig; diff --git a/lib/build/ingredients/emaildelivery/types.d.ts b/lib/build/ingredients/emaildelivery/types.d.ts index bccfdf888..2986807f5 100644 --- a/lib/build/ingredients/emaildelivery/types.d.ts +++ b/lib/build/ingredients/emaildelivery/types.d.ts @@ -2,19 +2,27 @@ import OverrideableBuilder from "supertokens-js-override"; import { UserContext } from "../../types"; export declare type EmailDeliveryInterface = { - sendEmail: (input: T & { - tenantId: string; - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: T & { + tenantId: string; + userContext: UserContext; + } + ) => Promise; }; /** * config class parameter when parent Recipe create a new EmailDeliveryIngredient object via constructor */ export interface TypeInput { service?: EmailDeliveryInterface; - override?: (originalImplementation: EmailDeliveryInterface, builder: OverrideableBuilder>) => EmailDeliveryInterface; + override?: ( + originalImplementation: EmailDeliveryInterface, + builder: OverrideableBuilder> + ) => EmailDeliveryInterface; } export interface TypeInputWithService { service: EmailDeliveryInterface; - override?: (originalImplementation: EmailDeliveryInterface, builder: OverrideableBuilder>) => EmailDeliveryInterface; + override?: ( + originalImplementation: EmailDeliveryInterface, + builder: OverrideableBuilder> + ) => EmailDeliveryInterface; } diff --git a/lib/build/ingredients/smsdelivery/index.js b/lib/build/ingredients/smsdelivery/index.js index 6a0c5d4dc..a52608dc0 100644 --- a/lib/build/ingredients/smsdelivery/index.js +++ b/lib/build/ingredients/smsdelivery/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); class SmsDelivery { diff --git a/lib/build/ingredients/smsdelivery/services/twilio.d.ts b/lib/build/ingredients/smsdelivery/services/twilio.d.ts index a2c850f48..03e22fb22 100644 --- a/lib/build/ingredients/smsdelivery/services/twilio.d.ts +++ b/lib/build/ingredients/smsdelivery/services/twilio.d.ts @@ -10,33 +10,40 @@ import { UserContext } from "../../../types"; * if none of "from" and "messagingServiceSid" is passed, error * should be thrown. */ -export declare type TwilioServiceConfig = { - accountSid: string; - authToken: string; - from: string; - opts?: ClientOpts; -} | { - accountSid: string; - authToken: string; - messagingServiceSid: string; - opts?: ClientOpts; -}; +export declare type TwilioServiceConfig = + | { + accountSid: string; + authToken: string; + from: string; + opts?: ClientOpts; + } + | { + accountSid: string; + authToken: string; + messagingServiceSid: string; + opts?: ClientOpts; + }; export interface GetContentResult { body: string; toPhoneNumber: string; } export declare type TypeInputSendRawSms = GetContentResult & { userContext: UserContext; -} & ({ - from: string; -} | { - messagingServiceSid: string; -}); +} & ( + | { + from: string; + } + | { + messagingServiceSid: string; + } + ); export declare type ServiceInterface = { sendRawSms: (input: TypeInputSendRawSms) => Promise; - getContent: (input: T & { - userContext: UserContext; - }) => Promise; + getContent: ( + input: T & { + userContext: UserContext; + } + ) => Promise; }; export declare type TypeInput = { twilioSettings: TwilioServiceConfig; diff --git a/lib/build/ingredients/smsdelivery/services/twilio.js b/lib/build/ingredients/smsdelivery/services/twilio.js index 05b97a999..73876eb37 100644 --- a/lib/build/ingredients/smsdelivery/services/twilio.js +++ b/lib/build/ingredients/smsdelivery/services/twilio.js @@ -3,9 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.normaliseUserInputConfig = void 0; function normaliseUserInputConfig(input) { let from = "from" in input.twilioSettings ? input.twilioSettings.from : undefined; - let messagingServiceSid = "messagingServiceSid" in input.twilioSettings ? input.twilioSettings.messagingServiceSid : undefined; - if ((from === undefined && messagingServiceSid === undefined) || - (from !== undefined && messagingServiceSid !== undefined)) { + let messagingServiceSid = + "messagingServiceSid" in input.twilioSettings ? input.twilioSettings.messagingServiceSid : undefined; + if ( + (from === undefined && messagingServiceSid === undefined) || + (from !== undefined && messagingServiceSid !== undefined) + ) { throw Error(`Please pass exactly one of "from" and "messagingServiceSid" config for twilioSettings.`); } return input; diff --git a/lib/build/ingredients/smsdelivery/types.d.ts b/lib/build/ingredients/smsdelivery/types.d.ts index 9a4daf908..781d1bfdf 100644 --- a/lib/build/ingredients/smsdelivery/types.d.ts +++ b/lib/build/ingredients/smsdelivery/types.d.ts @@ -2,19 +2,27 @@ import OverrideableBuilder from "supertokens-js-override"; import { UserContext } from "../../types"; export declare type SmsDeliveryInterface = { - sendSms: (input: T & { - tenantId: string; - userContext: UserContext; - }) => Promise; + sendSms: ( + input: T & { + tenantId: string; + userContext: UserContext; + } + ) => Promise; }; /** * config class parameter when parent Recipe create a new SmsDeliveryIngredient object via constructor */ export interface TypeInput { service?: SmsDeliveryInterface; - override?: (originalImplementation: SmsDeliveryInterface, builder: OverrideableBuilder>) => SmsDeliveryInterface; + override?: ( + originalImplementation: SmsDeliveryInterface, + builder: OverrideableBuilder> + ) => SmsDeliveryInterface; } export interface TypeInputWithService { service: SmsDeliveryInterface; - override?: (originalImplementation: SmsDeliveryInterface, builder: OverrideableBuilder>) => SmsDeliveryInterface; + override?: ( + originalImplementation: SmsDeliveryInterface, + builder: OverrideableBuilder> + ) => SmsDeliveryInterface; } diff --git a/lib/build/logger.js b/lib/build/logger.js index 088fa6b12..9b15f6d42 100644 --- a/lib/build/logger.js +++ b/lib/build/logger.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.enableDebugLogs = exports.logDebugMessage = void 0; const debug_1 = __importDefault(require("debug")); @@ -27,7 +29,11 @@ const SUPERTOKENS_DEBUG_NAMESPACE = "com.supertokens"; */ function logDebugMessage(message) { if (debug_1.default.enabled(SUPERTOKENS_DEBUG_NAMESPACE)) { - debug_1.default(SUPERTOKENS_DEBUG_NAMESPACE)(`{t: "${new Date().toISOString()}", message: \"${message}\", file: \"${getFileLocation()}\" sdkVer: "${version_1.version}"}`); + debug_1.default(SUPERTOKENS_DEBUG_NAMESPACE)( + `{t: "${new Date().toISOString()}", message: \"${message}\", file: \"${getFileLocation()}\" sdkVer: "${ + version_1.version + }"}` + ); console.log(); } } diff --git a/lib/build/nextjs.d.ts b/lib/build/nextjs.d.ts index 5d02daf1c..18adb76ac 100644 --- a/lib/build/nextjs.d.ts +++ b/lib/build/nextjs.d.ts @@ -16,18 +16,32 @@ declare type PartialNextRequest = { }; }; export default class NextJS { - static superTokensNextWrapper(middleware: (next: (middlewareError?: any) => void) => Promise, request: any, response: any): Promise; + static superTokensNextWrapper( + middleware: (next: (middlewareError?: any) => void) => Promise, + request: any, + response: any + ): Promise; static getAppDirRequestHandler(): (req: Request) => Promise; - static getSSRSession(cookies: Array<{ - name: string; - value: string; - }>): Promise<{ + static getSSRSession( + cookies: Array<{ + name: string; + value: string; + }> + ): Promise<{ accessTokenPayload: JWTPayload | undefined; hasToken: boolean; error: Error | undefined; }>; - static withSession(req: NextRequest, handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, options?: VerifySessionOptions, userContext?: Record): Promise; - static withPreParsedRequestResponse(req: NextRequest, handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise): Promise; + static withSession( + req: NextRequest, + handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, + options?: VerifySessionOptions, + userContext?: Record + ): Promise; + static withPreParsedRequestResponse( + req: NextRequest, + handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise + ): Promise; } export declare let superTokensNextWrapper: typeof NextJS.superTokensNextWrapper; export declare let getAppDirRequestHandler: typeof NextJS.getAppDirRequestHandler; diff --git a/lib/build/nextjs.js b/lib/build/nextjs.js index e3b5adfd1..6ced5eff5 100644 --- a/lib/build/nextjs.js +++ b/lib/build/nextjs.js @@ -42,8 +42,7 @@ class NextJS { if (!callbackCalled && !response.finished && !response.headersSent) { return resolve(result); } - } - catch (err) { + } catch (err) { await express_1.errorHandler()(err, request, response, (errorHandlerError) => { if (errorHandlerError !== undefined) { return reject(errorHandlerError); @@ -58,7 +57,10 @@ class NextJS { } static async getSSRSession(cookies) { var _a; - let accessToken = (_a = cookies.find((cookie) => cookie.name === "sAccessToken")) === null || _a === void 0 ? void 0 : _a.value; + let accessToken = + (_a = cookies.find((cookie) => cookie.name === "sAccessToken")) === null || _a === void 0 + ? void 0 + : _a.value; return await customFramework_1.getSessionForSSRUsingAccessToken(accessToken); } static async withSession(req, handler, options, userContext) { diff --git a/lib/build/normalisedURLDomain.js b/lib/build/normalisedURLDomain.js index 9562eda41..850678885 100644 --- a/lib/build/normalisedURLDomain.js +++ b/lib/build/normalisedURLDomain.js @@ -34,17 +34,14 @@ function normaliseURLDomainOrThrowError(input, ignoreProtocol = false) { if (ignoreProtocol) { if (urlObj.hostname.startsWith("localhost") || utils_1.isAnIpAddress(urlObj.hostname)) { input = "http://" + urlObj.host; - } - else { + } else { input = "https://" + urlObj.host; } - } - else { + } else { input = urlObj.protocol + "//" + urlObj.host; } return input; - } - catch (err) { } + } catch (err) {} // not a valid URL if (input.startsWith("/")) { throw Error("Please provide a valid domain name"); @@ -54,16 +51,17 @@ function normaliseURLDomainOrThrowError(input, ignoreProtocol = false) { } // If the input contains a . it means they have given a domain name. // So we try assuming that they have given a domain name - if ((input.indexOf(".") !== -1 || input.startsWith("localhost")) && + if ( + (input.indexOf(".") !== -1 || input.startsWith("localhost")) && !input.startsWith("http://") && - !input.startsWith("https://")) { + !input.startsWith("https://") + ) { input = "https://" + input; // at this point, it should be a valid URL. So we test that before doing a recursive call try { new URL(input); return normaliseURLDomainOrThrowError(input, true); - } - catch (err) { } + } catch (err) {} } throw Error("Please provide a valid domain name"); } diff --git a/lib/build/normalisedURLPath.js b/lib/build/normalisedURLPath.js index db02d9d5b..64d1a7944 100644 --- a/lib/build/normalisedURLPath.js +++ b/lib/build/normalisedURLPath.js @@ -48,14 +48,15 @@ function normaliseURLPathOrThrowError(input) { return input.substr(0, input.length - 1); } return input; - } - catch (err) { } + } catch (err) {} // not a valid URL // If the input contains a . it means they have given a domain name. // So we try assuming that they have given a domain name + path - if ((domainGiven(input) || input.startsWith("localhost")) && + if ( + (domainGiven(input) || input.startsWith("localhost")) && !input.startsWith("http://") && - !input.startsWith("https://")) { + !input.startsWith("https://") + ) { input = "http://" + input; return normaliseURLPathOrThrowError(input); } @@ -67,8 +68,7 @@ function normaliseURLPathOrThrowError(input) { // test that we can convert this to prevent an infinite loop new URL("http://example.com" + input); return normaliseURLPathOrThrowError("http://example.com" + input); - } - catch (err) { + } catch (err) { throw Error("Please provide a valid URL path"); } } @@ -80,12 +80,10 @@ function domainGiven(input) { try { let url = new URL(input); return url.hostname.indexOf(".") !== -1; - } - catch (ignored) { } + } catch (ignored) {} try { let url = new URL("http://" + input); return url.hostname.indexOf(".") !== -1; - } - catch (ignored) { } + } catch (ignored) {} return false; } diff --git a/lib/build/processState.d.ts b/lib/build/processState.d.ts index 078848436..138e253bf 100644 --- a/lib/build/processState.d.ts +++ b/lib/build/processState.d.ts @@ -8,7 +8,7 @@ export declare enum PROCESS_STATE { IS_SIGN_UP_ALLOWED_CALLED = 5, IS_SIGN_IN_ALLOWED_CALLED = 6, IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED = 7, - ADDING_NO_CACHE_HEADER_IN_FETCH = 8 + ADDING_NO_CACHE_HEADER_IN_FETCH = 8, } export declare class ProcessState { history: PROCESS_STATE[]; diff --git a/lib/build/processState.js b/lib/build/processState.js index 414a8d74f..9dd483422 100644 --- a/lib/build/processState.js +++ b/lib/build/processState.js @@ -18,16 +18,17 @@ exports.ProcessState = exports.PROCESS_STATE = void 0; const utils_1 = require("./utils"); var PROCESS_STATE; (function (PROCESS_STATE) { - PROCESS_STATE[PROCESS_STATE["CALLING_SERVICE_IN_VERIFY"] = 0] = "CALLING_SERVICE_IN_VERIFY"; - PROCESS_STATE[PROCESS_STATE["CALLING_SERVICE_IN_GET_API_VERSION"] = 1] = "CALLING_SERVICE_IN_GET_API_VERSION"; - PROCESS_STATE[PROCESS_STATE["CALLING_SERVICE_IN_REQUEST_HELPER"] = 2] = "CALLING_SERVICE_IN_REQUEST_HELPER"; - PROCESS_STATE[PROCESS_STATE["MULTI_JWKS_VALIDATION"] = 3] = "MULTI_JWKS_VALIDATION"; - PROCESS_STATE[PROCESS_STATE["IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS"] = 4] = "IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS"; - PROCESS_STATE[PROCESS_STATE["IS_SIGN_UP_ALLOWED_CALLED"] = 5] = "IS_SIGN_UP_ALLOWED_CALLED"; - PROCESS_STATE[PROCESS_STATE["IS_SIGN_IN_ALLOWED_CALLED"] = 6] = "IS_SIGN_IN_ALLOWED_CALLED"; - PROCESS_STATE[PROCESS_STATE["IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED"] = 7] = "IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED"; - PROCESS_STATE[PROCESS_STATE["ADDING_NO_CACHE_HEADER_IN_FETCH"] = 8] = "ADDING_NO_CACHE_HEADER_IN_FETCH"; -})(PROCESS_STATE = exports.PROCESS_STATE || (exports.PROCESS_STATE = {})); + PROCESS_STATE[(PROCESS_STATE["CALLING_SERVICE_IN_VERIFY"] = 0)] = "CALLING_SERVICE_IN_VERIFY"; + PROCESS_STATE[(PROCESS_STATE["CALLING_SERVICE_IN_GET_API_VERSION"] = 1)] = "CALLING_SERVICE_IN_GET_API_VERSION"; + PROCESS_STATE[(PROCESS_STATE["CALLING_SERVICE_IN_REQUEST_HELPER"] = 2)] = "CALLING_SERVICE_IN_REQUEST_HELPER"; + PROCESS_STATE[(PROCESS_STATE["MULTI_JWKS_VALIDATION"] = 3)] = "MULTI_JWKS_VALIDATION"; + PROCESS_STATE[(PROCESS_STATE["IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS"] = 4)] = + "IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS"; + PROCESS_STATE[(PROCESS_STATE["IS_SIGN_UP_ALLOWED_CALLED"] = 5)] = "IS_SIGN_UP_ALLOWED_CALLED"; + PROCESS_STATE[(PROCESS_STATE["IS_SIGN_IN_ALLOWED_CALLED"] = 6)] = "IS_SIGN_IN_ALLOWED_CALLED"; + PROCESS_STATE[(PROCESS_STATE["IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED"] = 7)] = "IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED"; + PROCESS_STATE[(PROCESS_STATE["ADDING_NO_CACHE_HEADER_IN_FETCH"] = 8)] = "ADDING_NO_CACHE_HEADER_IN_FETCH"; +})((PROCESS_STATE = exports.PROCESS_STATE || (exports.PROCESS_STATE = {}))); class ProcessState { constructor() { this.history = []; @@ -56,12 +57,10 @@ class ProcessState { if (result === undefined) { if (Date.now() - startTime > timeInMS) { resolve(undefined); - } - else { + } else { setTimeout(tryAndGet, 1000); } - } - else { + } else { resolve(result); } } diff --git a/lib/build/querier.d.ts b/lib/build/querier.d.ts index 4b7f18d1c..17d12763b 100644 --- a/lib/build/querier.d.ts +++ b/lib/build/querier.d.ts @@ -20,18 +20,42 @@ export declare class Querier { static reset(): void; getHostsAliveForTesting: () => Set; static getNewInstanceOrThrowError(rIdToCore?: string): Querier; - static init(hosts?: { - domain: NormalisedURLDomain; - basePath: NormalisedURLPath; - }[], apiKey?: string, networkInterceptor?: NetworkInterceptor, disableCache?: boolean): void; + static init( + hosts?: { + domain: NormalisedURLDomain; + basePath: NormalisedURLPath; + }[], + apiKey?: string, + networkInterceptor?: NetworkInterceptor, + disableCache?: boolean + ): void; sendPostRequest: (path: NormalisedURLPath, body: any, userContext: UserContext) => Promise; - sendDeleteRequest: (path: NormalisedURLPath, body: any, params: any | undefined, userContext: UserContext) => Promise; - sendGetRequest: (path: NormalisedURLPath, params: Record, userContext: UserContext) => Promise; - sendGetRequestWithResponseHeaders: (path: NormalisedURLPath, params: Record, inpHeaders: Record | undefined, userContext: UserContext) => Promise<{ + sendDeleteRequest: ( + path: NormalisedURLPath, + body: any, + params: any | undefined, + userContext: UserContext + ) => Promise; + sendGetRequest: ( + path: NormalisedURLPath, + params: Record, + userContext: UserContext + ) => Promise; + sendGetRequestWithResponseHeaders: ( + path: NormalisedURLPath, + params: Record, + inpHeaders: Record | undefined, + userContext: UserContext + ) => Promise<{ body: any; headers: Headers; }>; - sendPutRequest: (path: NormalisedURLPath, body: any, params: Record, userContext: UserContext) => Promise; + sendPutRequest: ( + path: NormalisedURLPath, + body: any, + params: Record, + userContext: UserContext + ) => Promise; sendPatchRequest: (path: NormalisedURLPath, body: any, userContext: UserContext) => Promise; invalidateCoreCallCache: (userContext: UserContext, updGlobalCacheTagIfNecessary?: boolean) => void; getAllCoreUrlsForPath(path: string): string[]; diff --git a/lib/build/querier.js b/lib/build/querier.js index 7b59adc6f..9ee1f364f 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Querier = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -34,7 +36,9 @@ class Querier { if (Querier.apiVersion !== undefined) { return Querier.apiVersion; } - processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.CALLING_SERVICE_IN_GET_API_VERSION); + processState_1.ProcessState.getInstance().addState( + processState_1.PROCESS_STATE.CALLING_SERVICE_IN_GET_API_VERSION + ); const st = supertokens_1.default.getInstanceOrThrowError(); const appInfo = st.appInfo; const request = st.getRequestFromUserContext(userContext); @@ -43,33 +47,46 @@ class Querier { websiteDomain: appInfo.getOrigin({ request, userContext }).getAsStringDangerous(), }; const queryParams = new URLSearchParams(queryParamsObj).toString(); - let { body: response } = await this.sendRequestHelper(new normalisedURLPath_1.default("/apiversion"), "GET", async (url) => { - let headers = {}; - if (Querier.apiKey !== undefined) { - headers = { - "api-key": Querier.apiKey, - }; - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor({ - url: url, - method: "get", - headers: headers, - params: queryParamsObj, - }, userContext); - url = request.url; - headers = request.headers; - } - let response = await utils_1.doFetch(url + `?${queryParams}`, { - method: "GET", - headers, - }); - return response; - }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); + let { body: response } = await this.sendRequestHelper( + new normalisedURLPath_1.default("/apiversion"), + "GET", + async (url) => { + let headers = {}; + if (Querier.apiKey !== undefined) { + headers = { + "api-key": Querier.apiKey, + }; + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "get", + headers: headers, + params: queryParamsObj, + }, + userContext + ); + url = request.url; + headers = request.headers; + } + let response = await utils_1.doFetch(url + `?${queryParams}`, { + method: "GET", + headers, + }); + return response; + }, + ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 + ); let cdiSupportedByServer = response.versions; - let supportedVersion = utils_1.getLargestVersionFromIntersection(cdiSupportedByServer, version_1.cdiSupported); + let supportedVersion = utils_1.getLargestVersionFromIntersection( + cdiSupportedByServer, + version_1.cdiSupported + ); if (supportedVersion === undefined) { - throw Error("The running SuperTokens core version is not compatible with this NodeJS SDK. Please visit https://supertokens.io/docs/community/compatibility to find the right versions"); + throw Error( + "The running SuperTokens core version is not compatible with this NodeJS SDK. Please visit https://supertokens.io/docs/community/compatibility to find the right versions" + ); } Querier.apiVersion = supportedVersion; return Querier.apiVersion; @@ -84,258 +101,335 @@ class Querier { this.sendPostRequest = async (path, body, userContext) => { var _a; this.invalidateCoreCallCache(userContext); - const { body: respBody } = await this.sendRequestHelper(path, "POST", async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = { - "cdi-version": apiVersion, - }; - headers["content-type"] = "application/json; charset=utf-8"; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor({ - url: url, - method: "post", - headers: headers, - body: body, - }, userContext); - url = request.url; - headers = request.headers; - if (request.body !== undefined) { - body = request.body; + const { body: respBody } = await this.sendRequestHelper( + path, + "POST", + async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = { + "cdi-version": apiVersion, + }; + headers["content-type"] = "application/json; charset=utf-8"; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); } - } - return utils_1.doFetch(url, { - method: "POST", - body: body !== undefined ? JSON.stringify(body) : undefined, - headers, - }); - }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "post", + headers: headers, + body: body, + }, + userContext + ); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; + } + } + return utils_1.doFetch(url, { + method: "POST", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, + ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 + ); return respBody; }; // path should start with "/" this.sendDeleteRequest = async (path, body, params, userContext) => { var _a; this.invalidateCoreCallCache(userContext); - const { body: respBody } = await this.sendRequestHelper(path, "DELETE", async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor({ - url: url, - method: "delete", - headers: headers, - params: params, - body: body, - }, userContext); - url = request.url; - headers = request.headers; - if (request.body !== undefined) { - body = request.body; + const { body: respBody } = await this.sendRequestHelper( + path, + "DELETE", + async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); } - if (request.params !== undefined) { - params = request.params; + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); } - } - const finalURL = new URL(url); - const searchParams = new URLSearchParams(params); - finalURL.search = searchParams.toString(); - return utils_1.doFetch(finalURL.toString(), { - method: "DELETE", - body: body !== undefined ? JSON.stringify(body) : undefined, - headers, - }); - }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "delete", + headers: headers, + params: params, + body: body, + }, + userContext + ); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; + } + if (request.params !== undefined) { + params = request.params; + } + } + const finalURL = new URL(url); + const searchParams = new URLSearchParams(params); + finalURL.search = searchParams.toString(); + return utils_1.doFetch(finalURL.toString(), { + method: "DELETE", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, + ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 + ); return respBody; }; // path should start with "/" this.sendGetRequest = async (path, params, userContext) => { var _a; - const { body: respBody } = await this.sendRequestHelper(path, "GET", async (url) => { - var _a, _b, _c, _d; - let apiVersion = await this.getAPIVersion(userContext); - let headers = { "cdi-version": apiVersion }; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - /* CACHE CHECK BEGIN */ - const sortedKeys = Object.keys(params).sort(); - const sortedHeaderKeys = Object.keys(headers).sort(); - let uniqueKey = path.getAsStringDangerous(); - for (const key of sortedKeys) { - const value = params[key]; - uniqueKey += `;${key}=${value}`; - } - uniqueKey += ";hdrs"; - for (const key of sortedHeaderKeys) { - const value = headers[key]; - uniqueKey += `;${key}=${value}`; - } - // If globalCacheTag doesn't match the current one (or if it's not defined), we invalidate the cache, because that means - // that there was a non-GET call that didn't have a proper userContext passed to it. - // However, we do not want to invalidate all global caches for a GET call even if it was made without a proper user context. - if (((_a = userContext._default) === null || _a === void 0 ? void 0 : _a.globalCacheTag) !== Querier.globalCacheTag) { - this.invalidateCoreCallCache(userContext, false); - } - if (!Querier.disableCache && uniqueKey in ((_c = (_b = userContext._default) === null || _b === void 0 ? void 0 : _b.coreCallCache) !== null && _c !== void 0 ? _c : {})) { - return userContext._default.coreCallCache[uniqueKey]; - } - /* CACHE CHECK END */ - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor({ - url: url, - method: "get", - headers: headers, - params: params, - }, userContext); - url = request.url; - headers = request.headers; - if (request.params !== undefined) { - params = request.params; + const { body: respBody } = await this.sendRequestHelper( + path, + "GET", + async (url) => { + var _a, _b, _c, _d; + let apiVersion = await this.getAPIVersion(userContext); + let headers = { "cdi-version": apiVersion }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + /* CACHE CHECK BEGIN */ + const sortedKeys = Object.keys(params).sort(); + const sortedHeaderKeys = Object.keys(headers).sort(); + let uniqueKey = path.getAsStringDangerous(); + for (const key of sortedKeys) { + const value = params[key]; + uniqueKey += `;${key}=${value}`; + } + uniqueKey += ";hdrs"; + for (const key of sortedHeaderKeys) { + const value = headers[key]; + uniqueKey += `;${key}=${value}`; + } + // If globalCacheTag doesn't match the current one (or if it's not defined), we invalidate the cache, because that means + // that there was a non-GET call that didn't have a proper userContext passed to it. + // However, we do not want to invalidate all global caches for a GET call even if it was made without a proper user context. + if ( + ((_a = userContext._default) === null || _a === void 0 ? void 0 : _a.globalCacheTag) !== + Querier.globalCacheTag + ) { + this.invalidateCoreCallCache(userContext, false); + } + if ( + !Querier.disableCache && + uniqueKey in + ((_c = + (_b = userContext._default) === null || _b === void 0 ? void 0 : _b.coreCallCache) !== + null && _c !== void 0 + ? _c + : {}) + ) { + return userContext._default.coreCallCache[uniqueKey]; + } + /* CACHE CHECK END */ + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "get", + headers: headers, + params: params, + }, + userContext + ); + url = request.url; + headers = request.headers; + if (request.params !== undefined) { + params = request.params; + } + } + const finalURL = new URL(url); + const searchParams = new URLSearchParams( + Object.entries(params).filter(([_, value]) => value !== undefined) + ); + finalURL.search = searchParams.toString(); + // Update cache and return + let response = await utils_1.doFetch(finalURL.toString(), { + method: "GET", + headers, + }); + if (response.status === 302) { + return response; + } + if (response.status === 200 && !Querier.disableCache) { + // If the request was successful, we save the result into the cache + // plus we update the cache tag + userContext._default = Object.assign(Object.assign({}, userContext._default), { + coreCallCache: Object.assign( + Object.assign( + {}, + (_d = userContext._default) === null || _d === void 0 ? void 0 : _d.coreCallCache + ), + { [uniqueKey]: response } + ), + globalCacheTag: Querier.globalCacheTag, + }); } - } - const finalURL = new URL(url); - const searchParams = new URLSearchParams(Object.entries(params).filter(([_, value]) => value !== undefined)); - finalURL.search = searchParams.toString(); - // Update cache and return - let response = await utils_1.doFetch(finalURL.toString(), { - method: "GET", - headers, - }); - if (response.status === 302) { return response; - } - if (response.status === 200 && !Querier.disableCache) { - // If the request was successful, we save the result into the cache - // plus we update the cache tag - userContext._default = Object.assign(Object.assign({}, userContext._default), { coreCallCache: Object.assign(Object.assign({}, (_d = userContext._default) === null || _d === void 0 ? void 0 : _d.coreCallCache), { [uniqueKey]: response }), globalCacheTag: Querier.globalCacheTag }); - } - return response; - }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); + }, + ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 + ); return respBody; }; this.sendGetRequestWithResponseHeaders = async (path, params, inpHeaders, userContext) => { var _a; - return await this.sendRequestHelper(path, "GET", async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = inpHeaders !== null && inpHeaders !== void 0 ? inpHeaders : {}; - headers["cdi-version"] = apiVersion; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor({ - url: url, - method: "get", - headers: headers, - params: params, - }, userContext); - url = request.url; - headers = request.headers; - if (request.params !== undefined) { - params = request.params; + return await this.sendRequestHelper( + path, + "GET", + async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = inpHeaders !== null && inpHeaders !== void 0 ? inpHeaders : {}; + headers["cdi-version"] = apiVersion; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); } - } - const finalURL = new URL(url); - const searchParams = new URLSearchParams(Object.entries(params).filter(([_, value]) => value !== undefined)); - finalURL.search = searchParams.toString(); - return utils_1.doFetch(finalURL.toString(), { - method: "GET", - headers, - }); - }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "get", + headers: headers, + params: params, + }, + userContext + ); + url = request.url; + headers = request.headers; + if (request.params !== undefined) { + params = request.params; + } + } + const finalURL = new URL(url); + const searchParams = new URLSearchParams( + Object.entries(params).filter(([_, value]) => value !== undefined) + ); + finalURL.search = searchParams.toString(); + return utils_1.doFetch(finalURL.toString(), { + method: "GET", + headers, + }); + }, + ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 + ); }; // path should start with "/" this.sendPutRequest = async (path, body, params, userContext) => { var _a; this.invalidateCoreCallCache(userContext); - const { body: respBody } = await this.sendRequestHelper(path, "PUT", async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor({ - url: url, - method: "put", - headers: headers, - body: body, - params: params, - }, userContext); - url = request.url; - headers = request.headers; - if (request.body !== undefined) { - body = request.body; + const { body: respBody } = await this.sendRequestHelper( + path, + "PUT", + async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); } - } - const finalURL = new URL(url); - const searchParams = new URLSearchParams(Object.entries(params).filter(([_, value]) => value !== undefined)); - finalURL.search = searchParams.toString(); - return utils_1.doFetch(finalURL.toString(), { - method: "PUT", - body: body !== undefined ? JSON.stringify(body) : undefined, - headers, - }); - }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "put", + headers: headers, + body: body, + params: params, + }, + userContext + ); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; + } + } + const finalURL = new URL(url); + const searchParams = new URLSearchParams( + Object.entries(params).filter(([_, value]) => value !== undefined) + ); + finalURL.search = searchParams.toString(); + return utils_1.doFetch(finalURL.toString(), { + method: "PUT", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, + ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 + ); return respBody; }; // path should start with "/" this.sendPatchRequest = async (path, body, userContext) => { var _a; this.invalidateCoreCallCache(userContext); - const { body: respBody } = await this.sendRequestHelper(path, "PATCH", async (url) => { - let apiVersion = await this.getAPIVersion(userContext); - let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; - if (Querier.apiKey !== undefined) { - headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); - } - if (path.isARecipePath() && this.rIdToCore !== undefined) { - headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); - } - if (Querier.networkInterceptor !== undefined) { - let request = Querier.networkInterceptor({ - url: url, - method: "patch", - headers: headers, - body: body, - }, userContext); - url = request.url; - headers = request.headers; - if (request.body !== undefined) { - body = request.body; + const { body: respBody } = await this.sendRequestHelper( + path, + "PATCH", + async (url) => { + let apiVersion = await this.getAPIVersion(userContext); + let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); } - } - return utils_1.doFetch(url, { - method: "PATCH", - body: body !== undefined ? JSON.stringify(body) : undefined, - headers, - }); - }, ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0); + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "patch", + headers: headers, + body: body, + }, + userContext + ); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; + } + } + return utils_1.doFetch(url, { + method: "PATCH", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, + ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 + ); return respBody; }; this.invalidateCoreCallCache = (userContext, updGlobalCacheTagIfNecessary = true) => { var _a; - if (updGlobalCacheTagIfNecessary && ((_a = userContext._default) === null || _a === void 0 ? void 0 : _a.keepCacheAlive) !== true) { + if ( + updGlobalCacheTagIfNecessary && + ((_a = userContext._default) === null || _a === void 0 ? void 0 : _a.keepCacheAlive) !== true + ) { Querier.globalCacheTag = Date.now(); } userContext._default = Object.assign(Object.assign({}, userContext._default), { coreCallCache: {} }); @@ -344,7 +438,9 @@ class Querier { this.sendRequestHelper = async (path, method, requestFunc, numberOfTries, retryInfoMap) => { var _a; if (this.__hosts === undefined) { - throw Error("No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using."); + throw Error( + "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." + ); } if (numberOfTries === 0) { throw Error("No SuperTokens core available to query"); @@ -363,7 +459,9 @@ class Querier { Querier.lastTriedIndex++; Querier.lastTriedIndex = Querier.lastTriedIndex % this.__hosts.length; try { - processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.CALLING_SERVICE_IN_REQUEST_HELPER); + processState_1.ProcessState.getInstance().addState( + processState_1.PROCESS_STATE.CALLING_SERVICE_IN_REQUEST_HELPER + ); logger_1.logDebugMessage(`core-call: ${method} ${url}`); let response = await requestFunc(url); if (utils_1.isTestEnv()) { @@ -372,17 +470,22 @@ class Querier { if (response.status !== 200) { throw response; } - if ((_a = response.headers.get("content-type")) === null || _a === void 0 ? void 0 : _a.startsWith("text")) { + if ( + (_a = response.headers.get("content-type")) === null || _a === void 0 + ? void 0 + : _a.startsWith("text") + ) { return { body: await response.clone().text(), headers: response.headers }; } return { body: await response.clone().json(), headers: response.headers }; - } - catch (err) { - if (err.message !== undefined && + } catch (err) { + if ( + err.message !== undefined && (err.message.includes("Failed to fetch") || err.message.includes("fetch failed") || err.message.includes("ECONNREFUSED") || - err.code === "ECONNREFUSED")) { + err.code === "ECONNREFUSED") + ) { return this.sendRequestHelper(path, method, requestFunc, numberOfTries - 1, retryInfoMap); } if ("status" in err && "text" in err) { @@ -396,14 +499,16 @@ class Querier { return this.sendRequestHelper(path, method, requestFunc, numberOfTries, retryInfoMap); } } - throw new Error("SuperTokens core threw an error for a " + - method + - " request to path: '" + - path.getAsStringDangerous() + - "' with status code: " + - err.status + - " and message: " + - (await err.text())); + throw new Error( + "SuperTokens core threw an error for a " + + method + + " request to path: '" + + path.getAsStringDangerous() + + "' with status code: " + + err.status + + " and message: " + + (await err.text()) + ); } throw err; } diff --git a/lib/build/recipe/accountlinking/index.d.ts b/lib/build/recipe/accountlinking/index.d.ts index 602f24222..6c8b57705 100644 --- a/lib/build/recipe/accountlinking/index.d.ts +++ b/lib/build/recipe/accountlinking/index.d.ts @@ -14,7 +14,12 @@ export default class Wrapper { * same as the input recipeUserId if it was made into a primary user, or if there was * no linking that happened. */ - static createPrimaryUserIdOrLinkAccounts(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainerInterface, userContext?: Record): Promise; + static createPrimaryUserIdOrLinkAccounts( + tenantId: string, + recipeUserId: RecipeUserId, + session?: SessionContainerInterface, + userContext?: Record + ): Promise; /** * This function returns the primary user that the input recipe ID can be * linked to. It can be used to determine which primary account the linking @@ -24,64 +29,121 @@ export default class Wrapper { * that the input recipe ID can be linked to, and therefore it can be made * into a primary user itself. */ - static getPrimaryUserThatCanBeLinkedToRecipeUserId(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record): Promise; - static canCreatePrimaryUser(recipeUserId: RecipeUserId, userContext?: Record): Promise<{ - status: "OK"; - wasAlreadyAPrimaryUser: boolean; - } | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - }>; - static createPrimaryUser(recipeUserId: RecipeUserId, userContext?: Record): Promise<{ - status: "OK"; - user: import("../../types").User; - wasAlreadyAPrimaryUser: boolean; - } | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - } | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - }>; - static canLinkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record): Promise<{ - status: "OK"; - accountsAlreadyLinked: boolean; - } | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - description: string; - primaryUserId: string; - } | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } | { - status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; - }>; - static linkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record): Promise<{ - status: "OK"; - accountsAlreadyLinked: boolean; - user: import("../../types").User; - } | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - user: import("../../types").User; - } | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } | { - status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; - }>; - static unlinkAccount(recipeUserId: RecipeUserId, userContext?: Record): Promise<{ + static getPrimaryUserThatCanBeLinkedToRecipeUserId( + tenantId: string, + recipeUserId: RecipeUserId, + userContext?: Record + ): Promise; + static canCreatePrimaryUser( + recipeUserId: RecipeUserId, + userContext?: Record + ): Promise< + | { + status: "OK"; + wasAlreadyAPrimaryUser: boolean; + } + | { + status: + | "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" + | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + >; + static createPrimaryUser( + recipeUserId: RecipeUserId, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: import("../../types").User; + wasAlreadyAPrimaryUser: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + >; + static canLinkAccounts( + recipeUserId: RecipeUserId, + primaryUserId: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + accountsAlreadyLinked: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + description: string; + primaryUserId: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; + } + >; + static linkAccounts( + recipeUserId: RecipeUserId, + primaryUserId: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + accountsAlreadyLinked: boolean; + user: import("../../types").User; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + user: import("../../types").User; + } + | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; + } + >; + static unlinkAccount( + recipeUserId: RecipeUserId, + userContext?: Record + ): Promise<{ status: "OK"; wasRecipeUserDeleted: boolean; wasLinked: boolean; }>; - static isSignUpAllowed(tenantId: string, newUser: AccountInfoWithRecipeId, isVerified: boolean, session?: SessionContainerInterface, userContext?: Record): Promise; - static isSignInAllowed(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainerInterface, userContext?: Record): Promise; - static isEmailChangeAllowed(recipeUserId: RecipeUserId, newEmail: string, isVerified: boolean, session?: SessionContainerInterface, userContext?: Record): Promise; + static isSignUpAllowed( + tenantId: string, + newUser: AccountInfoWithRecipeId, + isVerified: boolean, + session?: SessionContainerInterface, + userContext?: Record + ): Promise; + static isSignInAllowed( + tenantId: string, + recipeUserId: RecipeUserId, + session?: SessionContainerInterface, + userContext?: Record + ): Promise; + static isEmailChangeAllowed( + recipeUserId: RecipeUserId, + newEmail: string, + isVerified: boolean, + session?: SessionContainerInterface, + userContext?: Record + ): Promise; } export declare const init: typeof Recipe.init; export declare const canCreatePrimaryUser: typeof Wrapper.canCreatePrimaryUser; diff --git a/lib/build/recipe/accountlinking/index.js b/lib/build/recipe/accountlinking/index.js index 2f35585be..f65eeeaee 100644 --- a/lib/build/recipe/accountlinking/index.js +++ b/lib/build/recipe/accountlinking/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isEmailChangeAllowed = exports.isSignInAllowed = exports.isSignUpAllowed = exports.getPrimaryUserThatCanBeLinkedToRecipeUserId = exports.createPrimaryUserIdOrLinkAccounts = exports.unlinkAccount = exports.linkAccounts = exports.canLinkAccounts = exports.createPrimaryUser = exports.canCreatePrimaryUser = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); diff --git a/lib/build/recipe/accountlinking/recipe.d.ts b/lib/build/recipe/accountlinking/recipe.d.ts index b2c56256d..261a2faa8 100644 --- a/lib/build/recipe/accountlinking/recipe.d.ts +++ b/lib/build/recipe/accountlinking/recipe.d.ts @@ -13,26 +13,54 @@ export default class Recipe extends RecipeModule { static RECIPE_ID: string; config: TypeNormalisedInput; recipeInterfaceImpl: RecipeInterface; - constructor(recipeId: string, appInfo: NormalisedAppinfo, config: TypeInput | undefined, _recipes: {}, _ingredients: {}); + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + config: TypeInput | undefined, + _recipes: {}, + _ingredients: {} + ); static init(config?: TypeInput): RecipeListFunction; static getInstance(): Recipe; getAPIsHandled(): APIHandled[]; - handleAPIRequest(_id: string, _tenantId: string, _req: BaseRequest, _response: BaseResponse, _path: normalisedURLPath, _method: HTTPMethod): Promise; + handleAPIRequest( + _id: string, + _tenantId: string, + _req: BaseRequest, + _response: BaseResponse, + _path: normalisedURLPath, + _method: HTTPMethod + ): Promise; handleError(error: error, _request: BaseRequest, _response: BaseResponse): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; static reset(): void; - getPrimaryUserThatCanBeLinkedToRecipeUserId: ({ tenantId, user, userContext, }: { + getPrimaryUserThatCanBeLinkedToRecipeUserId: ({ + tenantId, + user, + userContext, + }: { tenantId: string; user: User; userContext: UserContext; }) => Promise; - getOldestUserThatCanBeLinkedToRecipeUser: ({ tenantId, user, userContext, }: { + getOldestUserThatCanBeLinkedToRecipeUser: ({ + tenantId, + user, + userContext, + }: { tenantId: string; user: User; userContext: UserContext; }) => Promise; - isSignInAllowed: ({ user, accountInfo, tenantId, session, signInVerifiesLoginMethod, userContext, }: { + isSignInAllowed: ({ + user, + accountInfo, + tenantId, + session, + signInVerifiesLoginMethod, + userContext, + }: { user: User; accountInfo: AccountInfoWithRecipeId | LoginMethod; session: SessionContainerInterface | undefined; @@ -40,14 +68,28 @@ export default class Recipe extends RecipeModule { tenantId: string; userContext: UserContext; }) => Promise; - isSignUpAllowed: ({ newUser, isVerified, session, tenantId, userContext, }: { + isSignUpAllowed: ({ + newUser, + isVerified, + session, + tenantId, + userContext, + }: { newUser: AccountInfoWithRecipeId; isVerified: boolean; session: SessionContainerInterface | undefined; tenantId: string; userContext: UserContext; }) => Promise; - isSignInUpAllowedHelper: ({ accountInfo, isVerified, session, tenantId, isSignIn, user, userContext, }: { + isSignInUpAllowedHelper: ({ + accountInfo, + isVerified, + session, + tenantId, + isSignIn, + user, + userContext, + }: { accountInfo: AccountInfoWithRecipeId | LoginMethod; isVerified: boolean; session: SessionContainerInterface | undefined; @@ -62,27 +104,43 @@ export default class Recipe extends RecipeModule { isVerified: boolean; session: SessionContainerInterface | undefined; userContext: UserContext; - }) => Promise<{ - allowed: true; - } | { - allowed: false; - reason: "PRIMARY_USER_CONFLICT" | "ACCOUNT_TAKEOVER_RISK"; - }>; + }) => Promise< + | { + allowed: true; + } + | { + allowed: false; + reason: "PRIMARY_USER_CONFLICT" | "ACCOUNT_TAKEOVER_RISK"; + } + >; verifyEmailForRecipeUserIfLinkedAccountsAreVerified: (input: { user: User; recipeUserId: RecipeUserId; userContext: UserContext; }) => Promise; - shouldBecomePrimaryUser(user: User, tenantId: string, session: SessionContainerInterface | undefined, userContext: UserContext): Promise; - tryLinkingByAccountInfoOrCreatePrimaryUser({ inputUser, session, tenantId, userContext, }: { + shouldBecomePrimaryUser( + user: User, + tenantId: string, + session: SessionContainerInterface | undefined, + userContext: UserContext + ): Promise; + tryLinkingByAccountInfoOrCreatePrimaryUser({ + inputUser, + session, + tenantId, + userContext, + }: { tenantId: string; inputUser: User; session: SessionContainerInterface | undefined; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - } | { - status: "NO_LINK"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + } + | { + status: "NO_LINK"; + } + >; } diff --git a/lib/build/recipe/accountlinking/recipe.js b/lib/build/recipe/accountlinking/recipe.js index 141f665eb..bc5719332 100644 --- a/lib/build/recipe/accountlinking/recipe.js +++ b/lib/build/recipe/accountlinking/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const utils_1 = require("./utils"); @@ -31,7 +33,7 @@ const utils_2 = require("../../utils"); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, config, _recipes, _ingredients) { super(recipeId, appInfo); - this.getPrimaryUserThatCanBeLinkedToRecipeUserId = async ({ tenantId, user, userContext, }) => { + this.getPrimaryUserThatCanBeLinkedToRecipeUserId = async ({ tenantId, user, userContext }) => { // first we check if this user itself is a primary user or not. If it is, we return that. if (user.isPrimaryUser) { return user; @@ -39,15 +41,22 @@ class Recipe extends recipeModule_1.default { // then, we try and find a primary user based on the email / phone number / third party ID / credentialId. let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo: Object.assign(Object.assign({}, user.loginMethods[0]), { - // omit webauthn so we have the correct type - webauthn: undefined }), + accountInfo: Object.assign(Object.assign({}, user.loginMethods[0]), { + // we don't need to list by (webauthn) credentialId because we are looking for + // a user to link to the current recipe user, but any search using the credentialId + // of the current user "will identify the same user" which is the current one. + webauthn: undefined, + }), doUnionOfAccountInfo: true, userContext, }); - logger_1.logDebugMessage(`getPrimaryUserThatCanBeLinkedToRecipeUserId found ${users.length} matching users`); + logger_1.logDebugMessage( + `getPrimaryUserThatCanBeLinkedToRecipeUserId found ${users.length} matching users` + ); let pUsers = users.filter((u) => u.isPrimaryUser); - logger_1.logDebugMessage(`getPrimaryUserThatCanBeLinkedToRecipeUserId found ${pUsers.length} matching primary users`); + logger_1.logDebugMessage( + `getPrimaryUserThatCanBeLinkedToRecipeUserId found ${pUsers.length} matching primary users` + ); if (pUsers.length > 1) { // this means that the new user has account info such that it's // spread across multiple primary user IDs. In this case, even @@ -75,7 +84,7 @@ class Recipe extends recipeModule_1.default { } return pUsers.length === 0 ? undefined : pUsers[0]; }; - this.getOldestUserThatCanBeLinkedToRecipeUser = async ({ tenantId, user, userContext, }) => { + this.getOldestUserThatCanBeLinkedToRecipeUser = async ({ tenantId, user, userContext }) => { // first we check if this user itself is a primary user or not. If it is, we return that since it cannot be linked to anything else if (user.isPrimaryUser) { return user; @@ -83,20 +92,31 @@ class Recipe extends recipeModule_1.default { // then, we try and find matching users based on the email / phone number / third party ID. let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo: Object.assign(Object.assign({}, user.loginMethods[0]), { - // omit webauthn so we have the correct type - webauthn: undefined }), + accountInfo: Object.assign(Object.assign({}, user.loginMethods[0]), { + // we don't need to list by (webauthn) credentialId because we are looking for + // a user to link to the current recipe user, but any search using the credentialId + // of the current user "will identify the same user" which is the current one. + webauthn: undefined, + }), doUnionOfAccountInfo: true, userContext, }); logger_1.logDebugMessage(`getOldestUserThatCanBeLinkedToRecipeUser found ${users.length} matching users`); // finally select the oldest one - const oldestUser = users.length === 0 - ? undefined - : users.reduce((min, curr) => (min.timeJoined < curr.timeJoined ? min : curr)); + const oldestUser = + users.length === 0 + ? undefined + : users.reduce((min, curr) => (min.timeJoined < curr.timeJoined ? min : curr)); return oldestUser; }; - this.isSignInAllowed = async ({ user, accountInfo, tenantId, session, signInVerifiesLoginMethod, userContext, }) => { + this.isSignInAllowed = async ({ + user, + accountInfo, + tenantId, + session, + signInVerifiesLoginMethod, + userContext, + }) => { processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.IS_SIGN_IN_ALLOWED_CALLED); if (user.isPrimaryUser || user.loginMethods[0].verified || signInVerifiesLoginMethod) { return true; @@ -111,7 +131,7 @@ class Recipe extends recipeModule_1.default { userContext, }); }; - this.isSignUpAllowed = async ({ newUser, isVerified, session, tenantId, userContext, }) => { + this.isSignUpAllowed = async ({ newUser, isVerified, session, tenantId, userContext }) => { processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.IS_SIGN_UP_ALLOWED_CALLED); if (newUser.email !== undefined && newUser.phoneNumber !== undefined) { // we do this check cause below when we call listUsersByAccountInfo, @@ -128,8 +148,18 @@ class Recipe extends recipeModule_1.default { isSignIn: false, }); }; - this.isSignInUpAllowedHelper = async ({ accountInfo, isVerified, session, tenantId, isSignIn, user, userContext, }) => { - processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED); + this.isSignInUpAllowedHelper = async ({ + accountInfo, + isVerified, + session, + tenantId, + isSignIn, + user, + userContext, + }) => { + processState_1.ProcessState.getInstance().addState( + processState_1.PROCESS_STATE.IS_SIGN_IN_UP_ALLOWED_HELPER_CALLED + ); // since this is a recipe level user, we have to do the following checks // before allowing sign in. We do these checks cause sign in also attempts // account linking: @@ -150,25 +180,33 @@ class Recipe extends recipeModule_1.default { // we do not pass in third party info, or both email or phone // cause we want to guarantee that the output array contains just one // primary user. - // map according to the correct types here so we don't have to change the types upstream let users = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, - accountInfo: Object.assign(Object.assign({}, accountInfo), { - // omit webauthn so we have the correct type - webauthn: undefined }), + accountInfo: Object.assign(Object.assign({}, accountInfo), { + // we don't need to list by (webauthn) credentialId because we are looking for + // a user to link to the current recipe user, but any search using the credentialId + // of the current user "will identify the same user" which is the current one. + webauthn: undefined, + }), doUnionOfAccountInfo: true, userContext, }); if (users.length === 0) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because no user with given account info"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning true because no user with given account info" + ); // this is a brand new email / phone number, so we allow sign up. return true; } if (isSignIn && user === undefined) { - throw new Error("This should never happen: isSignInUpAllowedHelper called with isSignIn: true, user: undefined"); + throw new Error( + "This should never happen: isSignInUpAllowedHelper called with isSignIn: true, user: undefined" + ); } if (users.length === 1 && isSignIn && user !== undefined && users[0].id === user.id) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because this is sign in and there is only a single user with the given account info"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning true because this is sign in and there is only a single user with the given account info" + ); return true; } // now we check if there exists some primary user with the same email / phone number @@ -189,13 +227,23 @@ class Recipe extends recipeModule_1.default { // account which is unverified sends an email verification email, the legit user might // click on the link and that account (which was unverified and could have been controlled // by an attacker), will end up getting linked to this account. - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(accountInfo, undefined, session, tenantId, userContext); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + accountInfo, + undefined, + session, + tenantId, + userContext + ); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because account linking is disabled"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning true because account linking is disabled" + ); return true; } if (!shouldDoAccountLinking.shouldRequireVerification) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because dec does not require email verification"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning true because dec does not require email verification" + ); // the dev says they do not care about verification before account linking // so we are OK with the risk mentioned above. return true; @@ -211,16 +259,24 @@ class Recipe extends recipeModule_1.default { } let thisIterationIsVerified = false; if (accountInfo.email !== undefined) { - if (currUser.loginMethods[0].hasSameEmailAs(accountInfo.email) && - currUser.loginMethods[0].verified) { - logger_1.logDebugMessage("isSignInUpAllowedHelper found same email for another user and verified"); + if ( + currUser.loginMethods[0].hasSameEmailAs(accountInfo.email) && + currUser.loginMethods[0].verified + ) { + logger_1.logDebugMessage( + "isSignInUpAllowedHelper found same email for another user and verified" + ); thisIterationIsVerified = true; } } if (accountInfo.phoneNumber !== undefined) { - if (currUser.loginMethods[0].hasSamePhoneNumberAs(accountInfo.phoneNumber) && - currUser.loginMethods[0].verified) { - logger_1.logDebugMessage("isSignInUpAllowedHelper found same phone number for another user and verified"); + if ( + currUser.loginMethods[0].hasSamePhoneNumberAs(accountInfo.phoneNumber) && + currUser.loginMethods[0].verified + ) { + logger_1.logDebugMessage( + "isSignInUpAllowedHelper found same phone number for another user and verified" + ); thisIterationIsVerified = true; } } @@ -231,35 +287,52 @@ class Recipe extends recipeModule_1.default { // users will just see an email already exists error and then will try another // login method. They can also still just go through the password reset flow // and then gain access to their email password account (which can then be verified). - logger_1.logDebugMessage("isSignInUpAllowedHelper returning false cause one of the other recipe level users is not verified"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning false cause one of the other recipe level users is not verified" + ); shouldAllow = false; break; } } - processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS); + processState_1.ProcessState.getInstance().addState( + processState_1.PROCESS_STATE.IS_SIGN_IN_UP_ALLOWED_NO_PRIMARY_USER_EXISTS + ); logger_1.logDebugMessage("isSignInUpAllowedHelper returning " + shouldAllow); return shouldAllow; - } - else { + } else { if (primaryUsers.length > 1) { - throw new Error("You have found a bug. Please report to https://github.com/supertokens/supertokens-node/issues"); + throw new Error( + "You have found a bug. Please report to https://github.com/supertokens/supertokens-node/issues" + ); } let primaryUser = primaryUsers[0]; logger_1.logDebugMessage("isSignInUpAllowedHelper primary user found"); - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(accountInfo, primaryUser, session, tenantId, userContext); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + accountInfo, + primaryUser, + session, + tenantId, + userContext + ); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because account linking is disabled"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning true because account linking is disabled" + ); return true; } if (!shouldDoAccountLinking.shouldRequireVerification) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning true because dec does not require email verification"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning true because dec does not require email verification" + ); // the dev says they do not care about verification before account linking // so we can link this new user to the primary user post recipe user creation // even if that user's email / phone number is not verified. return true; } if (!isVerified) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning false because new user's email is not verified, and primary user with the same email was found."); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning false because new user's email is not verified, and primary user with the same email was found." + ); // this will exist early with a false here cause it means that // if we come here, the newUser will be linked to the primary user post email // verification. Whilst this seems OK, there is a risk that the actual user might @@ -290,19 +363,24 @@ class Recipe extends recipeModule_1.default { let lM = primaryUser.loginMethods[i]; if (lM.email !== undefined) { if (lM.hasSameEmailAs(accountInfo.email) && lM.verified) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning true cause found same email for primary user and verified"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning true cause found same email for primary user and verified" + ); return true; } } if (lM.phoneNumber !== undefined) { if (lM.hasSamePhoneNumberAs(accountInfo.phoneNumber) && lM.verified) { - logger_1.logDebugMessage("isSignInUpAllowedHelper returning true cause found same phone number for primary user and verified"); + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning true cause found same phone number for primary user and verified" + ); return true; } } } - logger_1.logDebugMessage("isSignInUpAllowedHelper returning false cause primary user does not have the same email or phone number that is verified" - //"isSignInUpAllowedHelper returning false because new user's email is not verified, and primary user with the same email was found." + logger_1.logDebugMessage( + "isSignInUpAllowedHelper returning false cause primary user does not have the same email or phone number that is verified" + //"isSignInUpAllowedHelper returning false because new user's email is not verified, and primary user with the same email was found." ); return false; } @@ -340,67 +418,105 @@ class Recipe extends recipeModule_1.default { if (inputUser.isPrimaryUser) { // this is condition one from the above comment. if (otherPrimaryUserForNewEmail.length !== 0) { - logger_1.logDebugMessage(`isEmailChangeAllowed: returning false cause email change will lead to two primary users having same email on ${tenantId}`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: returning false cause email change will lead to two primary users having same email on ${tenantId}` + ); return { allowed: false, reason: "PRIMARY_USER_CONFLICT" }; } if (input.isVerified) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified and doesn't belong to any other primary user`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified and doesn't belong to any other primary user` + ); continue; } if (inputUser.loginMethods.some((lm) => lm.hasSameEmailAs(input.newEmail) && lm.verified)) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified in another login method and doesn't belong to any other primary user`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified in another login method and doesn't belong to any other primary user` + ); continue; } if (otherUsersWithNewEmail.length === 0) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is primary and the new email doesn't belong to any other user (primary or non-primary)`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary and the new email doesn't belong to any other user (primary or non-primary)` + ); continue; } - const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(otherUsersWithNewEmail[0].loginMethods[0], inputUser, input.session, tenantId, input.userContext); + const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + otherUsersWithNewEmail[0].loginMethods[0], + inputUser, + input.session, + tenantId, + input.userContext + ); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause linking is disabled`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause linking is disabled` + ); continue; } if (!shouldDoAccountLinking.shouldRequireVerification) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause linking is doesn't require email verification`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause linking is doesn't require email verification` + ); continue; } - logger_1.logDebugMessage(`isEmailChangeAllowed: returning false because the user hasn't verified the new email address and there exists another user with it on ${tenantId} and linking requires verification`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: returning false because the user hasn't verified the new email address and there exists another user with it on ${tenantId} and linking requires verification` + ); return { allowed: false, reason: "ACCOUNT_TAKEOVER_RISK" }; - } - else { + } else { if (input.isVerified) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is verified`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is verified` + ); continue; } if (inputUser.loginMethods[0].hasSameEmailAs(input.newEmail)) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is same as the older one`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is same as the older one` + ); continue; } if (otherPrimaryUserForNewEmail.length === 1) { - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(inputUser.loginMethods[0], otherPrimaryUserForNewEmail[0], input.session, tenantId, input.userContext); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + inputUser.loginMethods[0], + otherPrimaryUserForNewEmail[0], + input.session, + tenantId, + input.userContext + ); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary there exists a primary user exists with the new email, but the dev does not have account linking enabled.`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary there exists a primary user exists with the new email, but the dev does not have account linking enabled.` + ); continue; } if (!shouldDoAccountLinking.shouldRequireVerification) { - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary there exists a primary user exists with the new email, but the dev does not require email verification.`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary there exists a primary user exists with the new email, but the dev does not require email verification.` + ); continue; } - logger_1.logDebugMessage("isEmailChangeAllowed: returning false cause input user is not a primary there exists a primary user exists with the new email."); + logger_1.logDebugMessage( + "isEmailChangeAllowed: returning false cause input user is not a primary there exists a primary user exists with the new email." + ); return { allowed: false, reason: "ACCOUNT_TAKEOVER_RISK" }; } - logger_1.logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary no primary user exists with the new email`); + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary no primary user exists with the new email` + ); continue; } } - logger_1.logDebugMessage("isEmailChangeAllowed: returning true cause email change can happen on all tenants the user is part of"); + logger_1.logDebugMessage( + "isEmailChangeAllowed: returning true cause email change can happen on all tenants the user is part of" + ); return { allowed: true }; }; this.verifyEmailForRecipeUserIfLinkedAccountsAreVerified = async (input) => { try { recipe_1.default.getInstanceOrThrowError(); - } - catch (ignored) { + } catch (ignored) { // if email verification recipe is not initialized, we do a no-op return; } @@ -433,15 +549,17 @@ class Recipe extends recipeModule_1.default { } }); if (shouldVerifyEmail) { - let resp = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createEmailVerificationToken({ - // While the token we create here is tenant specific, the verification status is not - // So we can use any tenantId the user is associated with here as long as we use the - // same in the verifyEmailUsingToken call - tenantId: input.user.tenantIds[0], - recipeUserId: input.recipeUserId, - email: recipeUserEmail, - userContext: input.userContext, - }); + let resp = await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.createEmailVerificationToken({ + // While the token we create here is tenant specific, the verification status is not + // So we can use any tenantId the user is associated with here as long as we use the + // same in the verifyEmailUsingToken call + tenantId: input.user.tenantIds[0], + recipeUserId: input.recipeUserId, + email: recipeUserEmail, + userContext: input.userContext, + }); if (resp.status === "OK") { // we purposely pass in false below cause we don't want account // linking to happen @@ -459,19 +577,30 @@ class Recipe extends recipeModule_1.default { }; this.config = utils_1.validateAndNormaliseUserInput(appInfo, config); { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, this)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default( + querier_1.Querier.getNewInstanceOrThrowError(recipeId), + this.config, + this + ) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } } static init(config) { return (appInfo) => { if (Recipe.instance === undefined) { - Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, config, {}, { - emailDelivery: undefined, - }); + Recipe.instance = new Recipe( + Recipe.RECIPE_ID, + appInfo, + config, + {}, + { + emailDelivery: undefined, + } + ); return Recipe.instance; - } - else { + } else { throw new Error("AccountLinking recipe has already been initialised. Please check your code for bugs."); } }; @@ -483,7 +612,10 @@ class Recipe extends recipeModule_1.default { // is not in the recipeList). static getInstance() { if (Recipe.instance === undefined) { - Recipe.init()(supertokens_1.default.getInstanceOrThrowError().appInfo, supertokens_1.default.getInstanceOrThrowError().isInServerlessEnv); + Recipe.init()( + supertokens_1.default.getInstanceOrThrowError().appInfo, + supertokens_1.default.getInstanceOrThrowError().isInServerlessEnv + ); } return Recipe.instance; } @@ -513,19 +645,29 @@ class Recipe extends recipeModule_1.default { Recipe.instance = undefined; } async shouldBecomePrimaryUser(user, tenantId, session, userContext) { - const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(user.loginMethods[0], undefined, session, tenantId, userContext); + const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + user.loginMethods[0], + undefined, + session, + tenantId, + userContext + ); if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage("shouldBecomePrimaryUser returning false because shouldAutomaticallyLink is false"); + logger_1.logDebugMessage( + "shouldBecomePrimaryUser returning false because shouldAutomaticallyLink is false" + ); return false; } if (shouldDoAccountLinking.shouldRequireVerification && !user.loginMethods[0].verified) { - logger_1.logDebugMessage("shouldBecomePrimaryUser returning false because shouldRequireVerification is true but the login method is not verified"); + logger_1.logDebugMessage( + "shouldBecomePrimaryUser returning false because shouldRequireVerification is true but the login method is not verified" + ); return false; } logger_1.logDebugMessage("shouldBecomePrimaryUser returning true"); return true; } - async tryLinkingByAccountInfoOrCreatePrimaryUser({ inputUser, session, tenantId, userContext, }) { + async tryLinkingByAccountInfoOrCreatePrimaryUser({ inputUser, session, tenantId, userContext }) { let tries = 0; while (tries++ < 100) { const primaryUserThatCanBeLinkedToTheInputUser = await this.getPrimaryUserThatCanBeLinkedToRecipeUserId({ @@ -534,26 +676,47 @@ class Recipe extends recipeModule_1.default { userContext, }); if (primaryUserThatCanBeLinkedToTheInputUser !== undefined) { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: got primary user we can try linking"); + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser: got primary user we can try linking" + ); // we check if the inputUser and primaryUserThatCanBeLinkedToTheInputUser are linked based on recipeIds because the inputUser obj could be outdated - if (!primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some((lm) => lm.recipeUserId.getAsString() === inputUser.loginMethods[0].recipeUserId.getAsString())) { + if ( + !primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some( + (lm) => lm.recipeUserId.getAsString() === inputUser.loginMethods[0].recipeUserId.getAsString() + ) + ) { // If we got a primary user that can be linked to the input user and they are is not linked, we try to link them. // The input user in this case cannot be linked to anything else, otherwise multiple primary users would have the same email // we can use the 0 index cause targetUser is not a primary user. - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(inputUser.loginMethods[0], primaryUserThatCanBeLinkedToTheInputUser, session, tenantId, userContext); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + inputUser.loginMethods[0], + primaryUserThatCanBeLinkedToTheInputUser, + session, + tenantId, + userContext + ); // We already checked if factor setup is allowed by this point, but maybe we should check again? if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldAutomaticallyLink is false"); + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldAutomaticallyLink is false" + ); return { status: "NO_LINK" }; } - const accountInfoVerifiedInPrimUser = primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some((lm) => (inputUser.loginMethods[0].email !== undefined && - lm.hasSameEmailAs(inputUser.loginMethods[0].email)) || - (inputUser.loginMethods[0].phoneNumber !== undefined && - lm.hasSamePhoneNumberAs(inputUser.loginMethods[0].phoneNumber) && - lm.verified)); - if (shouldDoAccountLinking.shouldRequireVerification && - (!inputUser.loginMethods[0].verified || !accountInfoVerifiedInPrimUser)) { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified in the new or the primary user"); + const accountInfoVerifiedInPrimUser = primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some( + (lm) => + (inputUser.loginMethods[0].email !== undefined && + lm.hasSameEmailAs(inputUser.loginMethods[0].email)) || + (inputUser.loginMethods[0].phoneNumber !== undefined && + lm.hasSamePhoneNumberAs(inputUser.loginMethods[0].phoneNumber) && + lm.verified) + ); + if ( + shouldDoAccountLinking.shouldRequireVerification && + (!inputUser.loginMethods[0].verified || !accountInfoVerifiedInPrimUser) + ) { + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified in the new or the primary user" + ); return { status: "NO_LINK" }; } logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking"); @@ -565,29 +728,34 @@ class Recipe extends recipeModule_1.default { if (linkAccountsResult.status === "OK") { logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser successfully linked"); return { status: "OK", user: linkAccountsResult.user }; - } - else if (linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { + } else if ( + linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + ) { // this can happen cause of a race condition wherein the recipe user ID get's linked to // some other primary user whilst this function is running. // We can return this directly, because: // 1. a retry would result in the same // 2. the tryLinkingByAccountInfoOrCreatePrimaryUser doesn't specify where it should be linked // and we can't linked it to anything here after it became primary/linked to another primary user - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser already linked to another user"); + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser already linked to another user" + ); return { status: "OK", user: linkAccountsResult.user, }; - } - else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition"); + } else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition" + ); // this can be possible during a race condition wherein the primary user // that we fetched somehow is no more a primary user. This can happen if // the unlink function was called in parallel on that user. So we can just retry continue; - } - else { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition"); + } else { + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition" + ); // status is "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" // it can come here if the recipe user ID can't be linked to the primary user ID cause // the email / phone number is associated with some other primary user ID. @@ -608,11 +776,20 @@ class Recipe extends recipeModule_1.default { tenantId, userContext, }); - if (oldestUserThatCanBeLinkedToTheInputUser !== undefined && - oldestUserThatCanBeLinkedToTheInputUser.id !== inputUser.id) { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: got an older user we can try linking"); + if ( + oldestUserThatCanBeLinkedToTheInputUser !== undefined && + oldestUserThatCanBeLinkedToTheInputUser.id !== inputUser.id + ) { + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser: got an older user we can try linking" + ); // We know that the older user isn't primary, otherwise we'd hit the branch above. - let shouldMakeOlderUserPrimary = await this.shouldBecomePrimaryUser(oldestUserThatCanBeLinkedToTheInputUser, tenantId, session, userContext); + let shouldMakeOlderUserPrimary = await this.shouldBecomePrimaryUser( + oldestUserThatCanBeLinkedToTheInputUser, + tenantId, + session, + userContext + ); // if the app doesn't want to make the older user primary, we can't link to it // so we fall back to trying the newer user primary (and not linking) if (shouldMakeOlderUserPrimary) { @@ -620,24 +797,38 @@ class Recipe extends recipeModule_1.default { recipeUserId: oldestUserThatCanBeLinkedToTheInputUser.loginMethods[0].recipeUserId, userContext, }); - if (createPrimaryUserResult.status === - "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" || - createPrimaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR") { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: retrying because createPrimaryUser returned " + - createPrimaryUserResult.status); + if ( + createPrimaryUserResult.status === + "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" || + createPrimaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" + ) { + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser: retrying because createPrimaryUser returned " + + createPrimaryUserResult.status + ); continue; } // If we got a primary user that can be linked to the input user and they are is not linked, we try to link them. // The input user in this case cannot be linked to anything else, otherwise multiple primary users would have the same email // we can use the 0 index cause targetUser is not a primary user. - let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking(inputUser.loginMethods[0], createPrimaryUserResult.user, session, tenantId, userContext); + let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + inputUser.loginMethods[0], + createPrimaryUserResult.user, + session, + tenantId, + userContext + ); // We already checked if factor setup is allowed by this point, but maybe we should check again? if (!shouldDoAccountLinking.shouldAutomaticallyLink) { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldAutomaticallyLink is false"); + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldAutomaticallyLink is false" + ); return { status: "NO_LINK" }; } if (shouldDoAccountLinking.shouldRequireVerification && !inputUser.loginMethods[0].verified) { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified"); + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified" + ); return { status: "NO_LINK" }; } logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking"); @@ -649,29 +840,34 @@ class Recipe extends recipeModule_1.default { if (linkAccountsResult.status === "OK") { logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser successfully linked"); return { status: "OK", user: linkAccountsResult.user }; - } - else if (linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR") { + } else if ( + linkAccountsResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + ) { // this can happen cause of a race condition wherein the recipe user ID get's linked to // some other primary user whilst this function is running. // We can return this directly, because: // 1. a retry would result in the same // 2. the tryLinkingByAccountInfoOrCreatePrimaryUser doesn't specify where it should be linked // and we can't linked it to anything here after it became primary/linked to another primary user - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser already linked to another user"); + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser already linked to another user" + ); return { status: "OK", user: linkAccountsResult.user, }; - } - else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition"); + } else if (linkAccountsResult.status === "INPUT_USER_IS_NOT_A_PRIMARY_USER") { + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition" + ); // this can be possible during a race condition wherein the primary user // that we fetched somehow is no more a primary user. This can happen if // the unlink function was called in parallel on that user. So we can just retry continue; - } - else { - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition"); + } else { + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser linking failed because of a race condition" + ); // status is "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" // it can come here if the recipe user ID can't be linked to the primary user ID cause // the email / phone number is associated with some other primary user ID. @@ -683,7 +879,9 @@ class Recipe extends recipeModule_1.default { } } } - logger_1.logDebugMessage("tryLinkingByAccountInfoOrCreatePrimaryUser: trying to make the current user primary"); + logger_1.logDebugMessage( + "tryLinkingByAccountInfoOrCreatePrimaryUser: trying to make the current user primary" + ); // In this case we have no other account we can link to, so we check if the current user should become a primary user if (await this.shouldBecomePrimaryUser(inputUser, tenantId, session, userContext)) { let createPrimaryUserResult = await this.recipeInterfaceImpl.createPrimaryUser({ @@ -695,14 +893,15 @@ class Recipe extends recipeModule_1.default { // meaning that the recipe user ID is already linked to another primary user id (race condition), or that some other // primary user ID exists with the same email / phone number (again, race condition). // In this case we call a retry in createPrimaryUserIdOrLinkByAccountInfo - if (createPrimaryUserResult.status === - "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" || - createPrimaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR") { + if ( + createPrimaryUserResult.status === + "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" || + createPrimaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" + ) { continue; } return createPrimaryUserResult; - } - else { + } else { // If not we return it unchanged return { status: "OK", user: inputUser }; } diff --git a/lib/build/recipe/accountlinking/recipeImplementation.d.ts b/lib/build/recipe/accountlinking/recipeImplementation.d.ts index ca08e95b6..76484c9cd 100644 --- a/lib/build/recipe/accountlinking/recipeImplementation.d.ts +++ b/lib/build/recipe/accountlinking/recipeImplementation.d.ts @@ -2,4 +2,8 @@ import { RecipeInterface, TypeNormalisedInput } from "./types"; import { Querier } from "../../querier"; import type AccountLinkingRecipe from "./recipe"; -export default function getRecipeImplementation(querier: Querier, config: TypeNormalisedInput, recipeInstance: AccountLinkingRecipe): RecipeInterface; +export default function getRecipeImplementation( + querier: Querier, + config: TypeNormalisedInput, + recipeInstance: AccountLinkingRecipe +): RecipeInterface; diff --git a/lib/build/recipe/accountlinking/recipeImplementation.js b/lib/build/recipe/accountlinking/recipeImplementation.js index 3cef18ff6..55774660f 100644 --- a/lib/build/recipe/accountlinking/recipeImplementation.js +++ b/lib/build/recipe/accountlinking/recipeImplementation.js @@ -13,52 +13,96 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const user_1 = require("../../user"); function getRecipeImplementation(querier, config, recipeInstance) { return { - getUsers: async function ({ tenantId, timeJoinedOrder, limit, paginationToken, includeRecipeIds, query, userContext, }) { + getUsers: async function ({ + tenantId, + timeJoinedOrder, + limit, + paginationToken, + includeRecipeIds, + query, + userContext, + }) { let includeRecipeIdsStr = undefined; if (includeRecipeIds !== undefined) { includeRecipeIdsStr = includeRecipeIds.join(","); } - let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`${tenantId !== null && tenantId !== void 0 ? tenantId : "public"}/users`), Object.assign({ includeRecipeIds: includeRecipeIdsStr, timeJoinedOrder: timeJoinedOrder, limit: limit, paginationToken: paginationToken }, query), userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default( + `${tenantId !== null && tenantId !== void 0 ? tenantId : "public"}/users` + ), + Object.assign( + { + includeRecipeIds: includeRecipeIdsStr, + timeJoinedOrder: timeJoinedOrder, + limit: limit, + paginationToken: paginationToken, + }, + query + ), + userContext + ); return { users: response.users.map((u) => new user_1.User(u)), nextPaginationToken: response.nextPaginationToken, }; }, - canCreatePrimaryUser: async function ({ recipeUserId, userContext, }) { - return await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/primary/check"), { - recipeUserId: recipeUserId.getAsString(), - }, userContext); + canCreatePrimaryUser: async function ({ recipeUserId, userContext }) { + return await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/accountlinking/user/primary/check"), + { + recipeUserId: recipeUserId.getAsString(), + }, + userContext + ); }, - createPrimaryUser: async function ({ recipeUserId, userContext, }) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/primary"), { - recipeUserId: recipeUserId.getAsString(), - }, userContext); + createPrimaryUser: async function ({ recipeUserId, userContext }) { + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/accountlinking/user/primary"), + { + recipeUserId: recipeUserId.getAsString(), + }, + userContext + ); if (response.status === "OK") { response.user = new user_1.User(response.user); } return response; }, - canLinkAccounts: async function ({ recipeUserId, primaryUserId, userContext, }) { - let result = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/link/check"), { - recipeUserId: recipeUserId.getAsString(), - primaryUserId, - }, userContext); + canLinkAccounts: async function ({ recipeUserId, primaryUserId, userContext }) { + let result = await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/accountlinking/user/link/check"), + { + recipeUserId: recipeUserId.getAsString(), + primaryUserId, + }, + userContext + ); return result; }, - linkAccounts: async function ({ recipeUserId, primaryUserId, userContext, }) { - const accountsLinkingResult = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/link"), { - recipeUserId: recipeUserId.getAsString(), - primaryUserId, - }, userContext); - if (["OK", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"].includes(accountsLinkingResult.status)) { + linkAccounts: async function ({ recipeUserId, primaryUserId, userContext }) { + const accountsLinkingResult = await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/accountlinking/user/link"), + { + recipeUserId: recipeUserId.getAsString(), + primaryUserId, + }, + userContext + ); + if ( + ["OK", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"].includes( + accountsLinkingResult.status + ) + ) { accountsLinkingResult.user = new user_1.User(accountsLinkingResult.user); } if (accountsLinkingResult.status === "OK") { @@ -77,7 +121,9 @@ function getRecipeImplementation(querier, config, recipeInstance) { throw Error("this error should never be thrown"); } user = updatedUser; - let loginMethodInfo = user.loginMethods.find((u) => u.recipeUserId.getAsString() === recipeUserId.getAsString()); + let loginMethodInfo = user.loginMethods.find( + (u) => u.recipeUserId.getAsString() === recipeUserId.getAsString() + ); if (loginMethodInfo === undefined) { throw Error("this error should never be thrown"); } @@ -87,37 +133,55 @@ function getRecipeImplementation(querier, config, recipeInstance) { } return accountsLinkingResult; }, - unlinkAccount: async function ({ recipeUserId, userContext, }) { - let accountsUnlinkingResult = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/accountlinking/user/unlink"), { - recipeUserId: recipeUserId.getAsString(), - }, userContext); + unlinkAccount: async function ({ recipeUserId, userContext }) { + let accountsUnlinkingResult = await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/accountlinking/user/unlink"), + { + recipeUserId: recipeUserId.getAsString(), + }, + userContext + ); return accountsUnlinkingResult; }, getUser: async function ({ userId, userContext }) { - let result = await querier.sendGetRequest(new normalisedURLPath_1.default("/user/id"), { - userId, - }, userContext); + let result = await querier.sendGetRequest( + new normalisedURLPath_1.default("/user/id"), + { + userId, + }, + userContext + ); if (result.status === "OK") { return new user_1.User(result.user); } return undefined; }, - listUsersByAccountInfo: async function ({ tenantId, accountInfo, doUnionOfAccountInfo, userContext, }) { + listUsersByAccountInfo: async function ({ tenantId, accountInfo, doUnionOfAccountInfo, userContext }) { var _a, _b; - let result = await querier.sendGetRequest(new normalisedURLPath_1.default(`${tenantId !== null && tenantId !== void 0 ? tenantId : "public"}/users/by-accountinfo`), { - email: accountInfo.email, - phoneNumber: accountInfo.phoneNumber, - thirdPartyId: (_a = accountInfo.thirdParty) === null || _a === void 0 ? void 0 : _a.id, - thirdPartyUserId: (_b = accountInfo.thirdParty) === null || _b === void 0 ? void 0 : _b.userId, - doUnionOfAccountInfo, - }, userContext); + let result = await querier.sendGetRequest( + new normalisedURLPath_1.default( + `${tenantId !== null && tenantId !== void 0 ? tenantId : "public"}/users/by-accountinfo` + ), + { + email: accountInfo.email, + phoneNumber: accountInfo.phoneNumber, + thirdPartyId: (_a = accountInfo.thirdParty) === null || _a === void 0 ? void 0 : _a.id, + thirdPartyUserId: (_b = accountInfo.thirdParty) === null || _b === void 0 ? void 0 : _b.userId, + doUnionOfAccountInfo, + }, + userContext + ); return result.users.map((u) => new user_1.User(u)); }, - deleteUser: async function ({ userId, removeAllLinkedAccounts, userContext, }) { - return await querier.sendPostRequest(new normalisedURLPath_1.default("/user/remove"), { - userId, - removeAllLinkedAccounts, - }, userContext); + deleteUser: async function ({ userId, removeAllLinkedAccounts, userContext }) { + return await querier.sendPostRequest( + new normalisedURLPath_1.default("/user/remove"), + { + userId, + removeAllLinkedAccounts, + }, + userContext + ); }, }; } diff --git a/lib/build/recipe/accountlinking/types.d.ts b/lib/build/recipe/accountlinking/types.d.ts index 386a87c19..9d1d54e78 100644 --- a/lib/build/recipe/accountlinking/types.d.ts +++ b/lib/build/recipe/accountlinking/types.d.ts @@ -5,30 +5,54 @@ import RecipeUserId from "../../recipeUserId"; import { SessionContainerInterface } from "../session/types"; export declare type TypeInput = { onAccountLinked?: (user: User, newAccountInfo: RecipeLevelUser, userContext: UserContext) => Promise; - shouldDoAutomaticAccountLinking?: (newAccountInfo: AccountInfoWithRecipeId & { - recipeUserId?: RecipeUserId; - }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: UserContext) => Promise<{ - shouldAutomaticallyLink: false; - } | { - shouldAutomaticallyLink: true; - shouldRequireVerification: boolean; - }>; + shouldDoAutomaticAccountLinking?: ( + newAccountInfo: AccountInfoWithRecipeId & { + recipeUserId?: RecipeUserId; + }, + user: User | undefined, + session: SessionContainerInterface | undefined, + tenantId: string, + userContext: UserContext + ) => Promise< + | { + shouldAutomaticallyLink: false; + } + | { + shouldAutomaticallyLink: true; + shouldRequireVerification: boolean; + } + >; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; }; }; export declare type TypeNormalisedInput = { onAccountLinked: (user: User, newAccountInfo: RecipeLevelUser, userContext: UserContext) => Promise; - shouldDoAutomaticAccountLinking: (newAccountInfo: AccountInfoWithRecipeId & { - recipeUserId?: RecipeUserId; - }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: UserContext) => Promise<{ - shouldAutomaticallyLink: false; - } | { - shouldAutomaticallyLink: true; - shouldRequireVerification: boolean; - }>; + shouldDoAutomaticAccountLinking: ( + newAccountInfo: AccountInfoWithRecipeId & { + recipeUserId?: RecipeUserId; + }, + user: User | undefined, + session: SessionContainerInterface | undefined, + tenantId: string, + userContext: UserContext + ) => Promise< + | { + shouldAutomaticallyLink: false; + } + | { + shouldAutomaticallyLink: true; + shouldRequireVerification: boolean; + } + >; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; }; }; export declare type RecipeInterface = { @@ -49,66 +73,85 @@ export declare type RecipeInterface = { canCreatePrimaryUser: (input: { recipeUserId: RecipeUserId; userContext: UserContext; - }) => Promise<{ - status: "OK"; - wasAlreadyAPrimaryUser: boolean; - } | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - }>; + }) => Promise< + | { + status: "OK"; + wasAlreadyAPrimaryUser: boolean; + } + | { + status: + | "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" + | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + >; createPrimaryUser: (input: { recipeUserId: RecipeUserId; userContext: UserContext; - }) => Promise<{ - status: "OK"; - user: User; - wasAlreadyAPrimaryUser: boolean; - } | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - } | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - }>; + }) => Promise< + | { + status: "OK"; + user: User; + wasAlreadyAPrimaryUser: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + >; canLinkAccounts: (input: { recipeUserId: RecipeUserId; primaryUserId: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - accountsAlreadyLinked: boolean; - } | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - description: string; - primaryUserId: string; - } | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } | { - status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; - }>; + }) => Promise< + | { + status: "OK"; + accountsAlreadyLinked: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + description: string; + primaryUserId: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; + } + >; linkAccounts: (input: { recipeUserId: RecipeUserId; primaryUserId: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - accountsAlreadyLinked: boolean; - user: User; - } | { - status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - user: User; - } | { - status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; - primaryUserId: string; - description: string; - } | { - status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; - }>; + }) => Promise< + | { + status: "OK"; + accountsAlreadyLinked: boolean; + user: User; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + user: User; + } + | { + status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "INPUT_USER_IS_NOT_A_PRIMARY_USER"; + } + >; unlinkAccount: (input: { recipeUserId: RecipeUserId; userContext: UserContext; @@ -117,10 +160,7 @@ export declare type RecipeInterface = { wasRecipeUserDeleted: boolean; wasLinked: boolean; }>; - getUser: (input: { - userId: string; - userContext: UserContext; - }) => Promise; + getUser: (input: { userId: string; userContext: UserContext }) => Promise; listUsersByAccountInfo: (input: { tenantId: string; accountInfo: AccountInfoInput; diff --git a/lib/build/recipe/accountlinking/utils.js b/lib/build/recipe/accountlinking/utils.js index 105b1b3c9..13cd42e61 100644 --- a/lib/build/recipe/accountlinking/utils.js +++ b/lib/build/recipe/accountlinking/utils.js @@ -15,7 +15,7 @@ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = exports.recipeInitDefinedShouldDoAutomaticAccountLinking = void 0; -async function defaultOnAccountLinked() { } +async function defaultOnAccountLinked() {} async function defaultShouldDoAutomaticAccountLinking() { return { shouldAutomaticallyLink: false, @@ -26,9 +26,15 @@ function recipeInitDefinedShouldDoAutomaticAccountLinking(config) { } exports.recipeInitDefinedShouldDoAutomaticAccountLinking = recipeInitDefinedShouldDoAutomaticAccountLinking; function validateAndNormaliseUserInput(_, config) { - let onAccountLinked = (config === null || config === void 0 ? void 0 : config.onAccountLinked) || defaultOnAccountLinked; - let shouldDoAutomaticAccountLinking = (config === null || config === void 0 ? void 0 : config.shouldDoAutomaticAccountLinking) || defaultShouldDoAutomaticAccountLinking; - let override = Object.assign({ functions: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let onAccountLinked = + (config === null || config === void 0 ? void 0 : config.onAccountLinked) || defaultOnAccountLinked; + let shouldDoAutomaticAccountLinking = + (config === null || config === void 0 ? void 0 : config.shouldDoAutomaticAccountLinking) || + defaultShouldDoAutomaticAccountLinking; + let override = Object.assign( + { functions: (originalImplementation) => originalImplementation }, + config === null || config === void 0 ? void 0 : config.override + ); return { override, onAccountLinked, diff --git a/lib/build/recipe/dashboard/api/analytics.d.ts b/lib/build/recipe/dashboard/api/analytics.d.ts index 6e549751c..1b0c81d49 100644 --- a/lib/build/recipe/dashboard/api/analytics.d.ts +++ b/lib/build/recipe/dashboard/api/analytics.d.ts @@ -4,4 +4,9 @@ import { UserContext } from "../../../types"; export declare type Response = { status: "OK"; }; -export default function analyticsPost(_: APIInterface, ___: string, options: APIOptions, userContext: UserContext): Promise; +export default function analyticsPost( + _: APIInterface, + ___: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/analytics.js b/lib/build/recipe/dashboard/api/analytics.js index d897d50e7..18762e156 100644 --- a/lib/build/recipe/dashboard/api/analytics.js +++ b/lib/build/recipe/dashboard/api/analytics.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_1 = __importDefault(require("../../../supertokens")); const querier_1 = require("../../../querier"); @@ -51,9 +53,10 @@ async function analyticsPost(_, ___, options, userContext) { if (response.exists) { telemetryId = response.telemetryId; } - numberOfUsers = await supertokens_1.default.getInstanceOrThrowError().getUserCount(undefined, undefined, userContext); - } - catch (_) { + numberOfUsers = await supertokens_1.default + .getInstanceOrThrowError() + .getUserCount(undefined, undefined, userContext); + } catch (_) { // If either telemetry id API or user count fetch fails, no event should be sent return { status: "OK", @@ -83,8 +86,7 @@ async function analyticsPost(_, ___, options, userContext) { "content-type": "application/json; charset=utf-8", }, }); - } - catch (e) { + } catch (e) { // Ignored } return { diff --git a/lib/build/recipe/dashboard/api/apiKeyProtector.d.ts b/lib/build/recipe/dashboard/api/apiKeyProtector.d.ts index d8c92a94d..eb8ad8d0e 100644 --- a/lib/build/recipe/dashboard/api/apiKeyProtector.d.ts +++ b/lib/build/recipe/dashboard/api/apiKeyProtector.d.ts @@ -1,4 +1,10 @@ // @ts-nocheck import { UserContext } from "../../../types"; import { APIFunction, APIInterface, APIOptions } from "../types"; -export default function apiKeyProtector(apiImplementation: APIInterface, tenantId: string, options: APIOptions, apiFunction: APIFunction, userContext: UserContext): Promise; +export default function apiKeyProtector( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + apiFunction: APIFunction, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/apiKeyProtector.js b/lib/build/recipe/dashboard/api/apiKeyProtector.js index a8ed7440b..50a9d7031 100644 --- a/lib/build/recipe/dashboard/api/apiKeyProtector.js +++ b/lib/build/recipe/dashboard/api/apiKeyProtector.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../error")); const utils_1 = require("../utils"); @@ -13,8 +15,7 @@ async function apiKeyProtector(apiImplementation, tenantId, options, apiFunction config: options.config, userContext, }); - } - catch (e) { + } catch (e) { if (error_1.default.isErrorFromSuperTokens(e) && e.type === error_1.default.OPERATION_NOT_ALLOWED) { options.res.setStatusCode(403); options.res.sendJSONResponse({ diff --git a/lib/build/recipe/dashboard/api/dashboard.d.ts b/lib/build/recipe/dashboard/api/dashboard.d.ts index 560d91749..252a4acf3 100644 --- a/lib/build/recipe/dashboard/api/dashboard.d.ts +++ b/lib/build/recipe/dashboard/api/dashboard.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { UserContext } from "../../../types"; import { APIInterface, APIOptions } from "../types"; -export default function dashboard(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function dashboard( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/implementation.js b/lib/build/recipe/dashboard/api/implementation.js index a92b4b930..5423f37b4 100644 --- a/lib/build/recipe/dashboard/api/implementation.js +++ b/lib/build/recipe/dashboard/api/implementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLDomain_1 = __importDefault(require("../../../normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); @@ -29,18 +31,25 @@ function getAPIImplementation() { const bundleBasePathString = await input.options.recipeImplementation.getDashboardBundleLocation({ userContext: input.userContext, }); - const bundleDomain = new normalisedURLDomain_1.default(bundleBasePathString).getAsStringDangerous() + + const bundleDomain = + new normalisedURLDomain_1.default(bundleBasePathString).getAsStringDangerous() + new normalisedURLPath_1.default(bundleBasePathString).getAsStringDangerous(); let connectionURI = ""; const superTokensInstance = supertokens_1.default.getInstanceOrThrowError(); const authMode = input.options.config.authMode; if (superTokensInstance.supertokens !== undefined) { connectionURI = - new normalisedURLDomain_1.default(superTokensInstance.supertokens.connectionURI.split(";")[0]).getAsStringDangerous() + - new normalisedURLPath_1.default(superTokensInstance.supertokens.connectionURI.split(";")[0]).getAsStringDangerous(); + new normalisedURLDomain_1.default( + superTokensInstance.supertokens.connectionURI.split(";")[0] + ).getAsStringDangerous() + + new normalisedURLPath_1.default( + superTokensInstance.supertokens.connectionURI.split(";")[0] + ).getAsStringDangerous(); } let isSearchEnabled = false; - const cdiVersion = await querier_1.Querier.getNewInstanceOrThrowError(input.options.recipeId).getAPIVersion(input.userContext); + const cdiVersion = await querier_1.Querier.getNewInstanceOrThrowError(input.options.recipeId).getAPIVersion( + input.userContext + ); if (utils_1.maxVersion("2.20", cdiVersion) === cdiVersion) { // Only enable search if CDI version is 2.20 or above isSearchEnabled = true; @@ -66,8 +75,8 @@ function getAPIImplementation() { }); window.staticBasePath = "${bundleDomain}/static" window.dashboardAppPath = "${input.options.appInfo.apiBasePath - .appendPath(new normalisedURLPath_1.default(constants_1.DASHBOARD_API)) - .getAsStringDangerous()}" + .appendPath(new normalisedURLPath_1.default(constants_1.DASHBOARD_API)) + .getAsStringDangerous()}" window.connectionURI = "${connectionURI}" window.authMode = "${authMode}" window.isSearchEnabled = "${isSearchEnabled}" diff --git a/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.d.ts b/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.d.ts index b2477273e..97bbf0654 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.d.ts @@ -1,13 +1,21 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; - createdNew: boolean; -} | { - status: "UNKNOWN_TENANT_ERROR"; -} | { - status: "BOXY_ERROR"; - message: string; -}; -export default function createOrUpdateThirdPartyConfig(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + createdNew: boolean; + } + | { + status: "UNKNOWN_TENANT_ERROR"; + } + | { + status: "BOXY_ERROR"; + message: string; + }; +export default function createOrUpdateThirdPartyConfig( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.js b/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.js index a6bf1e185..e4f151e0d 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.js +++ b/lib/build/recipe/dashboard/api/multitenancy/createOrUpdateThirdPartyConfig.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -23,11 +25,23 @@ async function createOrUpdateThirdPartyConfig(_, tenantId, options, userContext) if (tenantRes.thirdParty.providers.length === 0) { // This means that the tenant was using the static list of providers, we need to add them all before adding the new one const mtRecipe = recipe_1.default.getInstance(); - const staticProviders = (_a = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && _a !== void 0 ? _a : []; - for (const provider of staticProviders.filter((provider) => provider.includeInNonPublicTenantsByDefault === true || tenantId === constants_1.DEFAULT_TENANT_ID)) { - await multitenancy_1.default.createOrUpdateThirdPartyConfig(tenantId, { - thirdPartyId: provider.config.thirdPartyId, - }, undefined, userContext); + const staticProviders = + (_a = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && + _a !== void 0 + ? _a + : []; + for (const provider of staticProviders.filter( + (provider) => + provider.includeInNonPublicTenantsByDefault === true || tenantId === constants_1.DEFAULT_TENANT_ID + )) { + await multitenancy_1.default.createOrUpdateThirdPartyConfig( + tenantId, + { + thirdPartyId: provider.config.thirdPartyId, + }, + undefined, + userContext + ); // delay after each provider to avoid rate limiting await new Promise((r) => setTimeout(r, 500)); // 500ms } @@ -36,14 +50,17 @@ async function createOrUpdateThirdPartyConfig(_, tenantId, options, userContext) const boxyURL = providerConfig.clients[0].additionalConfig.boxyURL; const boxyAPIKey = providerConfig.clients[0].additionalConfig.boxyAPIKey; providerConfig.clients[0].additionalConfig.boxyAPIKey = undefined; - if (boxyAPIKey && + if ( + boxyAPIKey && (providerConfig.clients[0].additionalConfig.samlInputType === "xml" || - providerConfig.clients[0].additionalConfig.samlInputType === "url")) { + providerConfig.clients[0].additionalConfig.samlInputType === "url") + ) { const requestBody = { name: "", label: "", description: "", - tenant: providerConfig.clients[0].additionalConfig.boxyTenant || + tenant: + providerConfig.clients[0].additionalConfig.boxyTenant || `${tenantId}-${providerConfig.thirdPartyId}`, product: providerConfig.clients[0].additionalConfig.boxyProduct || "supertokens", defaultRedirectUrl: providerConfig.clients[0].additionalConfig.redirectURLs[0], @@ -57,10 +74,14 @@ async function createOrUpdateThirdPartyConfig(_, tenantId, options, userContext) const normalisedDomain = new normalisedURLDomain_1.default(boxyURL); const normalisedBasePath = new normalisedURLPath_1.default(boxyURL); const connectionsPath = new normalisedURLPath_1.default("/api/v1/saml/config"); - const resp = await thirdpartyUtils_1.doPostRequest(normalisedDomain.getAsStringDangerous() + - normalisedBasePath.appendPath(connectionsPath).getAsStringDangerous(), requestBody, { - Authorization: `Api-Key ${boxyAPIKey}`, - }); + const resp = await thirdpartyUtils_1.doPostRequest( + normalisedDomain.getAsStringDangerous() + + normalisedBasePath.appendPath(connectionsPath).getAsStringDangerous(), + requestBody, + { + Authorization: `Api-Key ${boxyAPIKey}`, + } + ); if (resp.status !== 200) { if (resp.status === 401) { return { @@ -80,7 +101,12 @@ async function createOrUpdateThirdPartyConfig(_, tenantId, options, userContext) providerConfig.clients[0].clientSecret = resp.jsonResponse.clientSecret; } } - const thirdPartyRes = await multitenancy_1.default.createOrUpdateThirdPartyConfig(tenantId, providerConfig, undefined, userContext); + const thirdPartyRes = await multitenancy_1.default.createOrUpdateThirdPartyConfig( + tenantId, + providerConfig, + undefined, + userContext + ); return thirdPartyRes; } exports.default = createOrUpdateThirdPartyConfig; diff --git a/lib/build/recipe/dashboard/api/multitenancy/createTenant.d.ts b/lib/build/recipe/dashboard/api/multitenancy/createTenant.d.ts index 2cc3b257c..70b59720b 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/createTenant.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/createTenant.d.ts @@ -1,13 +1,21 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; - createdNew: boolean; -} | { - status: "MULTITENANCY_NOT_ENABLED_IN_CORE_ERROR" | "TENANT_ID_ALREADY_EXISTS_ERROR"; -} | { - status: "INVALID_TENANT_ID_ERROR"; - message: string; -}; -export default function createTenant(_: APIInterface, __: string, options: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + createdNew: boolean; + } + | { + status: "MULTITENANCY_NOT_ENABLED_IN_CORE_ERROR" | "TENANT_ID_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_TENANT_ID_ERROR"; + message: string; + }; +export default function createTenant( + _: APIInterface, + __: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/createTenant.js b/lib/build/recipe/dashboard/api/multitenancy/createTenant.js index 84f89d289..4db6c70d4 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/createTenant.js +++ b/lib/build/recipe/dashboard/api/multitenancy/createTenant.js @@ -1,24 +1,27 @@ "use strict"; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const error_1 = __importDefault(require("../../../../error")); async function createTenant(_, __, options, userContext) { const requestBody = await options.req.getJSONBody(); - const { tenantId } = requestBody, config = __rest(requestBody, ["tenantId"]); + const { tenantId } = requestBody, + config = __rest(requestBody, ["tenantId"]); if (typeof tenantId !== "string" || tenantId === "") { throw new error_1.default({ message: "Missing required parameter 'tenantId'", @@ -28,8 +31,7 @@ async function createTenant(_, __, options, userContext) { let tenantRes; try { tenantRes = await multitenancy_1.default.createOrUpdateTenant(tenantId, config, userContext); - } - catch (err) { + } catch (err) { const errMsg = err.message; if (errMsg.includes("SuperTokens core threw an error for a ")) { if (errMsg.includes("with status code: 402")) { diff --git a/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.d.ts b/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.d.ts index bf8c3e27c..60d1a433e 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.d.ts @@ -1,10 +1,17 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; - didExist: boolean; -} | { - status: "CANNOT_DELETE_PUBLIC_TENANT_ERROR"; -}; -export default function deleteTenant(_: APIInterface, tenantId: string, __: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + didExist: boolean; + } + | { + status: "CANNOT_DELETE_PUBLIC_TENANT_ERROR"; + }; +export default function deleteTenant( + _: APIInterface, + tenantId: string, + __: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.js b/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.js index d21feb322..7a7d7f863 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.js +++ b/lib/build/recipe/dashboard/api/multitenancy/deleteTenant.js @@ -1,15 +1,16 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); async function deleteTenant(_, tenantId, __, userContext) { try { const deleteTenantRes = await multitenancy_1.default.deleteTenant(tenantId, userContext); return deleteTenantRes; - } - catch (err) { + } catch (err) { const errMsg = err.message; if (errMsg.includes("SuperTokens core threw an error for a ") && errMsg.includes("with status code: 403")) { return { diff --git a/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.d.ts b/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.d.ts index 46ac0d97e..e2cdddbd8 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.d.ts @@ -1,10 +1,17 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; - didConfigExist: boolean; -} | { - status: "UNKNOWN_TENANT_ERROR"; -}; -export default function deleteThirdPartyConfig(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + didConfigExist: boolean; + } + | { + status: "UNKNOWN_TENANT_ERROR"; + }; +export default function deleteThirdPartyConfig( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.js b/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.js index 82bd4f5e5..6bd16f2f9 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.js +++ b/lib/build/recipe/dashboard/api/multitenancy/deleteThirdPartyConfig.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -27,17 +29,28 @@ async function deleteThirdPartyConfig(_, tenantId, options, userContext) { if (thirdPartyIdsFromCore.length === 0) { // this means that the tenant was using the static list of providers, we need to add them all before deleting one const mtRecipe = recipe_1.default.getInstance(); - const staticProviders = (_a = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && _a !== void 0 ? _a : []; - for (const provider of staticProviders.filter((provider) => provider.includeInNonPublicTenantsByDefault === true || tenantId === constants_1.DEFAULT_TENANT_ID)) { + const staticProviders = + (_a = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && + _a !== void 0 + ? _a + : []; + for (const provider of staticProviders.filter( + (provider) => + provider.includeInNonPublicTenantsByDefault === true || tenantId === constants_1.DEFAULT_TENANT_ID + )) { const providerId = provider.config.thirdPartyId; - await multitenancy_1.default.createOrUpdateThirdPartyConfig(tenantId, { - thirdPartyId: providerId, - }, undefined, userContext); + await multitenancy_1.default.createOrUpdateThirdPartyConfig( + tenantId, + { + thirdPartyId: providerId, + }, + undefined, + userContext + ); // delay after each provider to avoid rate limiting await new Promise((r) => setTimeout(r, 500)); // 500ms } - } - else if (thirdPartyIdsFromCore.length === 1 && thirdPartyIdsFromCore[0] === thirdPartyId) { + } else if (thirdPartyIdsFromCore.length === 1 && thirdPartyIdsFromCore[0] === thirdPartyId) { if (tenantRes.firstFactors === undefined) { // add all static first factors except thirdparty await multitenancy_1.default.createOrUpdateTenant(tenantId, { @@ -49,8 +62,7 @@ async function deleteThirdPartyConfig(_, tenantId, options, userContext) { multifactorauth_1.FactorIds.LINK_EMAIL, ], }); - } - else if (tenantRes.firstFactors.includes("thirdparty")) { + } else if (tenantRes.firstFactors.includes("thirdparty")) { // add all static first factors except thirdparty const newFirstFactors = tenantRes.firstFactors.filter((factor) => factor !== "thirdparty"); await multitenancy_1.default.createOrUpdateTenant(tenantId, { diff --git a/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.d.ts b/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.d.ts index f2abcd1a6..2db41aae6 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.d.ts @@ -1,22 +1,29 @@ // @ts-nocheck import { APIInterface, APIOptions, CoreConfigFieldInfo } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; - tenant: { - tenantId: string; - thirdParty: { - providers: { - thirdPartyId: string; - name: string; - }[]; - }; - firstFactors: string[]; - requiredSecondaryFactors?: string[] | null; - coreConfig: CoreConfigFieldInfo[]; - userCount: number; - }; -} | { - status: "UNKNOWN_TENANT_ERROR"; -}; -export default function getTenantInfo(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + tenant: { + tenantId: string; + thirdParty: { + providers: { + thirdPartyId: string; + name: string; + }[]; + }; + firstFactors: string[]; + requiredSecondaryFactors?: string[] | null; + coreConfig: CoreConfigFieldInfo[]; + userCount: number; + }; + } + | { + status: "UNKNOWN_TENANT_ERROR"; + }; +export default function getTenantInfo( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.js b/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.js index 3d5a90245..b7f39cc18 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.js +++ b/lib/build/recipe/dashboard/api/multitenancy/getTenantInfo.js @@ -1,18 +1,20 @@ "use strict"; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -30,35 +32,65 @@ async function getTenantInfo(_, tenantId, options, userContext) { status: "UNKNOWN_TENANT_ERROR", }; } - let { status } = tenantRes, tenantConfig = __rest(tenantRes, ["status"]); + let { status } = tenantRes, + tenantConfig = __rest(tenantRes, ["status"]); let firstFactors = utils_1.getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantConfig); if (tenantRes === undefined) { return { status: "UNKNOWN_TENANT_ERROR", }; } - const userCount = await supertokens_1.default.getInstanceOrThrowError().getUserCount(undefined, tenantId, userContext); - const providersFromCore = (_a = tenantRes === null || tenantRes === void 0 ? void 0 : tenantRes.thirdParty) === null || _a === void 0 ? void 0 : _a.providers; + const userCount = await supertokens_1.default + .getInstanceOrThrowError() + .getUserCount(undefined, tenantId, userContext); + const providersFromCore = + (_a = tenantRes === null || tenantRes === void 0 ? void 0 : tenantRes.thirdParty) === null || _a === void 0 + ? void 0 + : _a.providers; const mtRecipe = recipe_1.default.getInstance(); - const staticProviders = (_b = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && _b !== void 0 ? _b : []; - const mergedProvidersFromCoreAndStatic = configUtils_1.mergeProvidersFromCoreAndStatic(providersFromCore, staticProviders, tenantId === constants_1.DEFAULT_TENANT_ID); + const staticProviders = + (_b = mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) !== null && + _b !== void 0 + ? _b + : []; + const mergedProvidersFromCoreAndStatic = configUtils_1.mergeProvidersFromCoreAndStatic( + providersFromCore, + staticProviders, + tenantId === constants_1.DEFAULT_TENANT_ID + ); let querier = querier_1.Querier.getNewInstanceOrThrowError(options.recipeId); - let coreConfig = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/dashboard/tenant/core-config`), {}, userContext); + let coreConfig = await querier.sendGetRequest( + new normalisedURLPath_1.default(`/${tenantId}/recipe/dashboard/tenant/core-config`), + {}, + userContext + ); const tenant = { tenantId, thirdParty: { - providers: await Promise.all(mergedProvidersFromCoreAndStatic.map(async (provider) => { - try { - const providerInstance = await configUtils_1.findAndCreateProviderInstance(mergedProvidersFromCoreAndStatic, provider.config.thirdPartyId, provider.config.clients[0].clientType, userContext); - return { thirdPartyId: provider.config.thirdPartyId, name: providerInstance === null || providerInstance === void 0 ? void 0 : providerInstance.config.name }; - } - catch (_) { - return { - thirdPartyId: provider.config.thirdPartyId, - name: provider.config.thirdPartyId, - }; - } - })), + providers: await Promise.all( + mergedProvidersFromCoreAndStatic.map(async (provider) => { + try { + const providerInstance = await configUtils_1.findAndCreateProviderInstance( + mergedProvidersFromCoreAndStatic, + provider.config.thirdPartyId, + provider.config.clients[0].clientType, + userContext + ); + return { + thirdPartyId: provider.config.thirdPartyId, + name: + providerInstance === null || providerInstance === void 0 + ? void 0 + : providerInstance.config.name, + }; + } catch (_) { + return { + thirdPartyId: provider.config.thirdPartyId, + name: provider.config.thirdPartyId, + }; + } + }) + ), }, firstFactors: firstFactors, requiredSecondaryFactors: tenantRes.requiredSecondaryFactors, diff --git a/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.d.ts b/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.d.ts index bdb8977a3..9666f1919 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.d.ts @@ -2,14 +2,21 @@ import { APIInterface, APIOptions } from "../../types"; import { ProviderConfig } from "../../../thirdparty/types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; - providerConfig: ProviderConfig & { - isGetAuthorisationRedirectUrlOverridden: boolean; - isExchangeAuthCodeForOAuthTokensOverridden: boolean; - isGetUserInfoOverridden: boolean; - }; -} | { - status: "UNKNOWN_TENANT_ERROR"; -}; -export default function getThirdPartyConfig(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + providerConfig: ProviderConfig & { + isGetAuthorisationRedirectUrlOverridden: boolean; + isExchangeAuthCodeForOAuthTokensOverridden: boolean; + isGetUserInfoOverridden: boolean; + }; + } + | { + status: "UNKNOWN_TENANT_ERROR"; + }; +export default function getThirdPartyConfig( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.js b/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.js index f73f73471..91278ce5f 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.js +++ b/lib/build/recipe/dashboard/api/multitenancy/getThirdPartyConfig.js @@ -1,18 +1,20 @@ "use strict"; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const multitenancy_1 = __importDefault(require("../../../multitenancy")); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -32,10 +34,13 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { if (thirdPartyId === undefined) { throw new Error("Please provide thirdPartyId"); } - let providersFromCore = (_a = tenantRes === null || tenantRes === void 0 ? void 0 : tenantRes.thirdParty) === null || _a === void 0 ? void 0 : _a.providers; + let providersFromCore = + (_a = tenantRes === null || tenantRes === void 0 ? void 0 : tenantRes.thirdParty) === null || _a === void 0 + ? void 0 + : _a.providers; const mtRecipe = recipe_1.default.getInstance(); let staticProviders = (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.staticThirdPartyProviders) - ? mtRecipe.staticThirdPartyProviders.map((provider) => (Object.assign({}, provider))) + ? mtRecipe.staticThirdPartyProviders.map((provider) => Object.assign({}, provider)) : []; let additionalConfig = undefined; // filter out providers that is not matching thirdPartyId @@ -54,14 +59,12 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { if (oktaDomain !== undefined) { additionalConfig = { oktaDomain }; } - } - else if (thirdPartyId === "active-directory") { + } else if (thirdPartyId === "active-directory") { const directoryId = options.req.getKeyValueFromQuery("directoryId"); if (directoryId !== undefined) { additionalConfig = { directoryId }; } - } - else if (thirdPartyId === "boxy-saml") { + } else if (thirdPartyId === "boxy-saml") { let boxyURL = options.req.getKeyValueFromQuery("boxyUrl"); let boxyAPIKey = options.req.getKeyValueFromQuery("boxyAPIKey"); if (boxyURL !== undefined) { @@ -70,8 +73,7 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { additionalConfig = Object.assign(Object.assign({}, additionalConfig), { boxyAPIKey }); } } - } - else if (thirdPartyId === "google-workspaces") { + } else if (thirdPartyId === "google-workspaces") { const hd = options.req.getKeyValueFromQuery("hd"); if (hd !== undefined) { additionalConfig = { hd }; @@ -82,7 +84,14 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { providersFromCore[0].authorizationEndpoint = undefined; providersFromCore[0].tokenEndpoint = undefined; providersFromCore[0].userInfoEndpoint = undefined; - providersFromCore[0].clients = ((_b = providersFromCore[0].clients) !== null && _b !== void 0 ? _b : []).map((client) => (Object.assign(Object.assign({}, client), { additionalConfig: Object.assign(Object.assign({}, client.additionalConfig), additionalConfig) }))); + providersFromCore[0].clients = ((_b = providersFromCore[0].clients) !== null && _b !== void 0 + ? _b + : [] + ).map((client) => + Object.assign(Object.assign({}, client), { + additionalConfig: Object.assign(Object.assign({}, client.additionalConfig), additionalConfig), + }) + ); } } // filter out other providers from static @@ -101,17 +110,38 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { additionalConfig = { teamId: "", keyId: "", - privateKey: "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + privateKey: + "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", }; } if (staticProviders.length === 1) { // modify additional config if query param is passed if (additionalConfig !== undefined) { // we set these to undefined so that these can be computed using the query param that was provided - staticProviders[0] = Object.assign(Object.assign({}, staticProviders[0]), { config: Object.assign(Object.assign({}, staticProviders[0].config), { oidcDiscoveryEndpoint: undefined, authorizationEndpoint: undefined, tokenEndpoint: undefined, userInfoEndpoint: undefined, clients: ((_c = staticProviders[0].config.clients) !== null && _c !== void 0 ? _c : []).map((client) => (Object.assign(Object.assign({}, client), { additionalConfig: Object.assign(Object.assign({}, client.additionalConfig), additionalConfig) }))) }) }); + staticProviders[0] = Object.assign(Object.assign({}, staticProviders[0]), { + config: Object.assign(Object.assign({}, staticProviders[0].config), { + oidcDiscoveryEndpoint: undefined, + authorizationEndpoint: undefined, + tokenEndpoint: undefined, + userInfoEndpoint: undefined, + clients: ((_c = staticProviders[0].config.clients) !== null && _c !== void 0 ? _c : []).map( + (client) => + Object.assign(Object.assign({}, client), { + additionalConfig: Object.assign( + Object.assign({}, client.additionalConfig), + additionalConfig + ), + }) + ), + }), + }); } } - let mergedProvidersFromCoreAndStatic = configUtils_1.mergeProvidersFromCoreAndStatic(providersFromCore, staticProviders, true); + let mergedProvidersFromCoreAndStatic = configUtils_1.mergeProvidersFromCoreAndStatic( + providersFromCore, + staticProviders, + true + ); if (mergedProvidersFromCoreAndStatic.length !== 1) { throw new Error("should never come here!"); } @@ -119,7 +149,10 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { if (mergedProvider.config.thirdPartyId === thirdPartyId) { if (mergedProvider.config.clients === undefined || mergedProvider.config.clients.length === 0) { mergedProvider.config.clients = [ - Object.assign({ clientId: "nonguessable-temporary-client-id" }, (additionalConfig !== undefined ? { additionalConfig } : {})), + Object.assign( + { clientId: "nonguessable-temporary-client-id" }, + additionalConfig !== undefined ? { additionalConfig } : {} + ), ]; } } @@ -136,8 +169,22 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { let foundCorrectConfig = false; for (const client of (_d = provider.config.clients) !== null && _d !== void 0 ? _d : []) { try { - const providerInstance = await configUtils_1.findAndCreateProviderInstance(mergedProvidersFromCoreAndStatic, thirdPartyId, client.clientType, userContext); - const _f = providerInstance.config, { clientId, clientSecret, clientType, scope, additionalConfig, forcePKCE } = _f, commonConfig = __rest(_f, ["clientId", "clientSecret", "clientType", "scope", "additionalConfig", "forcePKCE"]); + const providerInstance = await configUtils_1.findAndCreateProviderInstance( + mergedProvidersFromCoreAndStatic, + thirdPartyId, + client.clientType, + userContext + ); + const _f = providerInstance.config, + { clientId, clientSecret, clientType, scope, additionalConfig, forcePKCE } = _f, + commonConfig = __rest(_f, [ + "clientId", + "clientSecret", + "clientType", + "scope", + "additionalConfig", + "forcePKCE", + ]); clients.push({ clientId, clientSecret, @@ -153,8 +200,10 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { if (beforeOverride.getAuthorisationRedirectURL !== afterOverride.getAuthorisationRedirectURL) { isGetAuthorisationRedirectUrlOverridden = true; } - if (beforeOverride.exchangeAuthCodeForOAuthTokens !== - afterOverride.exchangeAuthCodeForOAuthTokens) { + if ( + beforeOverride.exchangeAuthCodeForOAuthTokens !== + afterOverride.exchangeAuthCodeForOAuthTokens + ) { isExchangeAuthCodeForOAuthTokensOverridden = true; } if (beforeOverride.getUserInfo !== afterOverride.getUserInfo) { @@ -162,8 +211,7 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { } } foundCorrectConfig = true; - } - catch (err) { + } catch (err) { // ignore the error clients.push(client); } @@ -174,13 +222,20 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { break; } } - if ((additionalConfig === null || additionalConfig === void 0 ? void 0 : additionalConfig.privateKey) !== undefined) { + if ( + (additionalConfig === null || additionalConfig === void 0 ? void 0 : additionalConfig.privateKey) !== undefined + ) { additionalConfig.privateKey = ""; } const tempClients = clients.filter((client) => client.clientId === "nonguessable-temporary-client-id"); const finalClients = clients.filter((client) => client.clientId !== "nonguessable-temporary-client-id"); if (finalClients.length === 0) { - finalClients.push(Object.assign(Object.assign(Object.assign({}, tempClients[0]), { clientId: "", clientSecret: "" }), (additionalConfig !== undefined ? { additionalConfig } : {}))); + finalClients.push( + Object.assign( + Object.assign(Object.assign({}, tempClients[0]), { clientId: "", clientSecret: "" }), + additionalConfig !== undefined ? { additionalConfig } : {} + ) + ); } // fill in boxy info from boxy instance if (thirdPartyId.startsWith("boxy-saml")) { @@ -191,27 +246,41 @@ async function getThirdPartyConfig(_, tenantId, options, userContext) { const normalisedDomain = new normalisedURLDomain_1.default(boxyURL); const normalisedBasePath = new normalisedURLPath_1.default(boxyURL); const connectionsPath = new normalisedURLPath_1.default("/api/v1/saml/config"); - const resp = await thirdpartyUtils_1.doGetRequest(normalisedDomain.getAsStringDangerous() + - normalisedBasePath.appendPath(connectionsPath).getAsStringDangerous(), { - clientID: finalClients[0].clientId, - }, { - Authorization: `Api-Key ${boxyAPIKey}`, - }); + const resp = await thirdpartyUtils_1.doGetRequest( + normalisedDomain.getAsStringDangerous() + + normalisedBasePath.appendPath(connectionsPath).getAsStringDangerous(), + { + clientID: finalClients[0].clientId, + }, + { + Authorization: `Api-Key ${boxyAPIKey}`, + } + ); if (resp.status === 200) { // we don't care about non 200 status codes since we are just trying to populate whatever possible if (resp.jsonResponse === undefined) { throw new Error("should never happen"); } - finalClients[0].additionalConfig = Object.assign(Object.assign({}, finalClients[0].additionalConfig), { redirectURLs: resp.jsonResponse.redirectUrl, boxyTenant: resp.jsonResponse.tenant, boxyProduct: resp.jsonResponse.product }); + finalClients[0].additionalConfig = Object.assign( + Object.assign({}, finalClients[0].additionalConfig), + { + redirectURLs: resp.jsonResponse.redirectUrl, + boxyTenant: resp.jsonResponse.tenant, + boxyProduct: resp.jsonResponse.product, + } + ); } } } } return { status: "OK", - providerConfig: Object.assign(Object.assign({}, commonProviderConfig), { clients: finalClients, isGetAuthorisationRedirectUrlOverridden, + providerConfig: Object.assign(Object.assign({}, commonProviderConfig), { + clients: finalClients, + isGetAuthorisationRedirectUrlOverridden, isExchangeAuthCodeForOAuthTokensOverridden, - isGetUserInfoOverridden }), + isGetUserInfoOverridden, + }), }; } exports.default = getThirdPartyConfig; diff --git a/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.d.ts b/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.d.ts index ef658826e..cbbb9de24 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.d.ts @@ -9,5 +9,10 @@ export declare type Response = { status: "OK"; tenants: TenantWithLoginMethods[]; }; -export default function listAllTenantsWithLoginMethods(_: APIInterface, __: string, ___: APIOptions, userContext: UserContext): Promise; +export default function listAllTenantsWithLoginMethods( + _: APIInterface, + __: string, + ___: APIOptions, + userContext: UserContext +): Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.js b/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.js index fb3397bf0..27fa9c61a 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.js +++ b/lib/build/recipe/dashboard/api/multitenancy/listAllTenantsWithLoginMethods.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); const utils_1 = require("./utils"); diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.d.ts b/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.d.ts index 2bc2bcfa6..ee2738561 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.d.ts @@ -1,12 +1,20 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; -} | { - status: "UNKNOWN_TENANT_ERROR"; -} | { - status: "INVALID_CONFIG_ERROR"; - message: string; -}; -export default function updateTenantCoreConfig(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + } + | { + status: "UNKNOWN_TENANT_ERROR"; + } + | { + status: "INVALID_CONFIG_ERROR"; + message: string; + }; +export default function updateTenantCoreConfig( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.js b/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.js index f66aee851..98d4b45dc 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.js +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantCoreConfig.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); async function updateTenantCoreConfig(_, tenantId, options, userContext) { @@ -24,8 +26,7 @@ async function updateTenantCoreConfig(_, tenantId, options, userContext) { }, userContext, }); - } - catch (err) { + } catch (err) { const errMsg = err.message; if (errMsg.includes("SuperTokens core threw an error for a ") && errMsg.includes("with status code: 400")) { return { diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.d.ts b/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.d.ts index b7c034bc2..2c27af7ae 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.d.ts @@ -1,12 +1,20 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; -} | { - status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; - message: string; -} | { - status: "UNKNOWN_TENANT_ERROR"; -}; -export default function updateTenantFirstFactor(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + } + | { + status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; + message: string; + } + | { + status: "UNKNOWN_TENANT_ERROR"; + }; +export default function updateTenantFirstFactor( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.js b/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.js index 149cfc090..72964fd49 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.js +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantFirstFactor.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); const utils_1 = require("./utils"); @@ -10,14 +12,18 @@ async function updateTenantFirstFactor(_, tenantId, options, userContext) { const { factorId, enable } = requestBody; const mtRecipe = recipe_1.default.getInstance(); if (enable === true) { - if (!(mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.allAvailableFirstFactors.includes(factorId))) { + if ( + !(mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.allAvailableFirstFactors.includes(factorId)) + ) { return { status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR", message: utils_1.getFactorNotAvailableMessage(factorId, mtRecipe.allAvailableFirstFactors), }; } } - const tenantRes = await (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.recipeInterfaceImpl.getTenant({ tenantId, userContext })); + const tenantRes = await (mtRecipe === null || mtRecipe === void 0 + ? void 0 + : mtRecipe.recipeInterfaceImpl.getTenant({ tenantId, userContext })); if (tenantRes === undefined) { return { status: "UNKNOWN_TENANT_ERROR", @@ -28,17 +34,18 @@ async function updateTenantFirstFactor(_, tenantId, options, userContext) { if (!firstFactors.includes(factorId)) { firstFactors.push(factorId); } - } - else { + } else { firstFactors = firstFactors.filter((f) => f !== factorId); } - await (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.recipeInterfaceImpl.createOrUpdateTenant({ - tenantId, - config: { - firstFactors, - }, - userContext, - })); + await (mtRecipe === null || mtRecipe === void 0 + ? void 0 + : mtRecipe.recipeInterfaceImpl.createOrUpdateTenant({ + tenantId, + config: { + firstFactors, + }, + userContext, + })); return { status: "OK", }; diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.d.ts b/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.d.ts index 5c355687a..74e206124 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.d.ts @@ -1,15 +1,24 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -export declare type Response = { - status: "OK"; - isMFARequirementsForAuthOverridden: boolean; -} | { - status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; - message: string; -} | { - status: "MFA_NOT_INITIALIZED_ERROR"; -} | { - status: "UNKNOWN_TENANT_ERROR"; -}; -export default function updateTenantSecondaryFactor(_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export declare type Response = + | { + status: "OK"; + isMFARequirementsForAuthOverridden: boolean; + } + | { + status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; + message: string; + } + | { + status: "MFA_NOT_INITIALIZED_ERROR"; + } + | { + status: "UNKNOWN_TENANT_ERROR"; + }; +export default function updateTenantSecondaryFactor( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.js b/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.js index f85fceada..8729bcb42 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.js +++ b/lib/build/recipe/dashboard/api/multitenancy/updateTenantSecondaryFactor.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); const recipe_2 = __importDefault(require("../../../multifactorauth/recipe")); @@ -16,7 +18,9 @@ async function updateTenantSecondaryFactor(_, tenantId, options, userContext) { status: "MFA_NOT_INITIALIZED_ERROR", }; } - const tenantRes = await (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.recipeInterfaceImpl.getTenant({ tenantId, userContext })); + const tenantRes = await (mtRecipe === null || mtRecipe === void 0 + ? void 0 + : mtRecipe.recipeInterfaceImpl.getTenant({ tenantId, userContext })); if (tenantRes === undefined) { return { status: "UNKNOWN_TENANT_ERROR", @@ -31,22 +35,25 @@ async function updateTenantSecondaryFactor(_, tenantId, options, userContext) { }; } } - let secondaryFactors = utils_1.getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantRes); + let secondaryFactors = utils_1.getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit( + tenantRes + ); if (enable === true) { if (!secondaryFactors.includes(factorId)) { secondaryFactors.push(factorId); } - } - else { + } else { secondaryFactors = secondaryFactors.filter((f) => f !== factorId); } - await (mtRecipe === null || mtRecipe === void 0 ? void 0 : mtRecipe.recipeInterfaceImpl.createOrUpdateTenant({ - tenantId, - config: { - requiredSecondaryFactors: secondaryFactors.length > 0 ? secondaryFactors : null, - }, - userContext, - })); + await (mtRecipe === null || mtRecipe === void 0 + ? void 0 + : mtRecipe.recipeInterfaceImpl.createOrUpdateTenant({ + tenantId, + config: { + requiredSecondaryFactors: secondaryFactors.length > 0 ? secondaryFactors : null, + }, + userContext, + })); return { status: "OK", isMFARequirementsForAuthOverridden: mfaInstance.isGetMfaRequirementsForAuthOverridden, diff --git a/lib/build/recipe/dashboard/api/multitenancy/utils.d.ts b/lib/build/recipe/dashboard/api/multitenancy/utils.d.ts index 53cd451e2..76df5b8c0 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/utils.d.ts +++ b/lib/build/recipe/dashboard/api/multitenancy/utils.d.ts @@ -1,6 +1,10 @@ // @ts-nocheck import { TenantConfig } from "../../../multitenancy/types"; -export declare function getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantDetailsFromCore: TenantConfig): string[]; -export declare function getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantDetailsFromCore: TenantConfig): string[]; +export declare function getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit( + tenantDetailsFromCore: TenantConfig +): string[]; +export declare function getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit( + tenantDetailsFromCore: TenantConfig +): string[]; export declare function factorIdToRecipe(factorId: string): string; export declare function getFactorNotAvailableMessage(factorId: string, availableFactors: string[]): string; diff --git a/lib/build/recipe/dashboard/api/multitenancy/utils.js b/lib/build/recipe/dashboard/api/multitenancy/utils.js index 95da4fc2f..2dcb998ed 100644 --- a/lib/build/recipe/dashboard/api/multitenancy/utils.js +++ b/lib/build/recipe/dashboard/api/multitenancy/utils.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getFactorNotAvailableMessage = exports.factorIdToRecipe = exports.getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit = exports.getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit = void 0; const recipe_1 = __importDefault(require("../../../multitenancy/recipe")); @@ -13,11 +15,9 @@ function getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantDe let mtInstance = recipe_1.default.getInstanceOrThrowError(); if (tenantDetailsFromCore.firstFactors !== undefined) { firstFactors = tenantDetailsFromCore.firstFactors; // highest priority, config from core - } - else if (mtInstance.staticFirstFactors !== undefined) { + } else if (mtInstance.staticFirstFactors !== undefined) { firstFactors = mtInstance.staticFirstFactors; // next priority, static config - } - else { + } else { // Fallback to all available factors (de-duplicated) firstFactors = Array.from(new Set(mtInstance.allAvailableFirstFactors)); } @@ -28,12 +28,14 @@ function getNormalisedFirstFactorsBasedOnTenantConfigFromCoreAndSDKInit(tenantDe // enabled recipes in all cases irrespective of whether they are using MFA or not let validFirstFactors = []; for (const factorId of firstFactors) { - if (utils_1.isFactorConfiguredForTenant({ - tenantConfig: tenantDetailsFromCore, - allAvailableFirstFactors: mtInstance.allAvailableFirstFactors, - firstFactors: firstFactors, - factorId, - })) { + if ( + utils_1.isFactorConfiguredForTenant({ + tenantConfig: tenantDetailsFromCore, + allAvailableFirstFactors: mtInstance.allAvailableFirstFactors, + firstFactors: firstFactors, + factorId, + }) + ) { validFirstFactors.push(factorId); } } @@ -46,7 +48,12 @@ function getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKI return []; } let secondaryFactors = mfaInstance.getAllAvailableSecondaryFactorIds(tenantDetailsFromCore); - secondaryFactors = secondaryFactors.filter((factorId) => { var _a; return ((_a = tenantDetailsFromCore.requiredSecondaryFactors) !== null && _a !== void 0 ? _a : []).includes(factorId); }); + secondaryFactors = secondaryFactors.filter((factorId) => { + var _a; + return ((_a = tenantDetailsFromCore.requiredSecondaryFactors) !== null && _a !== void 0 ? _a : []).includes( + factorId + ); + }); return secondaryFactors; } exports.getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit = getNormalisedRequiredSecondaryFactorsBasedOnTenantConfigFromCoreAndSDKInit; @@ -68,12 +75,19 @@ function getFactorNotAvailableMessage(factorId, availableFactors) { if (recipeName !== "Passwordless") { return `Please initialise ${recipeName} recipe to be able to use this login method`; } - const passwordlessFactors = [multifactorauth_1.FactorIds.LINK_EMAIL, multifactorauth_1.FactorIds.LINK_PHONE, multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.OTP_PHONE]; + const passwordlessFactors = [ + multifactorauth_1.FactorIds.LINK_EMAIL, + multifactorauth_1.FactorIds.LINK_PHONE, + multifactorauth_1.FactorIds.OTP_EMAIL, + multifactorauth_1.FactorIds.OTP_PHONE, + ]; const passwordlessFactorsNotAvailable = passwordlessFactors.filter((f) => !availableFactors.includes(f)); if (passwordlessFactorsNotAvailable.length === 4) { return `Please initialise Passwordless recipe to be able to use this login method`; } const [flowType, contactMethod] = factorId.split("-"); - return `Please ensure that Passwordless recipe is initialised with contactMethod: ${contactMethod.toUpperCase()} and flowType: ${flowType === "otp" ? "USER_INPUT_CODE" : "MAGIC_LINK"}`; + return `Please ensure that Passwordless recipe is initialised with contactMethod: ${contactMethod.toUpperCase()} and flowType: ${ + flowType === "otp" ? "USER_INPUT_CODE" : "MAGIC_LINK" + }`; } exports.getFactorNotAvailableMessage = getFactorNotAvailableMessage; diff --git a/lib/build/recipe/dashboard/api/search/tagsGet.d.ts b/lib/build/recipe/dashboard/api/search/tagsGet.d.ts index 8824e6f5c..68ed9b3fe 100644 --- a/lib/build/recipe/dashboard/api/search/tagsGet.d.ts +++ b/lib/build/recipe/dashboard/api/search/tagsGet.d.ts @@ -5,5 +5,10 @@ declare type TagsResponse = { status: "OK"; tags: string[]; }; -export declare const getSearchTags: (_: APIInterface, ___: string, options: APIOptions, userContext: UserContext) => Promise; +export declare const getSearchTags: ( + _: APIInterface, + ___: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/search/tagsGet.js b/lib/build/recipe/dashboard/api/search/tagsGet.js index 4075a32d1..06c87156f 100644 --- a/lib/build/recipe/dashboard/api/search/tagsGet.js +++ b/lib/build/recipe/dashboard/api/search/tagsGet.js @@ -13,16 +13,22 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSearchTags = void 0; const querier_1 = require("../../../../querier"); const normalisedURLPath_1 = __importDefault(require("../../../../normalisedURLPath")); const getSearchTags = async (_, ___, options, userContext) => { let querier = querier_1.Querier.getNewInstanceOrThrowError(options.recipeId); - let tagsResponse = await querier.sendGetRequest(new normalisedURLPath_1.default("/user/search/tags"), {}, userContext); + let tagsResponse = await querier.sendGetRequest( + new normalisedURLPath_1.default("/user/search/tags"), + {}, + userContext + ); return tagsResponse; }; exports.getSearchTags = getSearchTags; diff --git a/lib/build/recipe/dashboard/api/signIn.js b/lib/build/recipe/dashboard/api/signIn.js index 27c82654e..5f52060a2 100644 --- a/lib/build/recipe/dashboard/api/signIn.js +++ b/lib/build/recipe/dashboard/api/signIn.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../../../error")); @@ -36,10 +38,14 @@ async function signIn(_, options, userContext) { }); } let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); - const signInResponse = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/dashboard/signin"), { - email, - password, - }, userContext); + const signInResponse = await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/dashboard/signin"), + { + email, + password, + }, + userContext + ); utils_1.send200Response(options.res, signInResponse); return true; } diff --git a/lib/build/recipe/dashboard/api/signOut.d.ts b/lib/build/recipe/dashboard/api/signOut.d.ts index 46f4609ac..b10c8509a 100644 --- a/lib/build/recipe/dashboard/api/signOut.d.ts +++ b/lib/build/recipe/dashboard/api/signOut.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../types"; import { UserContext } from "../../../types"; -export default function signOut(_: APIInterface, ___: string, options: APIOptions, userContext: UserContext): Promise; +export default function signOut( + _: APIInterface, + ___: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/signOut.js b/lib/build/recipe/dashboard/api/signOut.js index 66864f736..48e0d22fa 100644 --- a/lib/build/recipe/dashboard/api/signOut.js +++ b/lib/build/recipe/dashboard/api/signOut.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const querier_1 = require("../../../querier"); @@ -24,11 +26,16 @@ async function signOut(_, ___, options, userContext) { var _a; if (options.config.authMode === "api-key") { utils_1.send200Response(options.res, { status: "OK" }); - } - else { - const sessionIdFormAuthHeader = (_a = options.req.getHeaderValue("authorization")) === null || _a === void 0 ? void 0 : _a.split(" ")[1]; + } else { + const sessionIdFormAuthHeader = + (_a = options.req.getHeaderValue("authorization")) === null || _a === void 0 ? void 0 : _a.split(" ")[1]; let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); - const sessionDeleteResponse = await querier.sendDeleteRequest(new normalisedURLPath_1.default("/recipe/dashboard/session"), {}, { sessionId: sessionIdFormAuthHeader }, userContext); + const sessionDeleteResponse = await querier.sendDeleteRequest( + new normalisedURLPath_1.default("/recipe/dashboard/session"), + {}, + { sessionId: sessionIdFormAuthHeader }, + userContext + ); utils_1.send200Response(options.res, sessionDeleteResponse); } return true; diff --git a/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.d.ts b/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.d.ts index ac7d16c42..d35ed6a22 100644 --- a/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.d.ts +++ b/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.d.ts @@ -2,18 +2,27 @@ import { APIInterface, APIOptions } from "../../../types"; import { User, UserContext } from "../../../../../types"; import RecipeUserId from "../../../../../recipeUserId"; -declare type Response = { - status: "OK"; - user: User; - recipeUserId: RecipeUserId; -} | { - status: "EMAIL_ALREADY_EXISTS_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; -} | { - status: "EMAIL_VALIDATION_ERROR"; - message: string; -} | { - status: "PASSWORD_VALIDATION_ERROR"; - message: string; -}; -export declare const createEmailPasswordUser: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; +declare type Response = + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; + } + | { + status: "EMAIL_VALIDATION_ERROR"; + message: string; + } + | { + status: "PASSWORD_VALIDATION_ERROR"; + message: string; + }; +export declare const createEmailPasswordUser: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.js b/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.js index 0d082488d..cf84423bc 100644 --- a/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.js +++ b/lib/build/recipe/dashboard/api/user/create/emailpasswordUser.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createEmailPasswordUser = void 0; const error_1 = __importDefault(require("../../../../../error")); @@ -25,8 +27,7 @@ const createEmailPasswordUser = async (_, tenantId, options, userContext) => { let emailPassword = undefined; try { emailPassword = recipe_1.default.getInstanceOrThrowError(); - } - catch (error) { + } catch (error) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; @@ -66,9 +67,10 @@ const createEmailPasswordUser = async (_, tenantId, options, userContext) => { // For some reason TS complains if I check the other status codes then throw... if (response.status === "OK" || response.status === "EMAIL_ALREADY_EXISTS_ERROR") { return response; - } - else { - throw new Error("This should never happen: EmailPassword.signUp threw a session user related error without passing a session"); + } else { + throw new Error( + "This should never happen: EmailPassword.signUp threw a session user related error without passing a session" + ); } }; exports.createEmailPasswordUser = createEmailPasswordUser; diff --git a/lib/build/recipe/dashboard/api/user/create/passwordlessUser.d.ts b/lib/build/recipe/dashboard/api/user/create/passwordlessUser.d.ts index 937c286d7..2518b7a8c 100644 --- a/lib/build/recipe/dashboard/api/user/create/passwordlessUser.d.ts +++ b/lib/build/recipe/dashboard/api/user/create/passwordlessUser.d.ts @@ -2,19 +2,28 @@ import { APIInterface, APIOptions } from "../../../types"; import { User } from "../../../../../types"; import RecipeUserId from "../../../../../recipeUserId"; -declare type Response = { - status: string; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; -} | { - status: "FEATURE_NOT_ENABLED_ERROR"; -} | { - status: "EMAIL_VALIDATION_ERROR"; - message: string; -} | { - status: "PHONE_VALIDATION_ERROR"; - message: string; -}; -export declare const createPasswordlessUser: (_: APIInterface, tenantId: string, options: APIOptions, __: any) => Promise; +declare type Response = + | { + status: string; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "FEATURE_NOT_ENABLED_ERROR"; + } + | { + status: "EMAIL_VALIDATION_ERROR"; + message: string; + } + | { + status: "PHONE_VALIDATION_ERROR"; + message: string; + }; +export declare const createPasswordlessUser: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + __: any +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/user/create/passwordlessUser.js b/lib/build/recipe/dashboard/api/user/create/passwordlessUser.js index afe2777d4..05e44821e 100644 --- a/lib/build/recipe/dashboard/api/user/create/passwordlessUser.js +++ b/lib/build/recipe/dashboard/api/user/create/passwordlessUser.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createPasswordlessUser = void 0; const error_1 = __importDefault(require("../../../../../error")); @@ -26,8 +28,7 @@ const createPasswordlessUser = async (_, tenantId, options, __) => { let passwordlessRecipe = undefined; try { passwordlessRecipe = recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; @@ -41,9 +42,11 @@ const createPasswordlessUser = async (_, tenantId, options, __) => { message: "Please provide exactly one of email or phoneNumber", }); } - if (email !== undefined && + if ( + email !== undefined && (passwordlessRecipe.config.contactMethod === "EMAIL" || - passwordlessRecipe.config.contactMethod === "EMAIL_OR_PHONE")) { + passwordlessRecipe.config.contactMethod === "EMAIL_OR_PHONE") + ) { email = email.trim(); let validationError = undefined; validationError = await passwordlessRecipe.config.validateEmailAddress(email, tenantId); @@ -54,9 +57,11 @@ const createPasswordlessUser = async (_, tenantId, options, __) => { }; } } - if (phoneNumber !== undefined && + if ( + phoneNumber !== undefined && (passwordlessRecipe.config.contactMethod === "PHONE" || - passwordlessRecipe.config.contactMethod === "EMAIL_OR_PHONE")) { + passwordlessRecipe.config.contactMethod === "EMAIL_OR_PHONE") + ) { let validationError = undefined; validationError = await passwordlessRecipe.config.validatePhoneNumber(phoneNumber, tenantId); if (validationError !== undefined) { @@ -70,11 +75,12 @@ const createPasswordlessUser = async (_, tenantId, options, __) => { // this can come here if the user has provided their own impl of validatePhoneNumber and // the phone number is valid according to their impl, but not according to the libphonenumber-js lib. phoneNumber = phoneNumber.trim(); - } - else { + } else { phoneNumber = parsedPhoneNumber.format("E.164"); } } - return await passwordless_1.default.signInUp(email !== undefined ? { email, tenantId } : { phoneNumber: phoneNumber, tenantId }); + return await passwordless_1.default.signInUp( + email !== undefined ? { email, tenantId } : { phoneNumber: phoneNumber, tenantId } + ); }; exports.createPasswordlessUser = createPasswordlessUser; diff --git a/lib/build/recipe/dashboard/api/userdetails/userDelete.js b/lib/build/recipe/dashboard/api/userdetails/userDelete.js index 7fb9e72ef..7840c2141 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userDelete.js +++ b/lib/build/recipe/dashboard/api/userdetails/userDelete.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userDelete = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -12,7 +14,8 @@ const userDelete = async (_, ___, options, __) => { if (removeAllLinkedAccountsQueryValue !== undefined) { removeAllLinkedAccountsQueryValue = removeAllLinkedAccountsQueryValue.trim().toLowerCase(); } - const removeAllLinkedAccounts = removeAllLinkedAccountsQueryValue === undefined ? undefined : removeAllLinkedAccountsQueryValue === "true"; + const removeAllLinkedAccounts = + removeAllLinkedAccountsQueryValue === undefined ? undefined : removeAllLinkedAccountsQueryValue === "true"; if (userId === undefined || userId === "") { throw new error_1.default({ message: "Missing required parameter 'userId'", diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyGet.js b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyGet.js index 675e92f01..affe7a2ae 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyGet.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userEmailVerifyGet = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -19,13 +21,16 @@ const userEmailVerifyGet = async (_, ___, options, userContext) => { } try { recipe_1.default.getInstanceOrThrowError(); - } - catch (e) { + } catch (e) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; } - const response = await emailverification_1.default.isEmailVerified(new recipeUserId_1.default(recipeUserId), undefined, userContext); + const response = await emailverification_1.default.isEmailVerified( + new recipeUserId_1.default(recipeUserId), + undefined, + userContext + ); return { status: "OK", isVerified: response, diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.d.ts index 579bc5ceb..9b92ba163 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.d.ts @@ -4,5 +4,10 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK"; }; -export declare const userEmailVerifyPut: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; +export declare const userEmailVerifyPut: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.js b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.js index fed7bba1e..604be3c27 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyPut.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userEmailVerifyPut = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -24,20 +26,33 @@ const userEmailVerifyPut = async (_, tenantId, options, userContext) => { }); } if (verified) { - const tokenResponse = await emailverification_1.default.createEmailVerificationToken(tenantId, new recipeUserId_1.default(recipeUserId), undefined, userContext); + const tokenResponse = await emailverification_1.default.createEmailVerificationToken( + tenantId, + new recipeUserId_1.default(recipeUserId), + undefined, + userContext + ); if (tokenResponse.status === "EMAIL_ALREADY_VERIFIED_ERROR") { return { status: "OK", }; } - const verifyResponse = await emailverification_1.default.verifyEmailUsingToken(tenantId, tokenResponse.token, undefined, userContext); + const verifyResponse = await emailverification_1.default.verifyEmailUsingToken( + tenantId, + tokenResponse.token, + undefined, + userContext + ); if (verifyResponse.status === "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR") { // This should never happen because we consume the token immediately after creating it throw new Error("Should not come here"); } - } - else { - await emailverification_1.default.unverifyEmail(new recipeUserId_1.default(recipeUserId), undefined, userContext); + } else { + await emailverification_1.default.unverifyEmail( + new recipeUserId_1.default(recipeUserId), + undefined, + userContext + ); } return { status: "OK", diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.d.ts b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.d.ts index 22eba52b8..16b68a15c 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.d.ts @@ -4,5 +4,10 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK" | "EMAIL_ALREADY_VERIFIED_ERROR"; }; -export declare const userEmailVerifyTokenPost: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; +export declare const userEmailVerifyTokenPost: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.js b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.js index 23b94b39c..6eed7be0d 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.js +++ b/lib/build/recipe/dashboard/api/userdetails/userEmailVerifyTokenPost.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userEmailVerifyTokenPost = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -23,6 +25,12 @@ const userEmailVerifyTokenPost = async (_, tenantId, options, userContext) => { type: error_1.default.BAD_INPUT_ERROR, }); } - return await emailverification_1.default.sendEmailVerificationEmail(tenantId, user.id, __1.convertToRecipeUserId(recipeUserId), undefined, userContext); + return await emailverification_1.default.sendEmailVerificationEmail( + tenantId, + user.id, + __1.convertToRecipeUserId(recipeUserId), + undefined, + userContext + ); }; exports.userEmailVerifyTokenPost = userEmailVerifyTokenPost; diff --git a/lib/build/recipe/dashboard/api/userdetails/userGet.js b/lib/build/recipe/dashboard/api/userdetails/userGet.js index 791cf025d..9affeabb7 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userGet.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userGet = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -24,18 +26,23 @@ const userGet = async (_, ___, options, userContext) => { } try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "OK", - user: Object.assign(Object.assign({}, user.toJson()), { firstName: "FEATURE_NOT_ENABLED", lastName: "FEATURE_NOT_ENABLED" }), + user: Object.assign(Object.assign({}, user.toJson()), { + firstName: "FEATURE_NOT_ENABLED", + lastName: "FEATURE_NOT_ENABLED", + }), }; } const userMetaData = await usermetadata_1.default.getUserMetadata(userId, userContext); const { first_name, last_name } = userMetaData.metadata; return { status: "OK", - user: Object.assign(Object.assign({}, user.toJson()), { firstName: first_name === undefined ? "" : first_name, lastName: last_name === undefined ? "" : last_name }), + user: Object.assign(Object.assign({}, user.toJson()), { + firstName: first_name === undefined ? "" : first_name, + lastName: last_name === undefined ? "" : last_name, + }), }; }; exports.userGet = userGet; diff --git a/lib/build/recipe/dashboard/api/userdetails/userMetadataGet.js b/lib/build/recipe/dashboard/api/userdetails/userMetadataGet.js index 5e8a80c5b..b2e71cdce 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userMetadataGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userMetadataGet.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userMetaDataGet = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -17,8 +19,7 @@ const userMetaDataGet = async (_, ___, options, userContext) => { } try { recipe_1.default.getInstanceOrThrowError(); - } - catch (e) { + } catch (e) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.d.ts index 5fafc9caa..f6099dc09 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.d.ts @@ -4,5 +4,10 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK"; }; -export declare const userMetadataPut: (_: APIInterface, ___: string, options: APIOptions, userContext: UserContext) => Promise; +export declare const userMetadataPut: ( + _: APIInterface, + ___: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.js b/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.js index 38a4df027..0b77de89f 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userMetadataPut.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userMetadataPut = void 0; const recipe_1 = __importDefault(require("../../../usermetadata/recipe")); @@ -37,8 +39,7 @@ const userMetadataPut = async (_, ___, options, userContext) => { if (parsedData === null) { throw new Error(); } - } - catch (e) { + } catch (e) { throw new error_1.default({ message: "'data' must be a valid JSON body", type: error_1.default.BAD_INPUT_ERROR, diff --git a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts index ce43b0f15..eb0b5a928 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts @@ -1,11 +1,18 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -declare type Response = { - status: "OK"; -} | { - status: "INVALID_PASSWORD_ERROR"; - error: string; -}; -export declare const userPasswordPut: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; +declare type Response = + | { + status: "OK"; + } + | { + status: "INVALID_PASSWORD_ERROR"; + error: string; + }; +export declare const userPasswordPut: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js index dfc2230b1..9045520c5 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userPasswordPut = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -29,13 +31,14 @@ const userPasswordPut = async (_, tenantId, options, userContext) => { tenantIdForPasswordPolicy: tenantId, userContext, }); - if (updateResponse.status === "UNKNOWN_USER_ID_ERROR" || + if ( + updateResponse.status === "UNKNOWN_USER_ID_ERROR" || updateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR" || - updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { + updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" + ) { // Techincally it can but its an edge case so we assume that it wont throw new Error("Should never come here"); - } - else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { + } else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { return { status: "INVALID_PASSWORD_ERROR", error: updateResponse.failureReason, diff --git a/lib/build/recipe/dashboard/api/userdetails/userPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userPut.d.ts index f4b1b5471..b4156bb5c 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userPut.d.ts @@ -1,24 +1,36 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; import { UserContext } from "../../../../types"; -declare type Response = { - status: "OK"; -} | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; -} | { - status: "INVALID_EMAIL_ERROR"; - error: string; -} | { - status: "PHONE_ALREADY_EXISTS_ERROR"; -} | { - status: "INVALID_PHONE_ERROR"; - error: string; -} | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - error: string; -} | { - status: "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; - error: string; -}; -export declare const userPut: (_: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; +declare type Response = + | { + status: "OK"; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_EMAIL_ERROR"; + error: string; + } + | { + status: "PHONE_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_PHONE_ERROR"; + error: string; + } + | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + error: string; + } + | { + status: "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; + error: string; + }; +export declare const userPut: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userPut.js b/lib/build/recipe/dashboard/api/userdetails/userPut.js index 0563fe54c..ce83eb5be 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userPut.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userPut = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -17,7 +19,9 @@ const utils_2 = require("../../../passwordless/utils"); const recipeUserId_1 = __importDefault(require("../../../../recipeUserId")); const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, userContext) => { if (recipeId === "emailpassword") { - let emailFormFields = recipe_1.default.getInstanceOrThrowError().config.signUpFeature.formFields.filter((field) => field.id === constants_1.FORM_FIELD_EMAIL_ID); + let emailFormFields = recipe_1.default + .getInstanceOrThrowError() + .config.signUpFeature.formFields.filter((field) => field.id === constants_1.FORM_FIELD_EMAIL_ID); let validationError = await emailFormFields[0].validate(email, tenantId, userContext); if (validationError !== undefined) { return { @@ -34,14 +38,12 @@ const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, u return { status: "EMAIL_ALREADY_EXISTS_ERROR", }; - } - else if (emailUpdateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { + } else if (emailUpdateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { return { status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", reason: emailUpdateResponse.reason, }; - } - else if (emailUpdateResponse.status === "UNKNOWN_USER_ID_ERROR") { + } else if (emailUpdateResponse.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); } return { @@ -58,8 +60,7 @@ const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, u isValidEmail = false; validationError = validationResult; } - } - else { + } else { const validationResult = await passwordlessConfig.validateEmailAddress(email, tenantId); if (validationResult !== undefined) { isValidEmail = false; @@ -85,8 +86,10 @@ const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, u status: "EMAIL_ALREADY_EXISTS_ERROR", }; } - if (updateResult.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" || - updateResult.status === "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR") { + if ( + updateResult.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" || + updateResult.status === "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" + ) { return { status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", reason: updateResult.reason, @@ -111,8 +114,7 @@ const updatePhoneForRecipeId = async (recipeUserId, phone, tenantId, userContext isValidPhone = false; validationError = validationResult; } - } - else { + } else { const validationResult = await passwordlessConfig.validatePhoneNumber(phone, tenantId); if (validationResult !== undefined) { isValidPhone = false; @@ -198,7 +200,11 @@ const userPut = async (_, tenantId, options, userContext) => { type: error_1.default.BAD_INPUT_ERROR, }); } - let userResponse = await utils_1.getUserForRecipeId(new recipeUserId_1.default(recipeUserId), recipeId, userContext); + let userResponse = await utils_1.getUserForRecipeId( + new recipeUserId_1.default(recipeUserId), + recipeId, + userContext + ); if (userResponse.user === undefined || userResponse.recipe === undefined) { throw new Error("Should never come here"); } @@ -207,8 +213,7 @@ const userPut = async (_, tenantId, options, userContext) => { try { recipe_3.default.getInstanceOrThrowError(); isRecipeInitialised = true; - } - catch (_) { + } catch (_) { // no op } if (isRecipeInitialised) { @@ -223,7 +228,13 @@ const userPut = async (_, tenantId, options, userContext) => { } } if (email.trim() !== "") { - const emailUpdateResponse = await updateEmailForRecipeId(userResponse.recipe, new recipeUserId_1.default(recipeUserId), email.trim(), tenantId, userContext); + const emailUpdateResponse = await updateEmailForRecipeId( + userResponse.recipe, + new recipeUserId_1.default(recipeUserId), + email.trim(), + tenantId, + userContext + ); if (emailUpdateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { return { error: emailUpdateResponse.reason, @@ -235,7 +246,12 @@ const userPut = async (_, tenantId, options, userContext) => { } } if (phone.trim() !== "") { - const phoneUpdateResponse = await updatePhoneForRecipeId(new recipeUserId_1.default(recipeUserId), phone.trim(), tenantId, userContext); + const phoneUpdateResponse = await updatePhoneForRecipeId( + new recipeUserId_1.default(recipeUserId), + phone.trim(), + tenantId, + userContext + ); if (phoneUpdateResponse.status === "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR") { return { error: phoneUpdateResponse.reason, diff --git a/lib/build/recipe/dashboard/api/userdetails/userSessionsGet.js b/lib/build/recipe/dashboard/api/userdetails/userSessionsGet.js index c9ac1cab9..2528cf4ff 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userSessionsGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userSessionsGet.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userSessionsGet = void 0; const error_1 = __importDefault(require("../../../../error")); @@ -18,20 +20,21 @@ const userSessionsGet = async (_, ___, options, userContext) => { let sessions = []; let sessionInfoPromises = []; for (let i = 0; i < response.length; i++) { - sessionInfoPromises.push(new Promise(async (res, rej) => { - try { - const sessionResponse = await session_1.default.getSessionInformation(response[i], userContext); - if (sessionResponse !== undefined) { - const accessTokenPayload = sessionResponse.customClaimsInAccessTokenPayload; - delete sessionResponse.customClaimsInAccessTokenPayload; - sessions[i] = Object.assign(Object.assign({}, sessionResponse), { accessTokenPayload }); + sessionInfoPromises.push( + new Promise(async (res, rej) => { + try { + const sessionResponse = await session_1.default.getSessionInformation(response[i], userContext); + if (sessionResponse !== undefined) { + const accessTokenPayload = sessionResponse.customClaimsInAccessTokenPayload; + delete sessionResponse.customClaimsInAccessTokenPayload; + sessions[i] = Object.assign(Object.assign({}, sessionResponse), { accessTokenPayload }); + } + res(); + } catch (e) { + rej(e); } - res(); - } - catch (e) { - rej(e); - } - })); + }) + ); } await Promise.all(sessionInfoPromises); return { diff --git a/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.d.ts b/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.d.ts index 8de8f408c..1e9a3d118 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.d.ts @@ -4,5 +4,10 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK"; }; -export declare const userSessionsPost: (_: APIInterface, ___: string, options: APIOptions, userContext: UserContext) => Promise; +export declare const userSessionsPost: ( + _: APIInterface, + ___: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.js b/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.js index f744843f6..51ab7f94a 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.js +++ b/lib/build/recipe/dashboard/api/userdetails/userSessionsPost.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userSessionsPost = void 0; const error_1 = __importDefault(require("../../../../error")); diff --git a/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.d.ts b/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.d.ts index 4524da11c..15b661f25 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.d.ts @@ -4,5 +4,10 @@ import { UserContext } from "../../../../types"; declare type Response = { status: "OK"; }; -export declare const userUnlink: (_: APIInterface, ___: string, options: APIOptions, userContext: UserContext) => Promise; +export declare const userUnlink: ( + _: APIInterface, + ___: string, + options: APIOptions, + userContext: UserContext +) => Promise; export {}; diff --git a/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.js b/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.js index 7a4934507..11283fce1 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.js +++ b/lib/build/recipe/dashboard/api/userdetails/userUnlinkGet.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userUnlink = void 0; const error_1 = __importDefault(require("../../../../error")); diff --git a/lib/build/recipe/dashboard/api/userroles/addRoleToUser.d.ts b/lib/build/recipe/dashboard/api/userroles/addRoleToUser.d.ts index 64a486f8d..8485ed162 100644 --- a/lib/build/recipe/dashboard/api/userroles/addRoleToUser.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/addRoleToUser.d.ts @@ -1,9 +1,17 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; -declare const addRoleToUser: (_: APIInterface, tenantId: string, options: APIOptions, __: any) => Promise<{ - status: "OK"; - didUserAlreadyHaveRole: boolean; -} | { - status: "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; -}>; +declare const addRoleToUser: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + __: any +) => Promise< + | { + status: "OK"; + didUserAlreadyHaveRole: boolean; + } + | { + status: "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; + } +>; export default addRoleToUser; diff --git a/lib/build/recipe/dashboard/api/userroles/addRoleToUser.js b/lib/build/recipe/dashboard/api/userroles/addRoleToUser.js index 4f2319581..c7b1e0397 100644 --- a/lib/build/recipe/dashboard/api/userroles/addRoleToUser.js +++ b/lib/build/recipe/dashboard/api/userroles/addRoleToUser.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../userroles")); @@ -9,8 +11,7 @@ const error_1 = __importDefault(require("../../../../error")); const addRoleToUser = async (_, tenantId, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/getRolesForUser.d.ts b/lib/build/recipe/dashboard/api/userroles/getRolesForUser.d.ts index 1dcc29877..0de8b9c3e 100644 --- a/lib/build/recipe/dashboard/api/userroles/getRolesForUser.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/getRolesForUser.d.ts @@ -1,9 +1,17 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; -declare const getRolesForUser: (_: APIInterface, tenantId: string, options: APIOptions, __: any) => Promise<{ - status: "OK"; - roles: string[]; -} | { - status: "FEATURE_NOT_ENABLED_ERROR"; -}>; +declare const getRolesForUser: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + __: any +) => Promise< + | { + status: "OK"; + roles: string[]; + } + | { + status: "FEATURE_NOT_ENABLED_ERROR"; + } +>; export default getRolesForUser; diff --git a/lib/build/recipe/dashboard/api/userroles/getRolesForUser.js b/lib/build/recipe/dashboard/api/userroles/getRolesForUser.js index a9b079815..be76ed593 100644 --- a/lib/build/recipe/dashboard/api/userroles/getRolesForUser.js +++ b/lib/build/recipe/dashboard/api/userroles/getRolesForUser.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const userroles_1 = __importDefault(require("../../../userroles")); const recipe_1 = __importDefault(require("../../../userroles/recipe")); @@ -10,8 +12,7 @@ const getRolesForUser = async (_, tenantId, options, __) => { const userId = options.req.getKeyValueFromQuery("userId"); try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.d.ts b/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.d.ts index 2c4b06094..206ddc1af 100644 --- a/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.d.ts @@ -1,9 +1,17 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../../types"; -declare const getPermissionsForRole: (_: APIInterface, ___: string, options: APIOptions, __: any) => Promise<{ - status: "OK"; - permissions: string[]; -} | { - status: "FEATURE_NOT_ENABLED_ERROR" | "UNKNOWN_ROLE_ERROR"; -}>; +declare const getPermissionsForRole: ( + _: APIInterface, + ___: string, + options: APIOptions, + __: any +) => Promise< + | { + status: "OK"; + permissions: string[]; + } + | { + status: "FEATURE_NOT_ENABLED_ERROR" | "UNKNOWN_ROLE_ERROR"; + } +>; export default getPermissionsForRole; diff --git a/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.js b/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.js index 329519039..fac738710 100644 --- a/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.js +++ b/lib/build/recipe/dashboard/api/userroles/permissions/getPermissionsForRole.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../../userroles")); @@ -9,8 +11,7 @@ const error_1 = __importDefault(require("../../../../../error")); const getPermissionsForRole = async (_, ___, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.d.ts b/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.d.ts index ec4a827ad..e4904b004 100644 --- a/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.d.ts @@ -1,6 +1,11 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../../types"; -declare const removePermissionsFromRole: (_: APIInterface, ___: string, options: APIOptions, __: any) => Promise<{ +declare const removePermissionsFromRole: ( + _: APIInterface, + ___: string, + options: APIOptions, + __: any +) => Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; }>; export default removePermissionsFromRole; diff --git a/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.js b/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.js index 73209a3f4..cab2e0a91 100644 --- a/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.js +++ b/lib/build/recipe/dashboard/api/userroles/permissions/removePermissions.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../../userroles")); @@ -9,8 +11,7 @@ const error_1 = __importDefault(require("../../../../../error")); const removePermissionsFromRole = async (_, ___, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/removeUserRole.d.ts b/lib/build/recipe/dashboard/api/userroles/removeUserRole.d.ts index f771637d1..bdf782e49 100644 --- a/lib/build/recipe/dashboard/api/userroles/removeUserRole.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/removeUserRole.d.ts @@ -1,9 +1,17 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../types"; -declare const removeUserRole: (_: APIInterface, tenantId: string, options: APIOptions, __: any) => Promise<{ - status: "OK"; - didUserHaveRole: boolean; -} | { - status: "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; -}>; +declare const removeUserRole: ( + _: APIInterface, + tenantId: string, + options: APIOptions, + __: any +) => Promise< + | { + status: "OK"; + didUserHaveRole: boolean; + } + | { + status: "UNKNOWN_ROLE_ERROR" | "FEATURE_NOT_ENABLED_ERROR"; + } +>; export default removeUserRole; diff --git a/lib/build/recipe/dashboard/api/userroles/removeUserRole.js b/lib/build/recipe/dashboard/api/userroles/removeUserRole.js index 750534243..025c93fcd 100644 --- a/lib/build/recipe/dashboard/api/userroles/removeUserRole.js +++ b/lib/build/recipe/dashboard/api/userroles/removeUserRole.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../userroles")); @@ -9,8 +11,7 @@ const error_1 = __importDefault(require("../../../../error")); const removeUserRole = async (_, tenantId, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.d.ts b/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.d.ts index 7193f19c2..75663bcb0 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.d.ts @@ -1,9 +1,17 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../../types"; -declare const createRoleOrAddPermissions: (_: APIInterface, __: string, options: APIOptions, ___: any) => Promise<{ - status: "OK"; - createdNewRole: boolean; -} | { - status: "FEATURE_NOT_ENABLED_ERROR"; -}>; +declare const createRoleOrAddPermissions: ( + _: APIInterface, + __: string, + options: APIOptions, + ___: any +) => Promise< + | { + status: "OK"; + createdNewRole: boolean; + } + | { + status: "FEATURE_NOT_ENABLED_ERROR"; + } +>; export default createRoleOrAddPermissions; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.js b/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.js index f2f3f2310..1540fe51d 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.js +++ b/lib/build/recipe/dashboard/api/userroles/roles/createRoleOrAddPermissions.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../../userroles")); @@ -9,8 +11,7 @@ const error_1 = __importDefault(require("../../../../../error")); const createRoleOrAddPermissions = async (_, __, options, ___) => { try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.d.ts b/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.d.ts index 4dd5dc9af..14e5cf5b7 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.d.ts +++ b/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.d.ts @@ -1,9 +1,17 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../../../types"; -declare const deleteRole: (_: APIInterface, ___: string, options: APIOptions, __: any) => Promise<{ - status: "OK"; - didRoleExist: boolean; -} | { - status: "FEATURE_NOT_ENABLED_ERROR"; -}>; +declare const deleteRole: ( + _: APIInterface, + ___: string, + options: APIOptions, + __: any +) => Promise< + | { + status: "OK"; + didRoleExist: boolean; + } + | { + status: "FEATURE_NOT_ENABLED_ERROR"; + } +>; export default deleteRole; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.js b/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.js index ac282b88c..3814268fb 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.js +++ b/lib/build/recipe/dashboard/api/userroles/roles/deleteRole.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const userroles_1 = __importDefault(require("../../../../userroles")); @@ -9,8 +11,7 @@ const error_1 = __importDefault(require("../../../../../error")); const deleteRole = async (_, ___, options, __) => { try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/userroles/roles/getAllRoles.js b/lib/build/recipe/dashboard/api/userroles/roles/getAllRoles.js index c85556ed7..bce2b636d 100644 --- a/lib/build/recipe/dashboard/api/userroles/roles/getAllRoles.js +++ b/lib/build/recipe/dashboard/api/userroles/roles/getAllRoles.js @@ -1,15 +1,16 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const userroles_1 = __importDefault(require("../../../../userroles")); const recipe_1 = __importDefault(require("../../../../userroles/recipe")); const getAllRoles = async (_, __, ____) => { try { recipe_1.default.getInstanceOrThrowError(); - } - catch (_) { + } catch (_) { return { status: "FEATURE_NOT_ENABLED_ERROR", }; diff --git a/lib/build/recipe/dashboard/api/usersCountGet.d.ts b/lib/build/recipe/dashboard/api/usersCountGet.d.ts index da09c8db8..2f90538df 100644 --- a/lib/build/recipe/dashboard/api/usersCountGet.d.ts +++ b/lib/build/recipe/dashboard/api/usersCountGet.d.ts @@ -5,4 +5,9 @@ export declare type Response = { status: "OK"; count: number; }; -export default function usersCountGet(_: APIInterface, tenantId: string, __: APIOptions, userContext: UserContext): Promise; +export default function usersCountGet( + _: APIInterface, + tenantId: string, + __: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/dashboard/api/usersCountGet.js b/lib/build/recipe/dashboard/api/usersCountGet.js index d85a1e462..55757ab2d 100644 --- a/lib/build/recipe/dashboard/api/usersCountGet.js +++ b/lib/build/recipe/dashboard/api/usersCountGet.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_1 = __importDefault(require("../../../supertokens")); async function usersCountGet(_, tenantId, __, userContext) { diff --git a/lib/build/recipe/dashboard/api/usersGet.d.ts b/lib/build/recipe/dashboard/api/usersGet.d.ts index 5bfb9dd24..00834f077 100644 --- a/lib/build/recipe/dashboard/api/usersGet.d.ts +++ b/lib/build/recipe/dashboard/api/usersGet.d.ts @@ -6,6 +6,8 @@ export declare type Response = { users: UserWithFirstAndLastName[]; }; export default function usersGet(_: APIInterface, tenantId: string, options: APIOptions): Promise; -export declare function getSearchParamsFromURL(path: string): { +export declare function getSearchParamsFromURL( + path: string +): { [key: string]: string; }; diff --git a/lib/build/recipe/dashboard/api/usersGet.js b/lib/build/recipe/dashboard/api/usersGet.js index 00d8c6ff4..d843dabdd 100644 --- a/lib/build/recipe/dashboard/api/usersGet.js +++ b/lib/build/recipe/dashboard/api/usersGet.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSearchParamsFromURL = void 0; const error_1 = __importDefault(require("../../../error")); @@ -29,24 +31,24 @@ async function usersGet(_, tenantId, options) { } let paginationToken = options.req.getKeyValueFromQuery("paginationToken"); const query = getSearchParamsFromURL(options.req.getOriginalURL()); - let usersResponse = timeJoinedOrder === "DESC" - ? await __1.getUsersNewestFirst({ - tenantId, - query, - limit: parseInt(limit), - paginationToken, - }) - : await __1.getUsersOldestFirst({ - tenantId, - query, - limit: parseInt(limit), - paginationToken, - }); + let usersResponse = + timeJoinedOrder === "DESC" + ? await __1.getUsersNewestFirst({ + tenantId, + query, + limit: parseInt(limit), + paginationToken, + }) + : await __1.getUsersOldestFirst({ + tenantId, + query, + limit: parseInt(limit), + paginationToken, + }); // If the UserMetaData recipe has been initialised, fetch first and last name try { recipe_1.default.getInstanceOrThrowError(); - } - catch (e) { + } catch (e) { // Recipe has not been initialised, return without first name and last name return { status: "OK", @@ -58,18 +60,23 @@ async function usersGet(_, tenantId, options) { let metaDataFetchPromises = []; for (let i = 0; i < usersResponse.users.length; i++) { const userObj = usersResponse.users[i].toJson(); - metaDataFetchPromises.push(() => new Promise(async (resolve, reject) => { - try { - const userMetaDataResponse = await usermetadata_1.default.getUserMetadata(userObj.id); - const { first_name, last_name } = userMetaDataResponse.metadata; - updatedUsersArray[i] = Object.assign(Object.assign({}, userObj), { firstName: first_name, lastName: last_name }); - resolve(true); - } - catch (e) { - // Something went wrong when fetching user meta data - reject(e); - } - })); + metaDataFetchPromises.push( + () => + new Promise(async (resolve, reject) => { + try { + const userMetaDataResponse = await usermetadata_1.default.getUserMetadata(userObj.id); + const { first_name, last_name } = userMetaDataResponse.metadata; + updatedUsersArray[i] = Object.assign(Object.assign({}, userObj), { + firstName: first_name, + lastName: last_name, + }); + resolve(true); + } catch (e) { + // Something went wrong when fetching user meta data + reject(e); + } + }) + ); } let promiseArrayStartPosition = 0; let batchSize = 5; diff --git a/lib/build/recipe/dashboard/api/validateKey.js b/lib/build/recipe/dashboard/api/validateKey.js index b8655fc0d..4e1df7946 100644 --- a/lib/build/recipe/dashboard/api/validateKey.js +++ b/lib/build/recipe/dashboard/api/validateKey.js @@ -21,8 +21,7 @@ async function validateKey(_, options, userContext) { options.res.sendJSONResponse({ status: "OK", }); - } - else { + } else { utils_1.sendUnauthorisedAccess(options.res); } return true; diff --git a/lib/build/recipe/dashboard/error.js b/lib/build/recipe/dashboard/error.js index cd2f594ea..469d8e656 100644 --- a/lib/build/recipe/dashboard/error.js +++ b/lib/build/recipe/dashboard/error.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2022, VRAI Labs and/or its affiliates. All rights reserved. * diff --git a/lib/build/recipe/dashboard/index.js b/lib/build/recipe/dashboard/index.js index 772d2760c..7ad60228f 100644 --- a/lib/build/recipe/dashboard/index.js +++ b/lib/build/recipe/dashboard/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.init = void 0; /* Copyright (c) 2022, VRAI Labs and/or its affiliates. All rights reserved. @@ -19,8 +21,7 @@ exports.init = void 0; * under the License. */ const recipe_1 = __importDefault(require("./recipe")); -class Wrapper { -} +class Wrapper {} exports.default = Wrapper; Wrapper.init = recipe_1.default.init; exports.init = Wrapper.init; diff --git a/lib/build/recipe/dashboard/recipe.d.ts b/lib/build/recipe/dashboard/recipe.d.ts index fec1cb7b1..8fbe6583e 100644 --- a/lib/build/recipe/dashboard/recipe.d.ts +++ b/lib/build/recipe/dashboard/recipe.d.ts @@ -17,7 +17,15 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, __: NormalisedURLPath, ___: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + __: NormalisedURLPath, + ___: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: error, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is error; diff --git a/lib/build/recipe/dashboard/recipe.js b/lib/build/recipe/dashboard/recipe.js index 647b8c63b..5f68a6add 100644 --- a/lib/build/recipe/dashboard/recipe.js +++ b/lib/build/recipe/dashboard/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const recipeModule_1 = __importDefault(require("../../recipeModule")); @@ -81,259 +83,345 @@ class Recipe extends recipeModule_1.default { }, { id: constants_1.DASHBOARD_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase("/roles")), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase("/roles") + ), disabled: false, method: "get", }, { id: constants_1.DASHBOARD_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase("/tenants")), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase("/tenants") + ), disabled: false, method: "get", }, { id: constants_1.SIGN_IN_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.SIGN_IN_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.SIGN_IN_API) + ), disabled: false, method: "post", }, { id: constants_1.VALIDATE_KEY_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.VALIDATE_KEY_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.VALIDATE_KEY_API) + ), disabled: false, method: "post", }, { id: constants_1.SIGN_OUT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.SIGN_OUT_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.SIGN_OUT_API) + ), disabled: false, method: "post", }, { id: constants_1.USERS_LIST_GET_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERS_LIST_GET_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERS_LIST_GET_API) + ), disabled: false, method: "get", }, { id: constants_1.USERS_COUNT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERS_COUNT_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERS_COUNT_API) + ), disabled: false, method: "get", }, { id: constants_1.USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_API) + ), disabled: false, method: "get", }, { id: constants_1.USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_API) + ), disabled: false, method: "post", }, { id: constants_1.USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_API) + ), disabled: false, method: "put", }, { id: constants_1.USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_API) + ), disabled: false, method: "delete", }, { id: constants_1.USER_EMAIL_VERIFY_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_API) + ), disabled: false, method: "get", }, { id: constants_1.USER_EMAIL_VERIFY_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_API) + ), disabled: false, method: "put", }, { id: constants_1.USER_METADATA_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_METADATA_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_METADATA_API) + ), disabled: false, method: "get", }, { id: constants_1.USER_METADATA_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_METADATA_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_METADATA_API) + ), disabled: false, method: "put", }, { id: constants_1.USER_SESSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_SESSIONS_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_SESSIONS_API) + ), disabled: false, method: "get", }, { id: constants_1.USER_SESSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_SESSIONS_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_SESSIONS_API) + ), disabled: false, method: "post", }, { id: constants_1.USER_PASSWORD_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_PASSWORD_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_PASSWORD_API) + ), disabled: false, method: "put", }, { id: constants_1.USER_EMAIL_VERIFY_TOKEN_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_TOKEN_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USER_EMAIL_VERIFY_TOKEN_API) + ), disabled: false, method: "post", }, { id: constants_1.SEARCH_TAGS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.SEARCH_TAGS_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.SEARCH_TAGS_API) + ), disabled: false, method: "get", }, { id: constants_1.DASHBOARD_ANALYTICS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.DASHBOARD_ANALYTICS_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.DASHBOARD_ANALYTICS_API) + ), disabled: false, method: "post", }, { id: constants_1.UNLINK_USER, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.UNLINK_USER)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.UNLINK_USER) + ), disabled: false, method: "get", }, { id: constants_1.USERROLES_LIST_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_LIST_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_LIST_API) + ), disabled: false, method: "get", }, { id: constants_1.USERROLES_ROLE_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_ROLE_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_ROLE_API) + ), disabled: false, method: "put", }, { id: constants_1.USERROLES_ROLE_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_ROLE_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_ROLE_API) + ), disabled: false, method: "delete", }, { id: constants_1.USERROLES_PERMISSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_PERMISSIONS_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_PERMISSIONS_API) + ), disabled: false, method: "get", }, { id: constants_1.USERROLES_PERMISSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_PERMISSIONS_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_PERMISSIONS_API) + ), disabled: false, method: "put", }, { id: constants_1.USERROLES_REMOVE_PERMISSIONS_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_REMOVE_PERMISSIONS_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_REMOVE_PERMISSIONS_API) + ), disabled: false, method: "put", }, { id: constants_1.USERROLES_USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API) + ), disabled: false, method: "put", }, { id: constants_1.USERROLES_USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API) + ), disabled: false, method: "get", }, { id: constants_1.USERROLES_USER_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.USERROLES_USER_API) + ), disabled: false, method: "delete", }, { id: constants_1.CREATE_EMAIL_PASSWORD_USER, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.CREATE_EMAIL_PASSWORD_USER)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.CREATE_EMAIL_PASSWORD_USER) + ), disabled: false, method: "post", }, { id: constants_1.CREATE_PASSWORDLESS_USER, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.CREATE_PASSWORDLESS_USER)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.CREATE_PASSWORDLESS_USER) + ), disabled: false, method: "post", }, { id: constants_1.LIST_TENANTS_WITH_LOGIN_METHODS, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.LIST_TENANTS_WITH_LOGIN_METHODS)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.LIST_TENANTS_WITH_LOGIN_METHODS) + ), disabled: false, method: "get", }, { id: constants_1.TENANT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API) + ), disabled: false, method: "get", }, { id: constants_1.TENANT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API) + ), disabled: false, method: "delete", }, { id: constants_1.TENANT_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.TENANT_API) + ), disabled: false, method: "post", }, { id: constants_1.UPDATE_TENANT_FIRST_FACTOR_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_FIRST_FACTOR_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_FIRST_FACTOR_API) + ), disabled: false, method: "put", }, { id: constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API) + ), disabled: false, method: "put", }, { id: constants_1.UPDATE_TENANT_CORE_CONFIG_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_CORE_CONFIG_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.UPDATE_TENANT_CORE_CONFIG_API) + ), disabled: false, method: "put", }, { id: constants_1.TENANT_THIRD_PARTY_CONFIG_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API) + ), disabled: false, method: "get", }, { id: constants_1.TENANT_THIRD_PARTY_CONFIG_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API) + ), disabled: false, method: "put", }, { id: constants_1.TENANT_THIRD_PARTY_CONFIG_API, - pathWithoutApiBasePath: new normalisedURLPath_1.default(utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API)), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + utils_1.getApiPathWithDashboardBase(constants_1.TENANT_THIRD_PARTY_CONFIG_API) + ), disabled: false, method: "delete", }, @@ -363,11 +451,9 @@ class Recipe extends recipeModule_1.default { let apiFunction; if (id === constants_1.USERS_LIST_GET_API) { apiFunction = usersGet_1.default; - } - else if (id === constants_1.USERS_COUNT_API) { + } else if (id === constants_1.USERS_COUNT_API) { apiFunction = usersCountGet_1.default; - } - else if (id === constants_1.USER_API) { + } else if (id === constants_1.USER_API) { if (req.getMethod() === "get") { apiFunction = userGet_1.userGet; } @@ -377,69 +463,55 @@ class Recipe extends recipeModule_1.default { if (req.getMethod() === "put") { apiFunction = userPut_1.userPut; } - } - else if (id === constants_1.USER_EMAIL_VERIFY_API) { + } else if (id === constants_1.USER_EMAIL_VERIFY_API) { if (req.getMethod() === "get") { apiFunction = userEmailVerifyGet_1.userEmailVerifyGet; } if (req.getMethod() === "put") { apiFunction = userEmailVerifyPut_1.userEmailVerifyPut; } - } - else if (id === constants_1.USER_METADATA_API) { + } else if (id === constants_1.USER_METADATA_API) { if (req.getMethod() === "get") { apiFunction = userMetadataGet_1.userMetaDataGet; } if (req.getMethod() === "put") { apiFunction = userMetadataPut_1.userMetadataPut; } - } - else if (id === constants_1.USER_SESSIONS_API) { + } else if (id === constants_1.USER_SESSIONS_API) { if (req.getMethod() === "get") { apiFunction = userSessionsGet_1.userSessionsGet; } if (req.getMethod() === "post") { apiFunction = userSessionsPost_1.userSessionsPost; } - } - else if (id === constants_1.USER_PASSWORD_API) { + } else if (id === constants_1.USER_PASSWORD_API) { apiFunction = userPasswordPut_1.userPasswordPut; - } - else if (id === constants_1.USER_EMAIL_VERIFY_TOKEN_API) { + } else if (id === constants_1.USER_EMAIL_VERIFY_TOKEN_API) { apiFunction = userEmailVerifyTokenPost_1.userEmailVerifyTokenPost; - } - else if (id === constants_1.SEARCH_TAGS_API) { + } else if (id === constants_1.SEARCH_TAGS_API) { apiFunction = tagsGet_1.getSearchTags; - } - else if (id === constants_1.SIGN_OUT_API) { + } else if (id === constants_1.SIGN_OUT_API) { apiFunction = signOut_1.default; - } - else if (id === constants_1.DASHBOARD_ANALYTICS_API && req.getMethod() === "post") { + } else if (id === constants_1.DASHBOARD_ANALYTICS_API && req.getMethod() === "post") { apiFunction = analytics_1.default; - } - else if (id === constants_1.UNLINK_USER) { + } else if (id === constants_1.UNLINK_USER) { apiFunction = userUnlinkGet_1.userUnlink; - } - else if (id === constants_1.USERROLES_LIST_API) { + } else if (id === constants_1.USERROLES_LIST_API) { apiFunction = getAllRoles_1.default; - } - else if (id === constants_1.USERROLES_ROLE_API) { + } else if (id === constants_1.USERROLES_ROLE_API) { if (req.getMethod() === "put") { apiFunction = createRoleOrAddPermissions_1.default; } if (req.getMethod() === "delete") { apiFunction = deleteRole_1.default; } - } - else if (id === constants_1.USERROLES_PERMISSIONS_API) { + } else if (id === constants_1.USERROLES_PERMISSIONS_API) { if (req.getMethod() === "get") { apiFunction = getPermissionsForRole_1.default; } - } - else if (id === constants_1.USERROLES_REMOVE_PERMISSIONS_API) { + } else if (id === constants_1.USERROLES_REMOVE_PERMISSIONS_API) { apiFunction = removePermissions_1.default; - } - else if (id === constants_1.USERROLES_USER_API) { + } else if (id === constants_1.USERROLES_USER_API) { if (req.getMethod() === "put") { apiFunction = addRoleToUser_1.default; } @@ -449,23 +521,19 @@ class Recipe extends recipeModule_1.default { if (req.getMethod() === "delete") { apiFunction = removeUserRole_1.default; } - } - else if (id === constants_1.CREATE_EMAIL_PASSWORD_USER) { + } else if (id === constants_1.CREATE_EMAIL_PASSWORD_USER) { if (req.getMethod() === "post") { apiFunction = emailpasswordUser_1.createEmailPasswordUser; } - } - else if (id === constants_1.CREATE_PASSWORDLESS_USER) { + } else if (id === constants_1.CREATE_PASSWORDLESS_USER) { if (req.getMethod() === "post") { apiFunction = passwordlessUser_1.createPasswordlessUser; } - } - else if (id === constants_1.LIST_TENANTS_WITH_LOGIN_METHODS) { + } else if (id === constants_1.LIST_TENANTS_WITH_LOGIN_METHODS) { if (req.getMethod() === "get") { apiFunction = listAllTenantsWithLoginMethods_1.default; } - } - else if (id === constants_1.TENANT_API) { + } else if (id === constants_1.TENANT_API) { if (req.getMethod() === "post") { apiFunction = createTenant_1.default; } @@ -475,17 +543,13 @@ class Recipe extends recipeModule_1.default { if (req.getMethod() === "delete") { apiFunction = deleteTenant_1.default; } - } - else if (id === constants_1.UPDATE_TENANT_FIRST_FACTOR_API) { + } else if (id === constants_1.UPDATE_TENANT_FIRST_FACTOR_API) { apiFunction = updateTenantFirstFactor_1.default; - } - else if (id === constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API) { + } else if (id === constants_1.UPDATE_TENANT_REQUIRED_SECONDARY_FACTOR_API) { apiFunction = updateTenantSecondaryFactor_1.default; - } - else if (id === constants_1.UPDATE_TENANT_CORE_CONFIG_API) { + } else if (id === constants_1.UPDATE_TENANT_CORE_CONFIG_API) { apiFunction = updateTenantCoreConfig_1.default; - } - else if (id === constants_1.TENANT_THIRD_PARTY_CONFIG_API) { + } else if (id === constants_1.TENANT_THIRD_PARTY_CONFIG_API) { if (req.getMethod() === "get") { apiFunction = getThirdPartyConfig_1.default; } @@ -533,8 +597,7 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } - else { + } else { throw new Error("Dashboard recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/dashboard/recipeImplementation.js b/lib/build/recipe/dashboard/recipeImplementation.js index 4168d351b..9ad3fa57c 100644 --- a/lib/build/recipe/dashboard/recipeImplementation.js +++ b/lib/build/recipe/dashboard/recipeImplementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("./error")); const logger_1 = require("../../logger"); @@ -36,10 +38,17 @@ function getRecipeImplementation() { if (!input.config.apiKey) { // make the check for the API endpoint here with querier let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); - const authHeaderValue = (_a = input.req.getHeaderValue("authorization")) === null || _a === void 0 ? void 0 : _a.split(" ")[1]; - const sessionVerificationResponse = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/dashboard/session/verify"), { - sessionId: authHeaderValue, - }, input.userContext); + const authHeaderValue = + (_a = input.req.getHeaderValue("authorization")) === null || _a === void 0 + ? void 0 + : _a.split(" ")[1]; + const sessionVerificationResponse = await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/dashboard/session/verify"), + { + sessionId: authHeaderValue, + }, + input.userContext + ); if (sessionVerificationResponse.status !== "OK") { return false; } @@ -58,16 +67,22 @@ function getRecipeImplementation() { return true; } if (admins.length === 0) { - logger_1.logDebugMessage("User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin"); + logger_1.logDebugMessage( + "User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin" + ); throw new error_1.default(); } const userEmail = sessionVerificationResponse.email; if (userEmail === undefined || typeof userEmail !== "string") { - logger_1.logDebugMessage("User Dashboard: Returning Unauthorised because no email was returned from the core. Should never come here"); + logger_1.logDebugMessage( + "User Dashboard: Returning Unauthorised because no email was returned from the core. Should never come here" + ); return false; } if (!admins.includes(userEmail)) { - logger_1.logDebugMessage("User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin"); + logger_1.logDebugMessage( + "User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin" + ); throw new error_1.default(); } } diff --git a/lib/build/recipe/dashboard/types.d.ts b/lib/build/recipe/dashboard/types.d.ts index 792778876..9f8721a34 100644 --- a/lib/build/recipe/dashboard/types.d.ts +++ b/lib/build/recipe/dashboard/types.d.ts @@ -6,7 +6,10 @@ export declare type TypeInput = { apiKey?: string; admins?: string[]; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -15,14 +18,15 @@ export declare type TypeNormalisedInput = { admins?: string[]; authMode: AuthMode; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type RecipeInterface = { - getDashboardBundleLocation(input: { - userContext: UserContext; - }): Promise; + getDashboardBundleLocation(input: { userContext: UserContext }): Promise; shouldAllowAccess(input: { req: BaseRequest; config: TypeNormalisedInput; @@ -39,12 +43,14 @@ export declare type APIOptions = { appInfo: NormalisedAppinfo; }; export declare type APIInterface = { - dashboardGET: undefined | ((input: { - options: APIOptions; - userContext: UserContext; - }) => Promise); + dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise); }; -export declare type APIFunction = (apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext) => Promise; +export declare type APIFunction = ( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +) => Promise; export declare type RecipeIdForUser = "emailpassword" | "thirdparty" | "passwordless"; export declare type AuthMode = "api-key" | "email-password"; export declare type UserWithFirstAndLastName = User & { diff --git a/lib/build/recipe/dashboard/utils.d.ts b/lib/build/recipe/dashboard/utils.d.ts index 3302c7ae6..f742b1cd6 100644 --- a/lib/build/recipe/dashboard/utils.d.ts +++ b/lib/build/recipe/dashboard/utils.d.ts @@ -6,7 +6,11 @@ import { UserContext } from "../../types"; export declare function validateAndNormaliseUserInput(config?: TypeInput): TypeNormalisedInput; export declare function sendUnauthorisedAccess(res: BaseResponse): void; export declare function isValidRecipeId(recipeId: string): recipeId is RecipeIdForUser; -export declare function getUserForRecipeId(recipeUserId: RecipeUserId, recipeId: string, userContext: UserContext): Promise<{ +export declare function getUserForRecipeId( + recipeUserId: RecipeUserId, + recipeId: string, + userContext: UserContext +): Promise<{ user: UserWithFirstAndLastName | undefined; recipe: "emailpassword" | "thirdparty" | "passwordless" | undefined; }>; diff --git a/lib/build/recipe/dashboard/utils.js b/lib/build/recipe/dashboard/utils.js index e20575b64..9960c120c 100644 --- a/lib/build/recipe/dashboard/utils.js +++ b/lib/build/recipe/dashboard/utils.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getApiPathWithDashboardBase = exports.validateApiKey = exports.getUserForRecipeId = exports.isValidRecipeId = exports.sendUnauthorisedAccess = exports.validateAndNormaliseUserInput = void 0; const utils_1 = require("../../utils"); @@ -26,15 +28,28 @@ const recipe_3 = __importDefault(require("../thirdparty/recipe")); const recipe_4 = __importDefault(require("../passwordless/recipe")); const logger_1 = require("../../logger"); function validateAndNormaliseUserInput(config) { - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, (config === undefined ? {} : config.override)); - if ((config === null || config === void 0 ? void 0 : config.apiKey) !== undefined && (config === null || config === void 0 ? void 0 : config.admins) !== undefined) { + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === undefined ? {} : config.override + ); + if ( + (config === null || config === void 0 ? void 0 : config.apiKey) !== undefined && + (config === null || config === void 0 ? void 0 : config.admins) !== undefined + ) { logger_1.logDebugMessage("User Dashboard: Providing 'admins' has no effect when using an apiKey."); } let admins; if ((config === null || config === void 0 ? void 0 : config.admins) !== undefined) { admins = config.admins.map((email) => utils_1.normaliseEmail(email)); } - return Object.assign(Object.assign({}, config), { override, authMode: config !== undefined && config.apiKey ? "api-key" : "email-password", admins }); + return Object.assign(Object.assign({}, config), { + override, + authMode: config !== undefined && config.apiKey ? "api-key" : "email-password", + admins, + }); } exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; function sendUnauthorisedAccess(res) { @@ -69,7 +84,9 @@ async function _getUserForRecipeId(recipeUserId, recipeId, userContext) { recipe: undefined, }; } - const loginMethod = user.loginMethods.find((m) => m.recipeId === recipeId && m.recipeUserId.getAsString() === recipeUserId.getAsString()); + const loginMethod = user.loginMethods.find( + (m) => m.recipeId === recipeId && m.recipeUserId.getAsString() === recipeUserId.getAsString() + ); if (loginMethod === undefined) { return { user: undefined, @@ -81,26 +98,21 @@ async function _getUserForRecipeId(recipeUserId, recipeId, userContext) { // we detect if this recipe has been init or not.. recipe_2.default.getInstanceOrThrowError(); recipe = "emailpassword"; - } - catch (e) { + } catch (e) { // No - op } - } - else if (recipeId === recipe_3.default.RECIPE_ID) { + } else if (recipeId === recipe_3.default.RECIPE_ID) { try { recipe_3.default.getInstanceOrThrowError(); recipe = "thirdparty"; - } - catch (e) { + } catch (e) { // No - op } - } - else if (recipeId === recipe_4.default.RECIPE_ID) { + } else if (recipeId === recipe_4.default.RECIPE_ID) { try { recipe_4.default.getInstanceOrThrowError(); recipe = "passwordless"; - } - catch (e) { + } catch (e) { // No - op } } @@ -112,7 +124,8 @@ async function _getUserForRecipeId(recipeUserId, recipeId, userContext) { async function validateApiKey(input) { let apiKeyHeaderValue = input.req.getHeaderValue("authorization"); // We receieve the api key as `Bearer API_KEY`, this retrieves just the key - apiKeyHeaderValue = apiKeyHeaderValue === null || apiKeyHeaderValue === void 0 ? void 0 : apiKeyHeaderValue.split(" ")[1]; + apiKeyHeaderValue = + apiKeyHeaderValue === null || apiKeyHeaderValue === void 0 ? void 0 : apiKeyHeaderValue.split(" ")[1]; if (apiKeyHeaderValue === undefined) { return false; } diff --git a/lib/build/recipe/emailpassword/api/emailExists.d.ts b/lib/build/recipe/emailpassword/api/emailExists.d.ts index 478175dec..2f55b6d3b 100644 --- a/lib/build/recipe/emailpassword/api/emailExists.d.ts +++ b/lib/build/recipe/emailpassword/api/emailExists.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function emailExists(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function emailExists( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/emailpassword/api/emailExists.js b/lib/build/recipe/emailpassword/api/emailExists.js index b79014fa4..b76ac08c7 100644 --- a/lib/build/recipe/emailpassword/api/emailExists.js +++ b/lib/build/recipe/emailpassword/api/emailExists.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/emailpassword/api/generatePasswordResetToken.d.ts b/lib/build/recipe/emailpassword/api/generatePasswordResetToken.d.ts index 9ce0acc4b..146d43586 100644 --- a/lib/build/recipe/emailpassword/api/generatePasswordResetToken.d.ts +++ b/lib/build/recipe/emailpassword/api/generatePasswordResetToken.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function generatePasswordResetToken(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function generatePasswordResetToken( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/emailpassword/api/generatePasswordResetToken.js b/lib/build/recipe/emailpassword/api/generatePasswordResetToken.js index 20766cadb..f4e469a9d 100644 --- a/lib/build/recipe/emailpassword/api/generatePasswordResetToken.js +++ b/lib/build/recipe/emailpassword/api/generatePasswordResetToken.js @@ -23,7 +23,12 @@ async function generatePasswordResetToken(apiImplementation, tenantId, options, } const requestBody = await options.req.getJSONBody(); // step 1 - let formFields = await utils_2.validateFormFieldsOrThrowError(options.config.resetPasswordUsingTokenFeature.formFieldsForGenerateTokenForm, requestBody.formFields, tenantId, userContext); + let formFields = await utils_2.validateFormFieldsOrThrowError( + options.config.resetPasswordUsingTokenFeature.formFieldsForGenerateTokenForm, + requestBody.formFields, + tenantId, + userContext + ); let result = await apiImplementation.generatePasswordResetTokenPOST({ formFields, tenantId, diff --git a/lib/build/recipe/emailpassword/api/implementation.js b/lib/build/recipe/emailpassword/api/implementation.js index 9e877002d..501fe1f30 100644 --- a/lib/build/recipe/emailpassword/api/implementation.js +++ b/lib/build/recipe/emailpassword/api/implementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("../../../logger"); const __1 = require("../../../"); @@ -13,7 +15,7 @@ const authUtils_1 = require("../../../authUtils"); const utils_2 = require("../../thirdparty/utils"); function getAPIImplementation() { return { - emailExistsGET: async function ({ email, tenantId, userContext, }) { + emailExistsGET: async function ({ email, tenantId, userContext }) { // even if the above returns true, we still need to check if there // exists an email password user with the same email cause the function // above does not check for that. @@ -25,22 +27,27 @@ function getAPIImplementation() { doUnionOfAccountInfo: false, userContext, }); - let emailPasswordUserExists = users.find((u) => { - return (u.loginMethods.find((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)) !== - undefined); - }) !== undefined; + let emailPasswordUserExists = + users.find((u) => { + return ( + u.loginMethods.find((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)) !== + undefined + ); + }) !== undefined; return { status: "OK", exists: emailPasswordUserExists, }; }, - generatePasswordResetTokenPOST: async function ({ formFields, tenantId, options, userContext, }) { + generatePasswordResetTokenPOST: async function ({ formFields, tenantId, options, userContext }) { // NOTE: Check for email being a non-string value. This check will likely // never evaluate to `true` as there is an upper-level check for the type // in validation but kept here to be safe. const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; if (typeof emailAsUnknown !== "string") - throw new Error("Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"); + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); const email = emailAsUnknown; // this function will be reused in different parts of the flow below.. async function generateAndSendPasswordResetToken(primaryUserId, recipeUserId) { @@ -52,7 +59,11 @@ function getAPIImplementation() { userContext, }); if (response.status === "UNKNOWN_USER_ID_ERROR") { - logger_1.logDebugMessage(`Password reset email not sent, unknown user id: ${recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString()}`); + logger_1.logDebugMessage( + `Password reset email not sent, unknown user id: ${ + recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() + }` + ); return { status: "OK", }; @@ -95,7 +106,9 @@ function getAPIImplementation() { // for later use. let emailPasswordAccount = undefined; for (let i = 0; i < users.length; i++) { - let emailPasswordAccountTmp = users[i].loginMethods.find((l) => l.recipeId === "emailpassword" && l.hasSameEmailAs(email)); + let emailPasswordAccountTmp = users[i].loginMethods.find( + (l) => l.recipeId === "emailpassword" && l.hasSameEmailAs(email) + ); if (emailPasswordAccountTmp !== undefined) { emailPasswordAccount = emailPasswordAccountTmp; break; @@ -103,23 +116,43 @@ function getAPIImplementation() { } // we find the primary user ID from the user's list for later use. let linkingCandidate = users.find((u) => u.isPrimaryUser); - logger_1.logDebugMessage("generatePasswordResetTokenPOST: primary linking candidate: " + (linkingCandidate === null || linkingCandidate === void 0 ? void 0 : linkingCandidate.id)); + logger_1.logDebugMessage( + "generatePasswordResetTokenPOST: primary linking candidate: " + + (linkingCandidate === null || linkingCandidate === void 0 ? void 0 : linkingCandidate.id) + ); logger_1.logDebugMessage("generatePasswordResetTokenPOST: linking candidate count " + users.length); // If there is no existing primary user and there is a single option to link // we see if that user can become primary (and a candidate for linking) if (linkingCandidate === undefined && users.length > 0) { // If the only user that exists with this email is a non-primary emailpassword user, then we can just let them reset their password, because: // we are not going to link anything and there is no risk of account takeover. - if (users.length === 1 && + if ( + users.length === 1 && users[0].loginMethods[0].recipeUserId.getAsString() === - (emailPasswordAccount === null || emailPasswordAccount === void 0 ? void 0 : emailPasswordAccount.recipeUserId.getAsString())) { - return await generateAndSendPasswordResetToken(emailPasswordAccount.recipeUserId.getAsString(), emailPasswordAccount.recipeUserId); + (emailPasswordAccount === null || emailPasswordAccount === void 0 + ? void 0 + : emailPasswordAccount.recipeUserId.getAsString()) + ) { + return await generateAndSendPasswordResetToken( + emailPasswordAccount.recipeUserId.getAsString(), + emailPasswordAccount.recipeUserId + ); } const oldestUser = users.sort((a, b) => a.timeJoined - b.timeJoined)[0]; - logger_1.logDebugMessage(`generatePasswordResetTokenPOST: oldest recipe level-linking candidate: ${oldestUser.id} (w/ ${oldestUser.loginMethods[0].verified ? "verified" : "unverified"} email)`); + logger_1.logDebugMessage( + `generatePasswordResetTokenPOST: oldest recipe level-linking candidate: ${oldestUser.id} (w/ ${ + oldestUser.loginMethods[0].verified ? "verified" : "unverified" + } email)` + ); // Otherwise, we check if the user can become primary. - const shouldBecomePrimaryUser = await recipe_1.default.getInstance().shouldBecomePrimaryUser(oldestUser, tenantId, undefined, userContext); - logger_1.logDebugMessage(`generatePasswordResetTokenPOST: recipe level-linking candidate ${shouldBecomePrimaryUser ? "can" : "can not"} become primary`); + const shouldBecomePrimaryUser = await recipe_1.default + .getInstance() + .shouldBecomePrimaryUser(oldestUser, tenantId, undefined, userContext); + logger_1.logDebugMessage( + `generatePasswordResetTokenPOST: recipe level-linking candidate ${ + shouldBecomePrimaryUser ? "can" : "can not" + } become primary` + ); if (shouldBecomePrimaryUser) { linkingCandidate = oldestUser; } @@ -133,7 +166,10 @@ function getAPIImplementation() { status: "OK", }; } - return await generateAndSendPasswordResetToken(emailPasswordAccount.recipeUserId.getAsString(), emailPasswordAccount.recipeUserId); + return await generateAndSendPasswordResetToken( + emailPasswordAccount.recipeUserId.getAsString(), + emailPasswordAccount.recipeUserId + ); } /* This security measure helps prevent the following attack: @@ -157,48 +193,63 @@ function getAPIImplementation() { // First we check if there is any login method in which the input email is verified. // If that is the case, then it's proven that the user owns the email and we can // trust linking of the email password account. - let emailVerified = linkingCandidate.loginMethods.find((lm) => { - return lm.hasSameEmailAs(email) && lm.verified; - }) !== undefined; + let emailVerified = + linkingCandidate.loginMethods.find((lm) => { + return lm.hasSameEmailAs(email) && lm.verified; + }) !== undefined; // then, we check if the primary user has any other email / phone number // associated with this account - and if it does, then it means that // there is a risk of account takeover, so we do not allow the token to be generated - let hasOtherEmailOrPhone = linkingCandidate.loginMethods.find((lm) => { - // we do the extra undefined check below cause - // hasSameEmailAs returns false if the lm.email is undefined, and - // we want to check that the email is different as opposed to email - // not existing in lm. - return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; - }) !== undefined; + let hasOtherEmailOrPhone = + linkingCandidate.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + }) !== undefined; // If we allow this to pass, then: // 1. the if (!emailVerified && hasOtherEmailOrPhone) { return { status: "PASSWORD_RESET_NOT_ALLOWED", - reason: "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + reason: + "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", }; } if (linkingCandidate.isPrimaryUser && emailPasswordAccount !== undefined) { // If a primary user has the input email as verified or has no other emails then it is always allowed to reset their own password: // - there is no risk of account takeover, because they have verified this email or haven't linked it to anything else (checked above this block) // - there will be no linking as a result of this action, so we do not need to check for linking (checked here by seeing that the two accounts are already linked) - let areTheTwoAccountsLinked = linkingCandidate.loginMethods.find((lm) => { - return lm.recipeUserId.getAsString() === emailPasswordAccount.recipeUserId.getAsString(); - }) !== undefined; + let areTheTwoAccountsLinked = + linkingCandidate.loginMethods.find((lm) => { + return lm.recipeUserId.getAsString() === emailPasswordAccount.recipeUserId.getAsString(); + }) !== undefined; if (areTheTwoAccountsLinked) { - return await generateAndSendPasswordResetToken(linkingCandidate.id, emailPasswordAccount.recipeUserId); + return await generateAndSendPasswordResetToken( + linkingCandidate.id, + emailPasswordAccount.recipeUserId + ); } } // Here we know that the two accounts are NOT linked. We now need to check for an // extra security measure here to make sure that the input email in the primary user // is verified, and if not, we need to make sure that there is no other email / phone number // associated with the primary user account. If there is, then we do not proceed. - let shouldDoAccountLinkingResponse = await recipe_1.default.getInstance().config.shouldDoAutomaticAccountLinking(emailPasswordAccount !== undefined - ? emailPasswordAccount - : { - recipeId: "emailpassword", - email, - }, linkingCandidate, undefined, tenantId, userContext); + let shouldDoAccountLinkingResponse = await recipe_1.default + .getInstance() + .config.shouldDoAutomaticAccountLinking( + emailPasswordAccount !== undefined + ? emailPasswordAccount + : { + recipeId: "emailpassword", + email, + }, + linkingCandidate, + undefined, + tenantId, + userContext + ); // Now we need to check that if there exists any email password user at all // for the input email. If not, then it implies that when the token is consumed, // then we will create a new user - so we should only generate the token if @@ -212,7 +263,9 @@ function getAPIImplementation() { // code consume cannot be linked to the primary user - therefore, we should // not generate a password reset token if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { - logger_1.logDebugMessage(`Password reset email not sent, since email password user didn't exist, and account linking not enabled`); + logger_1.logDebugMessage( + `Password reset email not sent, since email password user didn't exist, and account linking not enabled` + ); return { status: "OK", }; @@ -232,9 +285,10 @@ function getAPIImplementation() { // we will be creating a new email password account when the token // is consumed and linking it to this primary user. return await generateAndSendPasswordResetToken(linkingCandidate.id, undefined); - } - else { - logger_1.logDebugMessage(`Password reset email not sent, isSignUpAllowed returned false for email: ${email}`); + } else { + logger_1.logDebugMessage( + `Password reset email not sent, isSignUpAllowed returned false for email: ${email}` + ); return { status: "OK", }; @@ -245,21 +299,26 @@ function getAPIImplementation() { // here we will go ahead with the token generation cause // even when the token is consumed, we will not be linking the accounts // so no need to check for anything - return await generateAndSendPasswordResetToken(emailPasswordAccount.recipeUserId.getAsString(), emailPasswordAccount.recipeUserId); + return await generateAndSendPasswordResetToken( + emailPasswordAccount.recipeUserId.getAsString(), + emailPasswordAccount.recipeUserId + ); } // Here we accounted for both `shouldRequireVerification` by the above checks (where we return ERR_CODE_001) return await generateAndSendPasswordResetToken(linkingCandidate.id, emailPasswordAccount.recipeUserId); }, - passwordResetPOST: async function ({ formFields, token, tenantId, options, userContext, }) { + passwordResetPOST: async function ({ formFields, token, tenantId, options, userContext }) { async function markEmailAsVerified(recipeUserId, email) { const emailVerificationInstance = recipe_2.default.getInstance(); if (emailVerificationInstance) { - const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken({ - tenantId, - recipeUserId, - email, - userContext, - }); + const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( + { + tenantId, + recipeUserId, + email, + userContext, + } + ); if (tokenResponse.status === "OK") { await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ tenantId, @@ -281,24 +340,23 @@ function getAPIImplementation() { password: newPassword, userContext, }); - if (updateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR" || - updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { + if ( + updateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR" || + updateResponse.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR" + ) { throw new Error("This should never come here because we are not updating the email"); - } - else if (updateResponse.status === "UNKNOWN_USER_ID_ERROR") { + } else if (updateResponse.status === "UNKNOWN_USER_ID_ERROR") { // This should happen only cause of a race condition where the user // might be deleted before token creation and consumption. return { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", }; - } - else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { + } else if (updateResponse.status === "PASSWORD_POLICY_VIOLATED_ERROR") { return { status: "PASSWORD_POLICY_VIOLATED_ERROR", failureReason: updateResponse.failureReason, }; - } - else { + } else { // status: "OK" // If the update was successful, we try to mark the email as verified. // We do this because we assume that the password reset token was delivered by email (and to the appropriate email address) @@ -310,7 +368,10 @@ function getAPIImplementation() { // If we verified (and linked) the existing user with the original password, User M would get access to the current user and any linked users. await markEmailAsVerified(recipeUserId, emailForWhomTokenWasGenerated); // We refresh the user information here, because the verification status may be updated, which is used during linking. - const updatedUserAfterEmailVerification = await __1.getUser(recipeUserId.getAsString(), userContext); + const updatedUserAfterEmailVerification = await __1.getUser( + recipeUserId.getAsString(), + userContext + ); if (updatedUserAfterEmailVerification === undefined) { throw new Error("Should never happen - user deleted after during password reset"); } @@ -333,7 +394,8 @@ function getAPIImplementation() { session: undefined, userContext, }); - const userAfterWeTriedLinking = linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; + const userAfterWeTriedLinking = + linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; return { status: "OK", email: emailForWhomTokenWasGenerated, @@ -346,7 +408,9 @@ function getAPIImplementation() { // in validation but kept here to be safe. const newPasswordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; if (typeof newPasswordAsUnknown !== "string") - throw new Error("Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError"); + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); let newPassword = newPasswordAsUnknown; let tokenConsumptionResponse = await options.recipeImplementation.consumePasswordResetToken({ token, @@ -378,32 +442,40 @@ function getAPIImplementation() { // primary user id (userIdForWhomTokenWasGenerated), in this case, // we still don't allow password update, cause the user should try again // and the token should be regenerated for the right recipe user. - return (lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && lm.recipeId === "emailpassword"); + return ( + lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && lm.recipeId === "emailpassword" + ); }); if (tokenGeneratedForEmailPasswordUser) { if (!existingUser.isPrimaryUser) { // If this is a recipe level emailpassword user, we can always allow them to reset their password. - return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary(new recipeUserId_1.default(userIdForWhomTokenWasGenerated)); + return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( + new recipeUserId_1.default(userIdForWhomTokenWasGenerated) + ); } // If the user is a primary user resetting the password of an emailpassword user linked to it // we need to check for account takeover risk (similar to what we do when generating the token) // We check if there is any login method in which the input email is verified. // If that is the case, then it's proven that the user owns the email and we can // trust linking of the email password account. - let emailVerified = existingUser.loginMethods.find((lm) => { - return lm.hasSameEmailAs(emailForWhomTokenWasGenerated) && lm.verified; - }) !== undefined; + let emailVerified = + existingUser.loginMethods.find((lm) => { + return lm.hasSameEmailAs(emailForWhomTokenWasGenerated) && lm.verified; + }) !== undefined; // finally, we check if the primary user has any other email / phone number // associated with this account - and if it does, then it means that // there is a risk of account takeover, so we do not allow the token to be generated - let hasOtherEmailOrPhone = existingUser.loginMethods.find((lm) => { - // we do the extra undefined check below cause - // hasSameEmailAs returns false if the lm.email is undefined, and - // we want to check that the email is different as opposed to email - // not existing in lm. - return ((lm.email !== undefined && !lm.hasSameEmailAs(emailForWhomTokenWasGenerated)) || - lm.phoneNumber !== undefined); - }) !== undefined; + let hasOtherEmailOrPhone = + existingUser.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return ( + (lm.email !== undefined && !lm.hasSameEmailAs(emailForWhomTokenWasGenerated)) || + lm.phoneNumber !== undefined + ); + }) !== undefined; if (!emailVerified && hasOtherEmailOrPhone) { // We can return an invalid token error, because in this case the token should not have been created // whenever they try to re-create it they'll see the appropriate error message @@ -412,7 +484,9 @@ function getAPIImplementation() { }; } // since this doesn't result in linking and there is no risk of account takeover, we can allow the password reset to proceed - return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary(new recipeUserId_1.default(userIdForWhomTokenWasGenerated)); + return doUpdatePasswordAndVerifyEmailAndTryLinkIfNotPrimary( + new recipeUserId_1.default(userIdForWhomTokenWasGenerated) + ); } // this means that the existingUser is primary but does not have an emailpassword user associated // with it. It could now mean that no emailpassword user exists, or it could mean that @@ -440,13 +514,15 @@ function getAPIImplementation() { return { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", }; - } - else { + } else { // we mark the email as verified because password reset also requires // access to the email to work.. This has a good side effect that // any other login method with the same email in existingAccount will also get marked // as verified. - await markEmailAsVerified(createUserResponse.user.loginMethods[0].recipeUserId, tokenConsumptionResponse.email); + await markEmailAsVerified( + createUserResponse.user.loginMethods[0].recipeUserId, + tokenConsumptionResponse.email + ); const updatedUser = await __1.getUser(createUserResponse.user.id, userContext); if (updatedUser === undefined) { throw new Error("Should never happen - user deleted after during password reset"); @@ -478,14 +554,26 @@ function getAPIImplementation() { }; } }, - signInPOST: async function ({ formFields, tenantId, session, shouldTryLinkingWithSessionUser, options, userContext, }) { + signInPOST: async function ({ + formFields, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }) { const errorCodeMap = { - SIGN_IN_NOT_ALLOWED: "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", + SIGN_IN_NOT_ALLOWED: + "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", LINKING_TO_SESSION_USER_FAILED: { - EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", - 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_012)", + EMAIL_VERIFICATION_REQUIRED: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", + 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_012)", }, }; const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; @@ -494,15 +582,21 @@ function getAPIImplementation() { // check for type is done in a parent function but they are kept // here to be on the safe side. if (typeof emailAsUnknown !== "string") - throw new Error("Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"); + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); if (typeof passwordAsUnknown !== "string") - throw new Error("Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError"); + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); let email = emailAsUnknown; let password = passwordAsUnknown; const recipeId = "emailpassword"; const checkCredentialsOnTenant = async (tenantId) => { - return ((await options.recipeImplementation.verifyCredentials({ email, password, tenantId, userContext })) - .status === "OK"); + return ( + (await options.recipeImplementation.verifyCredentials({ email, password, tenantId, userContext })) + .status === "OK" + ); }; if (utils_2.isFakeEmail(email) && session === undefined) { // Fake emails cannot be used as a first factor @@ -510,14 +604,16 @@ function getAPIImplementation() { status: "WRONG_CREDENTIALS_ERROR", }; } - const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ - accountInfo: { email }, - userContext, - recipeId, - session, - tenantId, - checkCredentialsOnTenant, - }); + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( + { + accountInfo: { email }, + userContext, + recipeId, + session, + tenantId, + checkCredentialsOnTenant, + } + ); const isVerified = authenticatingUser !== undefined && authenticatingUser.loginMethod.verified; // We check this before preAuthChecks, because that function assumes that if isSignUp is false, // then authenticatingUser is defined. While it wouldn't technically cause any problems with @@ -535,7 +631,8 @@ function getAPIImplementation() { }, factorIds: ["emailpassword"], isSignUp: false, - authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + authenticatingUser: + authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, isVerified, signInVerifiesLoginMethod: false, skipSessionUserUpdateInCore: false, @@ -548,7 +645,11 @@ function getAPIImplementation() { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); } if (preAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthChecks, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); } if (utils_2.isFakeEmail(email) && preAuthChecks.isFirstFactor) { // Fake emails cannot be used as a first factor @@ -568,7 +669,11 @@ function getAPIImplementation() { return signInResponse; } if (signInResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(signInResponse, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + signInResponse, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); } const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ authenticatedUser: signInResponse.user, @@ -582,7 +687,11 @@ function getAPIImplementation() { userContext, }); if (postAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + postAuthChecks, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); } return { status: "OK", @@ -590,14 +699,26 @@ function getAPIImplementation() { user: postAuthChecks.user, }; }, - signUpPOST: async function ({ formFields, tenantId, session, shouldTryLinkingWithSessionUser, options, userContext, }) { + signUpPOST: async function ({ + formFields, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }) { const errorCodeMap = { - SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + SIGN_UP_NOT_ALLOWED: + "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", LINKING_TO_SESSION_USER_FAILED: { - EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", - 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_016)", + EMAIL_VERIFICATION_REQUIRED: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", + 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_016)", }, }; const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; @@ -606,9 +727,13 @@ function getAPIImplementation() { // check for type is done in a parent function but they are kept // here to be on the safe side. if (typeof emailAsUnknown !== "string") - throw new Error("Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"); + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); if (typeof passwordAsUnknown !== "string") - throw new Error("Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError"); + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); let email = emailAsUnknown; let password = passwordAsUnknown; const preAuthCheckRes = await authUtils_1.AuthUtils.preAuthChecks({ @@ -628,22 +753,32 @@ function getAPIImplementation() { shouldTryLinkingWithSessionUser, }); if (preAuthCheckRes.status === "SIGN_UP_NOT_ALLOWED") { - const conflictingUsers = await recipe_1.default.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ - tenantId, - accountInfo: { - email, - }, - doUnionOfAccountInfo: false, - userContext, - }); - if (conflictingUsers.some((u) => u.loginMethods.some((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)))) { + const conflictingUsers = await recipe_1.default + .getInstance() + .recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); + if ( + conflictingUsers.some((u) => + u.loginMethods.some((lm) => lm.recipeId === "emailpassword" && lm.hasSameEmailAs(email)) + ) + ) { return { status: "EMAIL_ALREADY_EXISTS_ERROR", }; } } if (preAuthCheckRes.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthCheckRes, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthCheckRes, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); } if (utils_2.isFakeEmail(email) && preAuthCheckRes.isFirstFactor) { // Fake emails cannot be used as a first factor @@ -663,7 +798,11 @@ function getAPIImplementation() { return signUpResponse; } if (signUpResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(signUpResponse, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + signUpResponse, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); } const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ authenticatedUser: signUpResponse.user, @@ -680,7 +819,11 @@ function getAPIImplementation() { // It should never actually come here, but we do it cause of consistency. // If it does come here (in case there is a bug), it would make this func throw // anyway, cause there is no SIGN_IN_NOT_ALLOWED in the errorCodeMap. - authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + postAuthChecks, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); throw new Error("This should never happen"); } return { diff --git a/lib/build/recipe/emailpassword/api/passwordReset.d.ts b/lib/build/recipe/emailpassword/api/passwordReset.d.ts index 3a2304943..08aef7f99 100644 --- a/lib/build/recipe/emailpassword/api/passwordReset.d.ts +++ b/lib/build/recipe/emailpassword/api/passwordReset.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function passwordReset(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function passwordReset( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/emailpassword/api/passwordReset.js b/lib/build/recipe/emailpassword/api/passwordReset.js index f9275da56..0565d8dcd 100644 --- a/lib/build/recipe/emailpassword/api/passwordReset.js +++ b/lib/build/recipe/emailpassword/api/passwordReset.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -30,7 +32,12 @@ async function passwordReset(apiImplementation, tenantId, options, userContext) // - we want to throw this error before consuming the token, so that the user can try again // - there is a case in the api impl where we create a new user, and we want to assign // a password that meets the password policy. - let formFields = await utils_2.validateFormFieldsOrThrowError(options.config.resetPasswordUsingTokenFeature.formFieldsForPasswordResetForm, requestBody.formFields, tenantId, userContext); + let formFields = await utils_2.validateFormFieldsOrThrowError( + options.config.resetPasswordUsingTokenFeature.formFieldsForPasswordResetForm, + requestBody.formFields, + tenantId, + userContext + ); let token = requestBody.token; if (token === undefined) { throw new error_1.default({ @@ -65,11 +72,14 @@ async function passwordReset(apiImplementation, tenantId, options, userContext) message: "Error in input formFields", }); } - utils_1.send200Response(options.res, result.status === "OK" - ? { - status: "OK", - } - : result); + utils_1.send200Response( + options.res, + result.status === "OK" + ? { + status: "OK", + } + : result + ); return true; } exports.default = passwordReset; diff --git a/lib/build/recipe/emailpassword/api/signin.d.ts b/lib/build/recipe/emailpassword/api/signin.d.ts index 178b76b67..4a712efbe 100644 --- a/lib/build/recipe/emailpassword/api/signin.d.ts +++ b/lib/build/recipe/emailpassword/api/signin.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function signInAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function signInAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/emailpassword/api/signin.js b/lib/build/recipe/emailpassword/api/signin.js index 2f9685bef..42a7f16b3 100644 --- a/lib/build/recipe/emailpassword/api/signin.js +++ b/lib/build/recipe/emailpassword/api/signin.js @@ -24,9 +24,19 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { } const body = await options.req.getJSONBody(); // step 1 - let formFields = await utils_2.validateFormFieldsOrThrowError(options.config.signInFeature.formFields, body.formFields, tenantId, userContext); + let formFields = await utils_2.validateFormFieldsOrThrowError( + options.config.signInFeature.formFields, + body.formFields, + tenantId, + userContext + ); const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -39,9 +49,11 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { userContext, }); if (result.status === "OK") { - utils_1.send200Response(options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); - } - else { + utils_1.send200Response( + options.res, + Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) + ); + } else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/emailpassword/api/signup.d.ts b/lib/build/recipe/emailpassword/api/signup.d.ts index 7306003af..1487513c1 100644 --- a/lib/build/recipe/emailpassword/api/signup.d.ts +++ b/lib/build/recipe/emailpassword/api/signup.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function signUpAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function signUpAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/emailpassword/api/signup.js b/lib/build/recipe/emailpassword/api/signup.js index 5acf52ba8..6a793ec2c 100644 --- a/lib/build/recipe/emailpassword/api/signup.js +++ b/lib/build/recipe/emailpassword/api/signup.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -28,9 +30,22 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { } const requestBody = await options.req.getJSONBody(); // step 1 - let formFields = await utils_2.validateFormFieldsOrThrowError(options.config.signUpFeature.formFields, requestBody.formFields, tenantId, userContext); - const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); + let formFields = await utils_2.validateFormFieldsOrThrowError( + options.config.signUpFeature.formFields, + requestBody.formFields, + tenantId, + userContext + ); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( + options.req, + requestBody + ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -43,12 +58,13 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { userContext: userContext, }); if (result.status === "OK") { - utils_1.send200Response(options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); - } - else if (result.status === "GENERAL_ERROR") { + utils_1.send200Response( + options.res, + Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) + ); + } else if (result.status === "GENERAL_ERROR") { utils_1.send200Response(options.res, result); - } - else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { + } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { throw new error_1.default({ type: error_1.default.FIELD_ERROR, payload: [ @@ -59,8 +75,7 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { ], message: "Error in input formFields", }); - } - else { + } else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/emailpassword/api/utils.d.ts b/lib/build/recipe/emailpassword/api/utils.d.ts index bfba7a7e6..0f67df755 100644 --- a/lib/build/recipe/emailpassword/api/utils.d.ts +++ b/lib/build/recipe/emailpassword/api/utils.d.ts @@ -1,7 +1,14 @@ // @ts-nocheck import { NormalisedFormField } from "../types"; import { UserContext } from "../../../types"; -export declare function validateFormFieldsOrThrowError(configFormFields: NormalisedFormField[], formFieldsRaw: any, tenantId: string, userContext: UserContext): Promise<{ - id: string; - value: unknown; -}[]>; +export declare function validateFormFieldsOrThrowError( + configFormFields: NormalisedFormField[], + formFieldsRaw: any, + tenantId: string, + userContext: UserContext +): Promise< + { + id: string; + value: unknown; + }[] +>; diff --git a/lib/build/recipe/emailpassword/api/utils.js b/lib/build/recipe/emailpassword/api/utils.js index 96610fa0d..69da81aba 100644 --- a/lib/build/recipe/emailpassword/api/utils.js +++ b/lib/build/recipe/emailpassword/api/utils.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateFormFieldsOrThrowError = void 0; const error_1 = __importDefault(require("../error")); @@ -33,7 +35,9 @@ async function validateFormFieldsOrThrowError(configFormFields, formFieldsRaw, t // we trim the email: https://github.com/supertokens/supertokens-core/issues/99 formFields = formFields.map((field) => { if (field.id === constants_1.FORM_FIELD_EMAIL_ID) { - return Object.assign(Object.assign({}, field), { value: typeof field.value === "string" ? field.value.trim() : field.value }); + return Object.assign(Object.assign({}, field), { + value: typeof field.value === "string" ? field.value.trim() : field.value, + }); } return field; }); @@ -63,7 +67,8 @@ async function validateFormOrThrowError(inputs, configFormFields, tenantId, user const input = inputs.find((input) => input.id === formField.id); // Add the not optional error if input is not passed // and the field is not optional. - const isValidInput = !!input && + const isValidInput = + !!input && ((typeof input.value === "string" ? input.value.length > 0 : input.value !== null && typeof input.value !== "undefined") || diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.d.ts index 30d5ecabf..7184fcbbd 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.d.ts @@ -2,11 +2,14 @@ import { TypeEmailPasswordEmailDeliveryInput } from "../../../types"; import { NormalisedAppinfo, UserContext } from "../../../../../types"; import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; -export default class BackwardCompatibilityService implements EmailDeliveryInterface { +export default class BackwardCompatibilityService + implements EmailDeliveryInterface { private isInServerlessEnv; private appInfo; constructor(appInfo: NormalisedAppinfo, isInServerlessEnv: boolean); - sendEmail: (input: TypeEmailPasswordEmailDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: TypeEmailPasswordEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js index ea19c0863..8d799d719 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js @@ -9,14 +9,18 @@ class BackwardCompatibilityService { // will get reset by the getUserById call above. try { if (!this.isInServerlessEnv) { - passwordResetFunctions_1.createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.passwordResetLink).catch((_) => { }); - } - else { + passwordResetFunctions_1 + .createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.passwordResetLink) + .catch((_) => {}); + } else { // see https://github.com/supertokens/supertokens-node/pull/135 - await passwordResetFunctions_1.createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.passwordResetLink); + await passwordResetFunctions_1.createAndSendEmailUsingSupertokensService( + this.appInfo, + input.user, + input.passwordResetLink + ); } - } - catch (_) { } + } catch (_) {} }; this.isInServerlessEnv = isInServerlessEnv; this.appInfo = appInfo; diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/index.js index d648973c6..91700aeaf 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMTPService = void 0; const smtp_1 = __importDefault(require("./smtp")); diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.d.ts index ea7f79b14..357c2f852 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.d.ts +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.d.ts @@ -6,7 +6,9 @@ import { UserContext } from "../../../../../types"; export default class SMTPService implements EmailDeliveryInterface { serviceImpl: ServiceInterface; constructor(config: TypeInput); - sendEmail: (input: TypeEmailPasswordEmailDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: TypeEmailPasswordEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.js index da1ee9c8a..dedcbe33f 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const nodemailer_1 = require("nodemailer"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); @@ -10,7 +12,9 @@ class SMTPService { constructor(config) { this.sendEmail = async (input) => { let content = await this.serviceImpl.getContent(input); - await this.serviceImpl.sendRawEmail(Object.assign(Object.assign({}, content), { userContext: input.userContext })); + await this.serviceImpl.sendRawEmail( + Object.assign(Object.assign({}, content), { userContext: input.userContext }) + ); }; const transporter = nodemailer_1.createTransport({ host: config.smtpSettings.host, @@ -21,7 +25,9 @@ class SMTPService { }, secure: config.smtpSettings.secure, }); - let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from)); + let builder = new supertokens_js_override_1.default( + serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) + ); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.d.ts b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.d.ts index f54789fa9..34240509a 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.d.ts +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.d.ts @@ -1,5 +1,7 @@ // @ts-nocheck import { TypeEmailPasswordPasswordResetEmailDeliveryInput } from "../../../types"; import { GetContentResult } from "../../../../../ingredients/emaildelivery/services/smtp"; -export default function getPasswordResetEmailContent(input: TypeEmailPasswordPasswordResetEmailDeliveryInput): GetContentResult; +export default function getPasswordResetEmailContent( + input: TypeEmailPasswordPasswordResetEmailDeliveryInput +): GetContentResult; export declare function getPasswordResetEmailHTML(appName: string, email: string, resetLink: string): string; diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.js b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.js index 1649bca3b..62ec6c5a9 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/passwordReset.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPasswordResetEmailHTML = void 0; const supertokens_1 = __importDefault(require("../../../../../supertokens")); diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.d.ts b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.d.ts index 917f30d7d..95fdbda32 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.d.ts +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.d.ts @@ -2,7 +2,10 @@ import { TypeEmailPasswordEmailDeliveryInput } from "../../../../types"; import { Transporter } from "nodemailer"; import { ServiceInterface } from "../../../../../../ingredients/emaildelivery/services/smtp"; -export declare function getServiceImplementation(transporter: Transporter, from: { - name: string; - email: string; -}): ServiceInterface; +export declare function getServiceImplementation( + transporter: Transporter, + from: { + name: string; + email: string; + } +): ServiceInterface; diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.js index efaed1687..bc0d7525c 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/smtp/serviceImplementation/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const passwordReset_1 = __importDefault(require("../passwordReset")); @@ -29,8 +31,7 @@ function getServiceImplementation(transporter, from) { subject: input.subject, html: input.body, }); - } - else { + } else { await transporter.sendMail({ from: `${from.name} <${from.email}>`, to: input.toEmail, diff --git a/lib/build/recipe/emailpassword/error.d.ts b/lib/build/recipe/emailpassword/error.d.ts index e72600d1d..d4dc2cf9b 100644 --- a/lib/build/recipe/emailpassword/error.d.ts +++ b/lib/build/recipe/emailpassword/error.d.ts @@ -2,15 +2,19 @@ import STError from "../../error"; export default class SessionError extends STError { static FIELD_ERROR: "FIELD_ERROR"; - constructor(options: { - type: "FIELD_ERROR"; - payload: { - id: string; - error: string; - }[]; - message: string; - } | { - type: "BAD_INPUT_ERROR"; - message: string; - }); + constructor( + options: + | { + type: "FIELD_ERROR"; + payload: { + id: string; + error: string; + }[]; + message: string; + } + | { + type: "BAD_INPUT_ERROR"; + message: string; + } + ); } diff --git a/lib/build/recipe/emailpassword/error.js b/lib/build/recipe/emailpassword/error.js index 8a9e8ef0e..ce0b25647 100644 --- a/lib/build/recipe/emailpassword/error.js +++ b/lib/build/recipe/emailpassword/error.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/emailpassword/index.d.ts b/lib/build/recipe/emailpassword/index.d.ts index 05910e54f..42b4a07db 100644 --- a/lib/build/recipe/emailpassword/index.d.ts +++ b/lib/build/recipe/emailpassword/index.d.ts @@ -8,41 +8,92 @@ import { User } from "../../types"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static signUp(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - }>; - static signUp(tenantId: string, email: string, password: string, session: SessionContainerInterface, userContext?: Record): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } | { - 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"; - }>; - static signIn(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "WRONG_CREDENTIALS_ERROR"; - }>; - static signIn(tenantId: string, email: string, password: string, session: SessionContainerInterface, userContext?: Record): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "WRONG_CREDENTIALS_ERROR"; - } | { - 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"; - }>; - static verifyCredentials(tenantId: string, email: string, password: string, userContext?: Record): Promise<{ + static signUp( + tenantId: string, + email: string, + password: string, + session?: undefined, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + >; + static signUp( + tenantId: string, + email: string, + password: string, + session: SessionContainerInterface, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + 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"; + } + >; + static signIn( + tenantId: string, + email: string, + password: string, + session?: undefined, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >; + static signIn( + tenantId: string, + email: string, + password: string, + session: SessionContainerInterface, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + 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"; + } + >; + static verifyCredentials( + tenantId: string, + email: string, + password: string, + userContext?: Record + ): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR"; }>; /** @@ -56,25 +107,48 @@ export default class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static createResetPasswordToken(tenantId: string, userId: string, email: string, userContext?: Record): Promise<{ - status: "OK"; - token: string; - } | { - status: "UNKNOWN_USER_ID_ERROR"; - }>; - static resetPasswordUsingToken(tenantId: string, token: string, newPassword: string, userContext?: Record): Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - } | { - status: "PASSWORD_POLICY_VIOLATED_ERROR"; - failureReason: string; - }>; - static consumePasswordResetToken(tenantId: string, token: string, userContext?: Record): Promise<{ - status: "OK"; - email: string; - userId: string; - } | { - status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - }>; + static createResetPasswordToken( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + token: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; + static resetPasswordUsingToken( + tenantId: string, + token: string, + newPassword: string, + userContext?: Record + ): Promise< + | { + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + } + | { + status: "PASSWORD_POLICY_VIOLATED_ERROR"; + failureReason: string; + } + >; + static consumePasswordResetToken( + tenantId: string, + token: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + } + >; static updateEmailOrPassword(input: { recipeUserId: RecipeUserId; email?: string; @@ -82,27 +156,46 @@ export default class Wrapper { userContext?: Record; applyPasswordPolicy?: boolean; tenantIdForPasswordPolicy?: string; - }): Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; - } | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } | { - status: "PASSWORD_POLICY_VIOLATED_ERROR"; - failureReason: string; - }>; - static createResetPasswordLink(tenantId: string, userId: string, email: string, userContext?: Record): Promise<{ - status: "OK"; - link: string; - } | { - status: "UNKNOWN_USER_ID_ERROR"; - }>; - static sendResetPasswordEmail(tenantId: string, userId: string, email: string, userContext?: Record): Promise<{ + }): Promise< + | { + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } + | { + status: "PASSWORD_POLICY_VIOLATED_ERROR"; + failureReason: string; + } + >; + static createResetPasswordLink( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + link: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; + static sendResetPasswordEmail( + tenantId: string, + userId: string, + email: string, + userContext?: Record + ): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR"; }>; - static sendEmail(input: TypeEmailPasswordEmailDeliveryInput & { - userContext?: Record; - }): Promise; + static sendEmail( + input: TypeEmailPasswordEmailDeliveryInput & { + userContext?: Record; + } + ): Promise; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/emailpassword/index.js b/lib/build/recipe/emailpassword/index.js index 17e9aab7d..2c77224c0 100644 --- a/lib/build/recipe/emailpassword/index.js +++ b/lib/build/recipe/emailpassword/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sendEmail = exports.sendResetPasswordEmail = exports.createResetPasswordLink = exports.updateEmailOrPassword = exports.consumePasswordResetToken = exports.resetPasswordUsingToken = exports.createResetPasswordToken = exports.verifyCredentials = exports.signIn = exports.signUp = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -110,7 +112,15 @@ class Wrapper { }); } static updateEmailOrPassword(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.updateEmailOrPassword(Object.assign(Object.assign({}, input), { userContext: utils_2.getUserContext(input.userContext), tenantIdForPasswordPolicy: input.tenantIdForPasswordPolicy === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantIdForPasswordPolicy })); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.updateEmailOrPassword( + Object.assign(Object.assign({}, input), { + userContext: utils_2.getUserContext(input.userContext), + tenantIdForPasswordPolicy: + input.tenantIdForPasswordPolicy === undefined + ? constants_1.DEFAULT_TENANT_ID + : input.tenantIdForPasswordPolicy, + }) + ); } static async createResetPasswordLink(tenantId, userId, email, userContext) { const ctx = utils_2.getUserContext(userContext); @@ -160,7 +170,12 @@ class Wrapper { } static async sendEmail(input) { let recipeInstance = recipe_1.default.getInstanceOrThrowError(); - return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail(Object.assign(Object.assign({}, input), { tenantId: input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId, userContext: utils_2.getUserContext(input.userContext) })); + return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail( + Object.assign(Object.assign({}, input), { + tenantId: input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId, + userContext: utils_2.getUserContext(input.userContext), + }) + ); } } exports.default = Wrapper; diff --git a/lib/build/recipe/emailpassword/passwordResetFunctions.d.ts b/lib/build/recipe/emailpassword/passwordResetFunctions.d.ts index 5638c375e..e7ed30fe1 100644 --- a/lib/build/recipe/emailpassword/passwordResetFunctions.d.ts +++ b/lib/build/recipe/emailpassword/passwordResetFunctions.d.ts @@ -1,6 +1,10 @@ // @ts-nocheck import { NormalisedAppinfo } from "../../types"; -export declare function createAndSendEmailUsingSupertokensService(appInfo: NormalisedAppinfo, user: { - id: string; - email: string; -}, passwordResetURLWithToken: string): Promise; +export declare function createAndSendEmailUsingSupertokensService( + appInfo: NormalisedAppinfo, + user: { + id: string; + email: string; + }, + passwordResetURLWithToken: string +): Promise; diff --git a/lib/build/recipe/emailpassword/passwordResetFunctions.js b/lib/build/recipe/emailpassword/passwordResetFunctions.js index f3a8bdab6..23ec9186f 100644 --- a/lib/build/recipe/emailpassword/passwordResetFunctions.js +++ b/lib/build/recipe/emailpassword/passwordResetFunctions.js @@ -21,16 +21,21 @@ async function createAndSendEmailUsingSupertokensService(appInfo, user, password if (utils_1.isTestEnv()) { return; } - await utils_1.postWithFetch("https://api.supertokens.io/0/st/auth/password/reset", { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, { - email: user.email, - appName: appInfo.appName, - passwordResetURL: passwordResetURLWithToken, - }, { - successLog: `Password reset email sent to ${user.email}`, - errorLogHeader: "Error sending password reset email", - }); + await utils_1.postWithFetch( + "https://api.supertokens.io/0/st/auth/password/reset", + { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, + { + email: user.email, + appName: appInfo.appName, + passwordResetURL: passwordResetURLWithToken, + }, + { + successLog: `Password reset email sent to ${user.email}`, + errorLogHeader: "Error sending password reset email", + } + ); } exports.createAndSendEmailUsingSupertokensService = createAndSendEmailUsingSupertokensService; diff --git a/lib/build/recipe/emailpassword/recipe.d.ts b/lib/build/recipe/emailpassword/recipe.d.ts index 811ff0ea1..9ceb82533 100644 --- a/lib/build/recipe/emailpassword/recipe.d.ts +++ b/lib/build/recipe/emailpassword/recipe.d.ts @@ -15,14 +15,28 @@ export default class Recipe extends RecipeModule { apiImpl: APIInterface; isInServerlessEnv: boolean; emailDelivery: EmailDeliveryIngredient; - constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput | undefined, ingredients: { - emailDelivery: EmailDeliveryIngredient | undefined; - }); + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput | undefined, + ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + } + ); static getInstanceOrThrowError(): Recipe; static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _request: BaseRequest, response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/emailpassword/recipe.js b/lib/build/recipe/emailpassword/recipe.js index aaec67688..dc968c536 100644 --- a/lib/build/recipe/emailpassword/recipe.js +++ b/lib/build/recipe/emailpassword/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -58,7 +60,9 @@ class Recipe extends recipeModule_1.default { }, { method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.GENERATE_PASSWORD_RESET_TOKEN_API), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + constants_1.GENERATE_PASSWORD_RESET_TOKEN_API + ), id: constants_1.GENERATE_PASSWORD_RESET_TOKEN_API, disabled: this.apiImpl.generatePasswordResetTokenPOST === undefined, }, @@ -95,17 +99,13 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.SIGN_UP_API) { return await signup_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.SIGN_IN_API) { + } else if (id === constants_1.SIGN_IN_API) { return await signin_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.GENERATE_PASSWORD_RESET_TOKEN_API) { + } else if (id === constants_1.GENERATE_PASSWORD_RESET_TOKEN_API) { return await generatePasswordResetToken_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.PASSWORD_RESET_API) { + } else if (id === constants_1.PASSWORD_RESET_API) { return await passwordReset_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API || id === constants_1.SIGNUP_EMAIL_EXISTS_API_OLD) { + } else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API || id === constants_1.SIGNUP_EMAIL_EXISTS_API_OLD) { return await emailExists_1.default(this.apiImpl, tenantId, options, userContext); } return false; @@ -117,12 +117,10 @@ class Recipe extends recipeModule_1.default { status: "FIELD_ERROR", formFields: err.payload, }); - } - else { + } else { throw err; } - } - else { + } else { throw err; } }; @@ -136,7 +134,12 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); { const getEmailPasswordConfig = () => this.config; - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), getEmailPasswordConfig)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default( + querier_1.Querier.getNewInstanceOrThrowError(recipeId), + getEmailPasswordConfig + ) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -188,7 +191,9 @@ class Recipe extends recipeModule_1.default { return a.timeJoined - b.timeJoined; }); // Then we take the ones that belong to this recipe - const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter((lm) => lm.recipeId === Recipe.RECIPE_ID); + const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter( + (lm) => lm.recipeId === Recipe.RECIPE_ID + ); let result; if (recipeLoginMethodsOrderedByTimeJoinedOldestFirst.length !== 0) { // If there are login methods belonging to this recipe, the factor is set up @@ -215,16 +220,18 @@ class Recipe extends recipeModule_1.default { .map((lm) => lm.email), ]; // We handle moving the session email to the top of the list later - } - else { + } else { // This factor hasn't been set up, we list all emails belonging to the user - if (orderedLoginMethodsByTimeJoinedOldestFirst.some((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email))) { + if ( + orderedLoginMethodsByTimeJoinedOldestFirst.some( + (lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email) + ) + ) { // If there is at least one real email address linked to the user, we only suggest real addresses result = orderedLoginMethodsByTimeJoinedOldestFirst .filter((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email)) .map((lm) => lm.email); - } - else { + } else { // Else we use the fake ones result = orderedLoginMethodsByTimeJoinedOldestFirst .filter((lm) => lm.email !== undefined && utils_3.isFakeEmail(lm.email)) @@ -278,8 +285,7 @@ class Recipe extends recipeModule_1.default { emailDelivery: undefined, }); return Recipe.instance; - } - else { + } else { throw new Error("Emailpassword recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/emailpassword/recipeImplementation.d.ts b/lib/build/recipe/emailpassword/recipeImplementation.d.ts index 3d70a735b..cfb8e6ad0 100644 --- a/lib/build/recipe/emailpassword/recipeImplementation.d.ts +++ b/lib/build/recipe/emailpassword/recipeImplementation.d.ts @@ -1,4 +1,7 @@ // @ts-nocheck import { RecipeInterface, TypeNormalisedInput } from "./types"; import { Querier } from "../../querier"; -export default function getRecipeInterface(querier: Querier, getEmailPasswordConfig: () => TypeNormalisedInput): RecipeInterface; +export default function getRecipeInterface( + querier: Querier, + getEmailPasswordConfig: () => TypeNormalisedInput +): RecipeInterface; diff --git a/lib/build/recipe/emailpassword/recipeImplementation.js b/lib/build/recipe/emailpassword/recipeImplementation.js index 3f214d583..acbbb1c7f 100644 --- a/lib/build/recipe/emailpassword/recipeImplementation.js +++ b/lib/build/recipe/emailpassword/recipeImplementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../accountlinking/recipe")); const recipe_2 = __importDefault(require("../emailverification/recipe")); @@ -25,14 +27,16 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { return response; } let updatedUser = response.user; - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( + { + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + } + ); if (linkResult.status != "OK") { return linkResult; } @@ -44,10 +48,16 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { }; }, createNewRecipeUser: async function (input) { - const resp = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : input.tenantId}/recipe/signup`), { - email: input.email, - password: input.password, - }, input.userContext); + const resp = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${input.tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : input.tenantId}/recipe/signup` + ), + { + email: input.email, + password: input.password, + }, + input.userContext + ); if (resp.status === "OK") { return { status: "OK", @@ -62,7 +72,9 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { 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()); + const loginMethod = response.user.loginMethods.find( + (lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString() + ); if (!loginMethod.verified) { await recipe_1.default.getInstance().verifyEmailForRecipeUserIfLinkedAccountsAreVerified({ user: response.user, @@ -80,16 +92,18 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { // 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 __1.getUser(response.recipeUserId.getAsString(), userContext)); + response.user = await __1.getUser(response.recipeUserId.getAsString(), userContext); } - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( + { + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + } + ); if (linkResult.status === "LINKING_TO_SESSION_USER_FAILED") { return linkResult; } @@ -97,11 +111,17 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { } return response; }, - verifyCredentials: async function ({ email, password, tenantId, userContext, }) { - const response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/recipe/signin`), { - email, - password, - }, userContext); + verifyCredentials: async function ({ email, password, tenantId, userContext }) { + const response = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/recipe/signin` + ), + { + email, + password, + }, + userContext + ); if (response.status === "OK") { return { status: "OK", @@ -113,18 +133,34 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { status: "WRONG_CREDENTIALS_ERROR", }; }, - createResetPasswordToken: async function ({ userId, email, tenantId, userContext, }) { + createResetPasswordToken: async function ({ userId, email, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. - return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/recipe/user/password/reset/token`), { - userId, - email, - }, userContext); + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId + }/recipe/user/password/reset/token` + ), + { + userId, + email, + }, + userContext + ); }, - consumePasswordResetToken: async function ({ token, tenantId, userContext, }) { - return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/recipe/user/password/reset/token/consume`), { - method: "token", - token, - }, userContext); + consumePasswordResetToken: async function ({ token, tenantId, userContext }) { + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId + }/recipe/user/password/reset/token/consume` + ), + { + method: "token", + token, + }, + userContext + ); }, updateEmailOrPassword: async function (input) { const accountLinking = recipe_1.default.getInstance(); @@ -152,9 +188,10 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { 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.", + 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.", }; } } @@ -162,7 +199,11 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { 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); + const error = await passwordField.validate( + input.password, + input.tenantIdForPasswordPolicy, + input.userContext + ); if (error !== undefined) { return { status: "PASSWORD_POLICY_VIOLATED_ERROR", @@ -177,11 +218,16 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { // 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(new normalisedURLPath_1.default(`/recipe/user`), { - recipeUserId: input.recipeUserId.getAsString(), - email: input.email, - password: input.password, - }, {}, input.userContext); + let response = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/user`), + { + recipeUserId: input.recipeUserId.getAsString(), + email: input.email, + password: input.password, + }, + {}, + input.userContext + ); if (response.status === "OK") { const user = await __1.getUser(input.recipeUserId.getAsString(), input.userContext); if (user === undefined) { diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index 1e370bf30..fa3eb1408 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -2,17 +2,25 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; import { SessionContainerInterface } from "../session/types"; -import { TypeInput as EmailDeliveryTypeInput, TypeInputWithService as EmailDeliveryTypeInputWithService } from "../../ingredients/emaildelivery/types"; +import { + TypeInput as EmailDeliveryTypeInput, + TypeInputWithService as EmailDeliveryTypeInputWithService, +} from "../../ingredients/emaildelivery/types"; import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; export declare type TypeNormalisedInput = { signUpFeature: TypeNormalisedInputSignUp; signInFeature: TypeNormalisedInputSignIn; - getEmailDeliveryConfig: (isInServerlessEnv: boolean) => EmailDeliveryTypeInputWithService; + getEmailDeliveryConfig: ( + isInServerlessEnv: boolean + ) => EmailDeliveryTypeInputWithService; resetPasswordUsingTokenFeature: TypeNormalisedInputResetPasswordUsingTokenFeature; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -47,7 +55,10 @@ export declare type TypeInput = { signUpFeature?: TypeInputSignUp; emailDelivery?: EmailDeliveryTypeInput; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -59,28 +70,39 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } | { - 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"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + 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"; + } + >; createNewRecipeUser(input: { email: string; password: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + >; signIn(input: { email: string; password: string; @@ -88,28 +110,39 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "WRONG_CREDENTIALS_ERROR"; - } | { - 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"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + 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"; + } + >; verifyCredentials(input: { email: string; password: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "WRONG_CREDENTIALS_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + >; /** * We pass in the email as well to this function cause the input userId * may not be associated with an emailpassword account. In this case, we @@ -120,23 +153,29 @@ export declare type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - token: string; - } | { - status: "UNKNOWN_USER_ID_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + token: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; consumePasswordResetToken(input: { token: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - email: string; - userId: string; - } | { - status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + } + >; updateEmailOrPassword(input: { recipeUserId: RecipeUserId; email?: string; @@ -144,15 +183,19 @@ export declare type RecipeInterface = { userContext: UserContext; applyPasswordPolicy?: boolean; tenantIdForPasswordPolicy: string; - }): Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; - } | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } | { - status: "PASSWORD_POLICY_VIOLATED_ERROR"; - failureReason: string; - }>; + }): Promise< + | { + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } + | { + status: "PASSWORD_POLICY_VIOLATED_ERROR"; + failureReason: string; + } + >; }; export declare type APIOptions = { recipeImplementation: RecipeInterface; @@ -165,88 +208,120 @@ export declare type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; export declare type APIInterface = { - emailExistsGET: undefined | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - exists: boolean; - } | GeneralErrorResponse>); - generatePasswordResetTokenPOST: undefined | ((input: { - formFields: { - id: string; - value: unknown; - }[]; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - } | { - status: "PASSWORD_RESET_NOT_ALLOWED"; - reason: string; - } | GeneralErrorResponse>); - passwordResetPOST: undefined | ((input: { - formFields: { - id: string; - value: unknown; - }[]; - token: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - email: string; - user: User; - } | { - status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; - } | { - status: "PASSWORD_POLICY_VIOLATED_ERROR"; - failureReason: string; - } | GeneralErrorResponse>); - signInPOST: undefined | ((input: { - formFields: { - id: string; - value: unknown; - }[]; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - user: User; - session: SessionContainerInterface; - } | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } | { - status: "WRONG_CREDENTIALS_ERROR"; - } | GeneralErrorResponse>); - signUpPOST: undefined | ((input: { - formFields: { - id: string; - value: unknown; - }[]; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - user: User; - session: SessionContainerInterface; - } | { - status: "SIGN_UP_NOT_ALLOWED"; - reason: string; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } | GeneralErrorResponse>); + emailExistsGET: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + exists: boolean; + } + | GeneralErrorResponse + >); + generatePasswordResetTokenPOST: + | undefined + | ((input: { + formFields: { + id: string; + value: unknown; + }[]; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | { + status: "PASSWORD_RESET_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + >); + passwordResetPOST: + | undefined + | ((input: { + formFields: { + id: string; + value: unknown; + }[]; + token: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + email: string; + user: User; + } + | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + } + | { + status: "PASSWORD_POLICY_VIOLATED_ERROR"; + failureReason: string; + } + | GeneralErrorResponse + >); + signInPOST: + | undefined + | ((input: { + formFields: { + id: string; + value: unknown; + }[]; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | GeneralErrorResponse + >); + signUpPOST: + | undefined + | ((input: { + formFields: { + id: string; + value: unknown; + }[]; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + } + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | GeneralErrorResponse + >); }; export declare type TypeEmailPasswordPasswordResetEmailDeliveryInput = { type: "PASSWORD_RESET"; diff --git a/lib/build/recipe/emailpassword/utils.d.ts b/lib/build/recipe/emailpassword/utils.d.ts index b9aa6f378..a62eb7fe7 100644 --- a/lib/build/recipe/emailpassword/utils.d.ts +++ b/lib/build/recipe/emailpassword/utils.d.ts @@ -3,10 +3,25 @@ import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput, NormalisedFormField, TypeInputFormField } from "./types"; import { NormalisedAppinfo, UserContext } from "../../types"; import { BaseRequest } from "../../framework"; -export declare function validateAndNormaliseUserInput(recipeInstance: Recipe, appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + recipeInstance: Recipe, + appInfo: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; export declare function normaliseSignUpFormFields(formFields?: TypeInputFormField[]): NormalisedFormField[]; -export declare function defaultPasswordValidator(value: any): Promise<"Development bug: Please make sure the password field yields a string" | "Password must contain at least 8 characters, including a number" | "Password's length must be lesser than 100 characters" | "Password must contain at least one alphabet" | "Password must contain at least one number" | undefined>; -export declare function defaultEmailValidator(value: any): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; +export declare function defaultPasswordValidator( + value: any +): Promise< + | "Development bug: Please make sure the password field yields a string" + | "Password must contain at least 8 characters, including a number" + | "Password's length must be lesser than 100 characters" + | "Password must contain at least one alphabet" + | "Password must contain at least one number" + | undefined +>; +export declare function defaultEmailValidator( + value: any +): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; export declare function getPasswordResetLink(input: { appInfo: NormalisedAppinfo; token: string; diff --git a/lib/build/recipe/emailpassword/utils.js b/lib/build/recipe/emailpassword/utils.js index afc4c7a4d..18014af69 100644 --- a/lib/build/recipe/emailpassword/utils.js +++ b/lib/build/recipe/emailpassword/utils.js @@ -13,21 +13,36 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPasswordResetLink = exports.defaultEmailValidator = exports.defaultPasswordValidator = exports.normaliseSignUpFormFields = exports.validateAndNormaliseUserInput = void 0; const constants_1 = require("./constants"); const backwardCompatibility_1 = __importDefault(require("./emaildelivery/services/backwardCompatibility")); function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { - let signUpFeature = validateAndNormaliseSignupConfig(recipeInstance, appInfo, config === undefined ? undefined : config.signUpFeature); + let signUpFeature = validateAndNormaliseSignupConfig( + recipeInstance, + appInfo, + config === undefined ? undefined : config.signUpFeature + ); let signInFeature = validateAndNormaliseSignInConfig(recipeInstance, appInfo, signUpFeature); let resetPasswordUsingTokenFeature = validateAndNormaliseResetPasswordUsingTokenConfig(signUpFeature); - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); function getEmailDeliveryConfig(isInServerlessEnv) { var _a; - let emailService = (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; + let emailService = + (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 + ? void 0 + : _a.service; /** * If the user has not passed even that config, we use the default * createAndSendCustomEmail implementation which calls our supertokens API @@ -35,7 +50,7 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { if (emailService === undefined) { emailService = new backwardCompatibility_1.default(appInfo, isInServerlessEnv); } - return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { + return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { /** * if we do * let emailDelivery = { @@ -47,7 +62,8 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { * it it again get set to undefined, so we * set service at the end */ - service: emailService }); + service: emailService, + }); } return { signUpFeature, @@ -62,21 +78,21 @@ function validateAndNormaliseResetPasswordUsingTokenConfig(signUpConfig) { let formFieldsForPasswordResetForm = signUpConfig.formFields .filter((filter) => filter.id === constants_1.FORM_FIELD_PASSWORD_ID) .map((field) => { - return { - id: field.id, - validate: field.validate, - optional: false, - }; - }); + return { + id: field.id, + validate: field.validate, + optional: false, + }; + }); let formFieldsForGenerateTokenForm = signUpConfig.formFields .filter((filter) => filter.id === constants_1.FORM_FIELD_EMAIL_ID) .map((field) => { - return { - id: field.id, - validate: field.validate, - optional: false, - }; - }); + return { + id: field.id, + validate: field.validate, + optional: false, + }; + }); return { formFieldsForPasswordResetForm, formFieldsForGenerateTokenForm, @@ -84,15 +100,18 @@ function validateAndNormaliseResetPasswordUsingTokenConfig(signUpConfig) { } function normaliseSignInFormFields(formFields) { return formFields - .filter((filter) => filter.id === constants_1.FORM_FIELD_EMAIL_ID || filter.id === constants_1.FORM_FIELD_PASSWORD_ID) + .filter( + (filter) => + filter.id === constants_1.FORM_FIELD_EMAIL_ID || filter.id === constants_1.FORM_FIELD_PASSWORD_ID + ) .map((field) => { - return { - id: field.id, - // see issue: https://github.com/supertokens/supertokens-node/issues/36 - validate: field.id === constants_1.FORM_FIELD_EMAIL_ID ? field.validate : defaultValidator, - optional: false, - }; - }); + return { + id: field.id, + // see issue: https://github.com/supertokens/supertokens-node/issues/36 + validate: field.id === constants_1.FORM_FIELD_EMAIL_ID ? field.validate : defaultValidator, + optional: false, + }; + }); } function validateAndNormaliseSignInConfig(_, __, signUpConfig) { let formFields = normaliseSignInFormFields(signUpConfig.formFields); @@ -110,15 +129,13 @@ function normaliseSignUpFormFields(formFields) { validate: field.validate === undefined ? defaultPasswordValidator : field.validate, optional: false, }); - } - else if (field.id === constants_1.FORM_FIELD_EMAIL_ID) { + } else if (field.id === constants_1.FORM_FIELD_EMAIL_ID) { normalisedFormFields.push({ id: field.id, validate: field.validate === undefined ? defaultEmailValidator : field.validate, optional: false, }); - } - else { + } else { normalisedFormFields.push({ id: field.id, validate: field.validate === undefined ? defaultValidator : field.validate, @@ -184,23 +201,29 @@ async function defaultEmailValidator(value) { if (typeof value !== "string") { return "Development bug: Please make sure the email field yields a string"; } - if (value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) === null) { + if ( + value.match( + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ) === null + ) { return "Email is invalid"; } return undefined; } exports.defaultEmailValidator = defaultEmailValidator; function getPasswordResetLink(input) { - return (input.appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() + + return ( + input.appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + input.appInfo.websiteBasePath.getAsStringDangerous() + "/reset-password?token=" + input.token + "&tenantId=" + - input.tenantId); + input.tenantId + ); } exports.getPasswordResetLink = getPasswordResetLink; diff --git a/lib/build/recipe/emailverification/api/emailVerify.d.ts b/lib/build/recipe/emailverification/api/emailVerify.d.ts index 7025fa039..1f7b416c8 100644 --- a/lib/build/recipe/emailverification/api/emailVerify.d.ts +++ b/lib/build/recipe/emailverification/api/emailVerify.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function emailVerify(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function emailVerify( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/emailverification/api/emailVerify.js b/lib/build/recipe/emailverification/api/emailVerify.js index 3a8d5675b..96a693995 100644 --- a/lib/build/recipe/emailverification/api/emailVerify.js +++ b/lib/build/recipe/emailverification/api/emailVerify.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -41,7 +43,12 @@ async function emailVerify(apiImplementation, tenantId, options, userContext) { message: "The email verification token must be a string", }); } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: false }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [], sessionRequired: false }, + userContext + ); let response = await apiImplementation.verifyEmailPOST({ token, tenantId, @@ -54,16 +61,19 @@ async function emailVerify(apiImplementation, tenantId, options, userContext) { // automatically added to the response by the createNewSession function call // inside the verifyEmailPOST function. result = { status: "OK" }; - } - else { + } else { result = response; } - } - else { + } else { if (apiImplementation.isEmailVerifiedGET === undefined) { return false; } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [] }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [] }, + userContext + ); result = await apiImplementation.isEmailVerifiedGET({ options, session, diff --git a/lib/build/recipe/emailverification/api/generateEmailVerifyToken.d.ts b/lib/build/recipe/emailverification/api/generateEmailVerifyToken.d.ts index 8ec37da34..487897d0e 100644 --- a/lib/build/recipe/emailverification/api/generateEmailVerifyToken.d.ts +++ b/lib/build/recipe/emailverification/api/generateEmailVerifyToken.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function generateEmailVerifyToken(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function generateEmailVerifyToken( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/emailverification/api/generateEmailVerifyToken.js b/lib/build/recipe/emailverification/api/generateEmailVerifyToken.js index 203987e05..32ed210ee 100644 --- a/lib/build/recipe/emailverification/api/generateEmailVerifyToken.js +++ b/lib/build/recipe/emailverification/api/generateEmailVerifyToken.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -24,7 +26,12 @@ async function generateEmailVerifyToken(apiImplementation, options, userContext) if (apiImplementation.generateEmailVerifyTokenPOST === undefined) { return false; } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [] }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [] }, + userContext + ); const result = await apiImplementation.generateEmailVerifyTokenPOST({ options, session: session, diff --git a/lib/build/recipe/emailverification/api/implementation.js b/lib/build/recipe/emailverification/api/implementation.js index d414da90c..b41d7d7b1 100644 --- a/lib/build/recipe/emailverification/api/implementation.js +++ b/lib/build/recipe/emailverification/api/implementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("../../../logger"); const recipe_1 = __importDefault(require("../recipe")); @@ -21,13 +23,15 @@ function getAPIInterface() { return verifyTokenResponse; } // status: "OK" - let newSession = await recipe_1.default.getInstanceOrThrowError().updateSessionIfRequiredPostEmailVerification({ - req: options.req, - res: options.res, - session, - recipeUserIdWhoseEmailGotVerified: verifyTokenResponse.user.recipeUserId, - userContext, - }); + let newSession = await recipe_1.default + .getInstanceOrThrowError() + .updateSessionIfRequiredPostEmailVerification({ + req: options.req, + res: options.res, + session, + recipeUserIdWhoseEmailGotVerified: verifyTokenResponse.user.recipeUserId, + userContext, + }); return { status: "OK", user: verifyTokenResponse.user, @@ -36,7 +40,9 @@ function getAPIInterface() { }, isEmailVerifiedGET: async function ({ userContext, session, options }) { // In this API, we will check if the session's recipe user id's email is verified or not. - const emailInfo = await recipe_1.default.getInstanceOrThrowError().getEmailForRecipeUserId(undefined, session.getRecipeUserId(userContext), userContext); + const emailInfo = await recipe_1.default + .getInstanceOrThrowError() + .getEmailForRecipeUserId(undefined, session.getRecipeUserId(userContext), userContext); if (emailInfo.status === "OK") { const isVerified = await options.recipeImplementation.isEmailVerified({ recipeUserId: session.getRecipeUserId(userContext), @@ -49,35 +55,34 @@ function getAPIInterface() { // whilst the first browser is polling this API - in this case, // we want to have the same effect to the session as if the // email was opened on the original browser itself. - let newSession = await recipe_1.default.getInstanceOrThrowError().updateSessionIfRequiredPostEmailVerification({ - req: options.req, - res: options.res, - session, - recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), - userContext, - }); + let newSession = await recipe_1.default + .getInstanceOrThrowError() + .updateSessionIfRequiredPostEmailVerification({ + req: options.req, + res: options.res, + session, + recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), + userContext, + }); return { status: "OK", isVerified: true, newSession, }; - } - else { + } else { await session.setClaimValue(emailVerificationClaim_1.EmailVerificationClaim, false, userContext); return { status: "OK", isVerified: false, }; } - } - else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { // We consider people without email addresses as validated return { status: "OK", isVerified: true, }; - } - else { + } else { // this means that the user ID is not known to supertokens. This could // happen if the current session's user ID is not an auth user, // or if it belong to a recipe user ID that got deleted. Either way, @@ -91,26 +96,31 @@ function getAPIInterface() { generateEmailVerifyTokenPOST: async function ({ options, userContext, session }) { // In this API, we generate the email verification token for session's recipe user ID. const tenantId = session.getTenantId(); - const emailInfo = await recipe_1.default.getInstanceOrThrowError().getEmailForRecipeUserId(undefined, session.getRecipeUserId(userContext), userContext); + const emailInfo = await recipe_1.default + .getInstanceOrThrowError() + .getEmailForRecipeUserId(undefined, session.getRecipeUserId(userContext), userContext); if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { - logger_1.logDebugMessage(`Email verification email not sent to user ${session - .getRecipeUserId(userContext) - .getAsString()} because it doesn't have an email address.`); + logger_1.logDebugMessage( + `Email verification email not sent to user ${session + .getRecipeUserId(userContext) + .getAsString()} because it doesn't have an email address.` + ); // this can happen if the user ID was found, but it has no email. In this // case, we treat it as a success case. - let newSession = await recipe_1.default.getInstanceOrThrowError().updateSessionIfRequiredPostEmailVerification({ - req: options.req, - res: options.res, - session, - recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), - userContext, - }); + let newSession = await recipe_1.default + .getInstanceOrThrowError() + .updateSessionIfRequiredPostEmailVerification({ + req: options.req, + res: options.res, + session, + recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), + userContext, + }); return { status: "EMAIL_ALREADY_VERIFIED_ERROR", newSession, }; - } - else if (emailInfo.status === "OK") { + } else if (emailInfo.status === "OK") { let response = await options.recipeImplementation.createEmailVerificationToken({ recipeUserId: session.getRecipeUserId(userContext), email: emailInfo.email, @@ -120,16 +130,20 @@ function getAPIInterface() { // In case the email is already verified, we do the same thing // as what happens in the verifyEmailPOST API post email verification (cause maybe the session is outdated). if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") { - logger_1.logDebugMessage(`Email verification email not sent to user ${session - .getRecipeUserId(userContext) - .getAsString()} because it is already verified.`); - let newSession = await recipe_1.default.getInstanceOrThrowError().updateSessionIfRequiredPostEmailVerification({ - req: options.req, - res: options.res, - session, - recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), - userContext, - }); + logger_1.logDebugMessage( + `Email verification email not sent to user ${session + .getRecipeUserId(userContext) + .getAsString()} because it is already verified.` + ); + let newSession = await recipe_1.default + .getInstanceOrThrowError() + .updateSessionIfRequiredPostEmailVerification({ + req: options.req, + res: options.res, + session, + recipeUserIdWhoseEmailGotVerified: session.getRecipeUserId(userContext), + userContext, + }); return { status: "EMAIL_ALREADY_VERIFIED_ERROR", newSession, @@ -163,13 +177,14 @@ function getAPIInterface() { return { status: "OK", }; - } - else { + } else { // this means that the user ID is not known to supertokens. This could // happen if the current session's user ID is not an auth user, // or if it belong to a recipe user ID that got deleted. Either way, // we logout the user. - logger_1.logDebugMessage("generateEmailVerifyTokenPOST: Returning UNAUTHORISED because the user id provided is unknown"); + logger_1.logDebugMessage( + "generateEmailVerifyTokenPOST: Returning UNAUTHORISED because the user id provided is unknown" + ); throw new error_1.default({ type: error_1.default.UNAUTHORISED, message: "Unknown User ID provided" }); } }, diff --git a/lib/build/recipe/emailverification/emailVerificationClaim.js b/lib/build/recipe/emailverification/emailVerificationClaim.js index af2b89ddd..092eddd10 100644 --- a/lib/build/recipe/emailverification/emailVerificationClaim.js +++ b/lib/build/recipe/emailverification/emailVerificationClaim.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EmailVerificationClaim = exports.EmailVerificationClaimClass = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -22,35 +24,38 @@ class EmailVerificationClaimClass extends claims_1.BooleanClaim { email: emailInfo.email, userContext, }); - } - else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { // We consider people without email addresses as validated return true; - } - else { + } else { throw new Error("UNKNOWN_USER_ID"); } }, }); - this.validators = Object.assign(Object.assign({}, this.validators), { isVerified: (refetchTimeOnFalseInSeconds = 10, maxAgeInSeconds) => (Object.assign(Object.assign({}, this.validators.hasValue(true, maxAgeInSeconds)), { shouldRefetch: (payload, userContext) => { - const value = this.getValueFromPayload(payload, userContext); - if (value === undefined) { - return true; - } - const currentTime = Date.now(); - const lastRefetchTime = this.getLastRefetchTime(payload, userContext); - if (maxAgeInSeconds !== undefined) { - if (lastRefetchTime < currentTime - maxAgeInSeconds * 1000) { + this.validators = Object.assign(Object.assign({}, this.validators), { + isVerified: (refetchTimeOnFalseInSeconds = 10, maxAgeInSeconds) => + Object.assign(Object.assign({}, this.validators.hasValue(true, maxAgeInSeconds)), { + shouldRefetch: (payload, userContext) => { + const value = this.getValueFromPayload(payload, userContext); + if (value === undefined) { return true; } - } - if (value === false) { - if (lastRefetchTime < currentTime - refetchTimeOnFalseInSeconds * 1000) { - return true; + const currentTime = Date.now(); + const lastRefetchTime = this.getLastRefetchTime(payload, userContext); + if (maxAgeInSeconds !== undefined) { + if (lastRefetchTime < currentTime - maxAgeInSeconds * 1000) { + return true; + } } - } - return false; - } })) }); + if (value === false) { + if (lastRefetchTime < currentTime - refetchTimeOnFalseInSeconds * 1000) { + return true; + } + } + return false; + }, + }), + }); } } exports.EmailVerificationClaimClass = EmailVerificationClaimClass; diff --git a/lib/build/recipe/emailverification/emailVerificationFunctions.d.ts b/lib/build/recipe/emailverification/emailVerificationFunctions.d.ts index 8128246cb..2eb1c0427 100644 --- a/lib/build/recipe/emailverification/emailVerificationFunctions.d.ts +++ b/lib/build/recipe/emailverification/emailVerificationFunctions.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { UserEmailInfo } from "./types"; import { NormalisedAppinfo } from "../../types"; -export declare function createAndSendEmailUsingSupertokensService(appInfo: NormalisedAppinfo, user: UserEmailInfo, emailVerifyURLWithToken: string): Promise; +export declare function createAndSendEmailUsingSupertokensService( + appInfo: NormalisedAppinfo, + user: UserEmailInfo, + emailVerifyURLWithToken: string +): Promise; diff --git a/lib/build/recipe/emailverification/emailVerificationFunctions.js b/lib/build/recipe/emailverification/emailVerificationFunctions.js index d22a0dd6a..fb6b03bfc 100644 --- a/lib/build/recipe/emailverification/emailVerificationFunctions.js +++ b/lib/build/recipe/emailverification/emailVerificationFunctions.js @@ -20,16 +20,21 @@ async function createAndSendEmailUsingSupertokensService(appInfo, user, emailVer if (utils_1.isTestEnv()) { return; } - await utils_1.postWithFetch("https://api.supertokens.io/0/st/auth/email/verify", { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, { - email: user.email, - appName: appInfo.appName, - emailVerifyURL: emailVerifyURLWithToken, - }, { - successLog: `Email sent to ${user.email}`, - errorLogHeader: "Error sending verification email", - }); + await utils_1.postWithFetch( + "https://api.supertokens.io/0/st/auth/email/verify", + { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, + { + email: user.email, + appName: appInfo.appName, + emailVerifyURL: emailVerifyURLWithToken, + }, + { + successLog: `Email sent to ${user.email}`, + errorLogHeader: "Error sending verification email", + } + ); } exports.createAndSendEmailUsingSupertokensService = createAndSendEmailUsingSupertokensService; diff --git a/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.d.ts index f95580eb6..d5c7479ac 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.d.ts @@ -2,11 +2,14 @@ import { TypeEmailVerificationEmailDeliveryInput } from "../../../types"; import { NormalisedAppinfo, UserContext } from "../../../../../types"; import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; -export default class BackwardCompatibilityService implements EmailDeliveryInterface { +export default class BackwardCompatibilityService + implements EmailDeliveryInterface { private appInfo; private isInServerlessEnv; constructor(appInfo: NormalisedAppinfo, isInServerlessEnv: boolean); - sendEmail: (input: TypeEmailVerificationEmailDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: TypeEmailVerificationEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.js index fccc4aecc..7fc3cc6f6 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/backwardCompatibility/index.js @@ -6,14 +6,18 @@ class BackwardCompatibilityService { this.sendEmail = async (input) => { try { if (!this.isInServerlessEnv) { - emailVerificationFunctions_1.createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.emailVerifyLink).catch((_) => { }); - } - else { + emailVerificationFunctions_1 + .createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.emailVerifyLink) + .catch((_) => {}); + } else { // see https://github.com/supertokens/supertokens-node/pull/135 - await emailVerificationFunctions_1.createAndSendEmailUsingSupertokensService(this.appInfo, input.user, input.emailVerifyLink); + await emailVerificationFunctions_1.createAndSendEmailUsingSupertokensService( + this.appInfo, + input.user, + input.emailVerifyLink + ); } - } - catch (_) { } + } catch (_) {} }; this.appInfo = appInfo; this.isInServerlessEnv = isInServerlessEnv; diff --git a/lib/build/recipe/emailverification/emaildelivery/services/index.js b/lib/build/recipe/emailverification/emaildelivery/services/index.js index d648973c6..91700aeaf 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/index.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMTPService = void 0; const smtp_1 = __importDefault(require("./smtp")); diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/emailVerify.js b/lib/build/recipe/emailverification/emaildelivery/services/smtp/emailVerify.js index 000d552f8..5c7406a82 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/emailVerify.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/emailVerify.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEmailVerifyEmailHTML = void 0; const supertokens_1 = __importDefault(require("../../../../../supertokens")); diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.d.ts index 7693cfff6..cca4977ab 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.d.ts +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.d.ts @@ -6,7 +6,9 @@ import { UserContext } from "../../../../../types"; export default class SMTPService implements EmailDeliveryInterface { serviceImpl: ServiceInterface; constructor(config: TypeInput); - sendEmail: (input: TypeEmailVerificationEmailDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: TypeEmailVerificationEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.js b/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.js index da1ee9c8a..dedcbe33f 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const nodemailer_1 = require("nodemailer"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); @@ -10,7 +12,9 @@ class SMTPService { constructor(config) { this.sendEmail = async (input) => { let content = await this.serviceImpl.getContent(input); - await this.serviceImpl.sendRawEmail(Object.assign(Object.assign({}, content), { userContext: input.userContext })); + await this.serviceImpl.sendRawEmail( + Object.assign(Object.assign({}, content), { userContext: input.userContext }) + ); }; const transporter = nodemailer_1.createTransport({ host: config.smtpSettings.host, @@ -21,7 +25,9 @@ class SMTPService { }, secure: config.smtpSettings.secure, }); - let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from)); + let builder = new supertokens_js_override_1.default( + serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) + ); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.d.ts b/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.d.ts index d072890a9..3f668725f 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.d.ts +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.d.ts @@ -2,7 +2,10 @@ import { TypeEmailVerificationEmailDeliveryInput } from "../../../types"; import { Transporter } from "nodemailer"; import { ServiceInterface } from "../../../../../ingredients/emaildelivery/services/smtp"; -export declare function getServiceImplementation(transporter: Transporter, from: { - name: string; - email: string; -}): ServiceInterface; +export declare function getServiceImplementation( + transporter: Transporter, + from: { + name: string; + email: string; + } +): ServiceInterface; diff --git a/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.js b/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.js index 05fc06879..3160ed2a7 100644 --- a/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.js +++ b/lib/build/recipe/emailverification/emaildelivery/services/smtp/serviceImplementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const emailVerify_1 = __importDefault(require("./emailVerify")); @@ -29,8 +31,7 @@ function getServiceImplementation(transporter, from) { subject: input.subject, html: input.body, }); - } - else { + } else { await transporter.sendMail({ from: `${from.name} <${from.email}>`, to: input.toEmail, diff --git a/lib/build/recipe/emailverification/error.d.ts b/lib/build/recipe/emailverification/error.d.ts index d6412505c..486758b61 100644 --- a/lib/build/recipe/emailverification/error.d.ts +++ b/lib/build/recipe/emailverification/error.d.ts @@ -1,8 +1,5 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - constructor(options: { - type: "BAD_INPUT_ERROR"; - message: string; - }); + constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); } diff --git a/lib/build/recipe/emailverification/error.js b/lib/build/recipe/emailverification/error.js index 4f3bedcb7..b0baf2c94 100644 --- a/lib/build/recipe/emailverification/error.js +++ b/lib/build/recipe/emailverification/error.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/emailverification/index.d.ts b/lib/build/recipe/emailverification/index.d.ts index 8f90db852..d502537b3 100644 --- a/lib/build/recipe/emailverification/index.d.ts +++ b/lib/build/recipe/emailverification/index.d.ts @@ -1,45 +1,99 @@ // @ts-nocheck import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { RecipeInterface, APIOptions, APIInterface, UserEmailInfo, TypeEmailVerificationEmailDeliveryInput } from "./types"; +import { + RecipeInterface, + APIOptions, + APIInterface, + UserEmailInfo, + TypeEmailVerificationEmailDeliveryInput, +} from "./types"; import RecipeUserId from "../../recipeUserId"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; static EmailVerificationClaim: import("./emailVerificationClaim").EmailVerificationClaimClass; - static createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ - status: "OK"; - token: string; - } | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - }>; - static createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ - status: "OK"; - link: string; - } | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - }>; - static sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ - status: "OK"; - } | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - }>; - static verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record): Promise<{ - status: "OK"; - user: UserEmailInfo; - } | { - status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; - }>; - static isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise; - static revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ + static createEmailVerificationToken( + tenantId: string, + recipeUserId: RecipeUserId, + email?: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + token: string; + } + | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + } + >; + static createEmailVerificationLink( + tenantId: string, + recipeUserId: RecipeUserId, + email?: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + link: string; + } + | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + } + >; + static sendEmailVerificationEmail( + tenantId: string, + userId: string, + recipeUserId: RecipeUserId, + email?: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + } + | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + } + >; + static verifyEmailUsingToken( + tenantId: string, + token: string, + attemptAccountLinking?: boolean, + userContext?: Record + ): Promise< + | { + status: "OK"; + user: UserEmailInfo; + } + | { + status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; + } + >; + static isEmailVerified( + recipeUserId: RecipeUserId, + email?: string, + userContext?: Record + ): Promise; + static revokeEmailVerificationTokens( + tenantId: string, + recipeUserId: RecipeUserId, + email?: string, + userContext?: Record + ): Promise<{ status: string; }>; - static unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record): Promise<{ + static unverifyEmail( + recipeUserId: RecipeUserId, + email?: string, + userContext?: Record + ): Promise<{ status: string; }>; - static sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { - userContext?: Record; - }): Promise; + static sendEmail( + input: TypeEmailVerificationEmailDeliveryInput & { + userContext?: Record; + } + ): Promise; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/emailverification/index.js b/lib/build/recipe/emailverification/index.js index 12e01e62c..af145da81 100644 --- a/lib/build/recipe/emailverification/index.js +++ b/lib/build/recipe/emailverification/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EmailVerificationClaim = exports.sendEmail = exports.unverifyEmail = exports.revokeEmailVerificationTokens = exports.isEmailVerified = exports.verifyEmailUsingToken = exports.sendEmailVerificationEmail = exports.createEmailVerificationLink = exports.createEmailVerificationToken = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -32,13 +34,11 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } - else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { return { status: "EMAIL_ALREADY_VERIFIED_ERROR", }; - } - else { + } else { throw new global.Error("Unknown User ID provided without email"); } } @@ -77,13 +77,11 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } - else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { return { status: "EMAIL_ALREADY_VERIFIED_ERROR", }; - } - else { + } else { throw new global.Error("Unknown User ID provided without email"); } } @@ -123,11 +121,9 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } - else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { return true; - } - else { + } else { throw new global.Error("Unknown User ID provided without email"); } } @@ -147,16 +143,14 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } - else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { // This only happens for phone based passwordless users (or if the user added a custom getEmailForUserId) // We can return OK here, since there is no way to create an email verification token // if getEmailForUserId returns EMAIL_DOES_NOT_EXIST_ERROR. return { status: "OK", }; - } - else { + } else { throw new global.Error("Unknown User ID provided without email"); } } @@ -174,14 +168,12 @@ class Wrapper { const emailInfo = await recipeInstance.getEmailForRecipeUserId(undefined, recipeUserId, ctx); if (emailInfo.status === "OK") { email = emailInfo.email; - } - else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { + } else if (emailInfo.status === "EMAIL_DOES_NOT_EXIST_ERROR") { // Here we are returning OK since that's how it used to work, but a later call to isVerified will still return true return { status: "OK", }; - } - else { + } else { throw new global.Error("Unknown User ID provided without email"); } } @@ -193,7 +185,9 @@ class Wrapper { } static async sendEmail(input) { let recipeInstance = recipe_1.default.getInstanceOrThrowError(); - return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail(Object.assign(Object.assign({}, input), { userContext: utils_2.getUserContext(input.userContext) })); + return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail( + Object.assign(Object.assign({}, input), { userContext: utils_2.getUserContext(input.userContext) }) + ); } } exports.default = Wrapper; @@ -211,4 +205,9 @@ exports.revokeEmailVerificationTokens = Wrapper.revokeEmailVerificationTokens; exports.unverifyEmail = Wrapper.unverifyEmail; exports.sendEmail = Wrapper.sendEmail; var emailVerificationClaim_2 = require("./emailVerificationClaim"); -Object.defineProperty(exports, "EmailVerificationClaim", { enumerable: true, get: function () { return emailVerificationClaim_2.EmailVerificationClaim; } }); +Object.defineProperty(exports, "EmailVerificationClaim", { + enumerable: true, + get: function () { + return emailVerificationClaim_2.EmailVerificationClaim; + }, +}); diff --git a/lib/build/recipe/emailverification/recipe.d.ts b/lib/build/recipe/emailverification/recipe.d.ts index 1244c211e..fb0b8c778 100644 --- a/lib/build/recipe/emailverification/recipe.d.ts +++ b/lib/build/recipe/emailverification/recipe.d.ts @@ -17,15 +17,29 @@ export default class Recipe extends RecipeModule { apiImpl: APIInterface; isInServerlessEnv: boolean; emailDelivery: EmailDeliveryIngredient; - constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput, ingredients: { - emailDelivery: EmailDeliveryIngredient | undefined; - }); + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput, + ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + } + ); static getInstanceOrThrowError(): Recipe; static getInstance(): Recipe | undefined; static init(config: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _: NormalisedURLPath, + __: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/emailverification/recipe.js b/lib/build/recipe/emailverification/recipe.js index 45eb92844..3aefa5818 100644 --- a/lib/build/recipe/emailverification/recipe.js +++ b/lib/build/recipe/emailverification/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -45,7 +47,9 @@ class Recipe extends recipeModule_1.default { return [ { method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.GENERATE_EMAIL_VERIFY_TOKEN_API), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + constants_1.GENERATE_EMAIL_VERIFY_TOKEN_API + ), id: constants_1.GENERATE_EMAIL_VERIFY_TOKEN_API, disabled: this.apiImpl.generateEmailVerifyTokenPOST === undefined, }, @@ -76,8 +80,7 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.GENERATE_EMAIL_VERIFY_TOKEN_API) { return await generateEmailVerifyToken_1.default(this.apiImpl, options, userContext); - } - else { + } else { return await emailVerify_1.default(this.apiImpl, tenantId, options, userContext); } }; @@ -113,8 +116,7 @@ class Recipe extends recipeModule_1.default { email: currLM.email, status: "OK", }; - } - else { + } else { return { status: "EMAIL_DOES_NOT_EXIST_ERROR", }; @@ -147,7 +149,10 @@ class Recipe extends recipeModule_1.default { return primaryUser === null || primaryUser === void 0 ? void 0 : primaryUser.id; }; this.updateSessionIfRequiredPostEmailVerification = async (input) => { - let primaryUserId = await this.getPrimaryUserIdForRecipeUser(input.recipeUserIdWhoseEmailGotVerified, input.userContext); + let primaryUserId = await this.getPrimaryUserIdForRecipeUser( + input.recipeUserIdWhoseEmailGotVerified, + input.userContext + ); // if a session exists in the API, then we can update the session // claim related to email verification if (input.session !== undefined) { @@ -163,14 +168,20 @@ class Recipe extends recipeModule_1.default { // --> (Case 4) This is post login account linking, in which the account that got verified // got linked to ANOTHER primary account (user ID of account has changed to a different user ID != session.getUserId, but // we should ignore this since it will result in the user's session changing.) - if (input.session.getRecipeUserId(input.userContext).getAsString() === - input.recipeUserIdWhoseEmailGotVerified.getAsString()) { - logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification the session belongs to the verified user"); + if ( + input.session.getRecipeUserId(input.userContext).getAsString() === + input.recipeUserIdWhoseEmailGotVerified.getAsString() + ) { + logger_1.logDebugMessage( + "updateSessionIfRequiredPostEmailVerification the session belongs to the verified user" + ); // this means that the session's login method's account is the // one that just got verified and that we are NOT doing post login // account linking. So this is only for (Case 1) and (Case 2) if (input.session.getUserId() === primaryUserId) { - logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification the session userId matches the primary user id, so we are only refreshing the claim"); + logger_1.logDebugMessage( + "updateSessionIfRequiredPostEmailVerification the session userId matches the primary user id, so we are only refreshing the claim" + ); // if the session's primary user ID is equal to the // primary user ID that the account was linked to, then // this means that the new account became a primary user (Case 1) @@ -181,9 +192,11 @@ class Recipe extends recipeModule_1.default { try { // EmailVerificationClaim will be based on the recipeUserId // and not the primary user ID. - await input.session.fetchAndSetClaim(emailVerificationClaim_1.EmailVerificationClaim, input.userContext); - } - catch (err) { + await input.session.fetchAndSetClaim( + emailVerificationClaim_1.EmailVerificationClaim, + input.userContext + ); + } catch (err) { // This should never happen, since we've just set the status above. if (err.message === "UNKNOWN_USER_ID") { throw new error_2.default({ @@ -194,9 +207,10 @@ class Recipe extends recipeModule_1.default { throw err; } return; - } - else { - logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification the session user id doesn't match the primary user id, so we are revoking all sessions and creating a new one"); + } else { + logger_1.logDebugMessage( + "updateSessionIfRequiredPostEmailVerification the session user id doesn't match the primary user id, so we are revoking all sessions and creating a new one" + ); // if the session's primary user ID is NOT equal to the // primary user ID that the account that it was linked to, then // this means that the new account got linked to another primary user (Case 2) @@ -204,13 +218,27 @@ class Recipe extends recipeModule_1.default { // a new session // Revoke all session belonging to session.getRecipeUserId() // We do not really need to do this, but we do it anyway.. no harm. - await session_1.default.revokeAllSessionsForUser(input.recipeUserIdWhoseEmailGotVerified.getAsString(), false, undefined, input.userContext); + await session_1.default.revokeAllSessionsForUser( + input.recipeUserIdWhoseEmailGotVerified.getAsString(), + false, + undefined, + input.userContext + ); // create a new session and return that.. - return await session_1.default.createNewSession(input.req, input.res, input.session.getTenantId(), input.session.getRecipeUserId(input.userContext), {}, {}, input.userContext); + return await session_1.default.createNewSession( + input.req, + input.res, + input.session.getTenantId(), + input.session.getRecipeUserId(input.userContext), + {}, + {}, + input.userContext + ); } - } - else { - logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification the verified user doesn't match the session"); + } else { + logger_1.logDebugMessage( + "updateSessionIfRequiredPostEmailVerification the verified user doesn't match the session" + ); // this means that the session's login method's account was NOT the // one that just got verified and that we ARE doing post login // account linking. So this is only for (Case 3) and (Case 4) @@ -220,8 +248,7 @@ class Recipe extends recipeModule_1.default { // linked user's account). return undefined; } - } - else { + } else { logger_1.logDebugMessage("updateSessionIfRequiredPostEmailVerification got no session"); // the session is updated when the is email verification GET API is called // so we don't do anything in this API. @@ -231,7 +258,12 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.getEmailForRecipeUserId)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default( + querier_1.Querier.getNewInstanceOrThrowError(recipeId), + this.getEmailForRecipeUserId + ) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -263,15 +295,22 @@ class Recipe extends recipeModule_1.default { emailDelivery: undefined, }); postSuperTokensInitCallbacks_1.PostSuperTokensInitCallbacks.addPostInitCallback(() => { - recipe_1.default.getInstanceOrThrowError().addClaimFromOtherRecipe(emailVerificationClaim_1.EmailVerificationClaim); + recipe_1.default + .getInstanceOrThrowError() + .addClaimFromOtherRecipe(emailVerificationClaim_1.EmailVerificationClaim); if (config.mode === "REQUIRED") { - recipe_1.default.getInstanceOrThrowError().addClaimValidatorFromOtherRecipe(emailVerificationClaim_1.EmailVerificationClaim.validators.isVerified()); + recipe_1.default + .getInstanceOrThrowError() + .addClaimValidatorFromOtherRecipe( + emailVerificationClaim_1.EmailVerificationClaim.validators.isVerified() + ); } }); return Recipe.instance; - } - else { - throw new Error("Emailverification recipe has already been initialised. Please check your code for bugs."); + } else { + throw new Error( + "Emailverification recipe has already been initialised. Please check your code for bugs." + ); } }; } diff --git a/lib/build/recipe/emailverification/recipeImplementation.d.ts b/lib/build/recipe/emailverification/recipeImplementation.d.ts index 57ed3970a..625b1087e 100644 --- a/lib/build/recipe/emailverification/recipeImplementation.d.ts +++ b/lib/build/recipe/emailverification/recipeImplementation.d.ts @@ -2,4 +2,7 @@ import { RecipeInterface } from "./"; import { Querier } from "../../querier"; import { GetEmailForRecipeUserIdFunc } from "./types"; -export default function getRecipeInterface(querier: Querier, getEmailForRecipeUserId: GetEmailForRecipeUserIdFunc): RecipeInterface; +export default function getRecipeInterface( + querier: Querier, + getEmailForRecipeUserId: GetEmailForRecipeUserIdFunc +): RecipeInterface; diff --git a/lib/build/recipe/emailverification/recipeImplementation.js b/lib/build/recipe/emailverification/recipeImplementation.js index a07b06793..347bc8f44 100644 --- a/lib/build/recipe/emailverification/recipeImplementation.js +++ b/lib/build/recipe/emailverification/recipeImplementation.js @@ -1,35 +1,44 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const __1 = require("../.."); function getRecipeInterface(querier, getEmailForRecipeUserId) { return { - createEmailVerificationToken: async function ({ recipeUserId, email, tenantId, userContext, }) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/user/email/verify/token`), { - userId: recipeUserId.getAsString(), - email, - }, userContext); + createEmailVerificationToken: async function ({ recipeUserId, email, tenantId, userContext }) { + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${tenantId}/recipe/user/email/verify/token`), + { + userId: recipeUserId.getAsString(), + email, + }, + userContext + ); if (response.status === "OK") { return { status: "OK", token: response.token, }; - } - else { + } else { return { status: "EMAIL_ALREADY_VERIFIED_ERROR", }; } }, - verifyEmailUsingToken: async function ({ token, attemptAccountLinking, tenantId, userContext, }) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/user/email/verify`), { - method: "token", - token, - }, userContext); + verifyEmailUsingToken: async function ({ token, attemptAccountLinking, tenantId, userContext }) { + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${tenantId}/recipe/user/email/verify`), + { + method: "token", + token, + }, + userContext + ); if (response.status === "OK") { const recipeUserId = new recipeUserId_1.default(response.userId); if (attemptAccountLinking) { @@ -61,32 +70,43 @@ function getRecipeInterface(querier, getEmailForRecipeUserId) { email: response.email, }, }; - } - else { + } else { return { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR", }; } }, - isEmailVerified: async function ({ recipeUserId, email, userContext, }) { - let response = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/user/email/verify"), { - userId: recipeUserId.getAsString(), - email, - }, userContext); + isEmailVerified: async function ({ recipeUserId, email, userContext }) { + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/user/email/verify"), + { + userId: recipeUserId.getAsString(), + email, + }, + userContext + ); return response.isVerified; }, revokeEmailVerificationTokens: async function (input) { - await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/user/email/verify/token/remove`), { - userId: input.recipeUserId.getAsString(), - email: input.email, - }, input.userContext); + await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/user/email/verify/token/remove`), + { + userId: input.recipeUserId.getAsString(), + email: input.email, + }, + input.userContext + ); return { status: "OK" }; }, unverifyEmail: async function (input) { - await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/user/email/verify/remove"), { - userId: input.recipeUserId.getAsString(), - email: input.email, - }, input.userContext); + await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/user/email/verify/remove"), + { + userId: input.recipeUserId.getAsString(), + email: input.email, + }, + input.userContext + ); return { status: "OK" }; }, }; diff --git a/lib/build/recipe/emailverification/types.d.ts b/lib/build/recipe/emailverification/types.d.ts index cc8269b5d..fb0e7c24d 100644 --- a/lib/build/recipe/emailverification/types.d.ts +++ b/lib/build/recipe/emailverification/types.d.ts @@ -1,7 +1,10 @@ // @ts-nocheck import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; -import { TypeInput as EmailDeliveryTypeInput, TypeInputWithService as EmailDeliveryTypeInputWithService } from "../../ingredients/emaildelivery/types"; +import { + TypeInput as EmailDeliveryTypeInput, + TypeInputWithService as EmailDeliveryTypeInputWithService, +} from "../../ingredients/emaildelivery/types"; import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; import { GeneralErrorResponse, NormalisedAppinfo, UserContext } from "../../types"; import { SessionContainerInterface } from "../session/types"; @@ -10,28 +13,48 @@ import { User } from "../../types"; export declare type TypeInput = { mode: "REQUIRED" | "OPTIONAL"; emailDelivery?: EmailDeliveryTypeInput; - getEmailForRecipeUserId?: (recipeUserId: RecipeUserId, userContext: UserContext) => Promise<{ - status: "OK"; - email: string; - } | { - status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; - }>; + getEmailForRecipeUserId?: ( + recipeUserId: RecipeUserId, + userContext: UserContext + ) => Promise< + | { + status: "OK"; + email: string; + } + | { + status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; + } + >; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { mode: "REQUIRED" | "OPTIONAL"; - getEmailDeliveryConfig: (isInServerlessEnv: boolean) => EmailDeliveryTypeInputWithService; - getEmailForRecipeUserId?: (recipeUserId: RecipeUserId, userContext: UserContext) => Promise<{ - status: "OK"; - email: string; - } | { - status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; - }>; + getEmailDeliveryConfig: ( + isInServerlessEnv: boolean + ) => EmailDeliveryTypeInputWithService; + getEmailForRecipeUserId?: ( + recipeUserId: RecipeUserId, + userContext: UserContext + ) => Promise< + | { + status: "OK"; + email: string; + } + | { + status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; + } + >; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -45,28 +68,30 @@ export declare type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - token: string; - } | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + token: string; + } + | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + } + >; verifyEmailUsingToken(input: { token: string; attemptAccountLinking: boolean; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: UserEmailInfo; - } | { - status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; - }>; - isEmailVerified(input: { - recipeUserId: RecipeUserId; - email: string; - userContext: UserContext; - }): Promise; + }): Promise< + | { + status: "OK"; + user: UserEmailInfo; + } + | { + status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; + } + >; + isEmailVerified(input: { recipeUserId: RecipeUserId; email: string; userContext: UserContext }): Promise; revokeEmailVerificationTokens(input: { recipeUserId: RecipeUserId; email: string; @@ -94,38 +119,55 @@ export declare type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; export declare type APIInterface = { - verifyEmailPOST: undefined | ((input: { - token: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - session: SessionContainerInterface | undefined; - }) => Promise<{ - status: "OK"; - user: UserEmailInfo; - newSession?: SessionContainerInterface; - } | { - status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; - } | GeneralErrorResponse>); - isEmailVerifiedGET: undefined | ((input: { - options: APIOptions; - userContext: UserContext; - session: SessionContainerInterface; - }) => Promise<{ - status: "OK"; - isVerified: boolean; - newSession?: SessionContainerInterface; - } | GeneralErrorResponse>); - generateEmailVerifyTokenPOST: undefined | ((input: { - options: APIOptions; - userContext: UserContext; - session: SessionContainerInterface; - }) => Promise<{ - status: "OK"; - } | { - status: "EMAIL_ALREADY_VERIFIED_ERROR"; - newSession?: SessionContainerInterface; - } | GeneralErrorResponse>); + verifyEmailPOST: + | undefined + | ((input: { + token: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + session: SessionContainerInterface | undefined; + }) => Promise< + | { + status: "OK"; + user: UserEmailInfo; + newSession?: SessionContainerInterface; + } + | { + status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"; + } + | GeneralErrorResponse + >); + isEmailVerifiedGET: + | undefined + | ((input: { + options: APIOptions; + userContext: UserContext; + session: SessionContainerInterface; + }) => Promise< + | { + status: "OK"; + isVerified: boolean; + newSession?: SessionContainerInterface; + } + | GeneralErrorResponse + >); + generateEmailVerifyTokenPOST: + | undefined + | ((input: { + options: APIOptions; + userContext: UserContext; + session: SessionContainerInterface; + }) => Promise< + | { + status: "OK"; + } + | { + status: "EMAIL_ALREADY_VERIFIED_ERROR"; + newSession?: SessionContainerInterface; + } + | GeneralErrorResponse + >); }; export declare type TypeEmailVerificationEmailDeliveryInput = { type: "EMAIL_VERIFICATION"; @@ -137,9 +179,16 @@ export declare type TypeEmailVerificationEmailDeliveryInput = { emailVerifyLink: string; tenantId: string; }; -export declare type GetEmailForRecipeUserIdFunc = (user: User | undefined, recipeUserId: RecipeUserId, userContext: UserContext) => Promise<{ - status: "OK"; - email: string; -} | { - status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; -}>; +export declare type GetEmailForRecipeUserIdFunc = ( + user: User | undefined, + recipeUserId: RecipeUserId, + userContext: UserContext +) => Promise< + | { + status: "OK"; + email: string; + } + | { + status: "EMAIL_DOES_NOT_EXIST_ERROR" | "UNKNOWN_USER_ID_ERROR"; + } +>; diff --git a/lib/build/recipe/emailverification/utils.d.ts b/lib/build/recipe/emailverification/utils.d.ts index fdfc82551..60043dc8a 100644 --- a/lib/build/recipe/emailverification/utils.d.ts +++ b/lib/build/recipe/emailverification/utils.d.ts @@ -3,7 +3,11 @@ import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; import { NormalisedAppinfo, UserContext } from "../../types"; import { BaseRequest } from "../../framework"; -export declare function validateAndNormaliseUserInput(_: Recipe, appInfo: NormalisedAppinfo, config: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + _: Recipe, + appInfo: NormalisedAppinfo, + config: TypeInput +): TypeNormalisedInput; export declare function getEmailVerifyLink(input: { appInfo: NormalisedAppinfo; token: string; diff --git a/lib/build/recipe/emailverification/utils.js b/lib/build/recipe/emailverification/utils.js index cb1e28ec6..35d938da9 100644 --- a/lib/build/recipe/emailverification/utils.js +++ b/lib/build/recipe/emailverification/utils.js @@ -13,14 +13,22 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEmailVerifyLink = exports.validateAndNormaliseUserInput = void 0; const backwardCompatibility_1 = __importDefault(require("./emaildelivery/services/backwardCompatibility")); function validateAndNormaliseUserInput(_, appInfo, config) { - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config.override + ); function getEmailDeliveryConfig(isInServerlessEnv) { var _a; let emailService = (_a = config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; @@ -32,7 +40,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { if (emailService === undefined) { emailService = new backwardCompatibility_1.default(appInfo, isInServerlessEnv); } - return Object.assign(Object.assign({}, config.emailDelivery), { + return Object.assign(Object.assign({}, config.emailDelivery), { /** * if we do * let emailDelivery = { @@ -44,7 +52,8 @@ function validateAndNormaliseUserInput(_, appInfo, config) { * it it again get set to undefined, so we * set service at the end */ - service: emailService }); + service: emailService, + }); } return { mode: config.mode, @@ -55,17 +64,19 @@ function validateAndNormaliseUserInput(_, appInfo, config) { } exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; function getEmailVerifyLink(input) { - return (input.appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() + + return ( + input.appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + input.appInfo.websiteBasePath.getAsStringDangerous() + "/verify-email" + "?token=" + input.token + "&tenantId=" + - input.tenantId); + input.tenantId + ); } exports.getEmailVerifyLink = getEmailVerifyLink; diff --git a/lib/build/recipe/jwt/api/getJWKS.d.ts b/lib/build/recipe/jwt/api/getJWKS.d.ts index b1fd38fd9..92997a1a4 100644 --- a/lib/build/recipe/jwt/api/getJWKS.d.ts +++ b/lib/build/recipe/jwt/api/getJWKS.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { UserContext } from "../../../types"; import { APIInterface, APIOptions } from "../types"; -export default function getJWKS(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function getJWKS( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/jwt/api/getJWKS.js b/lib/build/recipe/jwt/api/getJWKS.js index 184eae22e..d4d77a9eb 100644 --- a/lib/build/recipe/jwt/api/getJWKS.js +++ b/lib/build/recipe/jwt/api/getJWKS.js @@ -25,8 +25,7 @@ async function getJWKS(apiImplementation, options, userContext) { }); if ("status" in result && result.status === "GENERAL_ERROR") { utils_1.send200Response(options.res, result); - } - else { + } else { options.res.setHeader("Access-Control-Allow-Origin", "*", false); utils_1.send200Response(options.res, result); } diff --git a/lib/build/recipe/jwt/api/implementation.js b/lib/build/recipe/jwt/api/implementation.js index 34f279ba5..e52174f38 100644 --- a/lib/build/recipe/jwt/api/implementation.js +++ b/lib/build/recipe/jwt/api/implementation.js @@ -16,7 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); function getAPIImplementation() { return { - getJWKSGET: async function ({ options, userContext, }) { + getJWKSGET: async function ({ options, userContext }) { const resp = await options.recipeImplementation.getJWKS({ userContext }); if (resp.validityInSeconds !== undefined) { options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false); diff --git a/lib/build/recipe/jwt/index.d.ts b/lib/build/recipe/jwt/index.d.ts index 5c41018e9..ed6ab4c4e 100644 --- a/lib/build/recipe/jwt/index.d.ts +++ b/lib/build/recipe/jwt/index.d.ts @@ -3,13 +3,23 @@ import Recipe from "./recipe"; import { APIInterface, RecipeInterface, APIOptions, JsonWebKey } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record): Promise<{ - status: "OK"; - jwt: string; - } | { - status: "UNSUPPORTED_ALGORITHM_ERROR"; - }>; - static getJWKS(userContext?: Record): Promise<{ + static createJWT( + payload: any, + validitySeconds?: number, + useStaticSigningKey?: boolean, + userContext?: Record + ): Promise< + | { + status: "OK"; + jwt: string; + } + | { + status: "UNSUPPORTED_ALGORITHM_ERROR"; + } + >; + static getJWKS( + userContext?: Record + ): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number | undefined; }>; diff --git a/lib/build/recipe/jwt/index.js b/lib/build/recipe/jwt/index.js index 6e496a60b..8ba933f81 100644 --- a/lib/build/recipe/jwt/index.js +++ b/lib/build/recipe/jwt/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getJWKS = exports.createJWT = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/jwt/recipe.d.ts b/lib/build/recipe/jwt/recipe.d.ts index d9eeb862b..076cc7c64 100644 --- a/lib/build/recipe/jwt/recipe.d.ts +++ b/lib/build/recipe/jwt/recipe.d.ts @@ -17,7 +17,15 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled(): APIHandled[]; - handleAPIRequest: (_id: string, _tenantId: string | undefined, req: BaseRequest, res: BaseResponse, _path: normalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + _id: string, + _tenantId: string | undefined, + req: BaseRequest, + res: BaseResponse, + _path: normalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; diff --git a/lib/build/recipe/jwt/recipe.js b/lib/build/recipe/jwt/recipe.js index 086c795b4..bb92d24a8 100644 --- a/lib/build/recipe/jwt/recipe.js +++ b/lib/build/recipe/jwt/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -45,7 +47,13 @@ class Recipe extends recipeModule_1.default { this.config = utils_2.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default( + querier_1.Querier.getNewInstanceOrThrowError(recipeId), + this.config, + appInfo + ) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -65,8 +73,7 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } - else { + } else { throw new Error("JWT recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/jwt/recipeImplementation.d.ts b/lib/build/recipe/jwt/recipeImplementation.d.ts index 55dd3d95e..5109fbcb1 100644 --- a/lib/build/recipe/jwt/recipeImplementation.d.ts +++ b/lib/build/recipe/jwt/recipeImplementation.d.ts @@ -2,4 +2,8 @@ import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; import { RecipeInterface, TypeNormalisedInput } from "./types"; -export default function getRecipeInterface(querier: Querier, config: TypeNormalisedInput, appInfo: NormalisedAppinfo): RecipeInterface; +export default function getRecipeInterface( + querier: Querier, + config: TypeNormalisedInput, + appInfo: NormalisedAppinfo +): RecipeInterface; diff --git a/lib/build/recipe/jwt/recipeImplementation.js b/lib/build/recipe/jwt/recipeImplementation.js index 93d8ad7f9..0ac8ddf6f 100644 --- a/lib/build/recipe/jwt/recipeImplementation.js +++ b/lib/build/recipe/jwt/recipeImplementation.js @@ -13,40 +13,50 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const defaultJWKSMaxAge = 60; // This corresponds to the dynamicSigningKeyOverlapMS in the core function getRecipeInterface(querier, config, appInfo) { return { - createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext, }) { + createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext }) { if (validitySeconds === undefined) { // If the user does not provide a validity to this function and the config validity is also undefined, use 100 years (in seconds) validitySeconds = config.jwtValiditySeconds; } - let response = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/jwt"), { - payload: payload !== null && payload !== void 0 ? payload : {}, - validity: validitySeconds, - useStaticSigningKey: useStaticSigningKey !== false, - algorithm: "RS256", - jwksDomain: appInfo.apiDomain.getAsStringDangerous(), - }, userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/jwt"), + { + payload: payload !== null && payload !== void 0 ? payload : {}, + validity: validitySeconds, + useStaticSigningKey: useStaticSigningKey !== false, + algorithm: "RS256", + jwksDomain: appInfo.apiDomain.getAsStringDangerous(), + }, + userContext + ); if (response.status === "OK") { return { status: "OK", jwt: response.jwt, }; - } - else { + } else { return { status: "UNSUPPORTED_ALGORITHM_ERROR", }; } }, getJWKS: async function ({ userContext }) { - const { body, headers } = await querier.sendGetRequestWithResponseHeaders(new normalisedURLPath_1.default("/.well-known/jwks.json"), {}, undefined, userContext); + const { body, headers } = await querier.sendGetRequestWithResponseHeaders( + new normalisedURLPath_1.default("/.well-known/jwks.json"), + {}, + undefined, + userContext + ); let validityInSeconds = defaultJWKSMaxAge; const cacheControl = headers.get("Cache-Control"); if (cacheControl) { diff --git a/lib/build/recipe/jwt/types.d.ts b/lib/build/recipe/jwt/types.d.ts index 2afde80ca..84c1802ae 100644 --- a/lib/build/recipe/jwt/types.d.ts +++ b/lib/build/recipe/jwt/types.d.ts @@ -13,14 +13,20 @@ export declare type JsonWebKey = { export declare type TypeInput = { jwtValiditySeconds?: number; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { jwtValiditySeconds: number; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -38,12 +44,15 @@ export declare type RecipeInterface = { validitySeconds?: number; useStaticSigningKey?: boolean; userContext: UserContext; - }): Promise<{ - status: "OK"; - jwt: string; - } | { - status: "UNSUPPORTED_ALGORITHM_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + jwt: string; + } + | { + status: "UNSUPPORTED_ALGORITHM_ERROR"; + } + >; getJWKS(input: { userContext: UserContext; }): Promise<{ @@ -52,10 +61,15 @@ export declare type RecipeInterface = { }>; }; export declare type APIInterface = { - getJWKSGET: undefined | ((input: { - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - keys: JsonWebKey[]; - } | GeneralErrorResponse>); + getJWKSGET: + | undefined + | ((input: { + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + keys: JsonWebKey[]; + } + | GeneralErrorResponse + >); }; diff --git a/lib/build/recipe/jwt/utils.d.ts b/lib/build/recipe/jwt/utils.d.ts index 133d4840f..4025b1b44 100644 --- a/lib/build/recipe/jwt/utils.d.ts +++ b/lib/build/recipe/jwt/utils.d.ts @@ -2,4 +2,8 @@ import { NormalisedAppinfo } from "../../types"; import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput(_: Recipe, __: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + _: Recipe, + __: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; diff --git a/lib/build/recipe/jwt/utils.js b/lib/build/recipe/jwt/utils.js index f3383e481..9c38d23ce 100644 --- a/lib/build/recipe/jwt/utils.js +++ b/lib/build/recipe/jwt/utils.js @@ -17,9 +17,18 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(_, __, config) { var _a; - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { - jwtValiditySeconds: (_a = config === null || config === void 0 ? void 0 : config.jwtValiditySeconds) !== null && _a !== void 0 ? _a : 3153600000, + jwtValiditySeconds: + (_a = config === null || config === void 0 ? void 0 : config.jwtValiditySeconds) !== null && _a !== void 0 + ? _a + : 3153600000, override, }; } diff --git a/lib/build/recipe/multifactorauth/api/implementation.js b/lib/build/recipe/multifactorauth/api/implementation.js index 97cea6a97..954ae11bd 100644 --- a/lib/build/recipe/multifactorauth/api/implementation.js +++ b/lib/build/recipe/multifactorauth/api/implementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const multiFactorAuthClaim_1 = require("../multiFactorAuthClaim"); const error_1 = __importDefault(require("../../session/error")); @@ -63,19 +65,29 @@ function getAPIInterface() { userContext, }); factorsAllowedToSetup.push(id); - } - catch (err) { + } catch (err) { if (!(error_1.default.isErrorFromSuperTokens(err) && err.type === error_1.default.INVALID_CLAIMS)) { throw err; } // ignore claims error and not add to the list of factors allowed to be set up } } - const nextSetOfUnsatisfiedFactors = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors(mfaInfo.completedFactors, mfaInfo.mfaRequirementsForAuth); - let getEmailsForFactorsResult = options.recipeInstance.getEmailsForFactors(sessionUser, session.getRecipeUserId(userContext)); - let getPhoneNumbersForFactorsResult = options.recipeInstance.getPhoneNumbersForFactors(sessionUser, session.getRecipeUserId(userContext)); - if (getEmailsForFactorsResult.status === "UNKNOWN_SESSION_RECIPE_USER_ID" || - getPhoneNumbersForFactorsResult.status === "UNKNOWN_SESSION_RECIPE_USER_ID") { + const nextSetOfUnsatisfiedFactors = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors( + mfaInfo.completedFactors, + mfaInfo.mfaRequirementsForAuth + ); + let getEmailsForFactorsResult = options.recipeInstance.getEmailsForFactors( + sessionUser, + session.getRecipeUserId(userContext) + ); + let getPhoneNumbersForFactorsResult = options.recipeInstance.getPhoneNumbersForFactors( + sessionUser, + session.getRecipeUserId(userContext) + ); + if ( + getEmailsForFactorsResult.status === "UNKNOWN_SESSION_RECIPE_USER_ID" || + getPhoneNumbersForFactorsResult.status === "UNKNOWN_SESSION_RECIPE_USER_ID" + ) { throw new error_1.default({ type: "UNAUTHORISED", message: "User no longer associated with the session", @@ -87,9 +99,15 @@ function getAPIInterface() { // where user has already setup a factor and not completed it, none of the factors will be allowed to // be setup, and that that will result in an empty next array. However, we want to show the factor // that the user has already setup in that case. - const next = nextSetOfUnsatisfiedFactors.factorIds.filter((factorId) => factorsAllowedToSetup.includes(factorId) || factorsSetUpForUser.includes(factorId)); + const next = nextSetOfUnsatisfiedFactors.factorIds.filter( + (factorId) => factorsAllowedToSetup.includes(factorId) || factorsSetUpForUser.includes(factorId) + ); if (next.length === 0 && nextSetOfUnsatisfiedFactors.factorIds.length !== 0) { - throw new Error(`The user is required to complete secondary factors they are not allowed to (${nextSetOfUnsatisfiedFactors.factorIds.join(", ")}), likely because of configuration issues.`); + throw new Error( + `The user is required to complete secondary factors they are not allowed to (${nextSetOfUnsatisfiedFactors.factorIds.join( + ", " + )}), likely because of configuration issues.` + ); } return { status: "OK", diff --git a/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.d.ts b/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.d.ts index 9e9fa464c..081461387 100644 --- a/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.d.ts +++ b/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function resyncSessionAndFetchMFAInfo(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function resyncSessionAndFetchMFAInfo( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.js b/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.js index 37e9caa14..c9819b6ba 100644 --- a/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.js +++ b/lib/build/recipe/multifactorauth/api/resyncSessionAndFetchMFAInfo.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -23,7 +25,12 @@ async function resyncSessionAndFetchMFAInfo(apiImplementation, options, userCont if (apiImplementation.resyncSessionAndFetchMFAInfoPUT === undefined) { return false; } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [], sessionRequired: true }, + userContext + ); let response = await apiImplementation.resyncSessionAndFetchMFAInfoPUT({ options, session, diff --git a/lib/build/recipe/multifactorauth/index.d.ts b/lib/build/recipe/multifactorauth/index.d.ts index 2cbad735b..b2d06cd51 100644 --- a/lib/build/recipe/multifactorauth/index.d.ts +++ b/lib/build/recipe/multifactorauth/index.d.ts @@ -17,13 +17,32 @@ export default class Wrapper { THIRDPARTY: string; TOTP: string; }; - static assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainerInterface, factorId: string, userContext?: Record): Promise; - static getMFARequirementsForAuth(session: SessionContainerInterface, userContext?: Record): Promise; - static markFactorAsCompleteInSession(session: SessionContainerInterface, factorId: string, userContext?: Record): Promise; + static assertAllowedToSetupFactorElseThrowInvalidClaimError( + session: SessionContainerInterface, + factorId: string, + userContext?: Record + ): Promise; + static getMFARequirementsForAuth( + session: SessionContainerInterface, + userContext?: Record + ): Promise; + static markFactorAsCompleteInSession( + session: SessionContainerInterface, + factorId: string, + userContext?: Record + ): Promise; static getFactorsSetupForUser(userId: string, userContext?: Record): Promise; static getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record): Promise; - static addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record): Promise; - static removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record): Promise; + static addToRequiredSecondaryFactorsForUser( + userId: string, + factorId: string, + userContext?: Record + ): Promise; + static removeFromRequiredSecondaryFactorsForUser( + userId: string, + factorId: string, + userContext?: Record + ): Promise; } export declare let init: typeof Recipe.init; export declare let assertAllowedToSetupFactorElseThrowInvalidClaimError: typeof Wrapper.assertAllowedToSetupFactorElseThrowInvalidClaimError; diff --git a/lib/build/recipe/multifactorauth/index.js b/lib/build/recipe/multifactorauth/index.js index f6a7e5c06..25e208c4c 100644 --- a/lib/build/recipe/multifactorauth/index.js +++ b/lib/build/recipe/multifactorauth/index.js @@ -13,19 +13,31 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FactorIds = exports.MultiFactorAuthClaim = exports.removeFromRequiredSecondaryFactorsForUser = exports.addToRequiredSecondaryFactorsForUser = exports.getMFARequirementsForAuth = exports.getRequiredSecondaryFactorsForUser = exports.getFactorsSetupForUser = exports.markFactorAsCompleteInSession = exports.assertAllowedToSetupFactorElseThrowInvalidClaimError = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); const multiFactorAuthClaim_1 = require("./multiFactorAuthClaim"); -Object.defineProperty(exports, "MultiFactorAuthClaim", { enumerable: true, get: function () { return multiFactorAuthClaim_1.MultiFactorAuthClaim; } }); +Object.defineProperty(exports, "MultiFactorAuthClaim", { + enumerable: true, + get: function () { + return multiFactorAuthClaim_1.MultiFactorAuthClaim; + }, +}); const __1 = require("../.."); const utils_1 = require("../../utils"); const utils_2 = require("./utils"); const types_1 = require("./types"); -Object.defineProperty(exports, "FactorIds", { enumerable: true, get: function () { return types_1.FactorIds; } }); +Object.defineProperty(exports, "FactorIds", { + enumerable: true, + get: function () { + return types_1.FactorIds; + }, +}); class Wrapper { static async assertAllowedToSetupFactorElseThrowInvalidClaimError(session, factorId, userContext) { let ctx = utils_1.getUserContext(userContext); @@ -34,17 +46,19 @@ class Wrapper { userContext: ctx, }); const factorsSetUpForUser = await Wrapper.getFactorsSetupForUser(session.getUserId(), ctx); - await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.assertAllowedToSetupFactorElseThrowInvalidClaimError({ - session, - factorId, - get factorsSetUpForUser() { - return Promise.resolve(factorsSetUpForUser); - }, - get mfaRequirementsForAuth() { - return Promise.resolve(mfaInfo.mfaRequirementsForAuth); - }, - userContext: ctx, - }); + await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.assertAllowedToSetupFactorElseThrowInvalidClaimError({ + session, + factorId, + get factorsSetUpForUser() { + return Promise.resolve(factorsSetUpForUser); + }, + get mfaRequirementsForAuth() { + return Promise.resolve(mfaInfo.mfaRequirementsForAuth); + }, + userContext: ctx, + }); } static async getMFARequirementsForAuth(session, userContext) { let ctx = utils_1.getUserContext(userContext); @@ -98,7 +112,8 @@ Wrapper.init = recipe_1.default.init; Wrapper.MultiFactorAuthClaim = multiFactorAuthClaim_1.MultiFactorAuthClaim; Wrapper.FactorIds = types_1.FactorIds; exports.init = Wrapper.init; -exports.assertAllowedToSetupFactorElseThrowInvalidClaimError = Wrapper.assertAllowedToSetupFactorElseThrowInvalidClaimError; +exports.assertAllowedToSetupFactorElseThrowInvalidClaimError = + Wrapper.assertAllowedToSetupFactorElseThrowInvalidClaimError; exports.markFactorAsCompleteInSession = Wrapper.markFactorAsCompleteInSession; exports.getFactorsSetupForUser = Wrapper.getFactorsSetupForUser; exports.getRequiredSecondaryFactorsForUser = Wrapper.getRequiredSecondaryFactorsForUser; diff --git a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts index ff964c9f0..07976ada7 100644 --- a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts +++ b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts @@ -14,26 +14,50 @@ export declare class MultiFactorAuthClaimClass extends SessionClaim SessionClaimValidator; hasCompletedRequirementList(requirementList: MFARequirementList, id?: string): SessionClaimValidator; }; - getNextSetOfUnsatisfiedFactors(completedFactors: MFAClaimValue["c"], requirementList: MFARequirementList): { + getNextSetOfUnsatisfiedFactors( + completedFactors: MFAClaimValue["c"], + requirementList: MFARequirementList + ): { factorIds: string[]; type: "string" | "oneOf" | "allOfInAnyOrder"; }; - fetchValue: (_userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext) => Promise<{ + fetchValue: ( + _userId: string, + recipeUserId: RecipeUserId, + tenantId: string, + currentPayload: JSONObject | undefined, + userContext: UserContext + ) => Promise<{ c: Record; v: boolean; }>; - addToPayload_internal: (payload: JSONObject, value: MFAClaimValue) => { - [x: string]: string | number | boolean | JSONObject | import("../../types").JSONArray | { - c: { - [x: string]: number | undefined; - }; - v: boolean; - } | null | undefined; + addToPayload_internal: ( + payload: JSONObject, + value: MFAClaimValue + ) => { + [x: string]: + | string + | number + | boolean + | JSONObject + | import("../../types").JSONArray + | { + c: { + [x: string]: number | undefined; + }; + v: boolean; + } + | null + | undefined; }; - removeFromPayload: (payload: JSONObject) => { + removeFromPayload: ( + payload: JSONObject + ) => { [x: string]: import("../../types").JSONValue; }; - removeFromPayloadByMerge_internal: (payload: JSONObject) => { + removeFromPayloadByMerge_internal: ( + payload: JSONObject + ) => { [x: string]: import("../../types").JSONValue; }; getValueFromPayload: (payload: JSONObject) => MFAClaimValue; diff --git a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js index c35e304ff..da46bd6b1 100644 --- a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js +++ b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js @@ -38,10 +38,15 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { }; this.addToPayload_internal = (payload, value) => { const prevValue = payload[this.key]; - return Object.assign(Object.assign({}, payload), { [this.key]: { - c: Object.assign(Object.assign({}, prevValue === null || prevValue === void 0 ? void 0 : prevValue.c), value.c), + return Object.assign(Object.assign({}, payload), { + [this.key]: { + c: Object.assign( + Object.assign({}, prevValue === null || prevValue === void 0 ? void 0 : prevValue.c), + value.c + ), v: value.v, - } }); + }, + }); }; this.removeFromPayload = (payload) => { const retVal = Object.assign({}, payload); @@ -70,11 +75,12 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { const { v } = claimVal; return { isValid: v, - reason: v === false - ? { - message: "MFA requirement for auth is not satisfied", - } - : undefined, + reason: + v === false + ? { + message: "MFA requirement for auth is not satisfied", + } + : undefined, }; }, }), @@ -96,7 +102,10 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { throw new Error("This should never happen, claim value not present in payload"); } const { c: completedFactors } = claimVal; - const nextSetOfUnsatisfiedFactors = this.getNextSetOfUnsatisfiedFactors(completedFactors, requirementList); + const nextSetOfUnsatisfiedFactors = this.getNextSetOfUnsatisfiedFactors( + completedFactors, + requirementList + ); if (nextSetOfUnsatisfiedFactors.factorIds.length === 0) { // No item in the requirementList is left unsatisfied, hence is Valid return { @@ -107,28 +116,29 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { return { isValid: false, reason: { - message: "Factor validation failed: " + + message: + "Factor validation failed: " + nextSetOfUnsatisfiedFactors.factorIds[0] + " not completed", factorId: nextSetOfUnsatisfiedFactors.factorIds[0], }, }; - } - else if (nextSetOfUnsatisfiedFactors.type === "oneOf") { + } else if (nextSetOfUnsatisfiedFactors.type === "oneOf") { return { isValid: false, reason: { - message: "None of these factors are complete in the session: " + + message: + "None of these factors are complete in the session: " + nextSetOfUnsatisfiedFactors.factorIds.join(", "), oneOf: nextSetOfUnsatisfiedFactors.factorIds, }, }; - } - else { + } else { return { isValid: false, reason: { - message: "Some of the factors are not complete in the session: " + + message: + "Some of the factors are not complete in the session: " + nextSetOfUnsatisfiedFactors.factorIds.join(", "), allOfInAnyOrder: nextSetOfUnsatisfiedFactors.factorIds, }, @@ -153,8 +163,7 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { type = "string"; nextFactors.add(req); } - } - else if ("oneOf" in req) { + } else if ("oneOf" in req) { let satisfied = false; for (const factorId of req.oneOf) { if (completedFactors[factorId] !== undefined) { @@ -167,8 +176,7 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { nextFactors.add(factorId); } } - } - else if ("allOfInAnyOrder" in req) { + } else if ("allOfInAnyOrder" in req) { for (const factorId of req.allOfInAnyOrder) { type = "allOfInAnyOrder"; if (completedFactors[factorId] === undefined) { diff --git a/lib/build/recipe/multifactorauth/recipe.d.ts b/lib/build/recipe/multifactorauth/recipe.d.ts index bdbefd773..c7abce064 100644 --- a/lib/build/recipe/multifactorauth/recipe.d.ts +++ b/lib/build/recipe/multifactorauth/recipe.d.ts @@ -4,7 +4,16 @@ import NormalisedURLPath from "../../normalisedURLPath"; import RecipeModule from "../../recipeModule"; import STError from "../../error"; import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; -import { APIInterface, GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc, GetEmailsForFactorFromOtherRecipesFunc, GetFactorsSetupForUserFromOtherRecipesFunc, GetPhoneNumbersForFactorsFromOtherRecipesFunc, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; +import { + APIInterface, + GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc, + GetEmailsForFactorFromOtherRecipesFunc, + GetFactorsSetupForUserFromOtherRecipesFunc, + GetPhoneNumbersForFactorsFromOtherRecipesFunc, + RecipeInterface, + TypeInput, + TypeNormalisedInput, +} from "./types"; import { User } from "../../user"; import RecipeUserId from "../../recipeUserId"; import { Querier } from "../../querier"; @@ -28,25 +37,45 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, _tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + _tenantId: string, + req: BaseRequest, + res: BaseResponse, + _: NormalisedURLPath, + __: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; - addFuncToGetAllAvailableSecondaryFactorIdsFromOtherRecipes: (f: GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc) => void; + addFuncToGetAllAvailableSecondaryFactorIdsFromOtherRecipes: ( + f: GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc + ) => void; getAllAvailableSecondaryFactorIds: (tenantConfig: TenantConfig) => string[]; addFuncToGetFactorsSetupForUserFromOtherRecipes: (func: GetFactorsSetupForUserFromOtherRecipesFunc) => void; addFuncToGetEmailsForFactorFromOtherRecipes: (func: GetEmailsForFactorFromOtherRecipesFunc) => void; - getEmailsForFactors: (user: User, sessionRecipeUserId: RecipeUserId) => { - status: "OK"; - factorIdToEmailsMap: Record; - } | { - status: "UNKNOWN_SESSION_RECIPE_USER_ID"; - }; + getEmailsForFactors: ( + user: User, + sessionRecipeUserId: RecipeUserId + ) => + | { + status: "OK"; + factorIdToEmailsMap: Record; + } + | { + status: "UNKNOWN_SESSION_RECIPE_USER_ID"; + }; addFuncToGetPhoneNumbersForFactorsFromOtherRecipes: (func: GetPhoneNumbersForFactorsFromOtherRecipesFunc) => void; - getPhoneNumbersForFactors: (user: User, sessionRecipeUserId: RecipeUserId) => { - status: "OK"; - factorIdToPhoneNumberMap: Record; - } | { - status: "UNKNOWN_SESSION_RECIPE_USER_ID"; - }; + getPhoneNumbersForFactors: ( + user: User, + sessionRecipeUserId: RecipeUserId + ) => + | { + status: "OK"; + factorIdToPhoneNumberMap: Record; + } + | { + status: "UNKNOWN_SESSION_RECIPE_USER_ID"; + }; } diff --git a/lib/build/recipe/multifactorauth/recipe.js b/lib/build/recipe/multifactorauth/recipe.js index 379536a17..31b8241a8 100644 --- a/lib/build/recipe/multifactorauth/recipe.js +++ b/lib/build/recipe/multifactorauth/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -46,7 +48,9 @@ class Recipe extends recipeModule_1.default { return [ { method: "put", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.RESYNC_SESSION_AND_FETCH_MFA_INFO), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + constants_1.RESYNC_SESSION_AND_FETCH_MFA_INFO + ), id: constants_1.RESYNC_SESSION_AND_FETCH_MFA_INFO, disabled: this.apiImpl.resyncSessionAndFetchMFAInfoPUT === undefined, }, @@ -109,7 +113,10 @@ class Recipe extends recipeModule_1.default { status: "UNKNOWN_SESSION_RECIPE_USER_ID", }; } - result.factorIdToEmailsMap = Object.assign(Object.assign({}, result.factorIdToEmailsMap), funcResult.factorIdToEmailsMap); + result.factorIdToEmailsMap = Object.assign( + Object.assign({}, result.factorIdToEmailsMap), + funcResult.factorIdToEmailsMap + ); } return result; }; @@ -128,7 +135,10 @@ class Recipe extends recipeModule_1.default { status: "UNKNOWN_SESSION_RECIPE_USER_ID", }; } - result.factorIdToPhoneNumberMap = Object.assign(Object.assign({}, result.factorIdToPhoneNumberMap), funcResult.factorIdToPhoneNumberMap); + result.factorIdToPhoneNumberMap = Object.assign( + Object.assign({}, result.factorIdToPhoneNumberMap), + funcResult.factorIdToPhoneNumberMap + ); } return result; }; @@ -138,7 +148,11 @@ class Recipe extends recipeModule_1.default { let originalImpl = recipeImplementation_1.default(this); let builder = new supertokens_js_override_1.default(originalImpl); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); - if (((_a = config === null || config === void 0 ? void 0 : config.override) === null || _a === void 0 ? void 0 : _a.functions) !== undefined) { + if ( + ((_a = config === null || config === void 0 ? void 0 : config.override) === null || _a === void 0 + ? void 0 + : _a.functions) !== undefined + ) { this.isGetMfaRequirementsForAuthOverridden = true; // assuming that's what most people will override } } @@ -154,7 +168,11 @@ class Recipe extends recipeModule_1.default { // We don't add MultiFactorAuthClaim as a global claim because the values are populated // on factor setup / completion any way (in the sign in / up APIs). // SessionRecipe.getInstanceOrThrowError().addClaimFromOtherRecipe(MultiFactorAuthClaim); - recipe_1.default.getInstanceOrThrowError().addClaimValidatorFromOtherRecipe(multiFactorAuthClaim_1.MultiFactorAuthClaim.validators.hasCompletedMFARequirementsForAuth()); + recipe_1.default + .getInstanceOrThrowError() + .addClaimValidatorFromOtherRecipe( + multiFactorAuthClaim_1.MultiFactorAuthClaim.validators.hasCompletedMFARequirementsForAuth() + ); }); this.querier = querier_1.Querier.getNewInstanceOrThrowError(recipeId); } @@ -172,9 +190,10 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } - else { - throw new Error("MultiFactorAuth recipe has already been initialised. Please check your code for bugs."); + } else { + throw new Error( + "MultiFactorAuth recipe has already been initialised. Please check your code for bugs." + ); } }; } diff --git a/lib/build/recipe/multifactorauth/recipeImplementation.js b/lib/build/recipe/multifactorauth/recipeImplementation.js index 55097e8ac..611743da9 100644 --- a/lib/build/recipe/multifactorauth/recipeImplementation.js +++ b/lib/build/recipe/multifactorauth/recipeImplementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const usermetadata_1 = __importDefault(require("../usermetadata")); const multiFactorAuthClaim_1 = require("./multiFactorAuthClaim"); @@ -43,7 +45,10 @@ function getRecipeInterface(recipeInstance) { } return factorIds; }, - getMFARequirementsForAuth: async function ({ requiredSecondaryFactorsForUser, requiredSecondaryFactorsForTenant, }) { + getMFARequirementsForAuth: async function ({ + requiredSecondaryFactorsForUser, + requiredSecondaryFactorsForTenant, + }) { // default requirements for Auth is the union of required factors for user and tenant // https://github.com/supertokens/supertokens-core/issues/554#issuecomment-1752852720 const allFactors = new Set(); @@ -78,32 +83,57 @@ function getRecipeInterface(recipeInstance) { throw new Error("should never happen"); } if (claimVal.v) { - logger_1.logDebugMessage(`assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: true because the session already satisfied auth reqs`); + logger_1.logDebugMessage( + `assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: true because the session already satisfied auth reqs` + ); return { isValid: true }; } - const setOfUnsatisfiedFactors = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors(claimVal.c, await input.mfaRequirementsForAuth); + const setOfUnsatisfiedFactors = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors( + claimVal.c, + await input.mfaRequirementsForAuth + ); const factorsSetUpForUserRes = await input.factorsSetUpForUser; if (setOfUnsatisfiedFactors.factorIds.some((id) => factorsSetUpForUserRes.includes(id))) { - logger_1.logDebugMessage(`assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: false because there are items already set up in the next set of unsatisfied factors: ${setOfUnsatisfiedFactors.factorIds.join(", ")}`); + logger_1.logDebugMessage( + `assertAllowedToSetupFactorElseThrowInvalidClaimError ${ + input.factorId + }: false because there are items already set up in the next set of unsatisfied factors: ${setOfUnsatisfiedFactors.factorIds.join( + ", " + )}` + ); return { isValid: false, reason: "Completed factors in the session does not satisfy the MFA requirements for auth", }; } - if (setOfUnsatisfiedFactors.factorIds.length > 0 && - !setOfUnsatisfiedFactors.factorIds.includes(input.factorId)) { + if ( + setOfUnsatisfiedFactors.factorIds.length > 0 && + !setOfUnsatisfiedFactors.factorIds.includes(input.factorId) + ) { // It can be a security issue if we don't do this check // Consider this case: // Requirements: [{oneOf: ["totp", "otp-email"]}, "otp-phone"] (this is what I call the lower sms costs case) // The user has setup otp-phone previously, but no totp or email // During sign-in, they'd be allowed to add a new phone number, then set up TOTP and complete sign-in, completely bypassing the old phone number. - logger_1.logDebugMessage(`assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: false because user is trying to set up factor that is not in the next set of unsatisfied factors: ${setOfUnsatisfiedFactors.factorIds.join(", ")}`); + logger_1.logDebugMessage( + `assertAllowedToSetupFactorElseThrowInvalidClaimError ${ + input.factorId + }: false because user is trying to set up factor that is not in the next set of unsatisfied factors: ${setOfUnsatisfiedFactors.factorIds.join( + ", " + )}` + ); return { isValid: false, reason: "Not allowed to setup factor that is not in the next set of unsatisfied factors", }; } - logger_1.logDebugMessage(`assertAllowedToSetupFactorElseThrowInvalidClaimError ${input.factorId}: true because the next set of unsatisfied factors is ${setOfUnsatisfiedFactors.factorIds.length === 0 ? "empty" : "cannot be completed otherwise"}`); + logger_1.logDebugMessage( + `assertAllowedToSetupFactorElseThrowInvalidClaimError ${ + input.factorId + }: true because the next set of unsatisfied factors is ${ + setOfUnsatisfiedFactors.factorIds.length === 0 ? "empty" : "cannot be completed otherwise" + }` + ); return { isValid: true }; }, }; @@ -119,31 +149,55 @@ function getRecipeInterface(recipeInstance) { getRequiredSecondaryFactorsForUser: async function ({ userId, userContext }) { var _a, _b; const metadata = await usermetadata_1.default.getUserMetadata(userId, userContext); - return (_b = (_a = metadata.metadata._supertokens) === null || _a === void 0 ? void 0 : _a.requiredSecondaryFactors) !== null && _b !== void 0 ? _b : []; + return (_b = + (_a = metadata.metadata._supertokens) === null || _a === void 0 + ? void 0 + : _a.requiredSecondaryFactors) !== null && _b !== void 0 + ? _b + : []; }, addToRequiredSecondaryFactorsForUser: async function ({ userId, factorId, userContext }) { var _a, _b; const metadata = await usermetadata_1.default.getUserMetadata(userId, userContext); - const factorIds = (_b = (_a = metadata.metadata._supertokens) === null || _a === void 0 ? void 0 : _a.requiredSecondaryFactors) !== null && _b !== void 0 ? _b : []; + const factorIds = + (_b = + (_a = metadata.metadata._supertokens) === null || _a === void 0 + ? void 0 + : _a.requiredSecondaryFactors) !== null && _b !== void 0 + ? _b + : []; if (factorIds.includes(factorId)) { return; } factorIds.push(factorId); - const metadataUpdate = Object.assign(Object.assign({}, metadata.metadata), { _supertokens: Object.assign(Object.assign({}, metadata.metadata._supertokens), { requiredSecondaryFactors: factorIds }) }); + const metadataUpdate = Object.assign(Object.assign({}, metadata.metadata), { + _supertokens: Object.assign(Object.assign({}, metadata.metadata._supertokens), { + requiredSecondaryFactors: factorIds, + }), + }); await usermetadata_1.default.updateUserMetadata(userId, metadataUpdate, userContext); }, removeFromRequiredSecondaryFactorsForUser: async function ({ userId, factorId, userContext }) { var _a, _b; const metadata = await usermetadata_1.default.getUserMetadata(userId, userContext); - if (((_a = metadata.metadata._supertokens) === null || _a === void 0 ? void 0 : _a.requiredSecondaryFactors) === undefined) { + if ( + ((_a = metadata.metadata._supertokens) === null || _a === void 0 + ? void 0 + : _a.requiredSecondaryFactors) === undefined + ) { return; } - let factorIds = (_b = metadata.metadata._supertokens.requiredSecondaryFactors) !== null && _b !== void 0 ? _b : []; + let factorIds = + (_b = metadata.metadata._supertokens.requiredSecondaryFactors) !== null && _b !== void 0 ? _b : []; if (!factorIds.includes(factorId)) { return; } factorIds = factorIds.filter((id) => id !== factorId); - const metadataUpdate = Object.assign(Object.assign({}, metadata.metadata), { _supertokens: Object.assign(Object.assign({}, metadata.metadata._supertokens), { requiredSecondaryFactors: factorIds }) }); + const metadataUpdate = Object.assign(Object.assign({}, metadata.metadata), { + _supertokens: Object.assign(Object.assign({}, metadata.metadata._supertokens), { + requiredSecondaryFactors: factorIds, + }), + }); await usermetadata_1.default.updateUserMetadata(userId, metadataUpdate, userContext); }, }; diff --git a/lib/build/recipe/multifactorauth/types.d.ts b/lib/build/recipe/multifactorauth/types.d.ts index 79cc8dc7a..e02cbadb4 100644 --- a/lib/build/recipe/multifactorauth/types.d.ts +++ b/lib/build/recipe/multifactorauth/types.d.ts @@ -8,11 +8,15 @@ import { SessionContainerInterface } from "../session/types"; import Recipe from "./recipe"; import { TenantConfig } from "../multitenancy/types"; import RecipeUserId from "../../recipeUserId"; -export declare type MFARequirementList = ({ - oneOf: string[]; -} | { - allOfInAnyOrder: string[]; -} | string)[]; +export declare type MFARequirementList = ( + | { + oneOf: string[]; + } + | { + allOfInAnyOrder: string[]; + } + | string +)[]; export declare type MFAClaimValue = { c: Record; v: boolean; @@ -20,14 +24,20 @@ export declare type MFAClaimValue = { export declare type TypeInput = { firstFactors?: string[]; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { firstFactors?: string[]; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -54,14 +64,8 @@ export declare type RecipeInterface = { factorId: string; userContext: UserContext; }) => Promise; - getFactorsSetupForUser: (input: { - user: User; - userContext: UserContext; - }) => Promise; - getRequiredSecondaryFactorsForUser: (input: { - userId: string; - userContext: UserContext; - }) => Promise; + getFactorsSetupForUser: (input: { user: User; userContext: UserContext }) => Promise; + getRequiredSecondaryFactorsForUser: (input: { userId: string; userContext: UserContext }) => Promise; addToRequiredSecondaryFactorsForUser: (input: { userId: string; factorId: string; @@ -83,35 +87,53 @@ export declare type APIOptions = { res: BaseResponse; }; export declare type APIInterface = { - resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - factors: { - next: string[]; - alreadySetup: string[]; - allowedToSetup: string[]; - }; - emails: Record; - phoneNumbers: Record; - } | GeneralErrorResponse>); + resyncSessionAndFetchMFAInfoPUT: + | undefined + | ((input: { + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + factors: { + next: string[]; + alreadySetup: string[]; + allowedToSetup: string[]; + }; + emails: Record; + phoneNumbers: Record; + } + | GeneralErrorResponse + >); }; -export declare type GetFactorsSetupForUserFromOtherRecipesFunc = (user: User, userContext: UserContext) => Promise; +export declare type GetFactorsSetupForUserFromOtherRecipesFunc = ( + user: User, + userContext: UserContext +) => Promise; export declare type GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc = (tenantConfig: TenantConfig) => string[]; -export declare type GetEmailsForFactorFromOtherRecipesFunc = (user: User, sessionRecipeUserId: RecipeUserId) => { - status: "OK"; - factorIdToEmailsMap: Record; -} | { - status: "UNKNOWN_SESSION_RECIPE_USER_ID"; -}; -export declare type GetPhoneNumbersForFactorsFromOtherRecipesFunc = (user: User, sessionRecipeUserId: RecipeUserId) => { - status: "OK"; - factorIdToPhoneNumberMap: Record; -} | { - status: "UNKNOWN_SESSION_RECIPE_USER_ID"; -}; +export declare type GetEmailsForFactorFromOtherRecipesFunc = ( + user: User, + sessionRecipeUserId: RecipeUserId +) => + | { + status: "OK"; + factorIdToEmailsMap: Record; + } + | { + status: "UNKNOWN_SESSION_RECIPE_USER_ID"; + }; +export declare type GetPhoneNumbersForFactorsFromOtherRecipesFunc = ( + user: User, + sessionRecipeUserId: RecipeUserId +) => + | { + status: "OK"; + factorIdToPhoneNumberMap: Record; + } + | { + status: "UNKNOWN_SESSION_RECIPE_USER_ID"; + }; export declare const FactorIds: { EMAILPASSWORD: string; WEBAUTHN: string; diff --git a/lib/build/recipe/multifactorauth/utils.d.ts b/lib/build/recipe/multifactorauth/utils.d.ts index 62de22b62..97f09f4e9 100644 --- a/lib/build/recipe/multifactorauth/utils.d.ts +++ b/lib/build/recipe/multifactorauth/utils.d.ts @@ -4,16 +4,21 @@ import { UserContext } from "../../types"; import { SessionContainerInterface } from "../session/types"; import { RecipeUserId } from "../.."; export declare function validateAndNormaliseUserInput(config?: TypeInput): TypeNormalisedInput; -export declare const updateAndGetMFARelatedInfoInSession: (input: ({ - sessionRecipeUserId: RecipeUserId; - tenantId: string; - accessTokenPayload: any; -} | { - session: SessionContainerInterface; -}) & { - updatedFactorId?: string; - userContext: UserContext; -}) => Promise<{ +export declare const updateAndGetMFARelatedInfoInSession: ( + input: ( + | { + sessionRecipeUserId: RecipeUserId; + tenantId: string; + accessTokenPayload: any; + } + | { + session: SessionContainerInterface; + } + ) & { + updatedFactorId?: string; + userContext: UserContext; + } +) => Promise<{ completedFactors: MFAClaimValue["c"]; mfaRequirementsForAuth: MFARequirementList; isMFARequirementsForAuthSatisfied: boolean; diff --git a/lib/build/recipe/multifactorauth/utils.js b/lib/build/recipe/multifactorauth/utils.js index e7a1b5b1f..f365b1e5a 100644 --- a/lib/build/recipe/multifactorauth/utils.js +++ b/lib/build/recipe/multifactorauth/utils.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateAndGetMFARelatedInfoInSession = exports.validateAndNormaliseUserInput = void 0; const multitenancy_1 = __importDefault(require("../multitenancy")); @@ -27,10 +29,19 @@ const error_1 = __importDefault(require("../session/error")); const types_1 = require("./types"); const utils_1 = require("../multitenancy/utils"); function validateAndNormaliseUserInput(config) { - if ((config === null || config === void 0 ? void 0 : config.firstFactors) !== undefined && (config === null || config === void 0 ? void 0 : config.firstFactors.length) === 0) { + if ( + (config === null || config === void 0 ? void 0 : config.firstFactors) !== undefined && + (config === null || config === void 0 ? void 0 : config.firstFactors.length) === 0 + ) { throw new Error("'firstFactors' can be either undefined or a non-empty array"); } - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { firstFactors: config === null || config === void 0 ? void 0 : config.firstFactors, override, @@ -48,8 +59,7 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { tenantId = input.session.getTenantId(input.userContext); accessTokenPayload = input.session.getAccessTokenPayload(input.userContext); sessionHandle = input.session.getHandle(input.userContext); - } - else { + } else { sessionRecipeUserId = input.sessionRecipeUserId; tenantId = input.tenantId; accessTokenPayload = input.accessTokenPayload; @@ -66,8 +76,7 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { }, v: true, // updated later in the function }; - } - else { + } else { updatedClaimVal = true; mfaClaimValue.c[input.updatedFactorId] = Math.floor(Date.now() / 1000); } @@ -95,32 +104,36 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { for (const lM of sessionUser.loginMethods) { if (lM.recipeUserId.getAsString() === sessionRecipeUserId.getAsString()) { if (lM.recipeId === "emailpassword") { - let validRes = await utils_1.isValidFirstFactor(tenantId, types_1.FactorIds.EMAILPASSWORD, input.userContext); + let validRes = await utils_1.isValidFirstFactor( + tenantId, + types_1.FactorIds.EMAILPASSWORD, + input.userContext + ); if (validRes.status === "TENANT_NOT_FOUND_ERROR") { throw new error_1.default({ type: error_1.default.UNAUTHORISED, message: "Tenant not found", }); - } - else if (validRes.status === "OK") { + } else if (validRes.status === "OK") { computedFirstFactorIdForSession = types_1.FactorIds.EMAILPASSWORD; break; } - } - else if (lM.recipeId === "thirdparty") { - let validRes = await utils_1.isValidFirstFactor(tenantId, types_1.FactorIds.THIRDPARTY, input.userContext); + } else if (lM.recipeId === "thirdparty") { + let validRes = await utils_1.isValidFirstFactor( + tenantId, + types_1.FactorIds.THIRDPARTY, + input.userContext + ); if (validRes.status === "TENANT_NOT_FOUND_ERROR") { throw new error_1.default({ type: error_1.default.UNAUTHORISED, message: "Tenant not found", }); - } - else if (validRes.status === "OK") { + } else if (validRes.status === "OK") { computedFirstFactorIdForSession = types_1.FactorIds.THIRDPARTY; break; } - } - else { + } else { let factorsToCheck = []; if (lM.email !== undefined) { factorsToCheck.push(types_1.FactorIds.LINK_EMAIL); @@ -137,8 +150,7 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { type: error_1.default.UNAUTHORISED, message: "Tenant not found", }); - } - else if (validRes.status === "OK") { + } else if (validRes.status === "OK") { computedFirstFactorIdForSession = factorId; break; } @@ -180,55 +192,68 @@ const updateAndGetMFARelatedInfoInSession = async function (input) { }); return userProm; } - const mfaRequirementsForAuth = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getMFARequirementsForAuth({ - accessTokenPayload, - tenantId, - get user() { - return userGetter(); - }, - get factorsSetUpForUser() { - return userGetter().then((user) => recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getFactorsSetupForUser({ - user, - userContext: input.userContext, - })); - }, - get requiredSecondaryFactorsForUser() { - return userGetter().then((sessionUser) => { - if (sessionUser === undefined) { - throw new error_1.default({ - type: error_1.default.UNAUTHORISED, - message: "Session user not found", - }); - } - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getRequiredSecondaryFactorsForUser({ - userId: sessionUser.id, - userContext: input.userContext, + const mfaRequirementsForAuth = await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.getMFARequirementsForAuth({ + accessTokenPayload, + tenantId, + get user() { + return userGetter(); + }, + get factorsSetUpForUser() { + return userGetter().then((user) => + recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getFactorsSetupForUser({ + user, + userContext: input.userContext, + }) + ); + }, + get requiredSecondaryFactorsForUser() { + return userGetter().then((sessionUser) => { + if (sessionUser === undefined) { + throw new error_1.default({ + type: error_1.default.UNAUTHORISED, + message: "Session user not found", + }); + } + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.getRequiredSecondaryFactorsForUser({ + userId: sessionUser.id, + userContext: input.userContext, + }); }); - }); - }, - get requiredSecondaryFactorsForTenant() { - return multitenancy_1.default.getTenant(tenantId, input.userContext).then((tenantInfo) => { - var _a; - if (tenantInfo === undefined) { - throw new error_1.default({ - type: error_1.default.UNAUTHORISED, - message: "Tenant not found", - }); - } - return (_a = tenantInfo.requiredSecondaryFactors) !== null && _a !== void 0 ? _a : []; - }); - }, - completedFactors, - userContext: input.userContext, - }); - const areAuthReqsComplete = multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors(completedFactors, mfaRequirementsForAuth).factorIds - .length === 0; + }, + get requiredSecondaryFactorsForTenant() { + return multitenancy_1.default.getTenant(tenantId, input.userContext).then((tenantInfo) => { + var _a; + if (tenantInfo === undefined) { + throw new error_1.default({ + type: error_1.default.UNAUTHORISED, + message: "Tenant not found", + }); + } + return (_a = tenantInfo.requiredSecondaryFactors) !== null && _a !== void 0 ? _a : []; + }); + }, + completedFactors, + userContext: input.userContext, + }); + const areAuthReqsComplete = + multiFactorAuthClaim_1.MultiFactorAuthClaim.getNextSetOfUnsatisfiedFactors( + completedFactors, + mfaRequirementsForAuth + ).factorIds.length === 0; if (mfaClaimValue.v !== areAuthReqsComplete) { updatedClaimVal = true; mfaClaimValue.v = areAuthReqsComplete; } if ("session" in input && updatedClaimVal) { - await input.session.setClaimValue(multiFactorAuthClaim_1.MultiFactorAuthClaim, mfaClaimValue, input.userContext); + await input.session.setClaimValue( + multiFactorAuthClaim_1.MultiFactorAuthClaim, + mfaClaimValue, + input.userContext + ); } return { completedFactors, diff --git a/lib/build/recipe/multitenancy/allowedDomainsClaim.js b/lib/build/recipe/multitenancy/allowedDomainsClaim.js index e3f9f25c1..d679919b5 100644 --- a/lib/build/recipe/multitenancy/allowedDomainsClaim.js +++ b/lib/build/recipe/multitenancy/allowedDomainsClaim.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AllowedDomainsClaim = exports.AllowedDomainsClaimClass = void 0; const claims_1 = require("../session/claims"); diff --git a/lib/build/recipe/multitenancy/api/implementation.js b/lib/build/recipe/multitenancy/api/implementation.js index ad2104c6f..a2528571b 100644 --- a/lib/build/recipe/multitenancy/api/implementation.js +++ b/lib/build/recipe/multitenancy/api/implementation.js @@ -15,11 +15,20 @@ function getAPIInterface() { } const providerInputsFromStatic = options.staticThirdPartyProviders; const providerConfigsFromCore = tenantConfigRes.thirdParty.providers; - const mergedProviders = configUtils_1.mergeProvidersFromCoreAndStatic(providerConfigsFromCore, providerInputsFromStatic, tenantId === constants_1.DEFAULT_TENANT_ID); + const mergedProviders = configUtils_1.mergeProvidersFromCoreAndStatic( + providerConfigsFromCore, + providerInputsFromStatic, + tenantId === constants_1.DEFAULT_TENANT_ID + ); const finalProviderList = []; for (const providerInput of mergedProviders) { try { - const providerInstance = await configUtils_1.findAndCreateProviderInstance(mergedProviders, providerInput.config.thirdPartyId, clientType, userContext); + const providerInstance = await configUtils_1.findAndCreateProviderInstance( + mergedProviders, + providerInput.config.thirdPartyId, + clientType, + userContext + ); if (providerInstance === undefined) { throw new Error("should never come here"); // because creating instance from the merged provider list itself } @@ -27,8 +36,7 @@ function getAPIInterface() { id: providerInstance.id, name: providerInstance.config.name, }); - } - catch (err) { + } catch (err) { if (err.type === "CLIENT_TYPE_NOT_FOUND_ERROR") { continue; } @@ -38,11 +46,9 @@ function getAPIInterface() { let firstFactors; if (tenantConfigRes.firstFactors !== undefined) { firstFactors = tenantConfigRes.firstFactors; // highest priority, config from core - } - else if (options.staticFirstFactors !== undefined) { + } else if (options.staticFirstFactors !== undefined) { firstFactors = options.staticFirstFactors; // next priority, static config - } - else { + } else { // Fallback to all available factors (de-duplicated) firstFactors = Array.from(new Set(options.allAvailableFirstFactors)); } @@ -71,7 +77,8 @@ function getAPIInterface() { providers: finalProviderList, }, passwordless: { - enabled: validFirstFactors.includes("otp-email") || + enabled: + validFirstFactors.includes("otp-email") || validFirstFactors.includes("otp-phone") || validFirstFactors.includes("link-email") || validFirstFactors.includes("link-phone"), diff --git a/lib/build/recipe/multitenancy/api/loginMethods.d.ts b/lib/build/recipe/multitenancy/api/loginMethods.d.ts index a6f390bb6..c497a7557 100644 --- a/lib/build/recipe/multitenancy/api/loginMethods.d.ts +++ b/lib/build/recipe/multitenancy/api/loginMethods.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function loginMethodsAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function loginMethodsAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/multitenancy/error.d.ts b/lib/build/recipe/multitenancy/error.d.ts index d6412505c..486758b61 100644 --- a/lib/build/recipe/multitenancy/error.d.ts +++ b/lib/build/recipe/multitenancy/error.d.ts @@ -1,8 +1,5 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - constructor(options: { - type: "BAD_INPUT_ERROR"; - message: string; - }); + constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); } diff --git a/lib/build/recipe/multitenancy/error.js b/lib/build/recipe/multitenancy/error.js index a0e8daf7f..c6c1f6ccc 100644 --- a/lib/build/recipe/multitenancy/error.js +++ b/lib/build/recipe/multitenancy/error.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/multitenancy/index.d.ts b/lib/build/recipe/multitenancy/index.d.ts index 543dd643c..6aef31375 100644 --- a/lib/build/recipe/multitenancy/index.d.ts +++ b/lib/build/recipe/multitenancy/index.d.ts @@ -6,47 +6,87 @@ import { AllowedDomainsClaim } from "./allowedDomainsClaim"; import RecipeUserId from "../../recipeUserId"; export default class Wrapper { static init: typeof Recipe.init; - static createOrUpdateTenant(tenantId: string, config?: { - firstFactors?: string[] | null; - requiredSecondaryFactors?: string[] | null; - coreConfig?: { - [key: string]: any; - }; - }, userContext?: Record): Promise<{ + static createOrUpdateTenant( + tenantId: string, + config?: { + firstFactors?: string[] | null; + requiredSecondaryFactors?: string[] | null; + coreConfig?: { + [key: string]: any; + }; + }, + userContext?: Record + ): Promise<{ status: "OK"; createdNew: boolean; }>; - static deleteTenant(tenantId: string, userContext?: Record): Promise<{ + static deleteTenant( + tenantId: string, + userContext?: Record + ): Promise<{ status: "OK"; didExist: boolean; }>; - static getTenant(tenantId: string, userContext?: Record): Promise<({ - status: "OK"; - } & TenantConfig) | undefined>; - static listAllTenants(userContext?: Record): Promise<{ + static getTenant( + tenantId: string, + userContext?: Record + ): Promise< + | ({ + status: "OK"; + } & TenantConfig) + | undefined + >; + static listAllTenants( + userContext?: Record + ): Promise<{ status: "OK"; tenants: ({ tenantId: string; } & TenantConfig)[]; }>; - static createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record): Promise<{ + static createOrUpdateThirdPartyConfig( + tenantId: string, + config: ProviderConfig, + skipValidation?: boolean, + userContext?: Record + ): Promise<{ status: "OK"; createdNew: boolean; }>; - static deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record): Promise<{ + static deleteThirdPartyConfig( + tenantId: string, + thirdPartyId: string, + userContext?: Record + ): Promise<{ status: "OK"; didConfigExist: boolean; }>; - static associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record): Promise<{ - status: "OK"; - wasAlreadyAssociated: boolean; - } | { - status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"; - } | { - status: "ASSOCIATION_NOT_ALLOWED_ERROR"; - reason: string; - }>; - static disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record): Promise<{ + static associateUserToTenant( + tenantId: string, + recipeUserId: RecipeUserId, + userContext?: Record + ): Promise< + | { + status: "OK"; + wasAlreadyAssociated: boolean; + } + | { + status: + | "UNKNOWN_USER_ID_ERROR" + | "EMAIL_ALREADY_EXISTS_ERROR" + | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" + | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"; + } + | { + status: "ASSOCIATION_NOT_ALLOWED_ERROR"; + reason: string; + } + >; + static disassociateUserFromTenant( + tenantId: string, + recipeUserId: RecipeUserId, + userContext?: Record + ): Promise<{ status: "OK"; wasAssociated: boolean; }>; diff --git a/lib/build/recipe/multitenancy/index.js b/lib/build/recipe/multitenancy/index.js index d2708f438..bb6446939 100644 --- a/lib/build/recipe/multitenancy/index.js +++ b/lib/build/recipe/multitenancy/index.js @@ -13,14 +13,21 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AllowedDomainsClaim = exports.disassociateUserFromTenant = exports.associateUserToTenant = exports.deleteThirdPartyConfig = exports.createOrUpdateThirdPartyConfig = exports.listAllTenants = exports.getTenant = exports.deleteTenant = exports.createOrUpdateTenant = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); const allowedDomainsClaim_1 = require("./allowedDomainsClaim"); -Object.defineProperty(exports, "AllowedDomainsClaim", { enumerable: true, get: function () { return allowedDomainsClaim_1.AllowedDomainsClaim; } }); +Object.defineProperty(exports, "AllowedDomainsClaim", { + enumerable: true, + get: function () { + return allowedDomainsClaim_1.AllowedDomainsClaim; + }, +}); const utils_1 = require("../../utils"); class Wrapper { static async createOrUpdateTenant(tenantId, config, userContext) { diff --git a/lib/build/recipe/multitenancy/recipe.d.ts b/lib/build/recipe/multitenancy/recipe.d.ts index 8e81aa641..e649d0782 100644 --- a/lib/build/recipe/multitenancy/recipe.d.ts +++ b/lib/build/recipe/multitenancy/recipe.d.ts @@ -23,7 +23,15 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _: NormalisedURLPath, + __: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/multitenancy/recipe.js b/lib/build/recipe/multitenancy/recipe.js index d2073745c..20f9c22be 100644 --- a/lib/build/recipe/multitenancy/recipe.js +++ b/lib/build/recipe/multitenancy/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -77,7 +79,9 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId))); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId)) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -102,16 +106,16 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance.getAllowedDomainsForTenantId !== undefined) { postSuperTokensInitCallbacks_1.PostSuperTokensInitCallbacks.addPostInitCallback(() => { try { - recipe_1.default.getInstanceOrThrowError().addClaimFromOtherRecipe(allowedDomainsClaim_1.AllowedDomainsClaim); - } - catch (_a) { + recipe_1.default + .getInstanceOrThrowError() + .addClaimFromOtherRecipe(allowedDomainsClaim_1.AllowedDomainsClaim); + } catch (_a) { // Skip adding claims if session recipe is not initialised } }); } return Recipe.instance; - } - else { + } else { throw new Error("Multitenancy recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/multitenancy/recipeImplementation.js b/lib/build/recipe/multitenancy/recipeImplementation.js index eec929ffb..044c7e319 100644 --- a/lib/build/recipe/multitenancy/recipeImplementation.js +++ b/lib/build/recipe/multitenancy/recipeImplementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const constants_1 = require("./constants"); @@ -11,49 +13,103 @@ function getRecipeInterface(querier) { return tenantIdFromFrontend; }, createOrUpdateTenant: async function ({ tenantId, config, userContext }) { - let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/v2`), Object.assign({ tenantId }, config), {}, userContext); + let response = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/v2`), + Object.assign({ tenantId }, config), + {}, + userContext + ); return response; }, deleteTenant: async function ({ tenantId, userContext }) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/remove`), { - tenantId, - }, userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/remove`), + { + tenantId, + }, + userContext + ); return response; }, getTenant: async function ({ tenantId, userContext }) { - let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/tenant/v2`), {}, userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/multitenancy/tenant/v2` + ), + {}, + userContext + ); if (response.status === "TENANT_NOT_FOUND_ERROR") { return undefined; } return response; }, listAllTenants: async function ({ userContext }) { - let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/list/v2`), {}, userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default(`/recipe/multitenancy/tenant/list/v2`), + {}, + userContext + ); return response; }, createOrUpdateThirdPartyConfig: async function ({ tenantId, config, skipValidation, userContext }) { - let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/config/thirdparty`), { - config, - skipValidation, - }, {}, userContext); + let response = await querier.sendPutRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/multitenancy/config/thirdparty` + ), + { + config, + skipValidation, + }, + {}, + userContext + ); return response; }, deleteThirdPartyConfig: async function ({ tenantId, thirdPartyId, userContext }) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/config/thirdparty/remove`), { - thirdPartyId, - }, userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/multitenancy/config/thirdparty/remove` + ), + { + thirdPartyId, + }, + userContext + ); return response; }, associateUserToTenant: async function ({ tenantId, recipeUserId, userContext }) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/tenant/user`), { - recipeUserId: recipeUserId.getAsString(), - }, userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/multitenancy/tenant/user` + ), + { + recipeUserId: recipeUserId.getAsString(), + }, + userContext + ); return response; }, disassociateUserFromTenant: async function ({ tenantId, recipeUserId, userContext }) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/multitenancy/tenant/user/remove`), { - recipeUserId: recipeUserId.getAsString(), - }, userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/multitenancy/tenant/user/remove` + ), + { + recipeUserId: recipeUserId.getAsString(), + }, + userContext + ); return response; }, }; diff --git a/lib/build/recipe/multitenancy/types.d.ts b/lib/build/recipe/multitenancy/types.d.ts index 0a2df0ec8..e3d0008a6 100644 --- a/lib/build/recipe/multitenancy/types.d.ts +++ b/lib/build/recipe/multitenancy/types.d.ts @@ -7,14 +7,20 @@ import RecipeUserId from "../../recipeUserId"; export declare type TypeInput = { getAllowedDomainsForTenantId?: (tenantId: string, userContext: UserContext) => Promise; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { getAllowedDomainsForTenantId?: (tenantId: string, userContext: UserContext) => Promise; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -29,10 +35,7 @@ export declare type TenantConfig = { }; }; export declare type RecipeInterface = { - getTenantId: (input: { - tenantIdFromFrontend: string; - userContext: UserContext; - }) => Promise; + getTenantId: (input: { tenantIdFromFrontend: string; userContext: UserContext }) => Promise; createOrUpdateTenant: (input: { tenantId: string; config?: { @@ -57,9 +60,12 @@ export declare type RecipeInterface = { getTenant: (input: { tenantId: string; userContext: UserContext; - }) => Promise<({ - status: "OK"; - } & TenantConfig) | undefined>; + }) => Promise< + | ({ + status: "OK"; + } & TenantConfig) + | undefined + >; listAllTenants: (input: { userContext: UserContext; }) => Promise<{ @@ -89,15 +95,23 @@ export declare type RecipeInterface = { tenantId: string; recipeUserId: RecipeUserId; userContext: UserContext; - }) => Promise<{ - status: "OK"; - wasAlreadyAssociated: boolean; - } | { - status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"; - } | { - status: "ASSOCIATION_NOT_ALLOWED_ERROR"; - reason: string; - }>; + }) => Promise< + | { + status: "OK"; + wasAlreadyAssociated: boolean; + } + | { + status: + | "UNKNOWN_USER_ID_ERROR" + | "EMAIL_ALREADY_EXISTS_ERROR" + | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" + | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"; + } + | { + status: "ASSOCIATION_NOT_ALLOWED_ERROR"; + reason: string; + } + >; disassociateUserFromTenant: (input: { tenantId: string; recipeUserId: RecipeUserId; @@ -124,21 +138,24 @@ export declare type APIInterface = { clientType?: string; options: APIOptions; userContext: UserContext; - }) => Promise<{ - status: "OK"; - emailPassword: { - enabled: boolean; - }; - thirdParty: { - enabled: boolean; - providers: { - id: string; - name?: string; - }[]; - }; - passwordless: { - enabled: boolean; - }; - firstFactors: string[]; - } | GeneralErrorResponse>; + }) => Promise< + | { + status: "OK"; + emailPassword: { + enabled: boolean; + }; + thirdParty: { + enabled: boolean; + providers: { + id: string; + name?: string; + }[]; + }; + passwordless: { + enabled: boolean; + }; + firstFactors: string[]; + } + | GeneralErrorResponse + >; }; diff --git a/lib/build/recipe/multitenancy/utils.d.ts b/lib/build/recipe/multitenancy/utils.d.ts index 7190e47b9..3bc6e2abc 100644 --- a/lib/build/recipe/multitenancy/utils.d.ts +++ b/lib/build/recipe/multitenancy/utils.d.ts @@ -2,14 +2,26 @@ import { TypeInput, TypeNormalisedInput, TenantConfig } from "./types"; import { UserContext } from "../../types"; export declare function validateAndNormaliseUserInput(config?: TypeInput): TypeNormalisedInput; -export declare const isValidFirstFactor: (tenantId: string, factorId: string, userContext: UserContext) => Promise<{ - status: "OK"; -} | { - status: "INVALID_FIRST_FACTOR_ERROR"; -} | { - status: "TENANT_NOT_FOUND_ERROR"; -}>; -export declare function isFactorConfiguredForTenant({ allAvailableFirstFactors, firstFactors, factorId, }: { +export declare const isValidFirstFactor: ( + tenantId: string, + factorId: string, + userContext: UserContext +) => Promise< + | { + status: "OK"; + } + | { + status: "INVALID_FIRST_FACTOR_ERROR"; + } + | { + status: "TENANT_NOT_FOUND_ERROR"; + } +>; +export declare function isFactorConfiguredForTenant({ + allAvailableFirstFactors, + firstFactors, + factorId, +}: { tenantConfig: TenantConfig; allAvailableFirstFactors: string[]; firstFactors: string[]; diff --git a/lib/build/recipe/multitenancy/utils.js b/lib/build/recipe/multitenancy/utils.js index 835493628..3883f88f5 100644 --- a/lib/build/recipe/multitenancy/utils.js +++ b/lib/build/recipe/multitenancy/utils.js @@ -13,29 +13,38 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isFactorConfiguredForTenant = exports.isValidFirstFactor = exports.validateAndNormaliseUserInput = void 0; const recipe_1 = __importDefault(require("./recipe")); const logger_1 = require("../../logger"); const types_1 = require("../multifactorauth/types"); function validateAndNormaliseUserInput(config) { - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { - getAllowedDomainsForTenantId: config === null || config === void 0 ? void 0 : config.getAllowedDomainsForTenantId, + getAllowedDomainsForTenantId: + config === null || config === void 0 ? void 0 : config.getAllowedDomainsForTenantId, override, }; } @@ -52,21 +61,29 @@ const isValidFirstFactor = async function (tenantId, factorId, userContext) { status: "TENANT_NOT_FOUND_ERROR", }; } - const { status: _ } = tenantInfo, tenantConfig = __rest(tenantInfo, ["status"]); + const { status: _ } = tenantInfo, + tenantConfig = __rest(tenantInfo, ["status"]); const firstFactorsFromMFA = mtRecipe.staticFirstFactors; - logger_1.logDebugMessage(`isValidFirstFactor got ${(_a = tenantConfig.firstFactors) === null || _a === void 0 ? void 0 : _a.join(", ")} from tenant config`); + logger_1.logDebugMessage( + `isValidFirstFactor got ${ + (_a = tenantConfig.firstFactors) === null || _a === void 0 ? void 0 : _a.join(", ") + } from tenant config` + ); logger_1.logDebugMessage(`isValidFirstFactor got ${firstFactorsFromMFA} from MFA`); // first factors configured in core is prioritised over the ones configured statically - let configuredFirstFactors = tenantConfig.firstFactors !== undefined ? tenantConfig.firstFactors : firstFactorsFromMFA; + let configuredFirstFactors = + tenantConfig.firstFactors !== undefined ? tenantConfig.firstFactors : firstFactorsFromMFA; if (configuredFirstFactors === undefined) { configuredFirstFactors = mtRecipe.allAvailableFirstFactors; } - if (isFactorConfiguredForTenant({ - tenantConfig, - allAvailableFirstFactors: mtRecipe.allAvailableFirstFactors, - firstFactors: configuredFirstFactors, - factorId, - })) { + if ( + isFactorConfiguredForTenant({ + tenantConfig, + allAvailableFirstFactors: mtRecipe.allAvailableFirstFactors, + firstFactors: configuredFirstFactors, + factorId, + }) + ) { return { status: "OK", }; @@ -76,11 +93,14 @@ const isValidFirstFactor = async function (tenantId, factorId, userContext) { }; }; exports.isValidFirstFactor = isValidFirstFactor; -function isFactorConfiguredForTenant({ allAvailableFirstFactors, firstFactors, factorId, }) { +function isFactorConfiguredForTenant({ allAvailableFirstFactors, firstFactors, factorId }) { // Here we filter the array so that we only have: // 1. Factors that other recipes have marked as available // 2. Custom factors (not in the built-in FactorIds list) - let configuredFirstFactors = firstFactors.filter((factorId) => allAvailableFirstFactors.includes(factorId) || !Object.values(types_1.FactorIds).includes(factorId)); + let configuredFirstFactors = firstFactors.filter( + (factorId) => + allAvailableFirstFactors.includes(factorId) || !Object.values(types_1.FactorIds).includes(factorId) + ); // Filter based on enabled recipes in the core is no more required // if (tenantConfig.emailPassword.enabled === false) { // configuredFirstFactors = configuredFirstFactors.filter( diff --git a/lib/build/recipe/oauth2client/api/implementation.js b/lib/build/recipe/oauth2client/api/implementation.js index ba3cc13cb..5a5e1d56f 100644 --- a/lib/build/recipe/oauth2client/api/implementation.js +++ b/lib/build/recipe/oauth2client/api/implementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const session_1 = __importDefault(require("../../session")); function getAPIInterface() { @@ -11,7 +13,9 @@ function getAPIInterface() { let normalisedClientId = clientId; if (normalisedClientId === undefined) { if (options.config.providerConfigs.length > 1) { - throw new Error("Should never come here: clientId is undefined and there are multiple providerConfigs"); + throw new Error( + "Should never come here: clientId is undefined and there are multiple providerConfigs" + ); } normalisedClientId = options.config.providerConfigs[0].clientId; } @@ -26,11 +30,9 @@ function getAPIInterface() { redirectURIInfo: input.redirectURIInfo, userContext, }); - } - else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { + } else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { oAuthTokensToUse = input.oAuthTokens; - } - else { + } else { throw Error("should never come here"); } const { userId, rawUserInfo } = await options.recipeImplementation.getUserInfo({ @@ -45,7 +47,15 @@ function getAPIInterface() { oAuthTokens: oAuthTokensToUse, userContext, }); - const session = await session_1.default.createNewSession(options.req, options.res, tenantId, recipeUserId, undefined, undefined, userContext); + const session = await session_1.default.createNewSession( + options.req, + options.res, + tenantId, + recipeUserId, + undefined, + undefined, + userContext + ); return { status: "OK", user, diff --git a/lib/build/recipe/oauth2client/api/signin.d.ts b/lib/build/recipe/oauth2client/api/signin.d.ts index 079e9b8fb..72cd6e46b 100644 --- a/lib/build/recipe/oauth2client/api/signin.d.ts +++ b/lib/build/recipe/oauth2client/api/signin.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function signInAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function signInAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2client/api/signin.js b/lib/build/recipe/oauth2client/api/signin.js index 2ba871909..57155dc81 100644 --- a/lib/build/recipe/oauth2client/api/signin.js +++ b/lib/build/recipe/oauth2client/api/signin.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../../error")); const utils_1 = require("../../../utils"); @@ -40,11 +42,9 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { }); } redirectURIInfo = bodyParams.redirectURIInfo; - } - else if (bodyParams.oAuthTokens !== undefined) { + } else if (bodyParams.oAuthTokens !== undefined) { oAuthTokens = bodyParams.oAuthTokens; - } - else { + } else { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "Please provide one of redirectURIInfo or oAuthTokens in the request body", @@ -59,9 +59,14 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { userContext, }); if (result.status === "OK") { - utils_1.send200Response(options.res, Object.assign({ status: result.status }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); - } - else { + utils_1.send200Response( + options.res, + Object.assign( + { status: result.status }, + utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext) + ) + ); + } else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/oauth2client/index.d.ts b/lib/build/recipe/oauth2client/index.d.ts index 9f66e4f71..ff01c3827 100644 --- a/lib/build/recipe/oauth2client/index.d.ts +++ b/lib/build/recipe/oauth2client/index.d.ts @@ -3,12 +3,19 @@ import Recipe from "./recipe"; import { RecipeInterface, APIInterface, APIOptions, OAuthTokens } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static exchangeAuthCodeForOAuthTokens(redirectURIInfo: { - redirectURI: string; - redirectURIQueryParams: any; - pkceCodeVerifier?: string | undefined; - }, clientId?: string, userContext?: Record): Promise; - static getUserInfo(oAuthTokens: OAuthTokens, userContext?: Record): Promise; + static exchangeAuthCodeForOAuthTokens( + redirectURIInfo: { + redirectURI: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string | undefined; + }, + clientId?: string, + userContext?: Record + ): Promise; + static getUserInfo( + oAuthTokens: OAuthTokens, + userContext?: Record + ): Promise; } export declare let init: typeof Recipe.init; export declare let exchangeAuthCodeForOAuthTokens: typeof Wrapper.exchangeAuthCodeForOAuthTokens; diff --git a/lib/build/recipe/oauth2client/index.js b/lib/build/recipe/oauth2client/index.js index 1d2fb99db..e24e2d323 100644 --- a/lib/build/recipe/oauth2client/index.js +++ b/lib/build/recipe/oauth2client/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getUserInfo = exports.exchangeAuthCodeForOAuthTokens = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/oauth2client/recipe.d.ts b/lib/build/recipe/oauth2client/recipe.d.ts index deae2b60a..180227169 100644 --- a/lib/build/recipe/oauth2client/recipe.d.ts +++ b/lib/build/recipe/oauth2client/recipe.d.ts @@ -12,12 +12,26 @@ export default class Recipe extends RecipeModule { recipeInterfaceImpl: RecipeInterface; apiImpl: APIInterface; isInServerlessEnv: boolean; - constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput, _recipes: {}); + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput, + _recipes: {} + ); static init(config: TypeInput): RecipeListFunction; static getInstanceOrThrowError(): Recipe; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/oauth2client/recipe.js b/lib/build/recipe/oauth2client/recipe.js index 33b7e36c3..daf6b07f3 100644 --- a/lib/build/recipe/oauth2client/recipe.js +++ b/lib/build/recipe/oauth2client/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const utils_1 = require("./utils"); @@ -67,7 +69,9 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -80,8 +84,7 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config, {}); return Recipe.instance; - } - else { + } else { throw new Error("OAuth2Client recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/oauth2client/recipeImplementation.js b/lib/build/recipe/oauth2client/recipeImplementation.js index 3e23a280c..351fa3a45 100644 --- a/lib/build/recipe/oauth2client/recipeImplementation.js +++ b/lib/build/recipe/oauth2client/recipeImplementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const thirdpartyUtils_1 = require("../../thirdpartyUtils"); @@ -11,7 +13,7 @@ const jose_1 = require("jose"); function getRecipeImplementation(_querier, config) { let providerConfigsWithOIDCInfo = {}; return { - signIn: async function ({ userId, tenantId, userContext, oAuthTokens, rawUserInfo, }) { + signIn: async function ({ userId, tenantId, userContext, oAuthTokens, rawUserInfo }) { const user = await __1.getUser(userId, userContext); if (user === undefined) { throw new Error(`Failed to getUser from the userId ${userId} in the ${tenantId} tenant`); @@ -28,7 +30,9 @@ function getRecipeImplementation(_querier, config) { if (providerConfigsWithOIDCInfo[clientId] !== undefined) { return providerConfigsWithOIDCInfo[clientId]; } - const providerConfig = config.providerConfigs.find((providerConfig) => providerConfig.clientId === clientId); + const providerConfig = config.providerConfigs.find( + (providerConfig) => providerConfig.clientId === clientId + ); const oidcInfo = await thirdpartyUtils_1.getOIDCDiscoveryInfo(providerConfig.oidcDiscoveryEndpoint); if (oidcInfo.authorization_endpoint === undefined) { throw new Error("Failed to authorization_endpoint from the oidcDiscoveryEndpoint."); @@ -42,7 +46,12 @@ function getRecipeImplementation(_querier, config) { if (oidcInfo.jwks_uri === undefined) { throw new Error("Failed to jwks_uri from the oidcDiscoveryEndpoint."); } - providerConfigsWithOIDCInfo[clientId] = Object.assign(Object.assign({}, providerConfig), { authorizationEndpoint: oidcInfo.authorization_endpoint, tokenEndpoint: oidcInfo.token_endpoint, userInfoEndpoint: oidcInfo.userinfo_endpoint, jwksURI: oidcInfo.jwks_uri }); + providerConfigsWithOIDCInfo[clientId] = Object.assign(Object.assign({}, providerConfig), { + authorizationEndpoint: oidcInfo.authorization_endpoint, + tokenEndpoint: oidcInfo.token_endpoint, + userInfoEndpoint: oidcInfo.userinfo_endpoint, + jwksURI: oidcInfo.jwks_uri, + }); return providerConfigsWithOIDCInfo[clientId]; }, exchangeAuthCodeForOAuthTokens: async function ({ providerConfig, redirectURIInfo }) { @@ -64,8 +73,12 @@ function getRecipeImplementation(_querier, config) { } const tokenResponse = await thirdpartyUtils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); if (tokenResponse.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); - throw new Error(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); + throw new Error( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); } return tokenResponse.jsonResponse; }, @@ -82,27 +95,38 @@ function getRecipeImplementation(_querier, config) { if (jwks === undefined) { jwks = jose_1.createRemoteJWKSet(new URL(providerConfig.jwksURI)); } - rawUserInfo.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload(idToken, jwks, { - audience: providerConfig.clientId, - }); + rawUserInfo.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( + idToken, + jwks, + { + audience: providerConfig.clientId, + } + ); } if (accessToken && providerConfig.userInfoEndpoint !== undefined) { const headers = { Authorization: "Bearer " + accessToken, }; const queryParams = {}; - const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest(providerConfig.userInfoEndpoint, queryParams, headers); + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( + providerConfig.userInfoEndpoint, + queryParams, + headers + ); if (userInfoFromAccessToken.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); - throw new Error(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); + throw new Error( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); } rawUserInfo.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; } let userId = undefined; if (((_a = rawUserInfo.fromIdTokenPayload) === null || _a === void 0 ? void 0 : _a.sub) !== undefined) { userId = rawUserInfo.fromIdTokenPayload["sub"]; - } - else if (((_b = rawUserInfo.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.sub) !== undefined) { + } else if (((_b = rawUserInfo.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.sub) !== undefined) { userId = rawUserInfo.fromUserInfoAPI["sub"]; } if (userId === undefined) { diff --git a/lib/build/recipe/oauth2client/types.d.ts b/lib/build/recipe/oauth2client/types.d.ts index 14e21c0af..84fab1847 100644 --- a/lib/build/recipe/oauth2client/types.d.ts +++ b/lib/build/recipe/oauth2client/types.d.ts @@ -42,22 +42,25 @@ export declare type OAuthTokenResponse = { export declare type TypeInput = { providerConfigs: ProviderConfigInput[]; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { providerConfigs: ProviderConfigInput[]; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type RecipeInterface = { - getProviderConfig(input: { - clientId: string; - userContext: UserContext; - }): Promise; + getProviderConfig(input: { clientId: string; userContext: UserContext }): Promise; signIn(input: { userId: string; oAuthTokens: OAuthTokens; @@ -110,35 +113,43 @@ export declare type APIOptions = { appInfo: NormalisedAppinfo; }; export declare type APIInterface = { - signInPOST: (input: { - tenantId: string; - clientId?: string; - options: APIOptions; - userContext: UserContext; - } & ({ - redirectURIInfo: { - redirectURI: string; - redirectURIQueryParams: any; - pkceCodeVerifier?: string; - }; - } | { - oAuthTokens: { - [key: string]: any; - }; - })) => Promise<{ - status: "OK"; - user: User; - session: SessionContainerInterface; - oAuthTokens: { - [key: string]: any; - }; - rawUserInfo: { - fromIdTokenPayload?: { - [key: string]: any; - }; - fromUserInfoAPI?: { - [key: string]: any; - }; - }; - } | GeneralErrorResponse>; + signInPOST: ( + input: { + tenantId: string; + clientId?: string; + options: APIOptions; + userContext: UserContext; + } & ( + | { + redirectURIInfo: { + redirectURI: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string; + }; + } + | { + oAuthTokens: { + [key: string]: any; + }; + } + ) + ) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + oAuthTokens: { + [key: string]: any; + }; + rawUserInfo: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + } + | GeneralErrorResponse + >; }; diff --git a/lib/build/recipe/oauth2client/utils.d.ts b/lib/build/recipe/oauth2client/utils.d.ts index 5e9f5c6dc..6a930e641 100644 --- a/lib/build/recipe/oauth2client/utils.d.ts +++ b/lib/build/recipe/oauth2client/utils.d.ts @@ -1,4 +1,7 @@ // @ts-nocheck import { NormalisedAppinfo } from "../../types"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput(_appInfo: NormalisedAppinfo, config: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + _appInfo: NormalisedAppinfo, + config: TypeInput +): TypeNormalisedInput; diff --git a/lib/build/recipe/oauth2client/utils.js b/lib/build/recipe/oauth2client/utils.js index 1f2287985..25e759254 100644 --- a/lib/build/recipe/oauth2client/utils.js +++ b/lib/build/recipe/oauth2client/utils.js @@ -23,12 +23,20 @@ function validateAndNormaliseUserInput(_appInfo, config) { throw new Error("Please pass clientId for all providerConfigs."); } if (!config.providerConfigs.every((providerConfig) => providerConfig.clientId.startsWith("stcl_"))) { - throw new Error(`Only Supertokens OAuth ClientIds are supported in the OAuth2Client recipe. For any other OAuth Clients use the ThirdParty recipe.`); + throw new Error( + `Only Supertokens OAuth ClientIds are supported in the OAuth2Client recipe. For any other OAuth Clients use the ThirdParty recipe.` + ); } if (config.providerConfigs.some((providerConfig) => providerConfig.oidcDiscoveryEndpoint === undefined)) { throw new Error("Please pass oidcDiscoveryEndpoint for all providerConfigs."); } - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { providerConfigs: config.providerConfigs, override, diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts index 3d5d6c0a6..7ffba30b1 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts +++ b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts @@ -139,6 +139,33 @@ export declare class OAuth2Client { * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. */ metadata: Record; - constructor({ clientId, clientSecret, clientName, scope, redirectUris, postLogoutRedirectUris, authorizationCodeGrantAccessTokenLifespan, authorizationCodeGrantIdTokenLifespan, authorizationCodeGrantRefreshTokenLifespan, clientCredentialsGrantAccessTokenLifespan, implicitGrantAccessTokenLifespan, implicitGrantIdTokenLifespan, refreshTokenGrantAccessTokenLifespan, refreshTokenGrantIdTokenLifespan, refreshTokenGrantRefreshTokenLifespan, tokenEndpointAuthMethod, clientUri, audience, grantTypes, responseTypes, logoUri, policyUri, tosUri, createdAt, updatedAt, metadata, }: OAuth2ClientOptions); + constructor({ + clientId, + clientSecret, + clientName, + scope, + redirectUris, + postLogoutRedirectUris, + authorizationCodeGrantAccessTokenLifespan, + authorizationCodeGrantIdTokenLifespan, + authorizationCodeGrantRefreshTokenLifespan, + clientCredentialsGrantAccessTokenLifespan, + implicitGrantAccessTokenLifespan, + implicitGrantIdTokenLifespan, + refreshTokenGrantAccessTokenLifespan, + refreshTokenGrantIdTokenLifespan, + refreshTokenGrantRefreshTokenLifespan, + tokenEndpointAuthMethod, + clientUri, + audience, + grantTypes, + responseTypes, + logoUri, + policyUri, + tosUri, + createdAt, + updatedAt, + metadata, + }: OAuth2ClientOptions); static fromAPIResponse(response: any): OAuth2Client; } diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.js b/lib/build/recipe/oauth2provider/OAuth2Client.js index d247b4fea..4c700f04f 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.js +++ b/lib/build/recipe/oauth2provider/OAuth2Client.js @@ -17,7 +17,34 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuth2Client = void 0; const utils_1 = require("../../utils"); class OAuth2Client { - constructor({ clientId, clientSecret, clientName, scope, redirectUris = null, postLogoutRedirectUris, authorizationCodeGrantAccessTokenLifespan = null, authorizationCodeGrantIdTokenLifespan = null, authorizationCodeGrantRefreshTokenLifespan = null, clientCredentialsGrantAccessTokenLifespan = null, implicitGrantAccessTokenLifespan = null, implicitGrantIdTokenLifespan = null, refreshTokenGrantAccessTokenLifespan = null, refreshTokenGrantIdTokenLifespan = null, refreshTokenGrantRefreshTokenLifespan = null, tokenEndpointAuthMethod, clientUri = "", audience = [], grantTypes = null, responseTypes = null, logoUri = "", policyUri = "", tosUri = "", createdAt, updatedAt, metadata = {}, }) { + constructor({ + clientId, + clientSecret, + clientName, + scope, + redirectUris = null, + postLogoutRedirectUris, + authorizationCodeGrantAccessTokenLifespan = null, + authorizationCodeGrantIdTokenLifespan = null, + authorizationCodeGrantRefreshTokenLifespan = null, + clientCredentialsGrantAccessTokenLifespan = null, + implicitGrantAccessTokenLifespan = null, + implicitGrantIdTokenLifespan = null, + refreshTokenGrantAccessTokenLifespan = null, + refreshTokenGrantIdTokenLifespan = null, + refreshTokenGrantRefreshTokenLifespan = null, + tokenEndpointAuthMethod, + clientUri = "", + audience = [], + grantTypes = null, + responseTypes = null, + logoUri = "", + policyUri = "", + tosUri = "", + createdAt, + updatedAt, + metadata = {}, + }) { /** * Metadata - JSON object * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. diff --git a/lib/build/recipe/oauth2provider/api/auth.d.ts b/lib/build/recipe/oauth2provider/api/auth.d.ts index bd8b4a391..059876918 100644 --- a/lib/build/recipe/oauth2provider/api/auth.d.ts +++ b/lib/build/recipe/oauth2provider/api/auth.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function authGET(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function authGET( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/auth.js b/lib/build/recipe/oauth2provider/api/auth.js index 16c4b64f4..b5c0e4afc 100644 --- a/lib/build/recipe/oauth2provider/api/auth.js +++ b/lib/build/recipe/oauth2provider/api/auth.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const set_cookie_parser_1 = __importDefault(require("set-cookie-parser")); @@ -33,13 +35,11 @@ async function authGET(apiImplementation, options, userContext) { try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); shouldTryRefresh = false; - } - catch (error) { + } catch (error) { session = undefined; if (error_1.default.isErrorFromSuperTokens(error) && error.type === error_1.default.TRY_REFRESH_TOKEN) { shouldTryRefresh = true; - } - else { + } else { // This should generally not happen, but we can handle this as if the session is not present, // because then we redirect to the frontend, which should handle the validation error shouldTryRefresh = false; @@ -58,18 +58,25 @@ async function authGET(apiImplementation, options, userContext) { const cookieStr = set_cookie_parser_1.default.splitCookiesString(response.cookies); const cookies = set_cookie_parser_1.default.parse(cookieStr); for (const cookie of cookies) { - options.res.setCookie(cookie.name, cookie.value, cookie.domain, !!cookie.secure, !!cookie.httpOnly, new Date(cookie.expires).getTime(), cookie.path || "/", cookie.sameSite); + options.res.setCookie( + cookie.name, + cookie.value, + cookie.domain, + !!cookie.secure, + !!cookie.httpOnly, + new Date(cookie.expires).getTime(), + cookie.path || "/", + cookie.sameSite + ); } } options.res.original.redirect(response.redirectTo); - } - else if ("statusCode" in response) { + } else if ("statusCode" in response) { utils_1.sendNon200Response(options.res, (_a = response.statusCode) !== null && _a !== void 0 ? _a : 400, { error: response.error, error_description: response.errorDescription, }); - } - else { + } else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/endSession.d.ts b/lib/build/recipe/oauth2provider/api/endSession.d.ts index 0f161e541..1f454cbd0 100644 --- a/lib/build/recipe/oauth2provider/api/endSession.d.ts +++ b/lib/build/recipe/oauth2provider/api/endSession.d.ts @@ -1,5 +1,13 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export declare function endSessionGET(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; -export declare function endSessionPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export declare function endSessionGET( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; +export declare function endSessionPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/endSession.js b/lib/build/recipe/oauth2provider/api/endSession.js index eb16ff12a..f0d1da3b2 100644 --- a/lib/build/recipe/oauth2provider/api/endSession.js +++ b/lib/build/recipe/oauth2provider/api/endSession.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.endSessionPOST = exports.endSessionGET = void 0; const utils_1 = require("../../../utils"); @@ -29,7 +31,12 @@ async function endSessionGET(apiImplementation, options, userContext) { const origURL = options.req.getOriginalURL(); const splitURL = origURL.split("?"); const params = new URLSearchParams(splitURL[1]); - return endSessionCommon(Object.fromEntries(params.entries()), apiImplementation.endSessionGET, options, userContext); + return endSessionCommon( + Object.fromEntries(params.entries()), + apiImplementation.endSessionGET, + options, + userContext + ); } exports.endSessionGET = endSessionGET; async function endSessionPOST(apiImplementation, options, userContext) { @@ -49,15 +56,13 @@ async function endSessionCommon(params, apiImplementation, options, userContext) try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); shouldTryRefresh = false; - } - catch (error) { + } catch (error) { // We can handle this as if the session is not present, because then we redirect to the frontend, // which should handle the validation error session = undefined; if (error_1.default.isErrorFromSuperTokens(error) && error.type === error_2.default.TRY_REFRESH_TOKEN) { shouldTryRefresh = true; - } - else { + } else { shouldTryRefresh = false; } } @@ -70,14 +75,12 @@ async function endSessionCommon(params, apiImplementation, options, userContext) }); if ("redirectTo" in response) { options.res.original.redirect(response.redirectTo); - } - else if ("error" in response) { + } else if ("error" in response) { utils_1.sendNon200Response(options.res, (_a = response.statusCode) !== null && _a !== void 0 ? _a : 400, { error: response.error, error_description: response.errorDescription, }); - } - else { + } else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index 0bf97f712..fa4232d17 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -109,16 +109,14 @@ function getAPIImplementation() { authorizationHeader: input.authorizationHeader, userContext: input.userContext, }); - } - else if ("clientId" in input && input.clientId !== undefined) { + } else if ("clientId" in input && input.clientId !== undefined) { return input.options.recipeImplementation.revokeToken({ token: input.token, clientId: input.clientId, clientSecret: input.clientSecret, userContext: input.userContext, }); - } - else { + } else { throw new Error(`Either of 'authorizationHeader' or 'clientId' must be provided`); } }, diff --git a/lib/build/recipe/oauth2provider/api/introspectToken.d.ts b/lib/build/recipe/oauth2provider/api/introspectToken.d.ts index 135880d83..3d2972c0d 100644 --- a/lib/build/recipe/oauth2provider/api/introspectToken.d.ts +++ b/lib/build/recipe/oauth2provider/api/introspectToken.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function introspectTokenPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function introspectTokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/login.d.ts b/lib/build/recipe/oauth2provider/api/login.d.ts index 1a1417232..6f4253cef 100644 --- a/lib/build/recipe/oauth2provider/api/login.d.ts +++ b/lib/build/recipe/oauth2provider/api/login.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function login(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function login( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/login.js b/lib/build/recipe/oauth2provider/api/login.js index d507416c0..186337139 100644 --- a/lib/build/recipe/oauth2provider/api/login.js +++ b/lib/build/recipe/oauth2provider/api/login.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const set_cookie_parser_1 = __importDefault(require("set-cookie-parser")); const utils_1 = require("../../../utils"); @@ -31,19 +33,20 @@ async function login(apiImplementation, options, userContext) { try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); shouldTryRefresh = false; - } - catch (error) { + } catch (error) { // We can handle this as if the session is not present, because then we redirect to the frontend, // which should handle the validation error session = undefined; if (error_1.default.isErrorFromSuperTokens(error) && error.type === error_2.default.TRY_REFRESH_TOKEN) { shouldTryRefresh = true; - } - else { + } else { shouldTryRefresh = false; } } - const loginChallenge = (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 ? _a : options.req.getKeyValueFromQuery("loginChallenge"); + const loginChallenge = + (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 + ? _a + : options.req.getKeyValueFromQuery("loginChallenge"); if (loginChallenge === undefined) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, @@ -62,20 +65,27 @@ async function login(apiImplementation, options, userContext) { const cookieStr = set_cookie_parser_1.default.splitCookiesString(response.cookies); const cookies = set_cookie_parser_1.default.parse(cookieStr); for (const cookie of cookies) { - options.res.setCookie(cookie.name, cookie.value, cookie.domain, !!cookie.secure, !!cookie.httpOnly, new Date(cookie.expires).getTime(), cookie.path || "/", cookie.sameSite); + options.res.setCookie( + cookie.name, + cookie.value, + cookie.domain, + !!cookie.secure, + !!cookie.httpOnly, + new Date(cookie.expires).getTime(), + cookie.path || "/", + cookie.sameSite + ); } } utils_1.send200Response(options.res, { frontendRedirectTo: response.frontendRedirectTo, }); - } - else if ("statusCode" in response) { + } else if ("statusCode" in response) { utils_1.sendNon200Response(options.res, (_b = response.statusCode) !== null && _b !== void 0 ? _b : 400, { error: response.error, error_description: response.errorDescription, }); - } - else { + } else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/loginInfo.d.ts b/lib/build/recipe/oauth2provider/api/loginInfo.d.ts index e9817552e..536858263 100644 --- a/lib/build/recipe/oauth2provider/api/loginInfo.d.ts +++ b/lib/build/recipe/oauth2provider/api/loginInfo.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function loginInfoGET(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function loginInfoGET( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/loginInfo.js b/lib/build/recipe/oauth2provider/api/loginInfo.js index e0f5be6c4..15b9da808 100644 --- a/lib/build/recipe/oauth2provider/api/loginInfo.js +++ b/lib/build/recipe/oauth2provider/api/loginInfo.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../../../error")); @@ -24,7 +26,10 @@ async function loginInfoGET(apiImplementation, options, userContext) { if (apiImplementation.loginInfoGET === undefined) { return false; } - const loginChallenge = (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 ? _a : options.req.getKeyValueFromQuery("loginChallenge"); + const loginChallenge = + (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 + ? _a + : options.req.getKeyValueFromQuery("loginChallenge"); if (loginChallenge === undefined) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, diff --git a/lib/build/recipe/oauth2provider/api/logout.d.ts b/lib/build/recipe/oauth2provider/api/logout.d.ts index e53197994..339a73e77 100644 --- a/lib/build/recipe/oauth2provider/api/logout.d.ts +++ b/lib/build/recipe/oauth2provider/api/logout.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export declare function logoutPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export declare function logoutPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/logout.js b/lib/build/recipe/oauth2provider/api/logout.js index 0f4e3c295..16a51b511 100644 --- a/lib/build/recipe/oauth2provider/api/logout.js +++ b/lib/build/recipe/oauth2provider/api/logout.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.logoutPOST = void 0; const utils_1 = require("../../../utils"); @@ -29,8 +31,7 @@ async function logoutPOST(apiImplementation, options, userContext) { let session; try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); - } - catch (_b) { + } catch (_b) { session = undefined; } const body = await options.req.getBodyAsJSONOrFormData(); @@ -48,14 +49,12 @@ async function logoutPOST(apiImplementation, options, userContext) { }); if ("status" in response && response.status === "OK") { utils_1.send200Response(options.res, response); - } - else if ("statusCode" in response) { + } else if ("statusCode" in response) { utils_1.sendNon200Response(options.res, (_a = response.statusCode) !== null && _a !== void 0 ? _a : 400, { error: response.error, error_description: response.errorDescription, }); - } - else { + } else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/revokeToken.d.ts b/lib/build/recipe/oauth2provider/api/revokeToken.d.ts index d79a68c45..902e734d5 100644 --- a/lib/build/recipe/oauth2provider/api/revokeToken.d.ts +++ b/lib/build/recipe/oauth2provider/api/revokeToken.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function revokeTokenPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function revokeTokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/revokeToken.js b/lib/build/recipe/oauth2provider/api/revokeToken.js index 86437176e..7a190ae5d 100644 --- a/lib/build/recipe/oauth2provider/api/revokeToken.js +++ b/lib/build/recipe/oauth2provider/api/revokeToken.js @@ -27,7 +27,11 @@ async function revokeTokenPOST(apiImplementation, options, userContext) { } const authorizationHeader = options.req.getHeaderValue("authorization"); if (authorizationHeader !== undefined && (body.client_id !== undefined || body.client_secret !== undefined)) { - utils_1.sendNon200ResponseWithMessage(options.res, "Only one of authorization header or client_id and client_secret can be provided", 400); + utils_1.sendNon200ResponseWithMessage( + options.res, + "Only one of authorization header or client_id and client_secret can be provided", + 400 + ); return true; } let response = await apiImplementation.revokeTokenPOST({ @@ -43,8 +47,7 @@ async function revokeTokenPOST(apiImplementation, options, userContext) { error: response.error, error_description: response.errorDescription, }); - } - else { + } else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/token.d.ts b/lib/build/recipe/oauth2provider/api/token.d.ts index 68d95e24e..c697b7744 100644 --- a/lib/build/recipe/oauth2provider/api/token.d.ts +++ b/lib/build/recipe/oauth2provider/api/token.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function tokenPOST(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function tokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/token.js b/lib/build/recipe/oauth2provider/api/token.js index 8f1b3274f..9aaa0bd38 100644 --- a/lib/build/recipe/oauth2provider/api/token.js +++ b/lib/build/recipe/oauth2provider/api/token.js @@ -32,8 +32,7 @@ async function tokenPOST(apiImplementation, options, userContext) { error: response.error, error_description: response.errorDescription, }); - } - else { + } else { utils_1.send200Response(options.res, response); } return true; diff --git a/lib/build/recipe/oauth2provider/api/userInfo.d.ts b/lib/build/recipe/oauth2provider/api/userInfo.d.ts index 92f0a98be..d0b8cdf4e 100644 --- a/lib/build/recipe/oauth2provider/api/userInfo.d.ts +++ b/lib/build/recipe/oauth2provider/api/userInfo.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function userInfoGET(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function userInfoGET( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/userInfo.js b/lib/build/recipe/oauth2provider/api/userInfo.js index 9d39b3549..cfa8f704b 100644 --- a/lib/build/recipe/oauth2provider/api/userInfo.js +++ b/lib/build/recipe/oauth2provider/api/userInfo.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../recipe")); const utils_1 = require("../../../utils"); @@ -32,22 +34,25 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { const accessToken = authHeader.replace(/^Bearer /, "").trim(); let accessTokenPayload; try { - const { payload, } = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ + const { + payload, + } = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ token: accessToken, userContext, }); accessTokenPayload = payload; - } - catch (error) { + } catch (error) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); utils_1.sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 401); return true; } - if (accessTokenPayload === null || + if ( + accessTokenPayload === null || typeof accessTokenPayload !== "object" || typeof accessTokenPayload.sub !== "string" || - !Array.isArray(accessTokenPayload.scp)) { + !Array.isArray(accessTokenPayload.scp) + ) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); utils_1.sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 401); @@ -58,7 +63,11 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { if (user === undefined) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); - utils_1.sendNon200ResponseWithMessage(options.res, "Couldn't find any user associated with the access token", 401); + utils_1.sendNon200ResponseWithMessage( + options.res, + "Couldn't find any user associated with the access token", + 401 + ); return true; } const response = await apiImplementation.userInfoGET({ diff --git a/lib/build/recipe/oauth2provider/api/utils.d.ts b/lib/build/recipe/oauth2provider/api/utils.d.ts index acc1c2a03..1bf243b1a 100644 --- a/lib/build/recipe/oauth2provider/api/utils.d.ts +++ b/lib/build/recipe/oauth2provider/api/utils.d.ts @@ -2,7 +2,15 @@ import { UserContext } from "../../../types"; import { SessionContainerInterface } from "../../session/types"; import { ErrorOAuth2, RecipeInterface } from "../types"; -export declare function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh, session, cookies, isDirectCall, userContext, }: { +export declare function loginGET({ + recipeImplementation, + loginChallenge, + shouldTryRefresh, + session, + cookies, + isDirectCall, + userContext, +}: { recipeImplementation: RecipeInterface; loginChallenge: string; session?: SessionContainerInterface; @@ -10,16 +18,27 @@ export declare function loginGET({ recipeImplementation, loginChallenge, shouldT cookies?: string; userContext: UserContext; isDirectCall: boolean; -}): Promise; -export declare function handleLoginInternalRedirects({ response, recipeImplementation, session, shouldTryRefresh, cookie, userContext, }: { +}): Promise< + | ErrorOAuth2 + | { + status: string; + redirectTo: string; + cookies: string | undefined; + } + | { + redirectTo: string; + cookies: string | undefined; + status?: undefined; + } +>; +export declare function handleLoginInternalRedirects({ + response, + recipeImplementation, + session, + shouldTryRefresh, + cookie, + userContext, +}: { response: { redirectTo: string; cookies?: string; @@ -29,17 +48,28 @@ export declare function handleLoginInternalRedirects({ response, recipeImplement shouldTryRefresh: boolean; cookie?: string; userContext: UserContext; -}): Promise<{ - redirectTo: string; - cookies?: string; -} | ErrorOAuth2>; -export declare function handleLogoutInternalRedirects({ response, recipeImplementation, session, userContext, }: { +}): Promise< + | { + redirectTo: string; + cookies?: string; + } + | ErrorOAuth2 +>; +export declare function handleLogoutInternalRedirects({ + response, + recipeImplementation, + session, + userContext, +}: { response: { redirectTo: string; }; recipeImplementation: RecipeInterface; session?: SessionContainerInterface; userContext: UserContext; -}): Promise<{ - redirectTo: string; -} | ErrorOAuth2>; +}): Promise< + | { + redirectTo: string; + } + | ErrorOAuth2 +>; diff --git a/lib/build/recipe/oauth2provider/api/utils.js b/lib/build/recipe/oauth2provider/api/utils.js index 1fd0e53ce..64d5fe32d 100644 --- a/lib/build/recipe/oauth2provider/api/utils.js +++ b/lib/build/recipe/oauth2provider/api/utils.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.handleLogoutInternalRedirects = exports.handleLoginInternalRedirects = exports.loginGET = void 0; const supertokens_1 = __importDefault(require("../../../supertokens")); @@ -11,7 +13,15 @@ const constants_2 = require("../constants"); const set_cookie_parser_1 = __importDefault(require("set-cookie-parser")); // API implementation for the loginGET function. // Extracted for use in both apiImplementation and handleInternalRedirects. -async function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh, session, cookies, isDirectCall, userContext, }) { +async function loginGET({ + recipeImplementation, + loginChallenge, + shouldTryRefresh, + session, + cookies, + isDirectCall, + userContext, +}) { var _a, _b; const loginRequest = await recipeImplementation.getLoginRequest({ challenge: loginChallenge, @@ -20,12 +30,20 @@ async function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh if (loginRequest.status === "ERROR") { return loginRequest; } - const sessionInfo = session !== undefined ? await session_1.getSessionInformation(session === null || session === void 0 ? void 0 : session.getHandle()) : undefined; + const sessionInfo = + session !== undefined + ? await session_1.getSessionInformation( + session === null || session === void 0 ? void 0 : session.getHandle() + ) + : undefined; if (!sessionInfo) { session = undefined; } const incomingAuthUrlQueryParams = new URLSearchParams(loginRequest.requestUrl.split("?")[1]); - const promptParam = (_a = incomingAuthUrlQueryParams.get("prompt")) !== null && _a !== void 0 ? _a : incomingAuthUrlQueryParams.get("st_prompt"); + const promptParam = + (_a = incomingAuthUrlQueryParams.get("prompt")) !== null && _a !== void 0 + ? _a + : incomingAuthUrlQueryParams.get("st_prompt"); const maxAgeParam = incomingAuthUrlQueryParams.get("max_age"); if (maxAgeParam !== null) { try { @@ -54,8 +72,7 @@ async function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh }); return { status: "REDIRECT", redirectTo: reject.redirectTo, cookies }; } - } - catch (_c) { + } catch (_c) { const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, error: { @@ -69,13 +86,15 @@ async function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh } } const tenantIdParam = incomingAuthUrlQueryParams.get("tenant_id"); - if (session && + if ( + session && (["", undefined].includes(loginRequest.subject) || session.getUserId() === loginRequest.subject) && (["", null].includes(tenantIdParam) || session.getTenantId() === tenantIdParam) && (promptParam !== "login" || isDirectCall) && (maxAgeParam === null || (maxAgeParam === "0" && isDirectCall) || - Number.parseInt(maxAgeParam) * 1000 > Date.now() - sessionInfo.timeCreated)) { + Number.parseInt(maxAgeParam) * 1000 > Date.now() - sessionInfo.timeCreated) + ) { const accept = await recipeImplementation.acceptLoginRequest({ challenge: loginChallenge, subject: session.getUserId(), @@ -100,7 +119,8 @@ async function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh error: { status: "ERROR", error: "login_required", - errorDescription: "The Authorization Server requires End-User authentication. Prompt 'none' was requested, but no existing or expired login session was found.", + errorDescription: + "The Authorization Server requires End-User authentication. Prompt 'none' was requested, but no existing or expired login session was found.", }, userContext, }); @@ -112,7 +132,8 @@ async function loginGET({ recipeImplementation, loginChallenge, shouldTryRefresh type: "login", loginChallenge, forceFreshAuth: session !== undefined || promptParam === "login", - tenantId: tenantIdParam !== null && tenantIdParam !== void 0 ? tenantIdParam : constants_1.DEFAULT_TENANT_ID, + tenantId: + tenantIdParam !== null && tenantIdParam !== void 0 ? tenantIdParam : constants_1.DEFAULT_TENANT_ID, hint: (_b = loginRequest.oidcContext) === null || _b === void 0 ? void 0 : _b.login_hint, userContext, }), @@ -132,8 +153,7 @@ function getMergedCookies({ origCookies = "", newCookies }) { for (const { name, value, expires } of setCookies) { if (expires && new Date(expires) < new Date()) { delete cookieMap[name]; - } - else { + } else { cookieMap[name] = value; } } @@ -163,7 +183,14 @@ function isLogoutInternalRedirect(redirectTo) { // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. // Currently, we only need to handle redirects to /oauth/login and /oauth/auth endpoints in the login flow. -async function handleLoginInternalRedirects({ response, recipeImplementation, session, shouldTryRefresh, cookie = "", userContext, }) { +async function handleLoginInternalRedirects({ + response, + recipeImplementation, + session, + shouldTryRefresh, + cookie = "", + userContext, +}) { var _a; if (!isLoginInternalRedirect(response.redirectTo)) { return response; @@ -177,7 +204,8 @@ async function handleLoginInternalRedirects({ response, recipeImplementation, se const queryString = response.redirectTo.split("?")[1]; const params = new URLSearchParams(queryString); if (response.redirectTo.includes(constants_2.LOGIN_PATH)) { - const loginChallenge = (_a = params.get("login_challenge")) !== null && _a !== void 0 ? _a : params.get("loginChallenge"); + const loginChallenge = + (_a = params.get("login_challenge")) !== null && _a !== void 0 ? _a : params.get("loginChallenge"); if (!loginChallenge) { throw new Error(`Expected loginChallenge in ${response.redirectTo}`); } @@ -197,8 +225,7 @@ async function handleLoginInternalRedirects({ response, recipeImplementation, se redirectTo: loginRes.redirectTo, cookies: mergeSetCookieHeaders(loginRes.cookies, response.cookies), }; - } - else if (response.redirectTo.includes(constants_2.AUTH_PATH)) { + } else if (response.redirectTo.includes(constants_2.AUTH_PATH)) { const authRes = await recipeImplementation.authorization({ params: Object.fromEntries(params.entries()), cookies: cookie, @@ -212,8 +239,7 @@ async function handleLoginInternalRedirects({ response, recipeImplementation, se redirectTo: authRes.redirectTo, cookies: mergeSetCookieHeaders(authRes.cookies, response.cookies), }; - } - else { + } else { throw new Error(`Unexpected internal redirect ${response.redirectTo}`); } redirectCount++; @@ -224,7 +250,7 @@ exports.handleLoginInternalRedirects = handleLoginInternalRedirects; // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. // Currently, we only need to handle redirects to /oauth/end_session endpoint in the logout flow. -async function handleLogoutInternalRedirects({ response, recipeImplementation, session, userContext, }) { +async function handleLogoutInternalRedirects({ response, recipeImplementation, session, userContext }) { if (!isLogoutInternalRedirect(response.redirectTo)) { return response; } @@ -249,8 +275,7 @@ async function handleLogoutInternalRedirects({ response, recipeImplementation, s return endSessionRes; } response = endSessionRes; - } - else { + } else { throw new Error(`Unexpected internal redirect ${response.redirectTo}`); } redirectCount++; diff --git a/lib/build/recipe/oauth2provider/index.d.ts b/lib/build/recipe/oauth2provider/index.d.ts index 585a3bf5d..f859cac0d 100644 --- a/lib/build/recipe/oauth2provider/index.d.ts +++ b/lib/build/recipe/oauth2provider/index.d.ts @@ -1,67 +1,134 @@ // @ts-nocheck import Recipe from "./recipe"; -import { APIInterface, RecipeInterface, APIOptions, CreateOAuth2ClientInput, UpdateOAuth2ClientInput, DeleteOAuth2ClientInput, GetOAuth2ClientsInput } from "./types"; +import { + APIInterface, + RecipeInterface, + APIOptions, + CreateOAuth2ClientInput, + UpdateOAuth2ClientInput, + DeleteOAuth2ClientInput, + GetOAuth2ClientsInput, +} from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static getOAuth2Client(clientId: string, userContext?: Record): Promise<{ - status: "OK"; - client: import("./OAuth2Client").OAuth2Client; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - static getOAuth2Clients(input: GetOAuth2ClientsInput, userContext?: Record): Promise<{ - status: "OK"; - clients: import("./OAuth2Client").OAuth2Client[]; - nextPaginationToken?: string | undefined; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - static createOAuth2Client(input: CreateOAuth2ClientInput, userContext?: Record): Promise<{ - status: "OK"; - client: import("./OAuth2Client").OAuth2Client; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - static updateOAuth2Client(input: UpdateOAuth2ClientInput, userContext?: Record): Promise<{ - status: "OK"; - client: import("./OAuth2Client").OAuth2Client; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - static deleteOAuth2Client(input: DeleteOAuth2ClientInput, userContext?: Record): Promise<{ - status: "OK"; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - static validateOAuth2AccessToken(token: string, requirements?: { - clientId?: string; - scopes?: string[]; - audience?: string; - }, checkDatabase?: boolean, userContext?: Record): Promise<{ + static getOAuth2Client( + clientId: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + static getOAuth2Clients( + input: GetOAuth2ClientsInput, + userContext?: Record + ): Promise< + | { + status: "OK"; + clients: import("./OAuth2Client").OAuth2Client[]; + nextPaginationToken?: string | undefined; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + static createOAuth2Client( + input: CreateOAuth2ClientInput, + userContext?: Record + ): Promise< + | { + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + static updateOAuth2Client( + input: UpdateOAuth2ClientInput, + userContext?: Record + ): Promise< + | { + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + static deleteOAuth2Client( + input: DeleteOAuth2ClientInput, + userContext?: Record + ): Promise< + | { + status: "OK"; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + static validateOAuth2AccessToken( + token: string, + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }, + checkDatabase?: boolean, + userContext?: Record + ): Promise<{ status: "OK"; payload: import("../usermetadata").JSONObject; }>; - static createTokenForClientCredentials(clientId: string, clientSecret: string, scope?: string[], audience?: string, userContext?: Record): Promise; - static revokeToken(token: string, clientId: string, clientSecret?: string, userContext?: Record): Promise; - static revokeTokensByClientId(clientId: string, userContext?: Record): Promise<{ + static createTokenForClientCredentials( + clientId: string, + clientSecret: string, + scope?: string[], + audience?: string, + userContext?: Record + ): Promise; + static revokeToken( + token: string, + clientId: string, + clientSecret?: string, + userContext?: Record + ): Promise< + | import("./types").ErrorOAuth2 + | { + status: "OK"; + } + >; + static revokeTokensByClientId( + clientId: string, + userContext?: Record + ): Promise<{ status: "OK"; }>; - static revokeTokensBySessionHandle(sessionHandle: string, userContext?: Record): Promise<{ + static revokeTokensBySessionHandle( + sessionHandle: string, + userContext?: Record + ): Promise<{ status: "OK"; }>; - static validateOAuth2RefreshToken(token: string, scopes?: string[], userContext?: Record): Promise; + static validateOAuth2RefreshToken( + token: string, + scopes?: string[], + userContext?: Record + ): Promise; } export declare let init: typeof Recipe.init; export declare let getOAuth2Client: typeof Wrapper.getOAuth2Client; diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index 656ec45f8..ee4d9b5ed 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.revokeTokensBySessionHandle = exports.revokeTokensByClientId = exports.revokeToken = exports.createTokenForClientCredentials = exports.validateOAuth2RefreshToken = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.getOAuth2Client = exports.init = void 0; const utils_1 = require("../../utils"); @@ -28,16 +30,32 @@ class Wrapper { }); } static async getOAuth2Clients(input, userContext) { - return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Clients(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) })); + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.getOAuth2Clients( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) + ); } static async createOAuth2Client(input, userContext) { - return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) })); + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.createOAuth2Client( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) + ); } static async updateOAuth2Client(input, userContext) { - return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.updateOAuth2Client(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) })); + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.updateOAuth2Client( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) + ); } static async deleteOAuth2Client(input, userContext) { - return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.deleteOAuth2Client(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) })); + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.deleteOAuth2Client( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) + ); } static validateOAuth2AccessToken(token, requirements, checkDatabase, userContext) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ @@ -70,8 +88,7 @@ class Wrapper { const { tokenEndpointAuthMethod } = res.client; if (tokenEndpointAuthMethod === "none") { authorizationHeader = "Basic " + Buffer.from(clientId + ":").toString("base64"); - } - else if (tokenEndpointAuthMethod === "client_secret_basic") { + } else if (tokenEndpointAuthMethod === "client_secret_basic") { authorizationHeader = "Basic " + Buffer.from(clientId + ":" + clientSecret).toString("base64"); } if (authorizationHeader !== undefined) { diff --git a/lib/build/recipe/oauth2provider/recipe.d.ts b/lib/build/recipe/oauth2provider/recipe.d.ts index 8948a0c20..d2739dbf7 100644 --- a/lib/build/recipe/oauth2provider/recipe.d.ts +++ b/lib/build/recipe/oauth2provider/recipe.d.ts @@ -4,7 +4,15 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import NormalisedURLPath from "../../normalisedURLPath"; import RecipeModule from "../../recipeModule"; import { APIHandled, HTTPMethod, JSONObject, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; -import { APIInterface, PayloadBuilderFunction, RecipeInterface, TypeInput, TypeNormalisedInput, UserInfo, UserInfoBuilderFunction } from "./types"; +import { + APIInterface, + PayloadBuilderFunction, + RecipeInterface, + TypeInput, + TypeNormalisedInput, + UserInfo, + UserInfoBuilderFunction, +} from "./types"; import { User } from "../../user"; export default class Recipe extends RecipeModule { static RECIPE_ID: string; @@ -25,11 +33,35 @@ export default class Recipe extends RecipeModule { addAccessTokenBuilderFromOtherRecipe: (accessTokenBuilders: PayloadBuilderFunction) => void; addIdTokenBuilderFromOtherRecipe: (idTokenBuilder: PayloadBuilderFunction) => void; getAPIsHandled(): APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + method: HTTPMethod, + userContext: UserContext + ) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; - getDefaultAccessTokenPayload(user: User, scopes: string[], sessionHandle: string, userContext: UserContext): Promise; - getDefaultIdTokenPayload(user: User, scopes: string[], sessionHandle: string, userContext: UserContext): Promise; - getDefaultUserInfoPayload(user: User, accessTokenPayload: JSONObject, scopes: string[], tenantId: string, userContext: UserContext): Promise; + getDefaultAccessTokenPayload( + user: User, + scopes: string[], + sessionHandle: string, + userContext: UserContext + ): Promise; + getDefaultIdTokenPayload( + user: User, + scopes: string[], + sessionHandle: string, + userContext: UserContext + ): Promise; + getDefaultUserInfoPayload( + user: User, + accessTokenPayload: JSONObject, + scopes: string[], + tenantId: string, + userContext: UserContext + ): Promise; } diff --git a/lib/build/recipe/oauth2provider/recipe.js b/lib/build/recipe/oauth2provider/recipe.js index a8b1c1020..4c31ae8cc 100644 --- a/lib/build/recipe/oauth2provider/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -95,7 +97,16 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo, this.getDefaultAccessTokenPayload.bind(this), this.getDefaultIdTokenPayload.bind(this), this.getDefaultUserInfoPayload.bind(this))); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default( + querier_1.Querier.getNewInstanceOrThrowError(recipeId), + this.config, + appInfo, + this.getDefaultAccessTokenPayload.bind(this), + this.getDefaultIdTokenPayload.bind(this), + this.getDefaultUserInfoPayload.bind(this) + ) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -118,8 +129,7 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } - else { + } else { throw new Error("OAuth2Provider recipe has already been initialised. Please check your code for bugs."); } }; @@ -208,7 +218,7 @@ class Recipe extends recipeModule_1.default { async getDefaultAccessTokenPayload(user, scopes, sessionHandle, userContext) { let payload = {}; for (const fn of this.accessTokenBuilders) { - payload = Object.assign(Object.assign({}, payload), (await fn(user, scopes, sessionHandle, userContext))); + payload = Object.assign(Object.assign({}, payload), await fn(user, scopes, sessionHandle, userContext)); } return payload; } @@ -216,16 +226,22 @@ class Recipe extends recipeModule_1.default { let payload = {}; if (scopes.includes("email")) { payload.email = user === null || user === void 0 ? void 0 : user.emails[0]; - payload.email_verified = user.loginMethods.some((lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified); + payload.email_verified = user.loginMethods.some( + (lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified + ); payload.emails = user.emails; } if (scopes.includes("phoneNumber")) { payload.phoneNumber = user === null || user === void 0 ? void 0 : user.phoneNumbers[0]; - payload.phoneNumber_verified = user.loginMethods.some((lm) => lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && lm.verified); + payload.phoneNumber_verified = user.loginMethods.some( + (lm) => + lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && + lm.verified + ); payload.phoneNumbers = user.phoneNumbers; } for (const fn of this.idTokenBuilders) { - payload = Object.assign(Object.assign({}, payload), (await fn(user, scopes, sessionHandle, userContext))); + payload = Object.assign(Object.assign({}, payload), await fn(user, scopes, sessionHandle, userContext)); } return payload; } @@ -236,16 +252,25 @@ class Recipe extends recipeModule_1.default { if (scopes.includes("email")) { // TODO: try and get the email based on the user id of the entire user object payload.email = user === null || user === void 0 ? void 0 : user.emails[0]; - payload.email_verified = user.loginMethods.some((lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified); + payload.email_verified = user.loginMethods.some( + (lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified + ); payload.emails = user.emails; } if (scopes.includes("phoneNumber")) { payload.phoneNumber = user === null || user === void 0 ? void 0 : user.phoneNumbers[0]; - payload.phoneNumber_verified = user.loginMethods.some((lm) => lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && lm.verified); + payload.phoneNumber_verified = user.loginMethods.some( + (lm) => + lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && + lm.verified + ); payload.phoneNumbers = user.phoneNumbers; } for (const fn of this.userInfoBuilders) { - payload = Object.assign(Object.assign({}, payload), (await fn(user, accessTokenPayload, scopes, tenantId, userContext))); + payload = Object.assign( + Object.assign({}, payload), + await fn(user, accessTokenPayload, scopes, tenantId, userContext) + ); } return payload; } diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.d.ts b/lib/build/recipe/oauth2provider/recipeImplementation.d.ts index 571e44ab4..4ecaeef69 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.d.ts +++ b/lib/build/recipe/oauth2provider/recipeImplementation.d.ts @@ -2,4 +2,11 @@ import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; import { RecipeInterface, TypeNormalisedInput, PayloadBuilderFunction, UserInfoBuilderFunction } from "./types"; -export default function getRecipeInterface(querier: Querier, _config: TypeNormalisedInput, appInfo: NormalisedAppinfo, getDefaultAccessTokenPayload: PayloadBuilderFunction, getDefaultIdTokenPayload: PayloadBuilderFunction, getDefaultUserInfoPayload: UserInfoBuilderFunction): RecipeInterface; +export default function getRecipeInterface( + querier: Querier, + _config: TypeNormalisedInput, + appInfo: NormalisedAppinfo, + getDefaultAccessTokenPayload: PayloadBuilderFunction, + getDefaultIdTokenPayload: PayloadBuilderFunction, + getDefaultUserInfoPayload: UserInfoBuilderFunction +): RecipeInterface; diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index db6b45638..cde274874 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -13,28 +13,47 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const jose = __importStar(require("jose")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -45,7 +64,10 @@ const recipe_1 = __importDefault(require("../session/recipe")); const recipe_2 = __importDefault(require("../openid/recipe")); const constants_1 = require("../multitenancy/constants"); function getUpdatedRedirectTo(appInfo, redirectTo) { - return redirectTo.replace("{apiDomain}", appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous()); + return redirectTo.replace( + "{apiDomain}", + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() + ); } function copyAndCleanRequestBodyInput(input) { let result = Object.assign({}, input); @@ -54,10 +76,21 @@ function copyAndCleanRequestBodyInput(input) { delete result.session; return result; } -function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayload, getDefaultIdTokenPayload, getDefaultUserInfoPayload) { +function getRecipeInterface( + querier, + _config, + appInfo, + getDefaultAccessTokenPayload, + getDefaultIdTokenPayload, + getDefaultUserInfoPayload +) { return { getLoginRequest: async function (input) { - const resp = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/oauth/auth/requests/login"), { loginChallenge: input.challenge }, input.userContext); + const resp = await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/oauth/auth/requests/login"), + { loginChallenge: input.challenge }, + input.userContext + ); if (resp.status !== "OK") { return { status: "ERROR", @@ -80,34 +113,48 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl }; }, acceptLoginRequest: async function (input) { - const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/accept`), { - acr: input.acr, - amr: input.amr, - context: input.context, - extendSessionLifespan: input.extendSessionLifespan, - identityProviderSessionId: input.identityProviderSessionId, - subject: input.subject, - }, { - loginChallenge: input.challenge, - }, input.userContext); + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/accept`), + { + acr: input.acr, + amr: input.amr, + context: input.context, + extendSessionLifespan: input.extendSessionLifespan, + identityProviderSessionId: input.identityProviderSessionId, + subject: input.subject, + }, + { + loginChallenge: input.challenge, + }, + input.userContext + ); return { redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, rejectLoginRequest: async function (input) { - const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/reject`), { - error: input.error.error, - errorDescription: input.error.errorDescription, - statusCode: input.error.statusCode, - }, { - login_challenge: input.challenge, - }, input.userContext); + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/reject`), + { + error: input.error.error, + errorDescription: input.error.errorDescription, + statusCode: input.error.statusCode, + }, + { + login_challenge: input.challenge, + }, + input.userContext + ); return { redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, getConsentRequest: async function (input) { - const resp = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/oauth/auth/requests/consent"), { consentChallenge: input.challenge }, input.userContext); + const resp = await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/oauth/auth/requests/consent"), + { consentChallenge: input.challenge }, + input.userContext + ); return { acr: resp.acr, amr: resp.amr, @@ -124,32 +171,42 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl }; }, acceptConsentRequest: async function (input) { - const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/accept`), { - context: input.context, - grantAccessTokenAudience: input.grantAccessTokenAudience, - grantScope: input.grantScope, - handledAt: input.handledAt, - iss: await recipe_2.default.getIssuer(input.userContext), - tId: input.tenantId, - rsub: input.rsub, - sessionHandle: input.sessionHandle, - initialAccessTokenPayload: input.initialAccessTokenPayload, - initialIdTokenPayload: input.initialIdTokenPayload, - }, { - consentChallenge: input.challenge, - }, input.userContext); + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/accept`), + { + context: input.context, + grantAccessTokenAudience: input.grantAccessTokenAudience, + grantScope: input.grantScope, + handledAt: input.handledAt, + iss: await recipe_2.default.getIssuer(input.userContext), + tId: input.tenantId, + rsub: input.rsub, + sessionHandle: input.sessionHandle, + initialAccessTokenPayload: input.initialAccessTokenPayload, + initialIdTokenPayload: input.initialIdTokenPayload, + }, + { + consentChallenge: input.challenge, + }, + input.userContext + ); return { redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, rejectConsentRequest: async function (input) { - const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/reject`), { - error: input.error.error, - errorDescription: input.error.errorDescription, - statusCode: input.error.statusCode, - }, { - consentChallenge: input.challenge, - }, input.userContext); + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/reject`), + { + error: input.error.error, + errorDescription: input.error.errorDescription, + statusCode: input.error.statusCode, + }, + { + consentChallenge: input.challenge, + }, + input.userContext + ); return { redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; @@ -177,7 +234,11 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl sessionHandle: (_c = input.session) === null || _c === void 0 ? void 0 : _c.getHandle(), userContext: input.userContext, }); - const responseTypes = (_e = (_d = input.params.response_type) === null || _d === void 0 ? void 0 : _d.split(" ")) !== null && _e !== void 0 ? _e : []; + const responseTypes = + (_e = (_d = input.params.response_type) === null || _d === void 0 ? void 0 : _d.split(" ")) !== null && + _e !== void 0 + ? _e + : []; if (input.session !== undefined) { const clientInfo = await this.getOAuth2Client({ clientId: input.params.client_id, @@ -203,34 +264,40 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl } // These default to an empty objects, because we want to keep them as a required input // but they'll not be actually used in the flows where we are not building them. - const idToken = scopes.includes("openid") && (responseTypes.includes("id_token") || responseTypes.includes("code")) - ? await this.buildIdTokenPayload({ - user, - client, - sessionHandle: input.session.getHandle(), - scopes, - userContext: input.userContext, - }) - : {}; - const accessToken = responseTypes.includes("token") || responseTypes.includes("code") - ? await this.buildAccessTokenPayload({ - user, - client, - sessionHandle: input.session.getHandle(), - scopes, - userContext: input.userContext, - }) - : {}; + const idToken = + scopes.includes("openid") && (responseTypes.includes("id_token") || responseTypes.includes("code")) + ? await this.buildIdTokenPayload({ + user, + client, + sessionHandle: input.session.getHandle(), + scopes, + userContext: input.userContext, + }) + : {}; + const accessToken = + responseTypes.includes("token") || responseTypes.includes("code") + ? await this.buildAccessTokenPayload({ + user, + client, + sessionHandle: input.session.getHandle(), + scopes, + userContext: input.userContext, + }) + : {}; payloads = { idToken, accessToken, }; } - const resp = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth`), { - params: Object.assign(Object.assign({}, input.params), { scope: scopes.join(" ") }), - cookies: input.cookies, - session: payloads, - }, input.userContext); + const resp = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/auth`), + { + params: Object.assign(Object.assign({}, input.params), { scope: scopes.join(" ") }), + cookies: input.cookies, + session: payloads, + }, + input.userContext + ); if (resp.status === "CLIENT_NOT_FOUND_ERROR") { return { status: "ERROR", @@ -300,7 +367,11 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl errorDescription: "client_id is required", }; } - const scopes = (_b = (_a = input.body.scope) === null || _a === void 0 ? void 0 : _a.split(" ")) !== null && _b !== void 0 ? _b : []; + const scopes = + (_b = (_a = input.body.scope) === null || _a === void 0 ? void 0 : _a.split(" ")) !== null && + _b !== void 0 + ? _b + : []; const clientInfo = await this.getOAuth2Client({ clientId: input.body.client_id, userContext: input.userContext, @@ -330,7 +401,11 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl }); } if (input.body.grant_type === "refresh_token") { - const scopes = (_d = (_c = input.body.scope) === null || _c === void 0 ? void 0 : _c.split(" ")) !== null && _d !== void 0 ? _d : []; + const scopes = + (_d = (_c = input.body.scope) === null || _c === void 0 ? void 0 : _c.split(" ")) !== null && + _d !== void 0 + ? _d + : []; const tokenInfo = await this.introspectToken({ token: input.body.refresh_token, scopes, @@ -379,7 +454,11 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl if (input.authorizationHeader) { body["authorizationHeader"] = input.authorizationHeader; } - const res = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/token`), body, input.userContext); + const res = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/token`), + body, + input.userContext + ); if (res.status !== "OK") { return { status: "ERROR", @@ -391,19 +470,23 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl return res; }, getOAuth2Clients: async function (input) { - let response = await querier.sendGetRequestWithResponseHeaders(new normalisedURLPath_1.default(`/recipe/oauth/clients/list`), { - pageSize: input.pageSize, - clientName: input.clientName, - pageToken: input.paginationToken, - }, {}, input.userContext); + let response = await querier.sendGetRequestWithResponseHeaders( + new normalisedURLPath_1.default(`/recipe/oauth/clients/list`), + { + pageSize: input.pageSize, + clientName: input.clientName, + pageToken: input.paginationToken, + }, + {}, + input.userContext + ); if (response.body.status === "OK") { return { status: "OK", clients: response.body.clients.map((client) => OAuth2Client_1.OAuth2Client.fromAPIResponse(client)), nextPaginationToken: response.body.nextPaginationToken, }; - } - else { + } else { return { status: "ERROR", error: response.body.error, @@ -412,21 +495,24 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl } }, getOAuth2Client: async function (input) { - let response = await querier.sendGetRequestWithResponseHeaders(new normalisedURLPath_1.default(`/recipe/oauth/clients`), { clientId: input.clientId }, {}, input.userContext); + let response = await querier.sendGetRequestWithResponseHeaders( + new normalisedURLPath_1.default(`/recipe/oauth/clients`), + { clientId: input.clientId }, + {}, + input.userContext + ); if (response.body.status === "OK") { return { status: "OK", client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response.body), }; - } - else if (response.body.status === "CLIENT_NOT_FOUND_ERROR") { + } else if (response.body.status === "CLIENT_NOT_FOUND_ERROR") { return { status: "ERROR", error: "invalid_request", errorDescription: "The provided client_id is not valid or unknown", }; - } - else { + } else { return { status: "ERROR", error: response.body.error, @@ -435,14 +521,17 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl } }, createOAuth2Client: async function (input) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/clients`), copyAndCleanRequestBodyInput(input), input.userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/clients`), + copyAndCleanRequestBodyInput(input), + input.userContext + ); if (response.status === "OK") { return { status: "OK", client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response), }; - } - else { + } else { return { status: "ERROR", error: response.error, @@ -451,14 +540,18 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl } }, updateOAuth2Client: async function (input) { - let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/clients`), copyAndCleanRequestBodyInput(input), { clientId: input.clientId }, input.userContext); + let response = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth/clients`), + copyAndCleanRequestBodyInput(input), + { clientId: input.clientId }, + input.userContext + ); if (response.status === "OK") { return { status: "OK", client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response), }; - } - else { + } else { return { status: "ERROR", error: response.error, @@ -467,11 +560,14 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl } }, deleteOAuth2Client: async function (input) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/clients/remove`), { clientId: input.clientId }, input.userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/clients/remove`), + { clientId: input.clientId }, + input.userContext + ); if (response.status === "OK") { return { status: "OK" }; - } - else { + } else { return { status: "ERROR", error: response.error, @@ -516,39 +612,55 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl queryParams.set("forceFreshAuth", "true"); } return `${websiteDomain}${websiteBasePath}?${queryParams.toString()}`; - } - else if (input.type === "try-refresh") { + } else if (input.type === "try-refresh") { return `${websiteDomain}${websiteBasePath}/try-refresh?loginChallenge=${input.loginChallenge}`; - } - else if (input.type === "post-logout-fallback") { + } else if (input.type === "post-logout-fallback") { return `${websiteDomain}${websiteBasePath}`; - } - else if (input.type === "logout-confirmation") { + } else if (input.type === "logout-confirmation") { return `${websiteDomain}${websiteBasePath}/oauth/logout?logoutChallenge=${input.logoutChallenge}`; } throw new Error("This should never happen: invalid type passed to getFrontendRedirectionURL"); }, validateOAuth2AccessToken: async function (input) { var _a, _b, _c; - const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS(recipe_1.default.getInstanceOrThrowError().config))).payload; + const payload = ( + await jose.jwtVerify( + input.token, + combinedRemoteJWKSet_1.getCombinedJWKS(recipe_1.default.getInstanceOrThrowError().config) + ) + ).payload; if (payload.stt !== 1) { throw new Error("Wrong token type"); } - if (((_a = input.requirements) === null || _a === void 0 ? void 0 : _a.clientId) !== undefined && payload.client_id !== input.requirements.clientId) { - throw new Error(`The token doesn't belong to the specified client (${input.requirements.clientId} !== ${payload.client_id})`); - } - if (((_b = input.requirements) === null || _b === void 0 ? void 0 : _b.scopes) !== undefined && - input.requirements.scopes.some((scope) => !payload.scp.includes(scope))) { + if ( + ((_a = input.requirements) === null || _a === void 0 ? void 0 : _a.clientId) !== undefined && + payload.client_id !== input.requirements.clientId + ) { + throw new Error( + `The token doesn't belong to the specified client (${input.requirements.clientId} !== ${payload.client_id})` + ); + } + if ( + ((_b = input.requirements) === null || _b === void 0 ? void 0 : _b.scopes) !== undefined && + input.requirements.scopes.some((scope) => !payload.scp.includes(scope)) + ) { throw new Error("The token is missing some required scopes"); } const aud = payload.aud instanceof Array ? payload.aud : [payload.aud]; - if (((_c = input.requirements) === null || _c === void 0 ? void 0 : _c.audience) !== undefined && !aud.includes(input.requirements.audience)) { + if ( + ((_c = input.requirements) === null || _c === void 0 ? void 0 : _c.audience) !== undefined && + !aud.includes(input.requirements.audience) + ) { throw new Error("The token doesn't belong to the specified audience"); } if (input.checkDatabase) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/introspect`), { - token: input.token, - }, input.userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/introspect`), + { + token: input.token, + }, + input.userContext + ); if (response.active !== true) { throw new Error("The token is expired, invalid or has been revoked"); } @@ -561,8 +673,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl }; if ("authorizationHeader" in input && input.authorizationHeader !== undefined) { requestBody.authorizationHeader = input.authorizationHeader; - } - else { + } else { if ("clientId" in input && input.clientId !== undefined) { requestBody.client_id = input.clientId; } @@ -570,7 +681,11 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl requestBody.client_secret = input.clientSecret; } } - const res = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/token/revoke`), requestBody, input.userContext); + const res = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/token/revoke`), + requestBody, + input.userContext + ); if (res.status !== "OK") { return { status: "ERROR", @@ -582,11 +697,19 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl return { status: "OK" }; }, revokeTokensBySessionHandle: async function (input) { - await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/session/revoke`), { sessionHandle: input.sessionHandle }, input.userContext); + await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/session/revoke`), + { sessionHandle: input.sessionHandle }, + input.userContext + ); return { status: "OK" }; }, revokeTokensByClientId: async function (input) { - await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/tokens/revoke`), { clientId: input.clientId }, input.userContext); + await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/tokens/revoke`), + { clientId: input.clientId }, + input.userContext + ); return { status: "OK" }; }, introspectToken: async function ({ token, scopes, userContext }) { @@ -602,17 +725,20 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl checkDatabase: false, userContext, }); - } - catch (error) { + } catch (error) { return { active: false }; } } // For tokens that passed local validation or if it's a refresh token, // validate the token with the database by calling the core introspection endpoint - const res = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/oauth/introspect`), { - token, - scope: scopes ? scopes.join(" ") : undefined, - }, userContext); + const res = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/introspect`), + { + token, + scope: scopes ? scopes.join(" ") : undefined, + }, + userContext + ); return res; }, endSession: async function (input) { @@ -628,13 +754,17 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl * CASE 3: `end_session` request with a `logout_verifier` (after accepting the logout request) * - Redirects to the `post_logout_redirect_uri` or the default logout fallback page. */ - const resp = await querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/oauth/sessions/logout`), { - clientId: input.params.client_id, - idTokenHint: input.params.id_token_hint, - postLogoutRedirectUri: input.params.post_logout_redirect_uri, - state: input.params.state, - logoutVerifier: input.params.logout_verifier, - }, input.userContext); + const resp = await querier.sendGetRequest( + new normalisedURLPath_1.default(`/recipe/oauth/sessions/logout`), + { + clientId: input.params.client_id, + idTokenHint: input.params.id_token_hint, + postLogoutRedirectUri: input.params.post_logout_redirect_uri, + state: input.params.state, + logoutVerifier: input.params.logout_verifier, + }, + input.userContext + ); if ("error" in resp) { return { status: "ERROR", @@ -657,13 +787,14 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl userContext: input.userContext, }), }; - } - else { + } else { // Accept the logout challenge immediately as there is no supertokens session - redirectTo = (await this.acceptLogoutRequest({ - challenge: logoutChallenge, - userContext: input.userContext, - })).redirectTo; + redirectTo = ( + await this.acceptLogoutRequest({ + challenge: logoutChallenge, + userContext: input.userContext, + }) + ).redirectTo; } } // CASE 2 or 3 (See above notes) @@ -680,7 +811,12 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl return { redirectTo }; }, acceptLogoutRequest: async function (input) { - const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/logout/accept`), { challenge: input.challenge }, {}, input.userContext); + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/logout/accept`), + { challenge: input.challenge }, + {}, + input.userContext + ); const redirectTo = getUpdatedRedirectTo(appInfo, resp.redirectTo); if (redirectTo.endsWith("/fallbacks/logout/callback")) { return { @@ -693,7 +829,12 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultAccessTokenPayl return { redirectTo }; }, rejectLogoutRequest: async function (input) { - const resp = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/logout/reject`), {}, { challenge: input.challenge }, input.userContext); + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/logout/reject`), + {}, + { challenge: input.challenge }, + input.userContext + ); if (resp.status != "OK") { throw new Error(resp.error); } diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index 9e8c0c43a..7d3987138 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -8,13 +8,19 @@ import { User } from "../../user"; import RecipeUserId from "../../recipeUserId"; export declare type TypeInput = { override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -82,30 +88,32 @@ export declare type UserInfo = { phoneNumber_verified?: boolean; [key: string]: JSONValue; }; -export declare type InstrospectTokenResponse = { - active: false; -} | ({ - active: true; -} & JSONObject); +export declare type InstrospectTokenResponse = + | { + active: false; + } + | ({ + active: true; + } & JSONObject); export declare type RecipeInterface = { authorization(input: { params: Record; cookies: string | undefined; session: SessionContainerInterface | undefined; userContext: UserContext; - }): Promise<{ - redirectTo: string; - cookies: string | undefined; - } | ErrorOAuth2>; + }): Promise< + | { + redirectTo: string; + cookies: string | undefined; + } + | ErrorOAuth2 + >; tokenExchange(input: { authorizationHeader?: string; body: Record; userContext: UserContext; }): Promise; - getConsentRequest(input: { - challenge: string; - userContext: UserContext; - }): Promise; + getConsentRequest(input: { challenge: string; userContext: UserContext }): Promise; acceptConsentRequest(input: { challenge: string; context?: any; @@ -131,9 +139,12 @@ export declare type RecipeInterface = { getLoginRequest(input: { challenge: string; userContext: UserContext; - }): Promise<(LoginRequest & { - status: "OK"; - }) | ErrorOAuth2>; + }): Promise< + | (LoginRequest & { + status: "OK"; + }) + | ErrorOAuth2 + >; acceptLoginRequest(input: { challenge: string; acr?: string; @@ -156,54 +167,77 @@ export declare type RecipeInterface = { getOAuth2Client(input: { clientId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - client: OAuth2Client; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - getOAuth2Clients(input: GetOAuth2ClientsInput & { - userContext: UserContext; - }): Promise<{ - status: "OK"; - clients: Array; - nextPaginationToken?: string; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - createOAuth2Client(input: CreateOAuth2ClientInput & { - userContext: UserContext; - }): Promise<{ - status: "OK"; - client: OAuth2Client; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - updateOAuth2Client(input: UpdateOAuth2ClientInput & { - userContext: UserContext; - }): Promise<{ - status: "OK"; - client: OAuth2Client; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; - deleteOAuth2Client(input: DeleteOAuth2ClientInput & { - userContext: UserContext; - }): Promise<{ - status: "OK"; - } | { - status: "ERROR"; - error: string; - errorDescription: string; - }>; + }): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + getOAuth2Clients( + input: GetOAuth2ClientsInput & { + userContext: UserContext; + } + ): Promise< + | { + status: "OK"; + clients: Array; + nextPaginationToken?: string; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + createOAuth2Client( + input: CreateOAuth2ClientInput & { + userContext: UserContext; + } + ): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + updateOAuth2Client( + input: UpdateOAuth2ClientInput & { + userContext: UserContext; + } + ): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; + deleteOAuth2Client( + input: DeleteOAuth2ClientInput & { + userContext: UserContext; + } + ): Promise< + | { + status: "OK"; + } + | { + status: "ERROR"; + error: string; + errorDescription: string; + } + >; validateOAuth2AccessToken(input: { token: string; requirements?: { @@ -245,36 +279,50 @@ export declare type RecipeInterface = { tenantId: string; userContext: UserContext; }): Promise; - getFrontendRedirectionURL(input: { - type: "login"; - loginChallenge: string; - tenantId: string; - forceFreshAuth: boolean; - hint: string | undefined; - userContext: UserContext; - } | { - type: "try-refresh"; - loginChallenge: string; - userContext: UserContext; - } | { - type: "logout-confirmation"; - logoutChallenge: string; - userContext: UserContext; - } | { - type: "post-logout-fallback"; - userContext: UserContext; - }): Promise; - revokeToken(input: { - token: string; - userContext: UserContext; - } & ({ - authorizationHeader: string; - } | { - clientId: string; - clientSecret?: string; - })): Promise<{ - status: "OK"; - } | ErrorOAuth2>; + getFrontendRedirectionURL( + input: + | { + type: "login"; + loginChallenge: string; + tenantId: string; + forceFreshAuth: boolean; + hint: string | undefined; + userContext: UserContext; + } + | { + type: "try-refresh"; + loginChallenge: string; + userContext: UserContext; + } + | { + type: "logout-confirmation"; + logoutChallenge: string; + userContext: UserContext; + } + | { + type: "post-logout-fallback"; + userContext: UserContext; + } + ): Promise; + revokeToken( + input: { + token: string; + userContext: UserContext; + } & ( + | { + authorizationHeader: string; + } + | { + clientId: string; + clientSecret?: string; + } + ) + ): Promise< + | { + status: "OK"; + } + | ErrorOAuth2 + >; revokeTokensByClientId(input: { clientId: string; userContext: UserContext; @@ -297,9 +345,12 @@ export declare type RecipeInterface = { session?: SessionContainerInterface; shouldTryRefresh: boolean; userContext: UserContext; - }): Promise<{ - redirectTo: string; - } | ErrorOAuth2>; + }): Promise< + | { + redirectTo: string; + } + | ErrorOAuth2 + >; acceptLogoutRequest(input: { challenge: string; userContext: UserContext; @@ -314,94 +365,146 @@ export declare type RecipeInterface = { }>; }; export declare type APIInterface = { - loginGET: undefined | ((input: { - loginChallenge: string; - options: APIOptions; - session?: SessionContainerInterface; - shouldTryRefresh: boolean; - userContext: UserContext; - }) => Promise<{ - frontendRedirectTo: string; - cookies?: string; - } | ErrorOAuth2 | GeneralErrorResponse>); - authGET: undefined | ((input: { - params: any; - cookie: string | undefined; - session: SessionContainerInterface | undefined; - shouldTryRefresh: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - redirectTo: string; - cookies?: string; - } | ErrorOAuth2 | GeneralErrorResponse>); - tokenPOST: undefined | ((input: { - authorizationHeader?: string; - body: any; - options: APIOptions; - userContext: UserContext; - }) => Promise); - loginInfoGET: undefined | ((input: { - loginChallenge: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - info: LoginInfo; - } | ErrorOAuth2 | GeneralErrorResponse>); - userInfoGET: undefined | ((input: { - accessTokenPayload: JSONObject; - user: User; - scopes: string[]; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise); - revokeTokenPOST: undefined | ((input: { - token: string; - options: APIOptions; - userContext: UserContext; - } & ({ - authorizationHeader: string; - } | { - clientId: string; - clientSecret?: string; - })) => Promise<{ - status: "OK"; - } | ErrorOAuth2>); - introspectTokenPOST: undefined | ((input: { - token: string; - scopes?: string[]; - options: APIOptions; - userContext: UserContext; - }) => Promise); - endSessionGET: undefined | ((input: { - params: Record; - session?: SessionContainerInterface; - shouldTryRefresh: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - redirectTo: string; - } | ErrorOAuth2 | GeneralErrorResponse>); - endSessionPOST: undefined | ((input: { - params: Record; - session?: SessionContainerInterface; - shouldTryRefresh: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - redirectTo: string; - } | ErrorOAuth2 | GeneralErrorResponse>); - logoutPOST: undefined | ((input: { - logoutChallenge: string; - options: APIOptions; - session?: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - frontendRedirectTo: string; - } | ErrorOAuth2 | GeneralErrorResponse>); + loginGET: + | undefined + | ((input: { + loginChallenge: string; + options: APIOptions; + session?: SessionContainerInterface; + shouldTryRefresh: boolean; + userContext: UserContext; + }) => Promise< + | { + frontendRedirectTo: string; + cookies?: string; + } + | ErrorOAuth2 + | GeneralErrorResponse + >); + authGET: + | undefined + | ((input: { + params: any; + cookie: string | undefined; + session: SessionContainerInterface | undefined; + shouldTryRefresh: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + cookies?: string; + } + | ErrorOAuth2 + | GeneralErrorResponse + >); + tokenPOST: + | undefined + | ((input: { + authorizationHeader?: string; + body: any; + options: APIOptions; + userContext: UserContext; + }) => Promise); + loginInfoGET: + | undefined + | ((input: { + loginChallenge: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + info: LoginInfo; + } + | ErrorOAuth2 + | GeneralErrorResponse + >); + userInfoGET: + | undefined + | ((input: { + accessTokenPayload: JSONObject; + user: User; + scopes: string[]; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise); + revokeTokenPOST: + | undefined + | (( + input: { + token: string; + options: APIOptions; + userContext: UserContext; + } & ( + | { + authorizationHeader: string; + } + | { + clientId: string; + clientSecret?: string; + } + ) + ) => Promise< + | { + status: "OK"; + } + | ErrorOAuth2 + >); + introspectTokenPOST: + | undefined + | ((input: { + token: string; + scopes?: string[]; + options: APIOptions; + userContext: UserContext; + }) => Promise); + endSessionGET: + | undefined + | ((input: { + params: Record; + session?: SessionContainerInterface; + shouldTryRefresh: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + } + | ErrorOAuth2 + | GeneralErrorResponse + >); + endSessionPOST: + | undefined + | ((input: { + params: Record; + session?: SessionContainerInterface; + shouldTryRefresh: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + } + | ErrorOAuth2 + | GeneralErrorResponse + >); + logoutPOST: + | undefined + | ((input: { + logoutChallenge: string; + options: APIOptions; + session?: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + frontendRedirectTo: string; + } + | ErrorOAuth2 + | GeneralErrorResponse + >); }; export declare type OAuth2ClientOptions = { clientId: string; @@ -445,8 +548,12 @@ export declare type GetOAuth2ClientsInput = { */ clientName?: string; }; -export declare type CreateOAuth2ClientInput = Partial>; -export declare type UpdateOAuth2ClientInput = NonNullableProperties> & { +export declare type CreateOAuth2ClientInput = Partial< + Omit +>; +export declare type UpdateOAuth2ClientInput = NonNullableProperties< + Omit +> & { clientId: string; redirectUris?: string[] | null; grantTypes?: string[] | null; @@ -456,5 +563,16 @@ export declare type UpdateOAuth2ClientInput = NonNullableProperties Promise; -export declare type UserInfoBuilderFunction = (user: User, accessTokenPayload: JSONObject, scopes: string[], tenantId: string, userContext: UserContext) => Promise; +export declare type PayloadBuilderFunction = ( + user: User, + scopes: string[], + sessionHandle: string, + userContext: UserContext +) => Promise; +export declare type UserInfoBuilderFunction = ( + user: User, + accessTokenPayload: JSONObject, + scopes: string[], + tenantId: string, + userContext: UserContext +) => Promise; diff --git a/lib/build/recipe/oauth2provider/utils.d.ts b/lib/build/recipe/oauth2provider/utils.d.ts index 133d4840f..4025b1b44 100644 --- a/lib/build/recipe/oauth2provider/utils.d.ts +++ b/lib/build/recipe/oauth2provider/utils.d.ts @@ -2,4 +2,8 @@ import { NormalisedAppinfo } from "../../types"; import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput(_: Recipe, __: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + _: Recipe, + __: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; diff --git a/lib/build/recipe/oauth2provider/utils.js b/lib/build/recipe/oauth2provider/utils.js index 00f9f3586..f0bbf7edd 100644 --- a/lib/build/recipe/oauth2provider/utils.js +++ b/lib/build/recipe/oauth2provider/utils.js @@ -16,7 +16,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(_, __, config) { - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { override, }; diff --git a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.d.ts b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.d.ts index 31f793b8c..45955e60f 100644 --- a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.d.ts +++ b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { UserContext } from "../../../types"; import { APIInterface, APIOptions } from "../types"; -export default function getOpenIdDiscoveryConfiguration(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function getOpenIdDiscoveryConfiguration( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js index fb7ef2e2e..05b9f8403 100644 --- a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js +++ b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js @@ -24,8 +24,7 @@ async function getOpenIdDiscoveryConfiguration(apiImplementation, options, userC id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, response_types_supported: result.response_types_supported, }); - } - else { + } else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/openid/index.d.ts b/lib/build/recipe/openid/index.d.ts index 7e921973b..84b55bd8c 100644 --- a/lib/build/recipe/openid/index.d.ts +++ b/lib/build/recipe/openid/index.d.ts @@ -2,7 +2,9 @@ import OpenIdRecipe from "./recipe"; export default class OpenIdRecipeWrapper { static init: typeof OpenIdRecipe.init; - static getOpenIdDiscoveryConfiguration(userContext?: Record): Promise<{ + static getOpenIdDiscoveryConfiguration( + userContext?: Record + ): Promise<{ status: "OK"; issuer: string; jwks_uri: string; diff --git a/lib/build/recipe/openid/index.js b/lib/build/recipe/openid/index.js index 4b605be32..227ea73f7 100644 --- a/lib/build/recipe/openid/index.js +++ b/lib/build/recipe/openid/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getOpenIdDiscoveryConfiguration = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/openid/recipe.d.ts b/lib/build/recipe/openid/recipe.d.ts index 6990632f1..cac8551d3 100644 --- a/lib/build/recipe/openid/recipe.d.ts +++ b/lib/build/recipe/openid/recipe.d.ts @@ -17,7 +17,15 @@ export default class OpenIdRecipe extends RecipeModule { static reset(): void; static getIssuer(userContext: UserContext): Promise; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, _tenantId: string, req: BaseRequest, response: BaseResponse, _path: normalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + _tenantId: string, + req: BaseRequest, + response: BaseResponse, + _path: normalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (error: STError) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/openid/recipe.js b/lib/build/recipe/openid/recipe.js index d54384f40..ab6336364 100644 --- a/lib/build/recipe/openid/recipe.js +++ b/lib/build/recipe/openid/recipe.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -50,8 +52,7 @@ class OpenIdRecipe extends recipeModule_1.default { }; if (id === constants_1.GET_DISCOVERY_CONFIG_URL) { return await getOpenIdDiscoveryConfiguration_1.default(this.apiImpl, apiOptions, userContext); - } - else { + } else { return false; } }; @@ -81,8 +82,7 @@ class OpenIdRecipe extends recipeModule_1.default { if (OpenIdRecipe.instance === undefined) { OpenIdRecipe.instance = new OpenIdRecipe(OpenIdRecipe.RECIPE_ID, appInfo, config); return OpenIdRecipe.instance; - } - else { + } else { throw new Error("OpenId recipe has already been initialised. Please check your code for bugs."); } }; @@ -94,7 +94,9 @@ class OpenIdRecipe extends recipeModule_1.default { OpenIdRecipe.instance = undefined; } static async getIssuer(userContext) { - return (await this.getInstanceOrThrowError().recipeImplementation.getOpenIdDiscoveryConfiguration({ userContext })).issuer; + return ( + await this.getInstanceOrThrowError().recipeImplementation.getOpenIdDiscoveryConfiguration({ userContext }) + ).issuer; } } exports.default = OpenIdRecipe; diff --git a/lib/build/recipe/openid/recipeImplementation.js b/lib/build/recipe/openid/recipeImplementation.js index 3ae2b7917..746c52a35 100644 --- a/lib/build/recipe/openid/recipeImplementation.js +++ b/lib/build/recipe/openid/recipeImplementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../jwt/recipe")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -11,8 +13,11 @@ function getRecipeInterface(appInfo) { return { getOpenIdDiscoveryConfiguration: async function () { let issuer = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - let jwks_uri = appInfo.apiDomain.getAsStringDangerous() + - appInfo.apiBasePath.appendPath(new normalisedURLPath_1.default(constants_1.GET_JWKS_API)).getAsStringDangerous(); + let jwks_uri = + appInfo.apiDomain.getAsStringDangerous() + + appInfo.apiBasePath + .appendPath(new normalisedURLPath_1.default(constants_1.GET_JWKS_API)) + .getAsStringDangerous(); const apiBasePath = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); return { status: "OK", @@ -29,7 +34,7 @@ function getRecipeInterface(appInfo) { response_types_supported: ["code", "id_token", "id_token token"], }; }, - createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext, }) { + createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext }) { payload = payload === undefined || payload === null ? {} : payload; let issuer = (await this.getOpenIdDiscoveryConfiguration({ userContext })).issuer; return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createJWT({ diff --git a/lib/build/recipe/openid/types.d.ts b/lib/build/recipe/openid/types.d.ts index 4a8181b12..b0dda95fd 100644 --- a/lib/build/recipe/openid/types.d.ts +++ b/lib/build/recipe/openid/types.d.ts @@ -4,13 +4,19 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import { GeneralErrorResponse, UserContext } from "../../types"; export declare type TypeInput = { override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -22,23 +28,28 @@ export declare type APIOptions = { res: BaseResponse; }; export declare type APIInterface = { - getOpenIdDiscoveryConfigurationGET: undefined | ((input: { - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - issuer: string; - jwks_uri: string; - authorization_endpoint: string; - token_endpoint: string; - userinfo_endpoint: string; - revocation_endpoint: string; - token_introspection_endpoint: string; - end_session_endpoint: string; - subject_types_supported: string[]; - id_token_signing_alg_values_supported: string[]; - response_types_supported: string[]; - } | GeneralErrorResponse>); + getOpenIdDiscoveryConfigurationGET: + | undefined + | ((input: { + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + issuer: string; + jwks_uri: string; + authorization_endpoint: string; + token_endpoint: string; + userinfo_endpoint: string; + revocation_endpoint: string; + token_introspection_endpoint: string; + end_session_endpoint: string; + subject_types_supported: string[]; + id_token_signing_alg_values_supported: string[]; + response_types_supported: string[]; + } + | GeneralErrorResponse + >); }; export declare type RecipeInterface = { getOpenIdDiscoveryConfiguration(input: { @@ -62,10 +73,13 @@ export declare type RecipeInterface = { validitySeconds?: number; useStaticSigningKey?: boolean; userContext: UserContext; - }): Promise<{ - status: "OK"; - jwt: string; - } | { - status: "UNSUPPORTED_ALGORITHM_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + jwt: string; + } + | { + status: "UNSUPPORTED_ALGORITHM_ERROR"; + } + >; }; diff --git a/lib/build/recipe/openid/utils.js b/lib/build/recipe/openid/utils.js index 9e51218c2..ad70f404c 100644 --- a/lib/build/recipe/openid/utils.js +++ b/lib/build/recipe/openid/utils.js @@ -2,7 +2,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(config) { - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { override, }; diff --git a/lib/build/recipe/passwordless/api/consumeCode.d.ts b/lib/build/recipe/passwordless/api/consumeCode.d.ts index 95eaca99c..a07ff159f 100644 --- a/lib/build/recipe/passwordless/api/consumeCode.d.ts +++ b/lib/build/recipe/passwordless/api/consumeCode.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function consumeCode(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function consumeCode( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/passwordless/api/consumeCode.js b/lib/build/recipe/passwordless/api/consumeCode.js index 470b125a2..7179971cf 100644 --- a/lib/build/recipe/passwordless/api/consumeCode.js +++ b/lib/build/recipe/passwordless/api/consumeCode.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -48,40 +50,49 @@ async function consumeCode(apiImplementation, tenantId, options, userContext) { message: "Please provide both deviceId and userInputCode", }); } - } - else if (linkCode === undefined) { + } else if (linkCode === undefined) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "Please provide one of (linkCode) or (deviceId+userInputCode) and not both", }); } const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); if (session !== undefined) { tenantId = session.getTenantId(); } - let result = await apiImplementation.consumeCodePOST(deviceId !== undefined - ? { - deviceId, - userInputCode, - preAuthSessionId, - tenantId, - session, - shouldTryLinkingWithSessionUser, - options, - userContext, - } - : { - linkCode, - options, - preAuthSessionId, - tenantId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }); + let result = await apiImplementation.consumeCodePOST( + deviceId !== undefined + ? { + deviceId, + userInputCode, + preAuthSessionId, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + } + : { + linkCode, + options, + preAuthSessionId, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + } + ); if (result.status === "OK") { - result = Object.assign(Object.assign({}, result), utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)); + result = Object.assign( + Object.assign({}, result), + utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext) + ); delete result.session; } utils_1.send200Response(options.res, result); diff --git a/lib/build/recipe/passwordless/api/createCode.d.ts b/lib/build/recipe/passwordless/api/createCode.d.ts index 593fdf54e..1d2619c75 100644 --- a/lib/build/recipe/passwordless/api/createCode.d.ts +++ b/lib/build/recipe/passwordless/api/createCode.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function createCode(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function createCode( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/passwordless/api/createCode.js b/lib/build/recipe/passwordless/api/createCode.js index 90bd2994d..13617a262 100644 --- a/lib/build/recipe/passwordless/api/createCode.js +++ b/lib/build/recipe/passwordless/api/createCode.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -47,8 +49,10 @@ async function createCode(apiImplementation, tenantId, options, userContext) { }); } // normalise and validate format of input - if (email !== undefined && - (options.config.contactMethod === "EMAIL" || options.config.contactMethod === "EMAIL_OR_PHONE")) { + if ( + email !== undefined && + (options.config.contactMethod === "EMAIL" || options.config.contactMethod === "EMAIL_OR_PHONE") + ) { email = email.trim(); const validateError = await options.config.validateEmailAddress(email, tenantId); if (validateError !== undefined) { @@ -59,8 +63,10 @@ async function createCode(apiImplementation, tenantId, options, userContext) { return true; } } - if (phoneNumber !== undefined && - (options.config.contactMethod === "PHONE" || options.config.contactMethod === "EMAIL_OR_PHONE")) { + if ( + phoneNumber !== undefined && + (options.config.contactMethod === "PHONE" || options.config.contactMethod === "EMAIL_OR_PHONE") + ) { const validateError = await options.config.validatePhoneNumber(phoneNumber, tenantId); if (validateError !== undefined) { utils_1.send200Response(options.res, { @@ -74,19 +80,25 @@ async function createCode(apiImplementation, tenantId, options, userContext) { // this can come here if the user has provided their own impl of validatePhoneNumber and // the phone number is valid according to their impl, but not according to the libphonenumber-js lib. phoneNumber = phoneNumber.trim(); - } - else { + } else { phoneNumber = parsedPhoneNumber.format("E.164"); } } const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); if (session !== undefined) { tenantId = session.getTenantId(); } - let result = await apiImplementation.createCodePOST(email !== undefined - ? { email, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } - : { phoneNumber: phoneNumber, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext }); + let result = await apiImplementation.createCodePOST( + email !== undefined + ? { email, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } + : { phoneNumber: phoneNumber, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } + ); utils_1.send200Response(options.res, result); return true; } diff --git a/lib/build/recipe/passwordless/api/emailExists.d.ts b/lib/build/recipe/passwordless/api/emailExists.d.ts index 478175dec..2f55b6d3b 100644 --- a/lib/build/recipe/passwordless/api/emailExists.d.ts +++ b/lib/build/recipe/passwordless/api/emailExists.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function emailExists(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function emailExists( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/passwordless/api/emailExists.js b/lib/build/recipe/passwordless/api/emailExists.js index 360b195d9..312816062 100644 --- a/lib/build/recipe/passwordless/api/emailExists.js +++ b/lib/build/recipe/passwordless/api/emailExists.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/passwordless/api/implementation.js b/lib/build/recipe/passwordless/api/implementation.js index d64fc1275..0936acc2c 100644 --- a/lib/build/recipe/passwordless/api/implementation.js +++ b/lib/build/recipe/passwordless/api/implementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("../../../logger"); const authUtils_1 = require("../../../authUtils"); @@ -16,14 +18,19 @@ function getAPIImplementation() { consumeCodePOST: async function (input) { var _a, _b, _c; 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_002)", - SIGN_IN_NOT_ALLOWED: "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)", + SIGN_UP_NOT_ALLOWED: + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)", + SIGN_IN_NOT_ALLOWED: + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)", LINKING_TO_SESSION_USER_FAILED: { // We should never get an email verification error here, since pwless automatically marks the user // email as verified - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_017)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_018)", - 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_019)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_017)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_018)", + 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_019)", }, }; const deviceInfo = await input.options.recipeImplementation.listCodesByPreAuthSessionId({ @@ -37,42 +44,47 @@ function getAPIImplementation() { }; } const recipeId = "passwordless"; - const accountInfo = deviceInfo.phoneNumber !== undefined - ? { - phoneNumber: deviceInfo.phoneNumber, - } - : { - email: deviceInfo.email, - }; + const accountInfo = + deviceInfo.phoneNumber !== undefined + ? { + phoneNumber: deviceInfo.phoneNumber, + } + : { + email: deviceInfo.email, + }; let checkCredentialsResponseProm; let checkCredentials = async () => { if (checkCredentialsResponseProm === undefined) { - checkCredentialsResponseProm = input.options.recipeImplementation.checkCode("deviceId" in input - ? { - preAuthSessionId: input.preAuthSessionId, - deviceId: input.deviceId, - userInputCode: input.userInputCode, - tenantId: input.tenantId, - userContext: input.userContext, - } - : { - preAuthSessionId: input.preAuthSessionId, - linkCode: input.linkCode, - tenantId: input.tenantId, - userContext: input.userContext, - }); + checkCredentialsResponseProm = input.options.recipeImplementation.checkCode( + "deviceId" in input + ? { + preAuthSessionId: input.preAuthSessionId, + deviceId: input.deviceId, + userInputCode: input.userInputCode, + tenantId: input.tenantId, + userContext: input.userContext, + } + : { + preAuthSessionId: input.preAuthSessionId, + linkCode: input.linkCode, + tenantId: input.tenantId, + userContext: input.userContext, + } + ); } const checkCredentialsResponse = await checkCredentialsResponseProm; return checkCredentialsResponse.status === "OK"; }; - const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ - accountInfo, - recipeId, - userContext: input.userContext, - session: input.session, - tenantId: input.tenantId, - checkCredentialsOnTenant: checkCredentials, - }); + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( + { + accountInfo, + recipeId, + userContext: input.userContext, + session: input.session, + tenantId: input.tenantId, + checkCredentialsOnTenant: checkCredentials, + } + ); const emailVerificationInstance = recipe_2.default.getInstance(); // If we have a session and emailverification was initialized plus this code was sent to an email // then we check if we can/should verify this email address for the session user. @@ -80,9 +92,11 @@ function getAPIImplementation() { // and making a user primary if they are verified, but the verification process itself involves account linking. // If a valid code was submitted, we can take that as the session (and the session user) having access to the email // which means that we can verify their email address - if (accountInfo.email !== undefined && + if ( + accountInfo.email !== undefined && input.session !== undefined && - emailVerificationInstance !== undefined) { + emailVerificationInstance !== undefined + ) { // We first load the session user, so we can check if verification is required // We do this first, it is better for caching if we group the post calls together (verifyIng the code and the email address) const sessionUser = await __1.getUser(input.session.getUserId(), input.userContext); @@ -92,7 +106,9 @@ function getAPIImplementation() { message: "Session user not found", }); } - const loginMethod = sessionUser.loginMethods.find((lm) => lm.recipeUserId.getAsString() === input.session.getRecipeUserId().getAsString()); + const loginMethod = sessionUser.loginMethods.find( + (lm) => lm.recipeUserId.getAsString() === input.session.getRecipeUserId().getAsString() + ); if (loginMethod === undefined) { throw new error_1.default({ type: error_1.default.UNAUTHORISED, @@ -104,12 +120,14 @@ function getAPIImplementation() { if (loginMethod.hasSameEmailAs(accountInfo.email) && !loginMethod.verified) { // We first check that the submitted code is actually valid if (await checkCredentials()) { - const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken({ - tenantId: input.tenantId, - recipeUserId: loginMethod.recipeUserId, - email: accountInfo.email, - userContext: input.userContext, - }); + const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( + { + tenantId: input.tenantId, + recipeUserId: loginMethod.recipeUserId, + email: accountInfo.email, + userContext: input.userContext, + } + ); if (tokenResponse.status === "OK") { await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ tenantId: input.tenantId, @@ -127,16 +145,13 @@ function getAPIImplementation() { if (deviceInfo.email !== undefined) { if ("userInputCode" in input) { factorId = multifactorauth_1.FactorIds.OTP_EMAIL; - } - else { + } else { factorId = multifactorauth_1.FactorIds.LINK_EMAIL; } - } - else { + } else { if ("userInputCode" in input) { factorId = multifactorauth_1.FactorIds.OTP_PHONE; - } - else { + } else { factorId = multifactorauth_1.FactorIds.LINK_PHONE; } } @@ -148,9 +163,16 @@ function getAPIImplementation() { phoneNumber: deviceInfo.phoneNumber, }, factorIds: [factorId], - authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + authenticatingUser: + authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, isSignUp, - isVerified: (_a = authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.loginMethod.verified) !== null && _a !== void 0 ? _a : true, + isVerified: + (_a = + authenticatingUser === null || authenticatingUser === void 0 + ? void 0 + : authenticatingUser.loginMethod.verified) !== null && _a !== void 0 + ? _a + : true, signInVerifiesLoginMethod: true, skipSessionUserUpdateInCore: false, tenantId: input.tenantId, @@ -161,7 +183,11 @@ function getAPIImplementation() { if (preAuthChecks.status !== "OK") { // 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"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthChecks, + errorCodeMap, + "SIGN_IN_UP_NOT_ALLOWED" + ); } if (checkCredentialsResponseProm !== undefined) { // We need to cast this because otherwise TS thinks that this is never updated for some reason. @@ -171,31 +197,39 @@ function getAPIImplementation() { return checkCredentialsResponse; } } - let response = await input.options.recipeImplementation.consumeCode("deviceId" in input - ? { - preAuthSessionId: input.preAuthSessionId, - deviceId: input.deviceId, - userInputCode: input.userInputCode, - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - tenantId: input.tenantId, - userContext: input.userContext, - } - : { - preAuthSessionId: input.preAuthSessionId, - linkCode: input.linkCode, - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - tenantId: input.tenantId, - userContext: input.userContext, - }); - if (response.status === "RESTART_FLOW_ERROR" || + let response = await input.options.recipeImplementation.consumeCode( + "deviceId" in input + ? { + preAuthSessionId: input.preAuthSessionId, + deviceId: input.deviceId, + userInputCode: input.userInputCode, + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + tenantId: input.tenantId, + userContext: input.userContext, + } + : { + preAuthSessionId: input.preAuthSessionId, + linkCode: input.linkCode, + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + tenantId: input.tenantId, + userContext: input.userContext, + } + ); + if ( + response.status === "RESTART_FLOW_ERROR" || response.status === "INCORRECT_USER_INPUT_CODE_ERROR" || - response.status === "EXPIRED_USER_INPUT_CODE_ERROR") { + response.status === "EXPIRED_USER_INPUT_CODE_ERROR" + ) { return response; } if (response.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(response, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); + 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 @@ -207,7 +241,10 @@ function getAPIImplementation() { factorId, isSignUp, authenticatedUser: (_b = response.user) !== null && _b !== void 0 ? _b : authenticatingUser.user, - recipeUserId: (_c = response.recipeUserId) !== null && _c !== void 0 ? _c : authenticatingUser.loginMethod.recipeUserId, + recipeUserId: + (_c = response.recipeUserId) !== null && _c !== void 0 + ? _c + : authenticatingUser.loginMethod.recipeUserId, req: input.options.req, res: input.options.res, tenantId: input.tenantId, @@ -215,7 +252,11 @@ function getAPIImplementation() { session: input.session, }); if (postAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + postAuthChecks, + errorCodeMap, + "SIGN_IN_UP_NOT_ALLOWED" + ); } return { status: "OK", @@ -227,9 +268,11 @@ function getAPIImplementation() { createCodePOST: async function (input) { var _a; 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_002)", + SIGN_UP_NOT_ALLOWED: + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)", LINKING_TO_SESSION_USER_FAILED: { - 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_019)", + 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_019)", }, }; const accountInfo = {}; @@ -242,30 +285,42 @@ function getAPIImplementation() { // Here we use do not use the helper from AuthUtil to check if this is going to be a sign in or up, because: // 1. At this point we have no way to check credentials // 2. We do not want to associate the relevant recipe user with the current tenant (yet) - const userWithMatchingLoginMethod = await getPasswordlessUserByAccountInfo(Object.assign(Object.assign({}, input), { accountInfo })); + const userWithMatchingLoginMethod = await getPasswordlessUserByAccountInfo( + Object.assign(Object.assign({}, input), { accountInfo }) + ); let factorIds; if (input.session !== undefined) { if (accountInfo.email !== undefined) { factorIds = [multifactorauth_1.FactorIds.OTP_EMAIL]; - } - else { + } else { factorIds = [multifactorauth_1.FactorIds.OTP_PHONE]; } - } - else { + } else { factorIds = utils_1.getEnabledPwlessFactors(input.options.config); if (accountInfo.email !== undefined) { - factorIds = factorIds.filter((factor) => [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.LINK_EMAIL].includes(factor)); - } - else { - factorIds = factorIds.filter((factor) => [multifactorauth_1.FactorIds.OTP_PHONE, multifactorauth_1.FactorIds.LINK_PHONE].includes(factor)); + factorIds = factorIds.filter((factor) => + [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.LINK_EMAIL].includes(factor) + ); + } else { + factorIds = factorIds.filter((factor) => + [multifactorauth_1.FactorIds.OTP_PHONE, multifactorauth_1.FactorIds.LINK_PHONE].includes(factor) + ); } } const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ authenticatingAccountInfo: Object.assign(Object.assign({}, accountInfo), { recipeId: "passwordless" }), isSignUp: userWithMatchingLoginMethod === undefined, - authenticatingUser: userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 ? void 0 : userWithMatchingLoginMethod.user, - isVerified: (_a = userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 ? void 0 : userWithMatchingLoginMethod.loginMethod.verified) !== null && _a !== void 0 ? _a : true, + authenticatingUser: + userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 + ? void 0 + : userWithMatchingLoginMethod.user, + isVerified: + (_a = + userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 + ? void 0 + : userWithMatchingLoginMethod.loginMethod.verified) !== null && _a !== void 0 + ? _a + : true, signInVerifiesLoginMethod: true, skipSessionUserUpdateInCore: true, tenantId: input.tenantId, @@ -277,31 +332,49 @@ function getAPIImplementation() { if (preAuthChecks.status !== "OK") { // 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"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthChecks, + errorCodeMap, + "SIGN_IN_UP_NOT_ALLOWED" + ); } - let response = await input.options.recipeImplementation.createCode("email" in input - ? { - userContext: input.userContext, - email: input.email, - userInputCode: input.options.config.getCustomUserInputCode === undefined - ? undefined - : await input.options.config.getCustomUserInputCode(input.tenantId, input.userContext), - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - tenantId: input.tenantId, - } - : { - userContext: input.userContext, - phoneNumber: input.phoneNumber, - userInputCode: input.options.config.getCustomUserInputCode === undefined - ? undefined - : await input.options.config.getCustomUserInputCode(input.tenantId, input.userContext), - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - tenantId: input.tenantId, - }); + let response = await input.options.recipeImplementation.createCode( + "email" in input + ? { + userContext: input.userContext, + email: input.email, + userInputCode: + input.options.config.getCustomUserInputCode === undefined + ? undefined + : await input.options.config.getCustomUserInputCode( + input.tenantId, + input.userContext + ), + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + tenantId: input.tenantId, + } + : { + userContext: input.userContext, + phoneNumber: input.phoneNumber, + userInputCode: + input.options.config.getCustomUserInputCode === undefined + ? undefined + : await input.options.config.getCustomUserInputCode( + input.tenantId, + input.userContext + ), + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + tenantId: input.tenantId, + } + ); if (response.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(response, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + response, + errorCodeMap, + "SIGN_IN_UP_NOT_ALLOWED" + ); } // now we send the email / text message. let magicLink = undefined; @@ -309,29 +382,27 @@ function getAPIImplementation() { let flowType = input.options.config.flowType; if (preAuthChecks.validFactorIds.every((id) => id.startsWith("link"))) { flowType = "MAGIC_LINK"; - } - else if (preAuthChecks.validFactorIds.every((id) => id.startsWith("otp"))) { + } else if (preAuthChecks.validFactorIds.every((id) => id.startsWith("otp"))) { flowType = "USER_INPUT_CODE"; - } - else { + } else { flowType = "USER_INPUT_CODE_AND_MAGIC_LINK"; } if (flowType === "MAGIC_LINK" || flowType === "USER_INPUT_CODE_AND_MAGIC_LINK") { magicLink = input.options.appInfo .getOrigin({ - request: input.options.req, - userContext: input.userContext, - }) + request: input.options.req, + userContext: input.userContext, + }) .getAsStringDangerous() + - input.options.appInfo.websiteBasePath.getAsStringDangerous() + - "/verify" + - "?preAuthSessionId=" + - response.preAuthSessionId + - "&tenantId=" + - input.tenantId + - "#" + - response.linkCode; + input.options.appInfo.websiteBasePath.getAsStringDangerous() + + "/verify" + + "?preAuthSessionId=" + + response.preAuthSessionId + + "&tenantId=" + + input.tenantId + + "#" + + response.linkCode; } if (flowType === "USER_INPUT_CODE" || flowType === "USER_INPUT_CODE_AND_MAGIC_LINK") { userInputCode = response.userInputCode; @@ -339,8 +410,10 @@ function getAPIImplementation() { // we don't do something special for serverless env here // cause we want to wait for service's reply since it can show // a UI error message for if sending an SMS / email failed or not. - if (input.options.config.contactMethod === "PHONE" || - (input.options.config.contactMethod === "EMAIL_OR_PHONE" && "phoneNumber" in input)) { + if ( + input.options.config.contactMethod === "PHONE" || + (input.options.config.contactMethod === "EMAIL_OR_PHONE" && "phoneNumber" in input) + ) { logger_1.logDebugMessage(`Sending passwordless login SMS to ${input.phoneNumber}`); await input.options.smsDelivery.ingredientInterfaceImpl.sendSms({ type: "PASSWORDLESS_LOGIN", @@ -353,8 +426,7 @@ function getAPIImplementation() { tenantId: input.tenantId, userContext: input.userContext, }); - } - else { + } else { logger_1.logDebugMessage(`Sending passwordless login email to ${input.email}`); await input.options.emailDelivery.ingredientInterfaceImpl.sendEmail({ type: "PASSWORDLESS_LOGIN", @@ -384,17 +456,24 @@ function getAPIImplementation() { doUnionOfAccountInfo: false, userContext: input.userContext, }); - const userExists = users.some((u) => u.loginMethods.some((lm) => lm.recipeId === "passwordless" && lm.hasSameEmailAs(input.email))); + const userExists = users.some((u) => + u.loginMethods.some((lm) => lm.recipeId === "passwordless" && lm.hasSameEmailAs(input.email)) + ); return { exists: userExists, status: "OK", }; }, phoneNumberExistsGET: async function (input) { - let users = await __1.listUsersByAccountInfo(input.tenantId, { - phoneNumber: input.phoneNumber, - // tenantId: input.tenantId, - }, false, input.userContext); + let users = await __1.listUsersByAccountInfo( + input.tenantId, + { + phoneNumber: input.phoneNumber, + // tenantId: input.tenantId, + }, + false, + input.userContext + ); return { exists: users.length > 0, status: "OK", @@ -411,18 +490,31 @@ function getAPIImplementation() { status: "RESTART_FLOW_ERROR", }; } - if ((input.options.config.contactMethod === "PHONE" && deviceInfo.phoneNumber === undefined) || - (input.options.config.contactMethod === "EMAIL" && deviceInfo.email === undefined)) { + if ( + (input.options.config.contactMethod === "PHONE" && deviceInfo.phoneNumber === undefined) || + (input.options.config.contactMethod === "EMAIL" && deviceInfo.email === undefined) + ) { return { status: "RESTART_FLOW_ERROR", }; } - const userWithMatchingLoginMethod = await getPasswordlessUserByAccountInfo(Object.assign(Object.assign({}, input), { accountInfo: deviceInfo })); - const authTypeInfo = await authUtils_1.AuthUtils.checkAuthTypeAndLinkingStatus(input.session, input.shouldTryLinkingWithSessionUser, { - recipeId: "passwordless", - email: deviceInfo.email, - phoneNumber: deviceInfo.phoneNumber, - }, userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 ? void 0 : userWithMatchingLoginMethod.user, true, input.userContext); + const userWithMatchingLoginMethod = await getPasswordlessUserByAccountInfo( + Object.assign(Object.assign({}, input), { accountInfo: deviceInfo }) + ); + const authTypeInfo = await authUtils_1.AuthUtils.checkAuthTypeAndLinkingStatus( + input.session, + input.shouldTryLinkingWithSessionUser, + { + recipeId: "passwordless", + email: deviceInfo.email, + phoneNumber: deviceInfo.phoneNumber, + }, + userWithMatchingLoginMethod === null || userWithMatchingLoginMethod === void 0 + ? void 0 + : userWithMatchingLoginMethod.user, + true, + input.userContext + ); if (authTypeInfo.status === "LINKING_TO_SESSION_USER_FAILED") { // This can happen in the following edge-cases: // 1. Either the session didn't exist during createCode or the app didn't want to link to the session user @@ -441,9 +533,10 @@ function getAPIImplementation() { let response = await input.options.recipeImplementation.createNewCodeForDevice({ userContext: input.userContext, deviceId: input.deviceId, - userInputCode: input.options.config.getCustomUserInputCode === undefined - ? undefined - : await input.options.config.getCustomUserInputCode(input.tenantId, input.userContext), + userInputCode: + input.options.config.getCustomUserInputCode === undefined + ? undefined + : await input.options.config.getCustomUserInputCode(input.tenantId, input.userContext), tenantId: input.tenantId, }); if (response.status === "USER_INPUT_CODE_ALREADY_USED_ERROR") { @@ -464,45 +557,46 @@ function getAPIImplementation() { if (!authTypeInfo.isFirstFactor) { if (deviceInfo.email !== undefined) { factorIds = [multifactorauth_1.FactorIds.OTP_EMAIL]; - } - else { + } else { factorIds = [multifactorauth_1.FactorIds.OTP_PHONE]; } // We do not do further filtering here, since we know the exact factor id and the fact that it was created // which means it was allowed and the user is allowed to re-send it. // We will execute all check when the code is consumed anyway. - } - else { + } else { factorIds = utils_1.getEnabledPwlessFactors(input.options.config); - factorIds = await authUtils_1.AuthUtils.filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid(factorIds, input.tenantId, false, input.userContext); + factorIds = await authUtils_1.AuthUtils.filterOutInvalidFirstFactorsOrThrowIfAllAreInvalid( + factorIds, + input.tenantId, + false, + input.userContext + ); } // This is correct because in createCodePOST we only allow OTP_EMAIL let flowType = input.options.config.flowType; if (factorIds.every((id) => id.startsWith("link"))) { flowType = "MAGIC_LINK"; - } - else if (factorIds.every((id) => id.startsWith("otp"))) { + } else if (factorIds.every((id) => id.startsWith("otp"))) { flowType = "USER_INPUT_CODE"; - } - else { + } else { flowType = "USER_INPUT_CODE_AND_MAGIC_LINK"; } if (flowType === "MAGIC_LINK" || flowType === "USER_INPUT_CODE_AND_MAGIC_LINK") { magicLink = input.options.appInfo .getOrigin({ - request: input.options.req, - userContext: input.userContext, - }) + request: input.options.req, + userContext: input.userContext, + }) .getAsStringDangerous() + - input.options.appInfo.websiteBasePath.getAsStringDangerous() + - "/verify" + - "?preAuthSessionId=" + - response.preAuthSessionId + - "&tenantId=" + - input.tenantId + - "#" + - response.linkCode; + input.options.appInfo.websiteBasePath.getAsStringDangerous() + + "/verify" + + "?preAuthSessionId=" + + response.preAuthSessionId + + "&tenantId=" + + input.tenantId + + "#" + + response.linkCode; } if (flowType === "USER_INPUT_CODE" || flowType === "USER_INPUT_CODE_AND_MAGIC_LINK") { userInputCode = response.userInputCode; @@ -510,9 +604,11 @@ function getAPIImplementation() { // we don't do something special for serverless env here // cause we want to wait for service's reply since it can show // a UI error message for if sending an SMS / email failed or not. - if (input.options.config.contactMethod === "PHONE" || + if ( + input.options.config.contactMethod === "PHONE" || (input.options.config.contactMethod === "EMAIL_OR_PHONE" && - deviceInfo.phoneNumber !== undefined)) { + deviceInfo.phoneNumber !== undefined) + ) { logger_1.logDebugMessage(`Sending passwordless login SMS to ${input.phoneNumber}`); await input.options.smsDelivery.ingredientInterfaceImpl.sendSms({ type: "PASSWORDLESS_LOGIN", @@ -525,8 +621,7 @@ function getAPIImplementation() { tenantId: input.tenantId, userContext: input.userContext, }); - } - else { + } else { logger_1.logDebugMessage(`Sending passwordless login email to ${input.email}`); await input.options.emailDelivery.ingredientInterfaceImpl.sendEmail({ type: "PASSWORDLESS_LOGIN", @@ -556,18 +651,29 @@ async function getPasswordlessUserByAccountInfo(input) { doUnionOfAccountInfo: false, userContext: input.userContext, }); - logger_1.logDebugMessage(`getPasswordlessUserByAccountInfo got ${existingUsers.length} from core resp ${JSON.stringify(input.accountInfo)}`); + logger_1.logDebugMessage( + `getPasswordlessUserByAccountInfo got ${existingUsers.length} from core resp ${JSON.stringify( + input.accountInfo + )}` + ); const usersWithMatchingLoginMethods = existingUsers .map((user) => ({ - user, - loginMethod: user.loginMethods.find((lm) => lm.recipeId === "passwordless" && - (lm.hasSameEmailAs(input.accountInfo.email) || - lm.hasSamePhoneNumberAs(input.accountInfo.phoneNumber))), - })) + user, + loginMethod: user.loginMethods.find( + (lm) => + lm.recipeId === "passwordless" && + (lm.hasSameEmailAs(input.accountInfo.email) || + lm.hasSamePhoneNumberAs(input.accountInfo.phoneNumber)) + ), + })) .filter(({ loginMethod }) => loginMethod !== undefined); - logger_1.logDebugMessage(`getPasswordlessUserByAccountInfo ${usersWithMatchingLoginMethods.length} has matching login methods`); + logger_1.logDebugMessage( + `getPasswordlessUserByAccountInfo ${usersWithMatchingLoginMethods.length} has matching login methods` + ); if (usersWithMatchingLoginMethods.length > 1) { - throw new Error("This should never happen: multiple users exist matching the accountInfo in passwordless createCode"); + throw new Error( + "This should never happen: multiple users exist matching the accountInfo in passwordless createCode" + ); } return usersWithMatchingLoginMethods[0]; } diff --git a/lib/build/recipe/passwordless/api/phoneNumberExists.d.ts b/lib/build/recipe/passwordless/api/phoneNumberExists.d.ts index 5ab652f23..9d545f9dc 100644 --- a/lib/build/recipe/passwordless/api/phoneNumberExists.d.ts +++ b/lib/build/recipe/passwordless/api/phoneNumberExists.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function phoneNumberExists(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function phoneNumberExists( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/passwordless/api/phoneNumberExists.js b/lib/build/recipe/passwordless/api/phoneNumberExists.js index a471555f0..887522533 100644 --- a/lib/build/recipe/passwordless/api/phoneNumberExists.js +++ b/lib/build/recipe/passwordless/api/phoneNumberExists.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/passwordless/api/resendCode.d.ts b/lib/build/recipe/passwordless/api/resendCode.d.ts index 3453d3e1a..d6fd98191 100644 --- a/lib/build/recipe/passwordless/api/resendCode.d.ts +++ b/lib/build/recipe/passwordless/api/resendCode.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function resendCode(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function resendCode( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/passwordless/api/resendCode.js b/lib/build/recipe/passwordless/api/resendCode.js index a44f9239b..131c04f89 100644 --- a/lib/build/recipe/passwordless/api/resendCode.js +++ b/lib/build/recipe/passwordless/api/resendCode.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -40,7 +42,12 @@ async function resendCode(apiImplementation, tenantId, options, userContext) { }); } const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); let result = await apiImplementation.resendCodePOST({ deviceId, preAuthSessionId, diff --git a/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.d.ts index 0ea2b47d1..3c47b9d5a 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.d.ts @@ -2,10 +2,13 @@ import { TypePasswordlessEmailDeliveryInput } from "../../../types"; import { EmailDeliveryInterface } from "../../../../../ingredients/emaildelivery/types"; import { NormalisedAppinfo, UserContext } from "../../../../../types"; -export default class BackwardCompatibilityService implements EmailDeliveryInterface { +export default class BackwardCompatibilityService + implements EmailDeliveryInterface { private appInfo; constructor(appInfo: NormalisedAppinfo); - sendEmail: (input: TypePasswordlessEmailDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: TypePasswordlessEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.js index d4ba3f038..aacff4c5d 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/backwardCompatibility/index.js @@ -5,20 +5,25 @@ async function createAndSendEmailUsingSupertokensService(input) { if (utils_1.isTestEnv()) { return; } - const result = await utils_1.postWithFetch("https://api.supertokens.io/0/st/auth/passwordless/login", { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, { - email: input.email, - appName: input.appInfo.appName, - codeLifetime: input.codeLifetime, - urlWithLinkCode: input.urlWithLinkCode, - userInputCode: input.userInputCode, - // isFirstFactor: input.isFirstFactor, - }, { - successLog: `Email sent to ${input.email}`, - errorLogHeader: "Error sending passwordless login email", - }); + const result = await utils_1.postWithFetch( + "https://api.supertokens.io/0/st/auth/passwordless/login", + { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, + { + email: input.email, + appName: input.appInfo.appName, + codeLifetime: input.codeLifetime, + urlWithLinkCode: input.urlWithLinkCode, + userInputCode: input.userInputCode, + // isFirstFactor: input.isFirstFactor, + }, + { + successLog: `Email sent to ${input.email}`, + errorLogHeader: "Error sending passwordless login email", + } + ); if ("error" in result) { throw result.error; } @@ -29,8 +34,7 @@ async function createAndSendEmailUsingSupertokensService(input) { * will be of type `{err: string}` */ throw new Error(result.resp.body.err); - } - else { + } else { throw new Error(`Request failed with status code ${result.resp.status}`); } } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/index.js b/lib/build/recipe/passwordless/emaildelivery/services/index.js index 3e4b76c1d..7e07f6706 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/index.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMTPService = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.d.ts index 68fe55216..188d41809 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.d.ts +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.d.ts @@ -6,7 +6,9 @@ import { UserContext } from "../../../../../types"; export default class SMTPService implements EmailDeliveryInterface { serviceImpl: ServiceInterface; constructor(config: TypeInput); - sendEmail: (input: TypePasswordlessEmailDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: TypePasswordlessEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.js b/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.js index da1ee9c8a..dedcbe33f 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const nodemailer_1 = require("nodemailer"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); @@ -10,7 +12,9 @@ class SMTPService { constructor(config) { this.sendEmail = async (input) => { let content = await this.serviceImpl.getContent(input); - await this.serviceImpl.sendRawEmail(Object.assign(Object.assign({}, content), { userContext: input.userContext })); + await this.serviceImpl.sendRawEmail( + Object.assign(Object.assign({}, content), { userContext: input.userContext }) + ); }; const transporter = nodemailer_1.createTransport({ host: config.smtpSettings.host, @@ -21,7 +25,9 @@ class SMTPService { }, secure: config.smtpSettings.secure, }); - let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from)); + let builder = new supertokens_js_override_1.default( + serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) + ); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.d.ts b/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.d.ts index d33aac81d..e3ae65d56 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.d.ts +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.d.ts @@ -2,4 +2,10 @@ import { TypePasswordlessEmailDeliveryInput } from "../../../types"; import { GetContentResult } from "../../../../../ingredients/emaildelivery/services/smtp"; export default function getPasswordlessLoginEmailContent(input: TypePasswordlessEmailDeliveryInput): GetContentResult; -export declare function getPasswordlessLoginEmailHTML(appName: string, email: string, codeLifetime: number, urlWithLinkCode?: string, userInputCode?: string): string; +export declare function getPasswordlessLoginEmailHTML( + appName: string, + email: string, + codeLifetime: number, + urlWithLinkCode?: string, + userInputCode?: string +): string; diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.js b/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.js index 0616ac918..6d0aae92f 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/passwordlessLogin.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPasswordlessLoginEmailHTML = void 0; const supertokens_1 = __importDefault(require("../../../../../supertokens")); @@ -9,7 +11,13 @@ const utils_1 = require("../../../../../utils"); function getPasswordlessLoginEmailContent(input) { let supertokens = supertokens_1.default.getInstanceOrThrowError(); let appName = supertokens.appInfo.appName; - let body = getPasswordlessLoginEmailHTML(appName, input.email, input.codeLifetime, input.urlWithLinkCode, input.userInputCode); + let body = getPasswordlessLoginEmailHTML( + appName, + input.email, + input.codeLifetime, + input.urlWithLinkCode, + input.userInputCode + ); return { body, toEmail: input.email, @@ -2844,13 +2852,24 @@ function getPasswordlessLoginOTPAndURLLinkBody(appName, email, codeLifetime, url } function getPasswordlessLoginEmailHTML(appName, email, codeLifetime, urlWithLinkCode, userInputCode) { if (urlWithLinkCode !== undefined && userInputCode !== undefined) { - return getPasswordlessLoginOTPAndURLLinkBody(appName, email, utils_1.humaniseMilliseconds(codeLifetime), urlWithLinkCode, userInputCode); + return getPasswordlessLoginOTPAndURLLinkBody( + appName, + email, + utils_1.humaniseMilliseconds(codeLifetime), + urlWithLinkCode, + userInputCode + ); } if (userInputCode !== undefined) { return getPasswordlessLoginOTPBody(appName, email, utils_1.humaniseMilliseconds(codeLifetime), userInputCode); } if (urlWithLinkCode !== undefined) { - return getPasswordlessLoginURLLinkBody(appName, email, utils_1.humaniseMilliseconds(codeLifetime), urlWithLinkCode); + return getPasswordlessLoginURLLinkBody( + appName, + email, + utils_1.humaniseMilliseconds(codeLifetime), + urlWithLinkCode + ); } throw Error("this should never be thrown"); } diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.d.ts b/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.d.ts index 894b09ef1..7a58ac4e4 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.d.ts +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.d.ts @@ -2,7 +2,10 @@ import { TypePasswordlessEmailDeliveryInput } from "../../../types"; import { Transporter } from "nodemailer"; import { ServiceInterface } from "../../../../../ingredients/emaildelivery/services/smtp"; -export declare function getServiceImplementation(transporter: Transporter, from: { - name: string; - email: string; -}): ServiceInterface; +export declare function getServiceImplementation( + transporter: Transporter, + from: { + name: string; + email: string; + } +): ServiceInterface; diff --git a/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.js b/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.js index 844e35ec1..abb00642c 100644 --- a/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.js +++ b/lib/build/recipe/passwordless/emaildelivery/services/smtp/serviceImplementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const passwordlessLogin_1 = __importDefault(require("./passwordlessLogin")); @@ -29,8 +31,7 @@ function getServiceImplementation(transporter, from) { subject: input.subject, html: input.body, }); - } - else { + } else { await transporter.sendMail({ from: `${from.name} <${from.email}>`, to: input.toEmail, diff --git a/lib/build/recipe/passwordless/error.d.ts b/lib/build/recipe/passwordless/error.d.ts index d6412505c..486758b61 100644 --- a/lib/build/recipe/passwordless/error.d.ts +++ b/lib/build/recipe/passwordless/error.d.ts @@ -1,8 +1,5 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - constructor(options: { - type: "BAD_INPUT_ERROR"; - message: string; - }); + constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); } diff --git a/lib/build/recipe/passwordless/error.js b/lib/build/recipe/passwordless/error.js index 3748d17e3..852278b6d 100644 --- a/lib/build/recipe/passwordless/error.js +++ b/lib/build/recipe/passwordless/error.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/passwordless/index.d.ts b/lib/build/recipe/passwordless/index.d.ts index 220cac810..e757af855 100644 --- a/lib/build/recipe/passwordless/index.d.ts +++ b/lib/build/recipe/passwordless/index.d.ts @@ -1,23 +1,34 @@ // @ts-nocheck import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { RecipeInterface, APIOptions, APIInterface, TypePasswordlessEmailDeliveryInput, TypePasswordlessSmsDeliveryInput } from "./types"; +import { + RecipeInterface, + APIOptions, + APIInterface, + TypePasswordlessEmailDeliveryInput, + TypePasswordlessSmsDeliveryInput, +} from "./types"; import RecipeUserId from "../../recipeUserId"; import { SessionContainerInterface } from "../session/types"; import { User } from "../../types"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static createCode(input: ({ - email: string; - } | { - phoneNumber: string; - }) & { - tenantId: string; - userInputCode?: string; - session?: SessionContainerInterface; - userContext?: Record; - }): Promise<{ + static createCode( + input: ( + | { + email: string; + } + | { + phoneNumber: string; + } + ) & { + tenantId: string; + userInputCode?: string; + session?: SessionContainerInterface; + userContext?: Record; + } + ): Promise<{ status: "OK"; preAuthSessionId: string; codeId: string; @@ -32,89 +43,113 @@ export default class Wrapper { userInputCode?: string; tenantId: string; userContext?: Record; - }): Promise<{ - status: "OK"; - preAuthSessionId: string; - codeId: string; - deviceId: string; - userInputCode: string; - linkCode: string; - codeLifetime: number; - timeCreated: number; - } | { - status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + preAuthSessionId: string; + codeId: string; + deviceId: string; + userInputCode: string; + linkCode: string; + codeLifetime: number; + timeCreated: number; + } + | { + status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR"; + } + >; /** * 1. verifies the code * 2. creates the user if it doesn't exist * 3. tries to link it * 4. marks the email as verified */ - static consumeCode(input: { - preAuthSessionId: string; - userInputCode: string; - deviceId: string; - session?: undefined; - tenantId: string; - userContext?: Record; - } | { - preAuthSessionId: string; - linkCode: string; - session?: undefined; - tenantId: string; - userContext?: Record; - }): Promise<{ - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } | { - status: "RESTART_FLOW_ERROR"; - }>; - static consumeCode(input: { - preAuthSessionId: string; - userInputCode: string; - deviceId: string; - session: SessionContainerInterface; - tenantId: string; - userContext?: Record; - } | { - preAuthSessionId: string; - linkCode: string; - session: SessionContainerInterface; - tenantId: string; - userContext?: Record; - }): Promise<{ - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } | { - status: "RESTART_FLOW_ERROR"; - } | { - 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"; - }>; + static consumeCode( + input: + | { + preAuthSessionId: string; + userInputCode: string; + deviceId: string; + session?: undefined; + tenantId: string; + userContext?: Record; + } + | { + preAuthSessionId: string; + linkCode: string; + session?: undefined; + tenantId: string; + userContext?: Record; + } + ): Promise< + | { + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } + | { + status: "RESTART_FLOW_ERROR"; + } + >; + static consumeCode( + input: + | { + preAuthSessionId: string; + userInputCode: string; + deviceId: string; + session: SessionContainerInterface; + tenantId: string; + userContext?: Record; + } + | { + preAuthSessionId: string; + linkCode: string; + session: SessionContainerInterface; + tenantId: string; + userContext?: Record; + } + ): Promise< + | { + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } + | { + status: "RESTART_FLOW_ERROR"; + } + | { + 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 will only verify the code (not consume it), and: * NOT create a new user if it doesn't exist @@ -122,63 +157,86 @@ export default class Wrapper { * NOT do any linking * NOT delete the code unless it returned RESTART_FLOW_ERROR */ - static checkCode(input: { - preAuthSessionId: string; - userInputCode: string; - deviceId: string; - tenantId: string; - userContext?: Record; - } | { - preAuthSessionId: string; - linkCode: string; - tenantId: string; - userContext?: Record; - }): Promise<{ - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - } | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } | { - status: "RESTART_FLOW_ERROR"; - }>; + static checkCode( + input: + | { + preAuthSessionId: string; + userInputCode: string; + deviceId: string; + tenantId: string; + userContext?: Record; + } + | { + preAuthSessionId: string; + linkCode: string; + tenantId: string; + userContext?: Record; + } + ): Promise< + | { + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + } + | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } + | { + status: "RESTART_FLOW_ERROR"; + } + >; static updateUser(input: { recipeUserId: RecipeUserId; email?: string | null; phoneNumber?: string | null; userContext?: Record; - }): Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR"; - } | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - }>; - static revokeAllCodes(input: { - email: string; - tenantId: string; - userContext?: Record; - } | { - phoneNumber: string; - tenantId: string; - userContext?: Record; - }): Promise<{ + }): Promise< + | { + status: + | "OK" + | "UNKNOWN_USER_ID_ERROR" + | "EMAIL_ALREADY_EXISTS_ERROR" + | "PHONE_NUMBER_ALREADY_EXISTS_ERROR"; + } + | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } + >; + static revokeAllCodes( + input: + | { + email: string; + tenantId: string; + userContext?: Record; + } + | { + phoneNumber: string; + tenantId: string; + userContext?: Record; + } + ): Promise<{ status: "OK"; }>; - static revokeCode(input: { - codeId: string; - tenantId: string; - userContext?: Record; - } | { - preAuthSessionId: string; - tenantId: string; - userContext?: Record; - }): Promise<{ + static revokeCode( + input: + | { + codeId: string; + tenantId: string; + userContext?: Record; + } + | { + preAuthSessionId: string; + tenantId: string; + userContext?: Record; + } + ): Promise<{ status: "OK"; }>; static listCodesByEmail(input: { @@ -201,37 +259,49 @@ export default class Wrapper { tenantId: string; userContext?: Record; }): Promise; - static createMagicLink(input: { - email: string; - tenantId: string; - userContext?: Record; - } | { - phoneNumber: string; - tenantId: string; - userContext?: Record; - }): Promise; - static signInUp(input: { - email: string; - tenantId: string; - session?: SessionContainerInterface; - userContext?: Record; - } | { - phoneNumber: string; - tenantId: string; - session?: SessionContainerInterface; - userContext?: Record; - }): Promise<{ + static createMagicLink( + input: + | { + email: string; + tenantId: string; + userContext?: Record; + } + | { + phoneNumber: string; + tenantId: string; + userContext?: Record; + } + ): Promise; + static signInUp( + input: + | { + email: string; + tenantId: string; + session?: SessionContainerInterface; + userContext?: Record; + } + | { + phoneNumber: string; + tenantId: string; + session?: SessionContainerInterface; + userContext?: Record; + } + ): Promise<{ status: string; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; user: User; }>; - static sendEmail(input: TypePasswordlessEmailDeliveryInput & { - userContext?: Record; - }): Promise; - static sendSms(input: TypePasswordlessSmsDeliveryInput & { - userContext?: Record; - }): Promise; + static sendEmail( + input: TypePasswordlessEmailDeliveryInput & { + userContext?: Record; + } + ): Promise; + static sendSms( + input: TypePasswordlessSmsDeliveryInput & { + userContext?: Record; + } + ): Promise; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/passwordless/index.js b/lib/build/recipe/passwordless/index.js index 8974b55bc..285ccb9ed 100644 --- a/lib/build/recipe/passwordless/index.js +++ b/lib/build/recipe/passwordless/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sendSms = exports.sendEmail = exports.checkCode = exports.signInUp = exports.createMagicLink = exports.revokeCode = exports.revokeAllCodes = exports.updateUser = exports.createNewCodeForDevice = exports.listCodesByPreAuthSessionId = exports.listCodesByPhoneNumber = exports.listCodesByEmail = exports.listCodesByDeviceId = exports.consumeCode = exports.createCode = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -24,13 +26,29 @@ const __1 = require("../.."); const utils_1 = require("../../utils"); class Wrapper { static createCode(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createCode(Object.assign(Object.assign({}, input), { session: input.session, shouldTryLinkingWithSessionUser: !!input.session, userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createCode( + Object.assign(Object.assign({}, input), { + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + userContext: utils_1.getUserContext(input.userContext), + }) + ); } static createNewCodeForDevice(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createNewCodeForDevice(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.createNewCodeForDevice( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static consumeCode(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeCode(Object.assign(Object.assign({}, input), { session: input.session, shouldTryLinkingWithSessionUser: !!input.session, userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeCode( + Object.assign(Object.assign({}, input), { + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + userContext: utils_1.getUserContext(input.userContext), + }) + ); } /** * This function will only verify the code (not consume it), and: @@ -40,41 +58,90 @@ class Wrapper { * NOT delete the code unless it returned RESTART_FLOW_ERROR */ static checkCode(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.checkCode(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.checkCode( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static updateUser(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.updateUser(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.updateUser( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static revokeAllCodes(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.revokeAllCodes(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.revokeAllCodes( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static revokeCode(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.revokeCode(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.revokeCode( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static listCodesByEmail(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.listCodesByEmail(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.listCodesByEmail( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static listCodesByPhoneNumber(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.listCodesByPhoneNumber(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.listCodesByPhoneNumber( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static listCodesByDeviceId(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.listCodesByDeviceId(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.listCodesByDeviceId( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static listCodesByPreAuthSessionId(input) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.listCodesByPreAuthSessionId(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.listCodesByPreAuthSessionId( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static createMagicLink(input) { const ctx = utils_1.getUserContext(input.userContext); - return recipe_1.default.getInstanceOrThrowError().createMagicLink(Object.assign(Object.assign({}, input), { request: __1.getRequestFromUserContext(ctx), userContext: ctx })); + return recipe_1.default.getInstanceOrThrowError().createMagicLink( + Object.assign(Object.assign({}, input), { + request: __1.getRequestFromUserContext(ctx), + userContext: ctx, + }) + ); } static signInUp(input) { - return recipe_1.default.getInstanceOrThrowError().signInUp(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return recipe_1.default + .getInstanceOrThrowError() + .signInUp( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static async sendEmail(input) { - return await recipe_1.default.getInstanceOrThrowError().emailDelivery.ingredientInterfaceImpl.sendEmail(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return await recipe_1.default + .getInstanceOrThrowError() + .emailDelivery.ingredientInterfaceImpl.sendEmail( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } static async sendSms(input) { - return await recipe_1.default.getInstanceOrThrowError().smsDelivery.ingredientInterfaceImpl.sendSms(Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) })); + return await recipe_1.default + .getInstanceOrThrowError() + .smsDelivery.ingredientInterfaceImpl.sendSms( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(input.userContext) }) + ); } } exports.default = Wrapper; diff --git a/lib/build/recipe/passwordless/recipe.d.ts b/lib/build/recipe/passwordless/recipe.d.ts index bcc885d48..9b3e95488 100644 --- a/lib/build/recipe/passwordless/recipe.d.ts +++ b/lib/build/recipe/passwordless/recipe.d.ts @@ -18,42 +18,64 @@ export default class Recipe extends RecipeModule { isInServerlessEnv: boolean; emailDelivery: EmailDeliveryIngredient; smsDelivery: SmsDeliveryIngredient; - constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput, ingredients: { - emailDelivery: EmailDeliveryIngredient | undefined; - smsDelivery: SmsDeliveryIngredient | undefined; - }); + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput, + ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + smsDelivery: SmsDeliveryIngredient | undefined; + } + ); static getInstanceOrThrowError(): Recipe; static init(config: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _: NormalisedURLPath, + __: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; - createMagicLink: (input: { - email: string; - tenantId: string; - session?: SessionContainerInterface; - request: BaseRequest | undefined; - userContext: UserContext; - } | { - phoneNumber: string; - tenantId: string; - session?: SessionContainerInterface; - request: BaseRequest | undefined; - userContext: UserContext; - }) => Promise; - signInUp: (input: { - email: string; - tenantId: string; - session?: SessionContainerInterface; - userContext: UserContext; - } | { - phoneNumber: string; - tenantId: string; - session?: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ + createMagicLink: ( + input: + | { + email: string; + tenantId: string; + session?: SessionContainerInterface; + request: BaseRequest | undefined; + userContext: UserContext; + } + | { + phoneNumber: string; + tenantId: string; + session?: SessionContainerInterface; + request: BaseRequest | undefined; + userContext: UserContext; + } + ) => Promise; + signInUp: ( + input: + | { + email: string; + tenantId: string; + session?: SessionContainerInterface; + userContext: UserContext; + } + | { + phoneNumber: string; + tenantId: string; + session?: SessionContainerInterface; + userContext: UserContext; + } + ) => Promise<{ status: string; createdNewRecipeUser: boolean; recipeUserId: import("../..").RecipeUserId; diff --git a/lib/build/recipe/passwordless/recipe.js b/lib/build/recipe/passwordless/recipe.js index 1f5146f26..6ff155b8b 100644 --- a/lib/build/recipe/passwordless/recipe.js +++ b/lib/build/recipe/passwordless/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -79,7 +81,9 @@ class Recipe extends recipeModule_1.default { id: constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD, disabled: this.apiImpl.phoneNumberExistsGET === undefined, method: "get", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD + ), }, { id: constants_1.RESEND_CODE_API, @@ -103,17 +107,16 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.CONSUME_CODE_API) { return await consumeCode_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.CREATE_CODE_API) { + } else if (id === constants_1.CREATE_CODE_API) { return await createCode_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.DOES_EMAIL_EXIST_API || id === constants_1.DOES_EMAIL_EXIST_API_OLD) { + } else if (id === constants_1.DOES_EMAIL_EXIST_API || id === constants_1.DOES_EMAIL_EXIST_API_OLD) { return await emailExists_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.DOES_PHONE_NUMBER_EXIST_API || id === constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD) { + } else if ( + id === constants_1.DOES_PHONE_NUMBER_EXIST_API || + id === constants_1.DOES_PHONE_NUMBER_EXIST_API_OLD + ) { return await phoneNumberExists_1.default(this.apiImpl, tenantId, options, userContext); - } - else { + } else { return await resendCode_1.default(this.apiImpl, tenantId, options, userContext); } }; @@ -128,36 +131,40 @@ class Recipe extends recipeModule_1.default { }; // helper functions below... this.createMagicLink = async (input) => { - let userInputCode = this.config.getCustomUserInputCode !== undefined - ? await this.config.getCustomUserInputCode(input.tenantId, input.userContext) - : undefined; - const codeInfo = await this.recipeInterfaceImpl.createCode("email" in input - ? { - email: input.email, - userInputCode, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - tenantId: input.tenantId, - userContext: input.userContext, - } - : { - phoneNumber: input.phoneNumber, - userInputCode, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - tenantId: input.tenantId, - userContext: input.userContext, - }); + let userInputCode = + this.config.getCustomUserInputCode !== undefined + ? await this.config.getCustomUserInputCode(input.tenantId, input.userContext) + : undefined; + const codeInfo = await this.recipeInterfaceImpl.createCode( + "email" in input + ? { + email: input.email, + userInputCode, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + tenantId: input.tenantId, + userContext: input.userContext, + } + : { + phoneNumber: input.phoneNumber, + userInputCode, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + tenantId: input.tenantId, + userContext: input.userContext, + } + ); if (codeInfo.status !== "OK") { throw new Error("Failed to create user. Please retry"); } const appInfo = this.getAppInfo(); - let magicLink = appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() + + let magicLink = + appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + appInfo.websiteBasePath.getAsStringDangerous() + "/verify" + "?preAuthSessionId=" + @@ -169,42 +176,46 @@ class Recipe extends recipeModule_1.default { return magicLink; }; this.signInUp = async (input) => { - let codeInfo = await this.recipeInterfaceImpl.createCode("email" in input - ? { - email: input.email, - tenantId: input.tenantId, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - userContext: input.userContext, - } - : { - phoneNumber: input.phoneNumber, - tenantId: input.tenantId, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - userContext: input.userContext, - }); + let codeInfo = await this.recipeInterfaceImpl.createCode( + "email" in input + ? { + email: input.email, + tenantId: input.tenantId, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + userContext: input.userContext, + } + : { + phoneNumber: input.phoneNumber, + tenantId: input.tenantId, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + userContext: input.userContext, + } + ); if (codeInfo.status !== "OK") { throw new Error("Failed to create user. Please retry"); } - let consumeCodeResponse = await this.recipeInterfaceImpl.consumeCode(this.config.flowType === "MAGIC_LINK" - ? { - preAuthSessionId: codeInfo.preAuthSessionId, - linkCode: codeInfo.linkCode, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - tenantId: input.tenantId, - userContext: input.userContext, - } - : { - preAuthSessionId: codeInfo.preAuthSessionId, - deviceId: codeInfo.deviceId, - userInputCode: codeInfo.userInputCode, - session: input.session, - shouldTryLinkingWithSessionUser: !!input.session, - tenantId: input.tenantId, - userContext: input.userContext, - }); + let consumeCodeResponse = await this.recipeInterfaceImpl.consumeCode( + this.config.flowType === "MAGIC_LINK" + ? { + preAuthSessionId: codeInfo.preAuthSessionId, + linkCode: codeInfo.linkCode, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + tenantId: input.tenantId, + userContext: input.userContext, + } + : { + preAuthSessionId: codeInfo.preAuthSessionId, + deviceId: codeInfo.deviceId, + userInputCode: codeInfo.userInputCode, + session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, + tenantId: input.tenantId, + userContext: input.userContext, + } + ); if (consumeCodeResponse.status === "OK") { return { status: "OK", @@ -212,15 +223,16 @@ class Recipe extends recipeModule_1.default { recipeUserId: consumeCodeResponse.recipeUserId, user: consumeCodeResponse.user, }; - } - else { + } else { throw new Error("Failed to create user. Please retry"); } }; this.isInServerlessEnv = isInServerlessEnv; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId))); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId)) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -262,12 +274,18 @@ class Recipe extends recipeModule_1.default { // so that the frontend asks the user to enter an email, // or uses the email of another login method. if (loginMethod.email !== undefined && !utils_2.isFakeEmail(loginMethod.email)) { - if (factorId === multifactorauth_1.FactorIds.OTP_EMAIL || factorId === multifactorauth_1.FactorIds.LINK_EMAIL) { + if ( + factorId === multifactorauth_1.FactorIds.OTP_EMAIL || + factorId === multifactorauth_1.FactorIds.LINK_EMAIL + ) { return true; } } if (loginMethod.phoneNumber !== undefined) { - if (factorId === multifactorauth_1.FactorIds.OTP_PHONE || factorId === multifactorauth_1.FactorIds.LINK_PHONE) { + if ( + factorId === multifactorauth_1.FactorIds.OTP_PHONE || + factorId === multifactorauth_1.FactorIds.LINK_PHONE + ) { return true; } } @@ -302,11 +320,15 @@ class Recipe extends recipeModule_1.default { // we want to ask the user to enter their email, or to use // another login method that has no fake email. if (orderedLoginMethodsByTimeJoinedOldestFirst[i].recipeId === Recipe.RECIPE_ID) { - if (orderedLoginMethodsByTimeJoinedOldestFirst[i].email !== undefined && - !utils_2.isFakeEmail(orderedLoginMethodsByTimeJoinedOldestFirst[i].email)) { + if ( + orderedLoginMethodsByTimeJoinedOldestFirst[i].email !== undefined && + !utils_2.isFakeEmail(orderedLoginMethodsByTimeJoinedOldestFirst[i].email) + ) { // loginmethods for passwordless are guaranteed to have unique emails // across all the loginmethods for a user. - nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.push(orderedLoginMethodsByTimeJoinedOldestFirst[i].email); + nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.push( + orderedLoginMethodsByTimeJoinedOldestFirst[i].email + ); } } } @@ -328,8 +350,10 @@ class Recipe extends recipeModule_1.default { emailsResult = [sessionLoginMethod.email]; } for (let i = 0; i < orderedLoginMethodsByTimeJoinedOldestFirst.length; i++) { - if (orderedLoginMethodsByTimeJoinedOldestFirst[i].email !== undefined && - !utils_2.isFakeEmail(orderedLoginMethodsByTimeJoinedOldestFirst[i].email)) { + if ( + orderedLoginMethodsByTimeJoinedOldestFirst[i].email !== undefined && + !utils_2.isFakeEmail(orderedLoginMethodsByTimeJoinedOldestFirst[i].email) + ) { // we have the if check below cause different loginMethods // across different recipes can have the same email. if (!emailsResult.includes(orderedLoginMethodsByTimeJoinedOldestFirst[i].email)) { @@ -348,17 +372,20 @@ class Recipe extends recipeModule_1.default { status: "OK", factorIdToEmailsMap, }; - } - else if (nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.length === 1) { + } else if (nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.length === 1) { // we return just this email and not others cause we want to // not create more loginMethods with passwordless for the user // object. let factorIdToEmailsMap = {}; if (allFactors.includes(multifactorauth_1.FactorIds.OTP_EMAIL)) { - factorIdToEmailsMap[multifactorauth_1.FactorIds.OTP_EMAIL] = nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined; + factorIdToEmailsMap[ + multifactorauth_1.FactorIds.OTP_EMAIL + ] = nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined; } if (allFactors.includes(multifactorauth_1.FactorIds.LINK_EMAIL)) { - factorIdToEmailsMap[multifactorauth_1.FactorIds.LINK_EMAIL] = nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined; + factorIdToEmailsMap[ + multifactorauth_1.FactorIds.LINK_EMAIL + ] = nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined; } return { status: "OK", @@ -370,8 +397,10 @@ class Recipe extends recipeModule_1.default { // if the session's email is in the list of // nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined (for better UX) let emailsResult = []; - if (sessionLoginMethod.email !== undefined && - nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.includes(sessionLoginMethod.email)) { + if ( + sessionLoginMethod.email !== undefined && + nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.includes(sessionLoginMethod.email) + ) { emailsResult = [sessionLoginMethod.email]; } for (let i = 0; i < nonFakeEmailsThatPasswordlessLoginMethodOrderedByTimeJoined.length; i++) { @@ -420,7 +449,9 @@ class Recipe extends recipeModule_1.default { if (orderedLoginMethodsByTimeJoinedOldestFirst[i].phoneNumber !== undefined) { // loginmethods for passwordless are guaranteed to have unique phone numbers // across all the loginmethods for a user. - phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.push(orderedLoginMethodsByTimeJoinedOldestFirst[i].phoneNumber); + phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.push( + orderedLoginMethodsByTimeJoinedOldestFirst[i].phoneNumber + ); } } } @@ -461,17 +492,20 @@ class Recipe extends recipeModule_1.default { status: "OK", factorIdToPhoneNumberMap, }; - } - else if (phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.length === 1) { + } else if (phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.length === 1) { // we return just this phone number and not others cause we want to // not create more loginMethods with passwordless for the user // object. let factorIdToPhoneNumberMap = {}; if (allFactors.includes(multifactorauth_1.FactorIds.OTP_PHONE)) { - factorIdToPhoneNumberMap[multifactorauth_1.FactorIds.OTP_PHONE] = phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined; + factorIdToPhoneNumberMap[ + multifactorauth_1.FactorIds.OTP_PHONE + ] = phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined; } if (allFactors.includes(multifactorauth_1.FactorIds.LINK_PHONE)) { - factorIdToPhoneNumberMap[multifactorauth_1.FactorIds.LINK_PHONE] = phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined; + factorIdToPhoneNumberMap[ + multifactorauth_1.FactorIds.LINK_PHONE + ] = phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined; } return { status: "OK", @@ -483,8 +517,12 @@ class Recipe extends recipeModule_1.default { // if the session's phone is in the list of // phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined (for better UX) let phonesResult = []; - if (sessionLoginMethod.phoneNumber !== undefined && - phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.includes(sessionLoginMethod.phoneNumber)) { + if ( + sessionLoginMethod.phoneNumber !== undefined && + phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.includes( + sessionLoginMethod.phoneNumber + ) + ) { phonesResult = [sessionLoginMethod.phoneNumber]; } for (let i = 0; i < phoneNumbersThatPasswordlessLoginMethodOrderedByTimeJoined.length; i++) { @@ -527,8 +565,7 @@ class Recipe extends recipeModule_1.default { smsDelivery: undefined, }); return Recipe.instance; - } - else { + } else { throw new Error("Passwordless recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/passwordless/recipeImplementation.js b/lib/build/recipe/passwordless/recipeImplementation.js index d0921cebb..d2fb36376 100644 --- a/lib/build/recipe/passwordless/recipeImplementation.js +++ b/lib/build/recipe/passwordless/recipeImplementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../accountlinking/recipe")); const recipe_2 = __importDefault(require("../emailverification/recipe")); @@ -24,7 +26,11 @@ function getRecipeInterface(querier) { } return { consumeCode: async function (input) { - const response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/consume`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + const response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/consume`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); if (response.status !== "OK") { return response; } @@ -33,23 +39,34 @@ function getRecipeInterface(querier) { response.recipeUserId = new recipeUserId_1.default(response.recipeUserId); // Attempt account linking (this is a sign up) let updatedUser = response.user; - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ - tenantId: input.tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session: input.session, - shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, - userContext: input.userContext, - }); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( + { + tenantId: input.tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, + userContext: input.userContext, + } + ); if (linkResult.status !== "OK") { return linkResult; } updatedUser = linkResult.user; response.user = updatedUser; - return Object.assign(Object.assign({}, response), { consumedDevice: response.consumedDevice, createdNewRecipeUser: response.createdNewUser, user: response.user, recipeUserId: response.recipeUserId }); + return Object.assign(Object.assign({}, response), { + consumedDevice: response.consumedDevice, + createdNewRecipeUser: response.createdNewUser, + user: response.user, + recipeUserId: response.recipeUserId, + }); }, checkCode: async function (input) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/check`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/check`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); if (response.status !== "OK") { return response; } @@ -57,37 +74,69 @@ function getRecipeInterface(querier) { return response; }, createCode: async function (input) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); return response; }, createNewCodeForDevice: async function (input) { - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); return response; }, listCodesByDeviceId: async function (input) { - let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); return response.devices.length === 1 ? response.devices[0] : undefined; }, listCodesByEmail: async function (input) { - let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); return response.devices; }, listCodesByPhoneNumber: async function (input) { - let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); return response.devices; }, listCodesByPreAuthSessionId: async function (input) { - let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); return response.devices.length === 1 ? response.devices[0] : undefined; }, revokeAllCodes: async function (input) { - await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes/remove`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/codes/remove`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); return { status: "OK", }; }, revokeCode: async function (input) { - await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/remove`), copyAndRemoveUserContextAndTenantId(input), input.userContext); + await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${input.tenantId}/recipe/signinup/code/remove`), + copyAndRemoveUserContextAndTenantId(input), + input.userContext + ); return { status: "OK" }; }, updateUser: async function (input) { @@ -116,13 +165,19 @@ function getRecipeInterface(querier) { 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.", + 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.", }; } } - let response = await querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/user`), copyAndRemoveUserContextAndTenantId(input), {}, input.userContext); + let response = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/user`), + copyAndRemoveUserContextAndTenantId(input), + {}, + input.userContext + ); if (response.status !== "OK") { return response; } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.d.ts index edd8c7e51..a0e2dbe84 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.d.ts @@ -4,7 +4,9 @@ import { SmsDeliveryInterface } from "../../../../../ingredients/smsdelivery/typ import { UserContext } from "../../../../../types"; export default class BackwardCompatibilityService implements SmsDeliveryInterface { constructor(); - sendSms: (input: TypePasswordlessSmsDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendSms: ( + input: TypePasswordlessSmsDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.js b/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.js index a6e7cf0ed..7b557bdf0 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/backwardCompatibility/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_1 = require("../../../../../ingredients/smsdelivery/services/supertokens"); const supertokens_2 = __importDefault(require("../../../../../supertokens")); @@ -9,23 +11,28 @@ const utils_1 = require("../../../../../utils"); async function createAndSendSmsUsingSupertokensService(input) { let supertokens = supertokens_2.default.getInstanceOrThrowError(); let appName = supertokens.appInfo.appName; - const result = await utils_1.postWithFetch(supertokens_1.SUPERTOKENS_SMS_SERVICE_URL, { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, { - smsInput: { - appName, - type: "PASSWORDLESS_LOGIN", - phoneNumber: input.phoneNumber, - userInputCode: input.userInputCode, - urlWithLinkCode: input.urlWithLinkCode, - codeLifetime: input.codeLifetime, - // isFirstFactor: input.isFirstFactor, + const result = await utils_1.postWithFetch( + supertokens_1.SUPERTOKENS_SMS_SERVICE_URL, + { + "api-version": "0", + "content-type": "application/json; charset=utf-8", }, - }, { - successLog: `Passwordless login SMS sent to ${input.phoneNumber}`, - errorLogHeader: "Error sending passwordless login SMS", - }); + { + smsInput: { + appName, + type: "PASSWORDLESS_LOGIN", + phoneNumber: input.phoneNumber, + userInputCode: input.userInputCode, + urlWithLinkCode: input.urlWithLinkCode, + codeLifetime: input.codeLifetime, + // isFirstFactor: input.isFirstFactor, + }, + }, + { + successLog: `Passwordless login SMS sent to ${input.phoneNumber}`, + errorLogHeader: "Error sending passwordless login SMS", + } + ); if ("error" in result) { throw result.error; } @@ -37,12 +44,10 @@ async function createAndSendSmsUsingSupertokensService(input) { * will be of type `{err: string}` */ throw new Error(result.resp.body.err); - } - else { + } else { throw new Error(`Request failed with status code ${result.resp.status}`); } - } - else { + } else { /** * if we do console.log(`SMS content: ${input}`); * Output would be: @@ -68,7 +73,9 @@ async function createAndSendSmsUsingSupertokensService(input) { * "b": 2 * } */ - console.log("Free daily SMS quota reached. If you want to use SuperTokens to send SMS, please sign up on supertokens.com to get your SMS API key, else you can also define your own method by overriding the service. For now, we are logging it below:"); + console.log( + "Free daily SMS quota reached. If you want to use SuperTokens to send SMS, please sign up on supertokens.com to get your SMS API key, else you can also define your own method by overriding the service. For now, we are logging it below:" + ); console.log(`\nSMS content: ${JSON.stringify(input, null, 2)}`); } } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/index.js b/lib/build/recipe/passwordless/smsdelivery/services/index.js index c4ba9a912..f85fb8900 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/index.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SupertokensService = exports.TwilioService = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. diff --git a/lib/build/recipe/passwordless/smsdelivery/services/supertokens/index.js b/lib/build/recipe/passwordless/smsdelivery/services/supertokens/index.js index dde086329..847991a4a 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/supertokens/index.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/supertokens/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -25,24 +27,29 @@ class SupertokensService { this.sendSms = async (input) => { let supertokens = supertokens_2.default.getInstanceOrThrowError(); let appName = supertokens.appInfo.appName; - const res = await utils_1.postWithFetch(supertokens_1.SUPERTOKENS_SMS_SERVICE_URL, { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, { - apiKey: this.apiKey, - smsInput: { - type: input.type, - phoneNumber: input.phoneNumber, - userInputCode: input.userInputCode, - urlWithLinkCode: input.urlWithLinkCode, - codeLifetime: input.codeLifetime, - isFirstFactor: input.isFirstFactor, - appName, + const res = await utils_1.postWithFetch( + supertokens_1.SUPERTOKENS_SMS_SERVICE_URL, + { + "api-version": "0", + "content-type": "application/json; charset=utf-8", }, - }, { - successLog: `Passwordless login SMS sent to ${input.phoneNumber}`, - errorLogHeader: "Error sending SMS", - }); + { + apiKey: this.apiKey, + smsInput: { + type: input.type, + phoneNumber: input.phoneNumber, + userInputCode: input.userInputCode, + urlWithLinkCode: input.urlWithLinkCode, + codeLifetime: input.codeLifetime, + isFirstFactor: input.isFirstFactor, + appName, + }, + }, + { + successLog: `Passwordless login SMS sent to ${input.phoneNumber}`, + errorLogHeader: "Error sending SMS", + } + ); if ("error" in res) { throw res.error; } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.d.ts b/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.d.ts index 3f5458100..d5e84942d 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.d.ts +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.d.ts @@ -7,7 +7,9 @@ export default class TwilioService implements SmsDeliveryInterface; private config; constructor(config: TypeInput); - sendSms: (input: TypePasswordlessSmsDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendSms: ( + input: TypePasswordlessSmsDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.js b/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.js index 320a82cad..21dabe0bc 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -26,15 +28,30 @@ class TwilioService { this.sendSms = async (input) => { let content = await this.serviceImpl.getContent(input); if ("from" in this.config.twilioSettings) { - await this.serviceImpl.sendRawSms(Object.assign(Object.assign({}, content), { userContext: input.userContext, from: this.config.twilioSettings.from })); - } - else { - await this.serviceImpl.sendRawSms(Object.assign(Object.assign({}, content), { userContext: input.userContext, messagingServiceSid: this.config.twilioSettings.messagingServiceSid })); + await this.serviceImpl.sendRawSms( + Object.assign(Object.assign({}, content), { + userContext: input.userContext, + from: this.config.twilioSettings.from, + }) + ); + } else { + await this.serviceImpl.sendRawSms( + Object.assign(Object.assign({}, content), { + userContext: input.userContext, + messagingServiceSid: this.config.twilioSettings.messagingServiceSid, + }) + ); } }; this.config = twilio_1.normaliseUserInputConfig(config); - const twilioClient = twilio_2.default(config.twilioSettings.accountSid, config.twilioSettings.authToken, config.twilioSettings.opts); - let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(twilioClient)); + const twilioClient = twilio_2.default( + config.twilioSettings.accountSid, + config.twilioSettings.authToken, + config.twilioSettings.opts + ); + let builder = new supertokens_js_override_1.default( + serviceImplementation_1.getServiceImplementation(twilioClient) + ); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/passwordlessLogin.js b/lib/build/recipe/passwordless/smsdelivery/services/twilio/passwordlessLogin.js index 2d6f5ce47..070ca9c75 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/passwordlessLogin.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/passwordlessLogin.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../../../utils"); const supertokens_1 = __importDefault(require("../../../../../supertokens")); @@ -19,11 +21,9 @@ function getPasswordlessLoginSmsBody(appName, codeLifetime, urlWithLinkCode, use let message = ""; if (urlWithLinkCode !== undefined && userInputCode !== undefined) { message += `OTP to login is ${userInputCode} for ${appName}\n\nOR click ${urlWithLinkCode} to login.\n\n`; - } - else if (urlWithLinkCode !== undefined) { + } else if (urlWithLinkCode !== undefined) { message += `Click ${urlWithLinkCode} to login to ${appName}\n\n`; - } - else { + } else { message += `OTP to login is ${userInputCode} for ${appName}\n\n`; } const humanisedCodeLifetime = utils_1.humaniseMilliseconds(codeLifetime); diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.d.ts b/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.d.ts index 7f73b5364..6aa22d4d2 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.d.ts +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.d.ts @@ -2,4 +2,6 @@ import { TypePasswordlessSmsDeliveryInput } from "../../../types"; import Twilio from "twilio/lib/rest/Twilio"; import { ServiceInterface } from "../../../../../ingredients/smsdelivery/services/twilio"; -export declare function getServiceImplementation(twilioClient: Twilio): ServiceInterface; +export declare function getServiceImplementation( + twilioClient: Twilio +): ServiceInterface; diff --git a/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.js b/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.js index f53201945..a9a078029 100644 --- a/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.js +++ b/lib/build/recipe/passwordless/smsdelivery/services/twilio/serviceImplementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const passwordlessLogin_1 = __importDefault(require("./passwordlessLogin")); @@ -28,8 +30,7 @@ function getServiceImplementation(twilioClient) { body: input.body, from: input.from, }); - } - else { + } else { await twilioClient.messages.create({ to: input.toPhoneNumber, body: input.body, diff --git a/lib/build/recipe/passwordless/types.d.ts b/lib/build/recipe/passwordless/types.d.ts index 54bed39ae..434f7902e 100644 --- a/lib/build/recipe/passwordless/types.d.ts +++ b/lib/build/recipe/passwordless/types.d.ts @@ -2,64 +2,101 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; import { SessionContainerInterface } from "../session/types"; -import { TypeInput as EmailDeliveryTypeInput, TypeInputWithService as EmailDeliveryTypeInputWithService } from "../../ingredients/emaildelivery/types"; +import { + TypeInput as EmailDeliveryTypeInput, + TypeInputWithService as EmailDeliveryTypeInputWithService, +} from "../../ingredients/emaildelivery/types"; import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; -import { TypeInput as SmsDeliveryTypeInput, TypeInputWithService as SmsDeliveryTypeInputWithService } from "../../ingredients/smsdelivery/types"; +import { + TypeInput as SmsDeliveryTypeInput, + TypeInputWithService as SmsDeliveryTypeInputWithService, +} from "../../ingredients/smsdelivery/types"; import SmsDeliveryIngredient from "../../ingredients/smsdelivery"; import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; -export declare type TypeInput = ({ - contactMethod: "PHONE"; - validatePhoneNumber?: (phoneNumber: string, tenantId: string) => Promise | string | undefined; -} | { - contactMethod: "EMAIL"; - validateEmailAddress?: (email: string, tenantId: string) => Promise | string | undefined; -} | { - contactMethod: "EMAIL_OR_PHONE"; - validateEmailAddress?: (email: string, tenantId: string) => Promise | string | undefined; - validatePhoneNumber?: (phoneNumber: string, tenantId: string) => Promise | string | undefined; -}) & { +export declare type TypeInput = ( + | { + contactMethod: "PHONE"; + validatePhoneNumber?: ( + phoneNumber: string, + tenantId: string + ) => Promise | string | undefined; + } + | { + contactMethod: "EMAIL"; + validateEmailAddress?: (email: string, tenantId: string) => Promise | string | undefined; + } + | { + contactMethod: "EMAIL_OR_PHONE"; + validateEmailAddress?: (email: string, tenantId: string) => Promise | string | undefined; + validatePhoneNumber?: ( + phoneNumber: string, + tenantId: string + ) => Promise | string | undefined; + } +) & { flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; emailDelivery?: EmailDeliveryTypeInput; smsDelivery?: SmsDeliveryTypeInput; getCustomUserInputCode?: (tenantId: string, userContext: UserContext) => Promise | string; override?: { - functions?: (originalImplementation: RecipeInterface, builder: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder: OverrideableBuilder) => APIInterface; }; }; -export declare type TypeNormalisedInput = ({ - contactMethod: "PHONE"; - validatePhoneNumber: (phoneNumber: string, tenantId: string) => Promise | string | undefined; -} | { - contactMethod: "EMAIL"; - validateEmailAddress: (email: string, tenantId: string) => Promise | string | undefined; -} | { - contactMethod: "EMAIL_OR_PHONE"; - validateEmailAddress: (email: string, tenantId: string) => Promise | string | undefined; - validatePhoneNumber: (phoneNumber: string, tenantId: string) => Promise | string | undefined; -}) & { +export declare type TypeNormalisedInput = ( + | { + contactMethod: "PHONE"; + validatePhoneNumber: ( + phoneNumber: string, + tenantId: string + ) => Promise | string | undefined; + } + | { + contactMethod: "EMAIL"; + validateEmailAddress: (email: string, tenantId: string) => Promise | string | undefined; + } + | { + contactMethod: "EMAIL_OR_PHONE"; + validateEmailAddress: (email: string, tenantId: string) => Promise | string | undefined; + validatePhoneNumber: ( + phoneNumber: string, + tenantId: string + ) => Promise | string | undefined; + } +) & { flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; getCustomUserInputCode?: (tenantId: string, userContext: UserContext) => Promise | string; getSmsDeliveryConfig: () => SmsDeliveryTypeInputWithService; getEmailDeliveryConfig: () => EmailDeliveryTypeInputWithService; override: { - functions: (originalImplementation: RecipeInterface, builder: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder: OverrideableBuilder) => APIInterface; }; }; export declare type RecipeInterface = { - createCode: (input: ({ - email: string; - } | { - phoneNumber: string; - }) & { - userInputCode?: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - tenantId: string; - userContext: UserContext; - }) => Promise<{ + createCode: ( + input: ( + | { + email: string; + } + | { + phoneNumber: string; + } + ) & { + userInputCode?: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + } + ) => Promise<{ status: "OK"; preAuthSessionId: string; codeId: string; @@ -74,118 +111,153 @@ export declare type RecipeInterface = { userInputCode?: string; tenantId: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - preAuthSessionId: string; - codeId: string; - deviceId: string; - userInputCode: string; - linkCode: string; - codeLifetime: number; - timeCreated: number; - } | { - status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR"; - }>; - consumeCode: (input: { - userInputCode: string; - deviceId: string; - preAuthSessionId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - tenantId: string; - userContext: UserContext; - } | { - linkCode: string; - preAuthSessionId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - tenantId: string; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } | { - status: "RESTART_FLOW_ERROR"; - } | { - 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"; - }>; - checkCode: (input: { - userInputCode: string; - deviceId: string; - preAuthSessionId: string; - tenantId: string; - userContext: UserContext; - } | { - linkCode: string; - preAuthSessionId: string; - tenantId: string; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - consumedDevice: { - preAuthSessionId: string; - failedCodeInputAttemptCount: number; - email?: string; - phoneNumber?: string; - }; - } | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } | { - status: "RESTART_FLOW_ERROR"; - }>; + }) => Promise< + | { + status: "OK"; + preAuthSessionId: string; + codeId: string; + deviceId: string; + userInputCode: string; + linkCode: string; + codeLifetime: number; + timeCreated: number; + } + | { + status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR"; + } + >; + consumeCode: ( + input: + | { + userInputCode: string; + deviceId: string; + preAuthSessionId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + } + | { + linkCode: string; + preAuthSessionId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + tenantId: string; + userContext: UserContext; + } + ) => Promise< + | { + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } + | { + status: "RESTART_FLOW_ERROR"; + } + | { + 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"; + } + >; + checkCode: ( + input: + | { + userInputCode: string; + deviceId: string; + preAuthSessionId: string; + tenantId: string; + userContext: UserContext; + } + | { + linkCode: string; + preAuthSessionId: string; + tenantId: string; + userContext: UserContext; + } + ) => Promise< + | { + status: "OK"; + consumedDevice: { + preAuthSessionId: string; + failedCodeInputAttemptCount: number; + email?: string; + phoneNumber?: string; + }; + } + | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } + | { + status: "RESTART_FLOW_ERROR"; + } + >; updateUser: (input: { recipeUserId: RecipeUserId; email?: string | null; phoneNumber?: string | null; userContext: UserContext; - }) => Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR"; - } | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - }>; - revokeAllCodes: (input: { - email: string; - tenantId: string; - userContext: UserContext; - } | { - phoneNumber: string; - tenantId: string; - userContext: UserContext; - }) => Promise<{ + }) => Promise< + | { + status: + | "OK" + | "UNKNOWN_USER_ID_ERROR" + | "EMAIL_ALREADY_EXISTS_ERROR" + | "PHONE_NUMBER_ALREADY_EXISTS_ERROR"; + } + | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } + >; + revokeAllCodes: ( + input: + | { + email: string; + tenantId: string; + userContext: UserContext; + } + | { + phoneNumber: string; + tenantId: string; + userContext: UserContext; + } + ) => Promise<{ status: "OK"; }>; - revokeCode: (input: { - codeId: string; - tenantId: string; - userContext: UserContext; - } | { - preAuthSessionId: string; - tenantId: string; - userContext: UserContext; - }) => Promise<{ + revokeCode: ( + input: + | { + codeId: string; + tenantId: string; + userContext: UserContext; + } + | { + preAuthSessionId: string; + tenantId: string; + userContext: UserContext; + } + ) => Promise<{ status: "OK"; }>; - listCodesByEmail: (input: { - email: string; - tenantId: string; - userContext: UserContext; - }) => Promise; + listCodesByEmail: (input: { email: string; tenantId: string; userContext: UserContext }) => Promise; listCodesByPhoneNumber: (input: { phoneNumber: string; tenantId: string; @@ -225,83 +297,114 @@ export declare type APIOptions = { smsDelivery: SmsDeliveryIngredient; }; export declare type APIInterface = { - createCodePOST?: (input: ({ - email: string; - } | { - phoneNumber: string; - }) & { - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; - } | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } | GeneralErrorResponse>; - resendCodePOST?: (input: { - deviceId: string; - preAuthSessionId: string; - } & { - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise; - consumeCodePOST?: (input: ({ - userInputCode: string; - deviceId: string; - preAuthSessionId: string; - } | { - linkCode: string; - preAuthSessionId: string; - }) & { - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - session: SessionContainerInterface; - } | { - status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; - failedCodeInputAttemptCount: number; - maximumCodeInputAttempts: number; - } | { - status: "RESTART_FLOW_ERROR"; - } | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } | GeneralErrorResponse>; + createCodePOST?: ( + input: ( + | { + email: string; + } + | { + phoneNumber: string; + } + ) & { + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + } + ) => Promise< + | { + status: "OK"; + deviceId: string; + preAuthSessionId: string; + flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + >; + resendCodePOST?: ( + input: { + deviceId: string; + preAuthSessionId: string; + } & { + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + } + ) => Promise< + | GeneralErrorResponse + | { + status: "RESTART_FLOW_ERROR" | "OK"; + } + >; + consumeCodePOST?: ( + input: ( + | { + userInputCode: string; + deviceId: string; + preAuthSessionId: string; + } + | { + linkCode: string; + preAuthSessionId: string; + } + ) & { + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + } + ) => Promise< + | { + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + session: SessionContainerInterface; + } + | { + status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR"; + failedCodeInputAttemptCount: number; + maximumCodeInputAttempts: number; + } + | { + status: "RESTART_FLOW_ERROR"; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + >; emailExistsGET?: (input: { email: string; tenantId: string; options: APIOptions; userContext: UserContext; - }) => Promise<{ - status: "OK"; - exists: boolean; - } | GeneralErrorResponse>; + }) => Promise< + | { + status: "OK"; + exists: boolean; + } + | GeneralErrorResponse + >; phoneNumberExistsGET?: (input: { phoneNumber: string; tenantId: string; options: APIOptions; userContext: UserContext; - }) => Promise<{ - status: "OK"; - exists: boolean; - } | GeneralErrorResponse>; + }) => Promise< + | { + status: "OK"; + exists: boolean; + } + | GeneralErrorResponse + >; }; export declare type TypePasswordlessEmailDeliveryInput = { type: "PASSWORDLESS_LOGIN"; diff --git a/lib/build/recipe/passwordless/utils.d.ts b/lib/build/recipe/passwordless/utils.d.ts index e72ef0708..8d1b4b9f2 100644 --- a/lib/build/recipe/passwordless/utils.d.ts +++ b/lib/build/recipe/passwordless/utils.d.ts @@ -2,7 +2,11 @@ import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; import { NormalisedAppinfo } from "../../types"; -export declare function validateAndNormaliseUserInput(_: Recipe, appInfo: NormalisedAppinfo, config: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + _: Recipe, + appInfo: NormalisedAppinfo, + config: TypeInput +): TypeNormalisedInput; export declare function defaultValidatePhoneNumber(value: string): Promise | string | undefined; export declare function defaultValidateEmail(value: string): Promise | string | undefined; export declare function getEnabledPwlessFactors(config: TypeInput): string[]; diff --git a/lib/build/recipe/passwordless/utils.js b/lib/build/recipe/passwordless/utils.js index 6e7649f70..951335f9f 100644 --- a/lib/build/recipe/passwordless/utils.js +++ b/lib/build/recipe/passwordless/utils.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEnabledPwlessFactors = exports.defaultValidateEmail = exports.defaultValidatePhoneNumber = exports.validateAndNormaliseUserInput = void 0; const max_1 = __importDefault(require("libphonenumber-js/max")); @@ -23,15 +25,23 @@ const backwardCompatibility_1 = __importDefault(require("./emaildelivery/service const backwardCompatibility_2 = __importDefault(require("./smsdelivery/services/backwardCompatibility")); const multifactorauth_1 = require("../multifactorauth"); function validateAndNormaliseUserInput(_, appInfo, config) { - if (config.contactMethod !== "PHONE" && + if ( + config.contactMethod !== "PHONE" && config.contactMethod !== "EMAIL" && - config.contactMethod !== "EMAIL_OR_PHONE") { + config.contactMethod !== "EMAIL_OR_PHONE" + ) { throw new Error('Please pass one of "PHONE", "EMAIL" or "EMAIL_OR_PHONE" as the contactMethod'); } if (config.flowType === undefined) { throw new Error("Please pass flowType argument in the config"); } - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config.override + ); function getEmailDeliveryConfig() { var _a; let emailService = (_a = config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; @@ -43,7 +53,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { if (emailService === undefined) { emailService = new backwardCompatibility_1.default(appInfo); } - let emailDelivery = Object.assign(Object.assign({}, config.emailDelivery), { + let emailDelivery = Object.assign(Object.assign({}, config.emailDelivery), { /** * if we do * let emailDelivery = { @@ -55,7 +65,8 @@ function validateAndNormaliseUserInput(_, appInfo, config) { * it it again get set to undefined, so we * set service at the end */ - service: emailService }); + service: emailService, + }); return emailDelivery; } function getSmsDeliveryConfig() { @@ -71,7 +82,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { if (smsService === undefined) { smsService = new backwardCompatibility_2.default(); } - let smsDelivery = Object.assign(Object.assign({}, config.smsDelivery), { + let smsDelivery = Object.assign(Object.assign({}, config.smsDelivery), { /** * if we do * let smsDelivery = { @@ -83,7 +94,8 @@ function validateAndNormaliseUserInput(_, appInfo, config) { * it it again get set to undefined, so we * set service at the end */ - service: smsService }); + service: smsService, + }); return smsDelivery; } if (config.contactMethod === "EMAIL") { @@ -93,30 +105,32 @@ function validateAndNormaliseUserInput(_, appInfo, config) { getSmsDeliveryConfig, flowType: config.flowType, contactMethod: "EMAIL", - validateEmailAddress: config.validateEmailAddress === undefined ? defaultValidateEmail : config.validateEmailAddress, + validateEmailAddress: + config.validateEmailAddress === undefined ? defaultValidateEmail : config.validateEmailAddress, getCustomUserInputCode: config.getCustomUserInputCode, }; - } - else if (config.contactMethod === "PHONE") { + } else if (config.contactMethod === "PHONE") { return { override, getEmailDeliveryConfig, getSmsDeliveryConfig, flowType: config.flowType, contactMethod: "PHONE", - validatePhoneNumber: config.validatePhoneNumber === undefined ? defaultValidatePhoneNumber : config.validatePhoneNumber, + validatePhoneNumber: + config.validatePhoneNumber === undefined ? defaultValidatePhoneNumber : config.validatePhoneNumber, getCustomUserInputCode: config.getCustomUserInputCode, }; - } - else { + } else { return { override, getEmailDeliveryConfig, getSmsDeliveryConfig, flowType: config.flowType, contactMethod: "EMAIL_OR_PHONE", - validateEmailAddress: config.validateEmailAddress === undefined ? defaultValidateEmail : config.validateEmailAddress, - validatePhoneNumber: config.validatePhoneNumber === undefined ? defaultValidatePhoneNumber : config.validatePhoneNumber, + validateEmailAddress: + config.validateEmailAddress === undefined ? defaultValidateEmail : config.validateEmailAddress, + validatePhoneNumber: + config.validatePhoneNumber === undefined ? defaultValidatePhoneNumber : config.validatePhoneNumber, getCustomUserInputCode: config.getCustomUserInputCode, }; } @@ -141,7 +155,11 @@ function defaultValidateEmail(value) { if (typeof value !== "string") { return "Development bug: Please make sure the email field is a string"; } - if (value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) === null) { + if ( + value.match( + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ) === null + ) { return "Email is invalid"; } return undefined; @@ -152,34 +170,31 @@ function getEnabledPwlessFactors(config) { if (config.flowType === "MAGIC_LINK") { if (config.contactMethod === "EMAIL") { allFactors = [multifactorauth_1.FactorIds.LINK_EMAIL]; - } - else if (config.contactMethod === "PHONE") { + } else if (config.contactMethod === "PHONE") { allFactors = [multifactorauth_1.FactorIds.LINK_PHONE]; - } - else { + } else { allFactors = [multifactorauth_1.FactorIds.LINK_EMAIL, multifactorauth_1.FactorIds.LINK_PHONE]; } - } - else if (config.flowType === "USER_INPUT_CODE") { + } else if (config.flowType === "USER_INPUT_CODE") { if (config.contactMethod === "EMAIL") { allFactors = [multifactorauth_1.FactorIds.OTP_EMAIL]; - } - else if (config.contactMethod === "PHONE") { + } else if (config.contactMethod === "PHONE") { allFactors = [multifactorauth_1.FactorIds.OTP_PHONE]; - } - else { + } else { allFactors = [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.OTP_PHONE]; } - } - else { + } else { if (config.contactMethod === "EMAIL") { allFactors = [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.LINK_EMAIL]; - } - else if (config.contactMethod === "PHONE") { + } else if (config.contactMethod === "PHONE") { allFactors = [multifactorauth_1.FactorIds.OTP_PHONE, multifactorauth_1.FactorIds.LINK_PHONE]; - } - else { - allFactors = [multifactorauth_1.FactorIds.OTP_EMAIL, multifactorauth_1.FactorIds.OTP_PHONE, multifactorauth_1.FactorIds.LINK_EMAIL, multifactorauth_1.FactorIds.LINK_PHONE]; + } else { + allFactors = [ + multifactorauth_1.FactorIds.OTP_EMAIL, + multifactorauth_1.FactorIds.OTP_PHONE, + multifactorauth_1.FactorIds.LINK_EMAIL, + multifactorauth_1.FactorIds.LINK_PHONE, + ]; } } return allFactors; diff --git a/lib/build/recipe/session/accessToken.d.ts b/lib/build/recipe/session/accessToken.d.ts index 01a4ece91..8b0685ec8 100644 --- a/lib/build/recipe/session/accessToken.d.ts +++ b/lib/build/recipe/session/accessToken.d.ts @@ -2,7 +2,11 @@ import { ParsedJWTInfo } from "./jwt"; import * as jose from "jose"; import RecipeUserId from "../../recipeUserId"; -export declare function getInfoFromAccessToken(jwtInfo: ParsedJWTInfo, jwks: jose.JWTVerifyGetKey, doAntiCsrfCheck: boolean): Promise<{ +export declare function getInfoFromAccessToken( + jwtInfo: ParsedJWTInfo, + jwks: jose.JWTVerifyGetKey, + doAntiCsrfCheck: boolean +): Promise<{ sessionHandle: string; userId: string; recipeUserId: RecipeUserId; diff --git a/lib/build/recipe/session/accessToken.js b/lib/build/recipe/session/accessToken.js index 472b47a78..37249db05 100644 --- a/lib/build/recipe/session/accessToken.js +++ b/lib/build/recipe/session/accessToken.js @@ -13,35 +13,79 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __asyncValues = (this && this.__asyncValues) || function (o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __asyncValues = + (this && this.__asyncValues) || + function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], + i; + return m + ? m.call(o) + : ((o = typeof __values === "function" ? __values(o) : o[Symbol.iterator]()), + (i = {}), + verb("next"), + verb("throw"), + verb("return"), + (i[Symbol.asyncIterator] = function () { + return this; + }), + i); + function verb(n) { + i[n] = + o[n] && + function (v) { + return new Promise(function (resolve, reject) { + (v = o[n](v)), settle(resolve, reject, v.done, v.value); + }); + }; + } + function settle(resolve, reject, d, v) { + Promise.resolve(v).then(function (v) { + resolve({ value: v, done: d }); + }, reject); + } + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sanitizeNumberInput = exports.validateAccessTokenStructure = exports.getInfoFromAccessToken = void 0; const error_1 = __importDefault(require("./error")); @@ -58,40 +102,48 @@ async function getInfoFromAccessToken(jwtInfo, jwks, doAntiCsrfCheck) { let payload = undefined; try { payload = (await jose.jwtVerify(jwtInfo.rawTokenString, jwks)).payload; - } - catch (error) { + } catch (error) { // We only want to opt-into this for V2 access tokens - if (jwtInfo.version === 2 && (error === null || error === void 0 ? void 0 : error.code) === "ERR_JWKS_MULTIPLE_MATCHING_KEYS") { + if ( + jwtInfo.version === 2 && + (error === null || error === void 0 ? void 0 : error.code) === "ERR_JWKS_MULTIPLE_MATCHING_KEYS" + ) { processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.MULTI_JWKS_VALIDATION); try { // We are trying to validate the token with each key. // Since the kid is missing from v2 tokens, this basically means we try all keys present in the cache. - for (var error_2 = __asyncValues(error), error_2_1; error_2_1 = await error_2.next(), !error_2_1.done;) { + for ( + var error_2 = __asyncValues(error), error_2_1; + (error_2_1 = await error_2.next()), !error_2_1.done; + + ) { const publicKey = error_2_1.value; try { payload = (await jose.jwtVerify(jwtInfo.rawTokenString, publicKey)).payload; break; - } - catch (innerError) { - if ((innerError === null || innerError === void 0 ? void 0 : innerError.code) === "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") { + } catch (innerError) { + if ( + (innerError === null || innerError === void 0 ? void 0 : innerError.code) === + "ERR_JWS_SIGNATURE_VERIFICATION_FAILED" + ) { continue; } throw innerError; } } - } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { + } catch (e_1_1) { + e_1 = { error: e_1_1 }; + } finally { try { if (error_2_1 && !error_2_1.done && (_a = error_2.return)) await _a.call(error_2); + } finally { + if (e_1) throw e_1.error; } - finally { if (e_1) throw e_1.error; } } if (payload === undefined) { throw new jose.errors.JWSSignatureVerificationFailed(); } - } - else { + } else { throw error; } } @@ -99,14 +151,16 @@ async function getInfoFromAccessToken(jwtInfo, jwks, doAntiCsrfCheck) { validateAccessTokenStructure(payload, jwtInfo.version); // We can mark these as defined (the ! after the calls), since validateAccessTokenPayload checks this let userId = jwtInfo.version === 2 ? sanitizeStringInput(payload.userId) : sanitizeStringInput(payload.sub); - let expiryTime = jwtInfo.version === 2 ? sanitizeNumberInput(payload.expiryTime) : sanitizeNumberInput(payload.exp) * 1000; - let timeCreated = jwtInfo.version === 2 - ? sanitizeNumberInput(payload.timeCreated) - : sanitizeNumberInput(payload.iat) * 1000; + let expiryTime = + jwtInfo.version === 2 ? sanitizeNumberInput(payload.expiryTime) : sanitizeNumberInput(payload.exp) * 1000; + let timeCreated = + jwtInfo.version === 2 ? sanitizeNumberInput(payload.timeCreated) : sanitizeNumberInput(payload.iat) * 1000; let userData = jwtInfo.version === 2 ? payload.userData : payload; let sessionHandle = sanitizeStringInput(payload.sessionHandle); // we use ?? below cause recipeUserId may be undefined for JWTs that are of an older version. - let recipeUserId = new recipeUserId_1.default((_b = sanitizeStringInput(payload.rsub)) !== null && _b !== void 0 ? _b : userId); + let recipeUserId = new recipeUserId_1.default( + (_b = sanitizeStringInput(payload.rsub)) !== null && _b !== void 0 ? _b : userId + ); let refreshTokenHash1 = sanitizeStringInput(payload.refreshTokenHash1); let parentRefreshTokenHash1 = sanitizeStringInput(payload.parentRefreshTokenHash1); let antiCsrfToken = sanitizeStringInput(payload.antiCsrfToken); @@ -132,10 +186,11 @@ async function getInfoFromAccessToken(jwtInfo, jwks, doAntiCsrfCheck) { recipeUserId, tenantId, }; - } - catch (err) { - logger_1.logDebugMessage("getInfoFromAccessToken: Returning TRY_REFRESH_TOKEN because access token validation failed - " + - err.message); + } catch (err) { + logger_1.logDebugMessage( + "getInfoFromAccessToken: Returning TRY_REFRESH_TOKEN because access token validation failed - " + + err.message + ); throw new error_1.default({ message: "Failed to verify access token", type: error_1.default.TRY_REFRESH_TOKEN, @@ -148,36 +203,40 @@ function validateAccessTokenStructure(payload, version) { throw Error("Wrong token type"); } if (version >= 5) { - if (typeof payload.sub !== "string" || + if ( + typeof payload.sub !== "string" || typeof payload.exp !== "number" || typeof payload.iat !== "number" || typeof payload.sessionHandle !== "string" || typeof payload.refreshTokenHash1 !== "string" || - typeof payload.rsub !== "string") { + typeof payload.rsub !== "string" + ) { logger_1.logDebugMessage("validateAccessTokenStructure: Access token is using version >= 4"); // The error message below will be logged by the error handler that translates this into a TRY_REFRESH_TOKEN_ERROR // it would come here if we change the structure of the JWT. throw Error("Access token does not contain all the information. Maybe the structure has changed?"); } - } - else if (version >= 4) { - if (typeof payload.sub !== "string" || + } else if (version >= 4) { + if ( + typeof payload.sub !== "string" || typeof payload.exp !== "number" || typeof payload.iat !== "number" || typeof payload.sessionHandle !== "string" || - typeof payload.refreshTokenHash1 !== "string") { + typeof payload.refreshTokenHash1 !== "string" + ) { logger_1.logDebugMessage("validateAccessTokenStructure: Access token is using version >= 4"); // The error message below will be logged by the error handler that translates this into a TRY_REFRESH_TOKEN_ERROR // it would come here if we change the structure of the JWT. throw Error("Access token does not contain all the information. Maybe the structure has changed?"); } - } - else if (version >= 3) { - if (typeof payload.sub !== "string" || + } else if (version >= 3) { + if ( + typeof payload.sub !== "string" || typeof payload.exp !== "number" || typeof payload.iat !== "number" || typeof payload.sessionHandle !== "string" || - typeof payload.refreshTokenHash1 !== "string") { + typeof payload.refreshTokenHash1 !== "string" + ) { logger_1.logDebugMessage("validateAccessTokenStructure: Access token is using version >= 3"); // The error message below will be logged by the error handler that translates this into a TRY_REFRESH_TOKEN_ERROR // it would come here if we change the structure of the JWT. @@ -188,13 +247,14 @@ function validateAccessTokenStructure(payload, version) { throw Error("Access token does not contain all the information. Maybe the structure has changed?"); } } - } - else if (typeof payload.sessionHandle !== "string" || + } else if ( + typeof payload.sessionHandle !== "string" || typeof payload.userId !== "string" || typeof payload.refreshTokenHash1 !== "string" || payload.userData === undefined || typeof payload.expiryTime !== "number" || - typeof payload.timeCreated !== "number") { + typeof payload.timeCreated !== "number" + ) { logger_1.logDebugMessage("validateAccessTokenStructure: Access token is using version < 3"); // The error message below will be logged by the error handler that translates this into a TRY_REFRESH_TOKEN_ERROR // it would come here if we change the structure of the JWT. @@ -212,8 +272,7 @@ function sanitizeStringInput(field) { try { let result = field.trim(); return result; - } - catch (err) { } + } catch (err) {} return undefined; } function sanitizeNumberInput(field) { diff --git a/lib/build/recipe/session/api/implementation.js b/lib/build/recipe/session/api/implementation.js index 8410f9ac9..d079f77e9 100644 --- a/lib/build/recipe/session/api/implementation.js +++ b/lib/build/recipe/session/api/implementation.js @@ -1,14 +1,16 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); const sessionRequestFunctions_1 = require("../sessionRequestFunctions"); function getAPIInterface() { return { - refreshPOST: async function ({ options, userContext, }) { + refreshPOST: async function ({ options, userContext }) { return sessionRequestFunctions_1.refreshSessionInRequest({ req: options.req, res: options.res, @@ -17,7 +19,7 @@ function getAPIInterface() { recipeInterfaceImpl: options.recipeImplementation, }); }, - verifySession: async function ({ verifySessionOptions, options, userContext, }) { + verifySession: async function ({ verifySessionOptions, options, userContext }) { let method = utils_1.normaliseHttpMethod(options.req.getMethod()); if (method === "options" || method === "trace") { return undefined; @@ -32,8 +34,7 @@ function getAPIInterface() { config: options.config, recipeInterfaceImpl: options.recipeImplementation, }); - } - else { + } else { return sessionRequestFunctions_1.getSessionFromRequest({ req: options.req, res: options.res, @@ -44,7 +45,7 @@ function getAPIInterface() { }); } }, - signOutPOST: async function ({ session, userContext, }) { + signOutPOST: async function ({ session, userContext }) { await session.revokeSession(userContext); return { status: "OK", diff --git a/lib/build/recipe/session/api/refresh.d.ts b/lib/build/recipe/session/api/refresh.d.ts index 5fb27d06f..63910c21a 100644 --- a/lib/build/recipe/session/api/refresh.d.ts +++ b/lib/build/recipe/session/api/refresh.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function handleRefreshAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function handleRefreshAPI( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/session/api/signout.d.ts b/lib/build/recipe/session/api/signout.d.ts index 128c06920..758ce9ed6 100644 --- a/lib/build/recipe/session/api/signout.d.ts +++ b/lib/build/recipe/session/api/signout.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function signOutAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function signOutAPI( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/session/claimBaseClasses/booleanClaim.js b/lib/build/recipe/session/claimBaseClasses/booleanClaim.js index f3d21b4de..37e233d3f 100644 --- a/lib/build/recipe/session/claimBaseClasses/booleanClaim.js +++ b/lib/build/recipe/session/claimBaseClasses/booleanClaim.js @@ -5,7 +5,10 @@ const primitiveClaim_1 = require("./primitiveClaim"); class BooleanClaim extends primitiveClaim_1.PrimitiveClaim { constructor(conf) { super(conf); - this.validators = Object.assign(Object.assign({}, this.validators), { isTrue: (maxAge, id) => this.validators.hasValue(true, maxAge, id), isFalse: (maxAge, id) => this.validators.hasValue(false, maxAge, id) }); + this.validators = Object.assign(Object.assign({}, this.validators), { + isTrue: (maxAge, id) => this.validators.hasValue(true, maxAge, id), + isFalse: (maxAge, id) => this.validators.hasValue(false, maxAge, id), + }); } } exports.BooleanClaim = BooleanClaim; diff --git a/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.d.ts b/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.d.ts index 075688786..83db6df32 100644 --- a/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.d.ts +++ b/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.d.ts @@ -3,13 +3,15 @@ import RecipeUserId from "../../../recipeUserId"; import { JSONObject, JSONPrimitive, UserContext } from "../../../types"; import { SessionClaim, SessionClaimValidator } from "../types"; export declare class PrimitiveArrayClaim extends SessionClaim { - readonly fetchValue: (userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext) => Promise | T[] | undefined; + readonly fetchValue: ( + userId: string, + recipeUserId: RecipeUserId, + tenantId: string, + currentPayload: JSONObject | undefined, + userContext: UserContext + ) => Promise | T[] | undefined; readonly defaultMaxAgeInSeconds: number | undefined; - constructor(config: { - key: string; - fetchValue: SessionClaim["fetchValue"]; - defaultMaxAgeInSeconds?: number; - }); + constructor(config: { key: string; fetchValue: SessionClaim["fetchValue"]; defaultMaxAgeInSeconds?: number }); addToPayload_internal(payload: any, value: T[], _userContext: UserContext): any; removeFromPayloadByMerge_internal(payload: any, _userContext: UserContext): any; removeFromPayload(payload: any, _userContext: UserContext): any; diff --git a/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.js b/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.js index 67004373b..d057552b5 100644 --- a/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.js +++ b/lib/build/recipe/session/claimBaseClasses/primitiveArrayClaim.js @@ -10,7 +10,8 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => + this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -18,7 +19,11 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { if (claimVal === undefined) { return { isValid: false, - reason: { message: "value does not exist", expectedToInclude: val, actualValue: claimVal }, + reason: { + message: "value does not exist", + expectedToInclude: val, + actualValue: claimVal, + }, }; } const ageInSeconds = (Date.now() - this.getLastRefetchTime(payload, ctx)) / 1000; @@ -46,7 +51,8 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => + this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -86,7 +92,8 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => + this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -94,7 +101,11 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { if (claimVal === undefined) { return { isValid: false, - reason: { message: "value does not exist", expectedToInclude: val, actualValue: claimVal }, + reason: { + message: "value does not exist", + expectedToInclude: val, + actualValue: claimVal, + }, }; } const ageInSeconds = (Date.now() - this.getLastRefetchTime(payload, ctx)) / 1000; @@ -113,9 +124,9 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return isValid ? { isValid } : { - isValid, - reason: { message: "wrong value", expectedToInclude: val, actualValue: claimVal }, - }; + isValid, + reason: { message: "wrong value", expectedToInclude: val, actualValue: claimVal }, + }; }, }; }, @@ -123,7 +134,8 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => + this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -154,13 +166,13 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return isValid ? { isValid: isValid } : { - isValid, - reason: { - message: "wrong value", - expectedToIncludeAtLeastOneOf: val, - actualValue: claimVal, - }, - }; + isValid, + reason: { + message: "wrong value", + expectedToIncludeAtLeastOneOf: val, + actualValue: claimVal, + }, + }; }, }; }, @@ -168,7 +180,8 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => + this.getValueFromPayload(payload, ctx) === undefined || // We know payload[this.id] is defined since the value is not undefined in this branch (maxAgeInSeconds !== undefined && payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -199,9 +212,9 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { return isValid ? { isValid: isValid } : { - isValid, - reason: { message: "wrong value", expectedToNotInclude: val, actualValue: claimVal }, - }; + isValid, + reason: { message: "wrong value", expectedToNotInclude: val, actualValue: claimVal }, + }; }, }; }, @@ -210,10 +223,12 @@ class PrimitiveArrayClaim extends types_1.SessionClaim { this.defaultMaxAgeInSeconds = config.defaultMaxAgeInSeconds; } addToPayload_internal(payload, value, _userContext) { - return Object.assign(Object.assign({}, payload), { [this.key]: { + return Object.assign(Object.assign({}, payload), { + [this.key]: { v: value, t: Date.now(), - } }); + }, + }); } removeFromPayloadByMerge_internal(payload, _userContext) { const res = Object.assign(Object.assign({}, payload), { [this.key]: null }); diff --git a/lib/build/recipe/session/claimBaseClasses/primitiveClaim.d.ts b/lib/build/recipe/session/claimBaseClasses/primitiveClaim.d.ts index 327ac6033..62c5bfe32 100644 --- a/lib/build/recipe/session/claimBaseClasses/primitiveClaim.d.ts +++ b/lib/build/recipe/session/claimBaseClasses/primitiveClaim.d.ts @@ -3,13 +3,15 @@ import RecipeUserId from "../../../recipeUserId"; import { JSONObject, JSONPrimitive, UserContext } from "../../../types"; import { SessionClaim, SessionClaimValidator } from "../types"; export declare class PrimitiveClaim extends SessionClaim { - readonly fetchValue: (userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext) => Promise | T | undefined; + readonly fetchValue: ( + userId: string, + recipeUserId: RecipeUserId, + tenantId: string, + currentPayload: JSONObject | undefined, + userContext: UserContext + ) => Promise | T | undefined; readonly defaultMaxAgeInSeconds: number | undefined; - constructor(config: { - key: string; - fetchValue: SessionClaim["fetchValue"]; - defaultMaxAgeInSeconds?: number; - }); + constructor(config: { key: string; fetchValue: SessionClaim["fetchValue"]; defaultMaxAgeInSeconds?: number }); addToPayload_internal(payload: any, value: T, _userContext: UserContext): any; removeFromPayloadByMerge_internal(payload: any, _userContext: UserContext): any; removeFromPayload(payload: any, _userContext: UserContext): any; diff --git a/lib/build/recipe/session/claimBaseClasses/primitiveClaim.js b/lib/build/recipe/session/claimBaseClasses/primitiveClaim.js index 35eee64fe..12407fabc 100644 --- a/lib/build/recipe/session/claimBaseClasses/primitiveClaim.js +++ b/lib/build/recipe/session/claimBaseClasses/primitiveClaim.js @@ -10,7 +10,8 @@ class PrimitiveClaim extends types_1.SessionClaim { return { claim: this, id: id !== null && id !== void 0 ? id : this.key, - shouldRefetch: (payload, ctx) => this.getValueFromPayload(payload, ctx) === undefined || + shouldRefetch: (payload, ctx) => + this.getValueFromPayload(payload, ctx) === undefined || (maxAgeInSeconds !== undefined && // We know payload[this.id] is defined since the value is not undefined in this branch payload[this.key].t < Date.now() - maxAgeInSeconds * 1000), validate: async (payload, ctx) => { @@ -47,10 +48,12 @@ class PrimitiveClaim extends types_1.SessionClaim { this.defaultMaxAgeInSeconds = config.defaultMaxAgeInSeconds; } addToPayload_internal(payload, value, _userContext) { - return Object.assign(Object.assign({}, payload), { [this.key]: { + return Object.assign(Object.assign({}, payload), { + [this.key]: { v: value, t: Date.now(), - } }); + }, + }); } removeFromPayloadByMerge_internal(payload, _userContext) { const res = Object.assign(Object.assign({}, payload), { [this.key]: null }); diff --git a/lib/build/recipe/session/claims.js b/lib/build/recipe/session/claims.js index 4a547fb2e..30fae1663 100644 --- a/lib/build/recipe/session/claims.js +++ b/lib/build/recipe/session/claims.js @@ -2,10 +2,30 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.BooleanClaim = exports.PrimitiveArrayClaim = exports.PrimitiveClaim = exports.SessionClaim = void 0; var types_1 = require("./types"); -Object.defineProperty(exports, "SessionClaim", { enumerable: true, get: function () { return types_1.SessionClaim; } }); +Object.defineProperty(exports, "SessionClaim", { + enumerable: true, + get: function () { + return types_1.SessionClaim; + }, +}); var primitiveClaim_1 = require("./claimBaseClasses/primitiveClaim"); -Object.defineProperty(exports, "PrimitiveClaim", { enumerable: true, get: function () { return primitiveClaim_1.PrimitiveClaim; } }); +Object.defineProperty(exports, "PrimitiveClaim", { + enumerable: true, + get: function () { + return primitiveClaim_1.PrimitiveClaim; + }, +}); var primitiveArrayClaim_1 = require("./claimBaseClasses/primitiveArrayClaim"); -Object.defineProperty(exports, "PrimitiveArrayClaim", { enumerable: true, get: function () { return primitiveArrayClaim_1.PrimitiveArrayClaim; } }); +Object.defineProperty(exports, "PrimitiveArrayClaim", { + enumerable: true, + get: function () { + return primitiveArrayClaim_1.PrimitiveArrayClaim; + }, +}); var booleanClaim_1 = require("./claimBaseClasses/booleanClaim"); -Object.defineProperty(exports, "BooleanClaim", { enumerable: true, get: function () { return booleanClaim_1.BooleanClaim; } }); +Object.defineProperty(exports, "BooleanClaim", { + enumerable: true, + get: function () { + return booleanClaim_1.BooleanClaim; + }, +}); diff --git a/lib/build/recipe/session/cookieAndHeaders.d.ts b/lib/build/recipe/session/cookieAndHeaders.d.ts index 7b08a4ec0..804655add 100644 --- a/lib/build/recipe/session/cookieAndHeaders.d.ts +++ b/lib/build/recipe/session/cookieAndHeaders.d.ts @@ -2,8 +2,19 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import { UserContext } from "../../types"; import { TokenTransferMethod, TokenType, TypeNormalisedInput } from "./types"; -export declare function clearSessionFromAllTokenTransferMethods(config: TypeNormalisedInput, res: BaseResponse, request: BaseRequest | undefined, userContext: UserContext): void; -export declare function clearSession(config: TypeNormalisedInput, res: BaseResponse, transferMethod: TokenTransferMethod, request: BaseRequest | undefined, userContext: UserContext): void; +export declare function clearSessionFromAllTokenTransferMethods( + config: TypeNormalisedInput, + res: BaseResponse, + request: BaseRequest | undefined, + userContext: UserContext +): void; +export declare function clearSession( + config: TypeNormalisedInput, + res: BaseResponse, + transferMethod: TokenTransferMethod, + request: BaseRequest | undefined, + userContext: UserContext +): void; export declare function getAntiCsrfTokenFromHeaders(req: BaseRequest): string | undefined; export declare function setAntiCsrfTokenInHeaders(res: BaseResponse, antiCsrfToken: string): void; export declare function buildFrontToken(userId: string, atExpiry: number, accessTokenPayload: any): string; @@ -11,8 +22,21 @@ export declare function setFrontTokenInHeaders(res: BaseResponse, frontToken: st export declare function getCORSAllowedHeaders(): string[]; export declare function getCookieNameFromTokenType(tokenType: TokenType): "sAccessToken" | "sRefreshToken"; export declare function getResponseHeaderNameForTokenType(tokenType: TokenType): "st-access-token" | "st-refresh-token"; -export declare function getToken(req: BaseRequest, tokenType: TokenType, transferMethod: TokenTransferMethod): string | undefined; -export declare function setToken(config: TypeNormalisedInput, res: BaseResponse, tokenType: TokenType, value: string, expires: number, transferMethod: TokenTransferMethod, req: BaseRequest | undefined, userContext: UserContext): void; +export declare function getToken( + req: BaseRequest, + tokenType: TokenType, + transferMethod: TokenTransferMethod +): string | undefined; +export declare function setToken( + config: TypeNormalisedInput, + res: BaseResponse, + tokenType: TokenType, + value: string, + expires: number, + transferMethod: TokenTransferMethod, + req: BaseRequest | undefined, + userContext: UserContext +): void; export declare function setHeader(res: BaseResponse, name: string, value: string): void; /** * @@ -25,7 +49,16 @@ export declare function setHeader(res: BaseResponse, name: string, value: string * @param expires * @param path */ -export declare function setCookie(config: TypeNormalisedInput, res: BaseResponse, name: string, value: string, expires: number, pathType: "refreshTokenPath" | "accessTokenPath", req: BaseRequest | undefined, userContext: UserContext): void; +export declare function setCookie( + config: TypeNormalisedInput, + res: BaseResponse, + name: string, + value: string, + expires: number, + pathType: "refreshTokenPath" | "accessTokenPath", + req: BaseRequest | undefined, + userContext: UserContext +): void; export declare function getAuthModeFromHeader(req: BaseRequest): string | undefined; /** * This function addresses an edge case where changing the cookieDomain config on the server can @@ -39,7 +72,12 @@ export declare function getAuthModeFromHeader(req: BaseRequest): string | undefi * * This function checks for multiple cookies with the same name and clears the cookies for the older domain */ -export declare function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContext, }: { +export declare function clearSessionCookiesFromOlderCookieDomain({ + req, + res, + config, + userContext, +}: { req: BaseRequest; res: BaseResponse; config: TypeNormalisedInput; diff --git a/lib/build/recipe/session/cookieAndHeaders.js b/lib/build/recipe/session/cookieAndHeaders.js index c029eca52..26c1d3475 100644 --- a/lib/build/recipe/session/cookieAndHeaders.js +++ b/lib/build/recipe/session/cookieAndHeaders.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.hasMultipleCookiesForTokenType = exports.clearSessionCookiesFromOlderCookieDomain = exports.getAuthModeFromHeader = exports.setCookie = exports.setHeader = exports.setToken = exports.getToken = exports.getResponseHeaderNameForTokenType = exports.getCookieNameFromTokenType = exports.getCORSAllowedHeaders = exports.setFrontTokenInHeaders = exports.buildFrontToken = exports.setAntiCsrfTokenInHeaders = exports.getAntiCsrfTokenFromHeaders = exports.clearSession = exports.clearSessionFromAllTokenTransferMethods = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -107,15 +109,13 @@ exports.getResponseHeaderNameForTokenType = getResponseHeaderNameForTokenType; function getToken(req, tokenType, transferMethod) { if (transferMethod === "cookie") { return req.getCookieValue(getCookieNameFromTokenType(tokenType)); - } - else if (transferMethod === "header") { + } else if (transferMethod === "header") { const value = req.getHeaderValue(authorizationHeaderKey); if (value === undefined || !value.startsWith("Bearer ")) { return undefined; } return value.replace(/^Bearer /, "").trim(); - } - else { + } else { throw new Error("Should never happen: Unknown transferMethod: " + transferMethod); } } @@ -123,9 +123,17 @@ exports.getToken = getToken; function setToken(config, res, tokenType, value, expires, transferMethod, req, userContext) { logger_1.logDebugMessage(`setToken: Setting ${tokenType} token as ${transferMethod}`); if (transferMethod === "cookie") { - setCookie(config, res, getCookieNameFromTokenType(tokenType), value, expires, tokenType === "refresh" ? "refreshTokenPath" : "accessTokenPath", req, userContext); - } - else if (transferMethod === "header") { + setCookie( + config, + res, + getCookieNameFromTokenType(tokenType), + value, + expires, + tokenType === "refresh" ? "refreshTokenPath" : "accessTokenPath", + req, + userContext + ); + } else if (transferMethod === "header") { setHeader(res, getResponseHeaderNameForTokenType(tokenType), value); } } @@ -156,8 +164,7 @@ function setCookie(config, res, name, value, expires, pathType, req, userContext let path = ""; if (pathType === "refreshTokenPath") { path = config.refreshTokenPath.getAsStringDangerous(); - } - else if (pathType === "accessTokenPath") { + } else if (pathType === "accessTokenPath") { path = config.accessTokenPath.getAsStringDangerous() === "" ? "/" : config.accessTokenPath.getAsStringDangerous(); } @@ -182,7 +189,7 @@ exports.getAuthModeFromHeader = getAuthModeFromHeader; * * This function checks for multiple cookies with the same name and clears the cookies for the older domain */ -function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContext, }) { +function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContext }) { const allowedTransferMethod = config.getTokenTransferMethod({ req, forCreateNewSession: false, @@ -201,16 +208,30 @@ function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContex // Using the wrong cookie can cause an infinite refresh loop. To avoid this, // we throw a 500 error asking the user to set 'olderCookieDomain'. if (config.olderCookieDomain === undefined) { - throw new Error(`The request contains multiple session cookies. This may happen if you've changed the 'cookieDomain' value in your configuration. To clear tokens from the previous domain, set 'olderCookieDomain' in your config.`); + throw new Error( + `The request contains multiple session cookies. This may happen if you've changed the 'cookieDomain' value in your configuration. To clear tokens from the previous domain, set 'olderCookieDomain' in your config.` + ); } - logger_1.logDebugMessage(`clearSessionCookiesFromOlderCookieDomain: Clearing duplicate ${token} cookie with domain ${config.olderCookieDomain}`); - setToken(Object.assign(Object.assign({}, config), { cookieDomain: config.olderCookieDomain }), res, token, "", 0, "cookie", req, userContext); + logger_1.logDebugMessage( + `clearSessionCookiesFromOlderCookieDomain: Clearing duplicate ${token} cookie with domain ${config.olderCookieDomain}` + ); + setToken( + Object.assign(Object.assign({}, config), { cookieDomain: config.olderCookieDomain }), + res, + token, + "", + 0, + "cookie", + req, + userContext + ); didClearCookies = true; } } if (didClearCookies) { throw new error_1.default({ - message: "The request contains multiple session cookies. We are clearing the cookie from olderCookieDomain. Session will be refreshed in the next refresh call.", + message: + "The request contains multiple session cookies. We are clearing the cookie from olderCookieDomain. Session will be refreshed in the next refresh call.", type: error_1.default.CLEAR_DUPLICATE_SESSION_COOKIES, }); } @@ -236,19 +257,17 @@ function parseCookieStringFromRequestHeaderAllowingDuplicates(cookieString) { .trim() .split("=") .map((part) => { - try { - return decodeURIComponent(part); - } - catch (e) { - // this is there in case the cookie value is not encoded. This can happe - // if the user has set their own cookie in a different format. - return part; - } - }); + try { + return decodeURIComponent(part); + } catch (e) { + // this is there in case the cookie value is not encoded. This can happe + // if the user has set their own cookie in a different format. + return part; + } + }); if (cookies.hasOwnProperty(name)) { cookies[name].push(value); - } - else { + } else { cookies[name] = [value]; } } diff --git a/lib/build/recipe/session/error.d.ts b/lib/build/recipe/session/error.d.ts index 8e472802c..fae7a4fd4 100644 --- a/lib/build/recipe/session/error.d.ts +++ b/lib/build/recipe/session/error.d.ts @@ -8,29 +8,36 @@ export default class SessionError extends STError { static TOKEN_THEFT_DETECTED: "TOKEN_THEFT_DETECTED"; static INVALID_CLAIMS: "INVALID_CLAIMS"; static CLEAR_DUPLICATE_SESSION_COOKIES: "CLEAR_DUPLICATE_SESSION_COOKIES"; - constructor(options: { - message: string; - type: "UNAUTHORISED"; - payload?: { - clearTokens: boolean; - }; - } | { - message: string; - type: "TRY_REFRESH_TOKEN"; - } | { - message: string; - type: "TOKEN_THEFT_DETECTED"; - payload: { - userId: string; - recipeUserId: RecipeUserId; - sessionHandle: string; - }; - } | { - message: string; - type: "INVALID_CLAIMS"; - payload: ClaimValidationError[]; - } | { - message: string; - type: "CLEAR_DUPLICATE_SESSION_COOKIES"; - }); + constructor( + options: + | { + message: string; + type: "UNAUTHORISED"; + payload?: { + clearTokens: boolean; + }; + } + | { + message: string; + type: "TRY_REFRESH_TOKEN"; + } + | { + message: string; + type: "TOKEN_THEFT_DETECTED"; + payload: { + userId: string; + recipeUserId: RecipeUserId; + sessionHandle: string; + }; + } + | { + message: string; + type: "INVALID_CLAIMS"; + payload: ClaimValidationError[]; + } + | { + message: string; + type: "CLEAR_DUPLICATE_SESSION_COOKIES"; + } + ); } diff --git a/lib/build/recipe/session/error.js b/lib/build/recipe/session/error.js index 803151c21..620ec994a 100644 --- a/lib/build/recipe/session/error.js +++ b/lib/build/recipe/session/error.js @@ -13,17 +13,24 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { constructor(options) { - super(options.type === "UNAUTHORISED" && options.payload === undefined - ? Object.assign(Object.assign({}, options), { payload: { - clearTokens: true, - } }) : Object.assign({}, options)); + super( + options.type === "UNAUTHORISED" && options.payload === undefined + ? Object.assign(Object.assign({}, options), { + payload: { + clearTokens: true, + }, + }) + : Object.assign({}, options) + ); this.fromRecipe = "session"; } } diff --git a/lib/build/recipe/session/framework/awsLambda.js b/lib/build/recipe/session/framework/awsLambda.js index ce8a217b3..1e6b919f7 100644 --- a/lib/build/recipe/session/framework/awsLambda.js +++ b/lib/build/recipe/session/framework/awsLambda.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; const framework_1 = require("../../../framework/awsLambda/framework"); @@ -19,8 +21,7 @@ function verifySession(handler, verifySessionOptions) { event.session = await sessionRecipe.verifySession(verifySessionOptions, request, response, userContext); let handlerResult = await handler(event, context, callback); return response.sendResponse(handlerResult); - } - catch (err) { + } catch (err) { await supertokens.errorHandler(err, request, response, userContext); if (response.responseSet) { return response.sendResponse({}); diff --git a/lib/build/recipe/session/framework/custom.d.ts b/lib/build/recipe/session/framework/custom.d.ts index c35f771cd..23c82a5c2 100644 --- a/lib/build/recipe/session/framework/custom.d.ts +++ b/lib/build/recipe/session/framework/custom.d.ts @@ -3,6 +3,8 @@ import type { VerifySessionOptions } from ".."; import { BaseRequest, BaseResponse } from "../../../framework"; import { NextFunction } from "../../../framework/custom/framework"; import { SessionContainerInterface } from "../types"; -export declare function verifySession(options?: VerifySessionOptions): (req: T, res: BaseResponse, next?: NextFunction | undefined) => Promise; +export declare function verifySession< + T extends BaseRequest & { + session?: SessionContainerInterface; + } +>(options?: VerifySessionOptions): (req: T, res: BaseResponse, next?: NextFunction | undefined) => Promise; diff --git a/lib/build/recipe/session/framework/custom.js b/lib/build/recipe/session/framework/custom.js index 4e39856a2..0615fe8b7 100644 --- a/lib/build/recipe/session/framework/custom.js +++ b/lib/build/recipe/session/framework/custom.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -31,14 +33,12 @@ function verifySession(options) { next(); } return undefined; - } - catch (err) { + } catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, req, res, userContext); return undefined; - } - catch (_a) { + } catch (_a) { if (next !== undefined) { next(err); } diff --git a/lib/build/recipe/session/framework/express.d.ts b/lib/build/recipe/session/framework/express.d.ts index 48db51d80..bcb0bf234 100644 --- a/lib/build/recipe/session/framework/express.d.ts +++ b/lib/build/recipe/session/framework/express.d.ts @@ -2,4 +2,6 @@ import type { VerifySessionOptions } from ".."; import type { SessionRequest } from "../../../framework/express/framework"; import type { NextFunction, Response } from "express"; -export declare function verifySession(options?: VerifySessionOptions): (req: SessionRequest, res: Response, next: NextFunction) => Promise; +export declare function verifySession( + options?: VerifySessionOptions +): (req: SessionRequest, res: Response, next: NextFunction) => Promise; diff --git a/lib/build/recipe/session/framework/express.js b/lib/build/recipe/session/framework/express.js index 885a7522e..a99ef67ce 100644 --- a/lib/build/recipe/session/framework/express.js +++ b/lib/build/recipe/session/framework/express.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -31,13 +33,11 @@ function verifySession(options) { const sessionRecipe = recipe_1.default.getInstanceOrThrowError(); req.session = await sessionRecipe.verifySession(options, request, response, userContext); next(); - } - catch (err) { + } catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); - } - catch (_a) { + } catch (_a) { next(err); } } diff --git a/lib/build/recipe/session/framework/fastify.d.ts b/lib/build/recipe/session/framework/fastify.d.ts index 1dc347fa7..74724d38d 100644 --- a/lib/build/recipe/session/framework/fastify.d.ts +++ b/lib/build/recipe/session/framework/fastify.d.ts @@ -2,4 +2,6 @@ import { VerifySessionOptions } from ".."; import { SessionRequest } from "../../../framework/fastify/framework"; import { FastifyReply, FastifyRequest as OriginalFastifyRequest } from "../../../framework/fastify/types"; -export declare function verifySession(options?: VerifySessionOptions): (req: SessionRequest, res: FastifyReply) => Promise; +export declare function verifySession( + options?: VerifySessionOptions +): (req: SessionRequest, res: FastifyReply) => Promise; diff --git a/lib/build/recipe/session/framework/fastify.js b/lib/build/recipe/session/framework/fastify.js index cfa4e4161..119d5dd24 100644 --- a/lib/build/recipe/session/framework/fastify.js +++ b/lib/build/recipe/session/framework/fastify.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -30,8 +32,7 @@ function verifySession(options) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { req.session = await sessionRecipe.verifySession(options, request, response, userContext); - } - catch (err) { + } catch (err) { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); throw err; diff --git a/lib/build/recipe/session/framework/hapi.d.ts b/lib/build/recipe/session/framework/hapi.d.ts index 8241dc4a2..97456f934 100644 --- a/lib/build/recipe/session/framework/hapi.d.ts +++ b/lib/build/recipe/session/framework/hapi.d.ts @@ -2,4 +2,6 @@ import { VerifySessionOptions } from ".."; import { ResponseToolkit } from "@hapi/hapi"; import { SessionRequest } from "../../../framework/hapi/framework"; -export declare function verifySession(options?: VerifySessionOptions): (req: SessionRequest, h: ResponseToolkit) => Promise; +export declare function verifySession( + options?: VerifySessionOptions +): (req: SessionRequest, h: ResponseToolkit) => Promise; diff --git a/lib/build/recipe/session/framework/hapi.js b/lib/build/recipe/session/framework/hapi.js index 9b1353c8f..be8581c64 100644 --- a/lib/build/recipe/session/framework/hapi.js +++ b/lib/build/recipe/session/framework/hapi.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -30,8 +32,7 @@ function verifySession(options) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { req.session = await sessionRecipe.verifySession(options, request, response, userContext); - } - catch (err) { + } catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); @@ -42,8 +43,7 @@ function verifySession(options) { }); return resObj.takeover(); } - } - catch (_a) { + } catch (_a) { // We catch and ignore since we want to re-throw the original error if handling wasn't successful throw err; } diff --git a/lib/build/recipe/session/framework/index.js b/lib/build/recipe/session/framework/index.js index a5541f367..8aa3b653e 100644 --- a/lib/build/recipe/session/framework/index.js +++ b/lib/build/recipe/session/framework/index.js @@ -1,23 +1,40 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.awsLambda = exports.koa = exports.loopback = exports.hapi = exports.fastify = exports.express = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. diff --git a/lib/build/recipe/session/framework/koa.d.ts b/lib/build/recipe/session/framework/koa.d.ts index 7a73cb667..9e23ae80b 100644 --- a/lib/build/recipe/session/framework/koa.d.ts +++ b/lib/build/recipe/session/framework/koa.d.ts @@ -2,4 +2,6 @@ import type { VerifySessionOptions } from ".."; import type { Next } from "koa"; import type { SessionContext } from "../../../framework/koa/framework"; -export declare function verifySession(options?: VerifySessionOptions): (ctx: SessionContext, next: Next) => Promise; +export declare function verifySession( + options?: VerifySessionOptions +): (ctx: SessionContext, next: Next) => Promise; diff --git a/lib/build/recipe/session/framework/koa.js b/lib/build/recipe/session/framework/koa.js index 179c641a1..12f1d474a 100644 --- a/lib/build/recipe/session/framework/koa.js +++ b/lib/build/recipe/session/framework/koa.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -30,14 +32,12 @@ function verifySession(options) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { ctx.session = await sessionRecipe.verifySession(options, request, response, userContext); - } - catch (err) { + } catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); return; - } - catch (_a) { + } catch (_a) { // We catch and ignore since we want to re-throw the original error if handling wasn't successful throw err; } diff --git a/lib/build/recipe/session/framework/loopback.js b/lib/build/recipe/session/framework/loopback.js index 07aee4b41..7b4feb2da 100644 --- a/lib/build/recipe/session/framework/loopback.js +++ b/lib/build/recipe/session/framework/loopback.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -31,14 +33,12 @@ function verifySession(options) { const userContext = utils_1.makeDefaultUserContextFromAPI(request); try { middlewareCtx.session = await sessionRecipe.verifySession(options, request, response, userContext); - } - catch (err) { + } catch (err) { try { const supertokens = supertokens_1.default.getInstanceOrThrowError(); await supertokens.errorHandler(err, request, response, userContext); return; - } - catch (_a) { + } catch (_a) { // We catch and ignore since we want to re-throw the original error if handling wasn't successful throw err; } diff --git a/lib/build/recipe/session/index.d.ts b/lib/build/recipe/session/index.d.ts index 257dba1ff..8115d4727 100644 --- a/lib/build/recipe/session/index.d.ts +++ b/lib/build/recipe/session/index.d.ts @@ -1,28 +1,79 @@ // @ts-nocheck import SuperTokensError from "./error"; -import { VerifySessionOptions, SessionContainerInterface as SessionContainer, SessionInformation, APIInterface, APIOptions, SessionClaimValidator, SessionClaim, ClaimValidationError, RecipeInterface } from "./types"; +import { + VerifySessionOptions, + SessionContainerInterface as SessionContainer, + SessionInformation, + APIInterface, + APIOptions, + SessionClaimValidator, + SessionClaim, + ClaimValidationError, + RecipeInterface, +} from "./types"; import Recipe from "./recipe"; import { JSONObject, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; export default class SessionWrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static createNewSession(req: any, res: any, tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, userContext?: Record): Promise; - static createNewSessionWithoutRequestResponse(tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, disableAntiCsrf?: boolean, userContext?: Record): Promise; - static validateClaimsForSessionHandle(sessionHandle: string, overrideGlobalClaimValidators?: (globalClaimValidators: SessionClaimValidator[], sessionInfo: SessionInformation, userContext: UserContext) => Promise | SessionClaimValidator[], userContext?: Record): Promise<{ - status: "SESSION_DOES_NOT_EXIST_ERROR"; - } | { - status: "OK"; - invalidClaims: ClaimValidationError[]; - }>; + static createNewSession( + req: any, + res: any, + tenantId: string, + recipeUserId: RecipeUserId, + accessTokenPayload?: any, + sessionDataInDatabase?: any, + userContext?: Record + ): Promise; + static createNewSessionWithoutRequestResponse( + tenantId: string, + recipeUserId: RecipeUserId, + accessTokenPayload?: any, + sessionDataInDatabase?: any, + disableAntiCsrf?: boolean, + userContext?: Record + ): Promise; + static validateClaimsForSessionHandle( + sessionHandle: string, + overrideGlobalClaimValidators?: ( + globalClaimValidators: SessionClaimValidator[], + sessionInfo: SessionInformation, + userContext: UserContext + ) => Promise | SessionClaimValidator[], + userContext?: Record + ): Promise< + | { + status: "SESSION_DOES_NOT_EXIST_ERROR"; + } + | { + status: "OK"; + invalidClaims: ClaimValidationError[]; + } + >; static getSession(req: any, res: any): Promise; - static getSession(req: any, res: any, options?: VerifySessionOptions & { - sessionRequired?: true; - }, userContext?: Record): Promise; - static getSession(req: any, res: any, options?: VerifySessionOptions & { - sessionRequired: false; - }, userContext?: Record): Promise; - static getSession(req: any, res: any, options?: VerifySessionOptions, userContext?: Record): Promise; + static getSession( + req: any, + res: any, + options?: VerifySessionOptions & { + sessionRequired?: true; + }, + userContext?: Record + ): Promise; + static getSession( + req: any, + res: any, + options?: VerifySessionOptions & { + sessionRequired: false; + }, + userContext?: Record + ): Promise; + static getSession( + req: any, + res: any, + options?: VerifySessionOptions, + userContext?: Record + ): Promise; /** * Tries to validate an access token and build a Session object from it. * @@ -44,33 +95,86 @@ export default class SessionWrapper { * @param userContext User context */ static getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string): Promise; - static getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { - sessionRequired?: true; - }, userContext?: Record): Promise; - static getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { - sessionRequired: false; - }, userContext?: Record): Promise; - static getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions, userContext?: Record): Promise; - static getSessionInformation(sessionHandle: string, userContext?: Record): Promise; + static getSessionWithoutRequestResponse( + accessToken: string, + antiCsrfToken?: string, + options?: VerifySessionOptions & { + sessionRequired?: true; + }, + userContext?: Record + ): Promise; + static getSessionWithoutRequestResponse( + accessToken: string, + antiCsrfToken?: string, + options?: VerifySessionOptions & { + sessionRequired: false; + }, + userContext?: Record + ): Promise; + static getSessionWithoutRequestResponse( + accessToken: string, + antiCsrfToken?: string, + options?: VerifySessionOptions, + userContext?: Record + ): Promise; + static getSessionInformation( + sessionHandle: string, + userContext?: Record + ): Promise; static refreshSession(req: any, res: any, userContext?: Record): Promise; - static refreshSessionWithoutRequestResponse(refreshToken: string, disableAntiCsrf?: boolean, antiCsrfToken?: string, userContext?: Record): Promise; - static revokeAllSessionsForUser(userId: string, revokeSessionsForLinkedAccounts?: boolean, tenantId?: string, userContext?: Record): Promise; - static getAllSessionHandlesForUser(userId: string, fetchSessionsForAllLinkedAccounts?: boolean, tenantId?: string, userContext?: Record): Promise; + static refreshSessionWithoutRequestResponse( + refreshToken: string, + disableAntiCsrf?: boolean, + antiCsrfToken?: string, + userContext?: Record + ): Promise; + static revokeAllSessionsForUser( + userId: string, + revokeSessionsForLinkedAccounts?: boolean, + tenantId?: string, + userContext?: Record + ): Promise; + static getAllSessionHandlesForUser( + userId: string, + fetchSessionsForAllLinkedAccounts?: boolean, + tenantId?: string, + userContext?: Record + ): Promise; static revokeSession(sessionHandle: string, userContext?: Record): Promise; static revokeMultipleSessions(sessionHandles: string[], userContext?: Record): Promise; - static updateSessionDataInDatabase(sessionHandle: string, newSessionData: any, userContext?: Record): Promise; - static mergeIntoAccessTokenPayload(sessionHandle: string, accessTokenPayloadUpdate: JSONObject, userContext?: Record): Promise; - static createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record): Promise<{ - status: "OK"; - jwt: string; - } | { - status: "UNSUPPORTED_ALGORITHM_ERROR"; - }>; - static getJWKS(userContext?: Record): Promise<{ + static updateSessionDataInDatabase( + sessionHandle: string, + newSessionData: any, + userContext?: Record + ): Promise; + static mergeIntoAccessTokenPayload( + sessionHandle: string, + accessTokenPayloadUpdate: JSONObject, + userContext?: Record + ): Promise; + static createJWT( + payload?: any, + validitySeconds?: number, + useStaticSigningKey?: boolean, + userContext?: Record + ): Promise< + | { + status: "OK"; + jwt: string; + } + | { + status: "UNSUPPORTED_ALGORITHM_ERROR"; + } + >; + static getJWKS( + userContext?: Record + ): Promise<{ keys: import("../jwt").JsonWebKey[]; validityInSeconds?: number | undefined; }>; - static getOpenIdDiscoveryConfiguration(userContext?: Record): Promise<{ + static getOpenIdDiscoveryConfiguration( + userContext?: Record + ): Promise<{ status: "OK"; issuer: string; jwks_uri: string; @@ -84,15 +188,35 @@ export default class SessionWrapper { id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; }>; - static fetchAndSetClaim(sessionHandle: string, claim: SessionClaim, userContext?: Record): Promise; - static setClaimValue(sessionHandle: string, claim: SessionClaim, value: T, userContext?: Record): Promise; - static getClaimValue(sessionHandle: string, claim: SessionClaim, userContext?: Record): Promise<{ - status: "SESSION_DOES_NOT_EXIST_ERROR"; - } | { - status: "OK"; - value: T | undefined; - }>; - static removeClaim(sessionHandle: string, claim: SessionClaim, userContext?: Record): Promise; + static fetchAndSetClaim( + sessionHandle: string, + claim: SessionClaim, + userContext?: Record + ): Promise; + static setClaimValue( + sessionHandle: string, + claim: SessionClaim, + value: T, + userContext?: Record + ): Promise; + static getClaimValue( + sessionHandle: string, + claim: SessionClaim, + userContext?: Record + ): Promise< + | { + status: "SESSION_DOES_NOT_EXIST_ERROR"; + } + | { + status: "OK"; + value: T | undefined; + } + >; + static removeClaim( + sessionHandle: string, + claim: SessionClaim, + userContext?: Record + ): Promise; } export declare let init: typeof Recipe.init; export declare let createNewSession: typeof SessionWrapper.createNewSession; @@ -117,4 +241,12 @@ export declare let Error: typeof SuperTokensError; export declare let createJWT: typeof SessionWrapper.createJWT; export declare let getJWKS: typeof SessionWrapper.getJWKS; export declare let getOpenIdDiscoveryConfiguration: typeof SessionWrapper.getOpenIdDiscoveryConfiguration; -export type { VerifySessionOptions, RecipeInterface, SessionContainer, APIInterface, APIOptions, SessionInformation, SessionClaimValidator, }; +export type { + VerifySessionOptions, + RecipeInterface, + SessionContainer, + APIInterface, + APIOptions, + SessionInformation, + SessionClaimValidator, +}; diff --git a/lib/build/recipe/session/index.js b/lib/build/recipe/session/index.js index 6aa1bb705..ad7855e98 100644 --- a/lib/build/recipe/session/index.js +++ b/lib/build/recipe/session/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getOpenIdDiscoveryConfiguration = exports.getJWKS = exports.createJWT = exports.Error = exports.validateClaimsForSessionHandle = exports.removeClaim = exports.getClaimValue = exports.setClaimValue = exports.fetchAndSetClaim = exports.mergeIntoAccessTokenPayload = exports.updateSessionDataInDatabase = exports.revokeMultipleSessions = exports.revokeSession = exports.getAllSessionHandlesForUser = exports.revokeAllSessionsForUser = exports.refreshSessionWithoutRequestResponse = exports.refreshSession = exports.getSessionInformation = exports.getSessionWithoutRequestResponse = exports.getSession = exports.createNewSessionWithoutRequestResponse = exports.createNewSession = exports.init = void 0; const error_1 = __importDefault(require("./error")); @@ -29,7 +31,15 @@ const constants_1 = require("../multitenancy/constants"); const constants_2 = require("./constants"); const utils_2 = require("../../utils"); class SessionWrapper { - static async createNewSession(req, res, tenantId, recipeUserId, accessTokenPayload = {}, sessionDataInDatabase = {}, userContext) { + static async createNewSession( + req, + res, + tenantId, + recipeUserId, + accessTokenPayload = {}, + sessionDataInDatabase = {}, + userContext + ) { const recipeInstance = recipe_1.default.getInstanceOrThrowError(); const config = recipeInstance.config; const appInfo = recipeInstance.getAppInfo(); @@ -52,7 +62,14 @@ class SessionWrapper { tenantId, }); } - static async createNewSessionWithoutRequestResponse(tenantId, recipeUserId, accessTokenPayload = {}, sessionDataInDatabase = {}, disableAntiCsrf = false, userContext) { + static async createNewSessionWithoutRequestResponse( + tenantId, + recipeUserId, + accessTokenPayload = {}, + sessionDataInDatabase = {}, + disableAntiCsrf = false, + userContext + ) { const ctx = utils_2.getUserContext(userContext); const recipeInstance = recipe_1.default.getInstanceOrThrowError(); const claimsAddedByOtherRecipes = recipeInstance.getClaimsAddedByOtherRecipes(); @@ -92,7 +109,9 @@ class SessionWrapper { status: "SESSION_DOES_NOT_EXIST_ERROR", }; } - const claimValidatorsAddedByOtherRecipes = recipe_1.default.getInstanceOrThrowError().getClaimValidatorsAddedByOtherRecipes(); + const claimValidatorsAddedByOtherRecipes = recipe_1.default + .getInstanceOrThrowError() + .getClaimValidatorsAddedByOtherRecipes(); const globalClaimValidators = await recipeImpl.getGlobalClaimValidators({ userId: sessionInfo.userId, recipeUserId: sessionInfo.recipeUserId, @@ -100,9 +119,10 @@ class SessionWrapper { claimValidatorsAddedByOtherRecipes, userContext: ctx, }); - const claimValidators = overrideGlobalClaimValidators !== undefined - ? await overrideGlobalClaimValidators(globalClaimValidators, sessionInfo, ctx) - : globalClaimValidators; + const claimValidators = + overrideGlobalClaimValidators !== undefined + ? await overrideGlobalClaimValidators(globalClaimValidators, sessionInfo, ctx) + : globalClaimValidators; let claimValidationResponse = await recipeImpl.validateClaims({ userId: sessionInfo.userId, recipeUserId: sessionInfo.recipeUserId, @@ -111,11 +131,13 @@ class SessionWrapper { userContext: ctx, }); if (claimValidationResponse.accessTokenPayloadUpdate !== undefined) { - if (!(await recipeImpl.mergeIntoAccessTokenPayload({ - sessionHandle, - accessTokenPayloadUpdate: claimValidationResponse.accessTokenPayloadUpdate, - userContext: ctx, - }))) { + if ( + !(await recipeImpl.mergeIntoAccessTokenPayload({ + sessionHandle, + accessTokenPayloadUpdate: claimValidationResponse.accessTokenPayloadUpdate, + userContext: ctx, + })) + ) { return { status: "SESSION_DOES_NOT_EXIST_ERROR", }; @@ -149,7 +171,11 @@ class SessionWrapper { userContext: ctx, }); if (session !== undefined) { - const claimValidators = await utils_1.getRequiredClaimValidators(session, options === null || options === void 0 ? void 0 : options.overrideGlobalClaimValidators, ctx); + const claimValidators = await utils_1.getRequiredClaimValidators( + session, + options === null || options === void 0 ? void 0 : options.overrideGlobalClaimValidators, + ctx + ); await session.assertClaims(claimValidators, ctx); } return session; diff --git a/lib/build/recipe/session/jwt.js b/lib/build/recipe/session/jwt.js index fa9ecd03d..8d8fd813e 100644 --- a/lib/build/recipe/session/jwt.js +++ b/lib/build/recipe/session/jwt.js @@ -18,16 +18,20 @@ exports.parseJWTWithoutSignatureVerification = void 0; const logger_1 = require("../../logger"); const utils_1 = require("../../utils"); const HEADERS = new Set([ - utils_1.encodeBase64(JSON.stringify({ - alg: "RS256", - typ: "JWT", - version: "1", - })), - utils_1.encodeBase64(JSON.stringify({ - alg: "RS256", - typ: "JWT", - version: "2", - })), + utils_1.encodeBase64( + JSON.stringify({ + alg: "RS256", + typ: "JWT", + version: "1", + }) + ), + utils_1.encodeBase64( + JSON.stringify({ + alg: "RS256", + typ: "JWT", + version: "2", + }) + ), ]); function parseJWTWithoutSignatureVerification(jwt) { const splittedInput = jwt.split("."); @@ -49,9 +53,10 @@ function parseJWTWithoutSignatureVerification(jwt) { } version = Number.parseInt(parsedHeader.version); logger_1.logDebugMessage("parseJWTWithoutSignatureVerification: version from header: " + version); - } - else { - logger_1.logDebugMessage("parseJWTWithoutSignatureVerification: assuming latest version (3) because version header is missing"); + } else { + logger_1.logDebugMessage( + "parseJWTWithoutSignatureVerification: assuming latest version (3) because version header is missing" + ); version = latestVersion; } kid = parsedHeader.kid; diff --git a/lib/build/recipe/session/recipe.d.ts b/lib/build/recipe/session/recipe.d.ts index 10d3ea7e8..ecb163142 100644 --- a/lib/build/recipe/session/recipe.d.ts +++ b/lib/build/recipe/session/recipe.d.ts @@ -1,6 +1,14 @@ // @ts-nocheck import RecipeModule from "../../recipeModule"; -import { TypeInput, TypeNormalisedInput, RecipeInterface, APIInterface, VerifySessionOptions, SessionClaimValidator, SessionClaim } from "./types"; +import { + TypeInput, + TypeNormalisedInput, + RecipeInterface, + APIInterface, + VerifySessionOptions, + SessionClaimValidator, + SessionClaim, +} from "./types"; import STError from "./error"; import { NormalisedAppinfo, RecipeListFunction, APIHandled, HTTPMethod, UserContext } from "../../types"; import NormalisedURLPath from "../../normalisedURLPath"; @@ -23,10 +31,28 @@ export default class SessionRecipe extends RecipeModule { addClaimValidatorFromOtherRecipe: (builder: SessionClaimValidator) => void; getClaimValidatorsAddedByOtherRecipes: () => SessionClaimValidator[]; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, _tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; - handleError: (err: STError, request: BaseRequest, response: BaseResponse, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + _tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ) => Promise; + handleError: ( + err: STError, + request: BaseRequest, + response: BaseResponse, + userContext: UserContext + ) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; - verifySession: (options: VerifySessionOptions | undefined, request: BaseRequest, response: BaseResponse, userContext: UserContext) => Promise; + verifySession: ( + options: VerifySessionOptions | undefined, + request: BaseRequest, + response: BaseResponse, + userContext: UserContext + ) => Promise; getNormalisedOverwriteSessionDuringSignInUp: (req: any) => boolean; } diff --git a/lib/build/recipe/session/recipe.js b/lib/build/recipe/session/recipe.js index 716d00e35..fcec65ac9 100644 --- a/lib/build/recipe/session/recipe.js +++ b/lib/build/recipe/session/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -85,11 +87,9 @@ class SessionRecipe extends recipeModule_1.default { }; if (id === constants_1.REFRESH_API_PATH) { return await refresh_1.default(this.apiImpl, options, userContext); - } - else if (id === constants_1.SIGNOUT_API_PATH) { + } else if (id === constants_1.SIGNOUT_API_PATH) { return await signout_1.default(this.apiImpl, options, userContext); - } - else { + } else { return false; } }; @@ -97,40 +97,63 @@ class SessionRecipe extends recipeModule_1.default { if (err.fromRecipe === SessionRecipe.RECIPE_ID) { if (err.type === error_1.default.UNAUTHORISED) { logger_1.logDebugMessage("errorHandler: returning UNAUTHORISED"); - if (err.payload === undefined || + if ( + err.payload === undefined || err.payload.clearTokens === undefined || - err.payload.clearTokens === true) { + err.payload.clearTokens === true + ) { logger_1.logDebugMessage("errorHandler: Clearing tokens because of UNAUTHORISED response"); - cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods(this.config, response, request, userContext); + cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods( + this.config, + response, + request, + userContext + ); } return await this.config.errorHandlers.onUnauthorised(err.message, request, response, userContext); - } - else if (err.type === error_1.default.TRY_REFRESH_TOKEN) { + } else if (err.type === error_1.default.TRY_REFRESH_TOKEN) { logger_1.logDebugMessage("errorHandler: returning TRY_REFRESH_TOKEN"); - return await this.config.errorHandlers.onTryRefreshToken(err.message, request, response, userContext); - } - else if (err.type === error_1.default.TOKEN_THEFT_DETECTED) { + return await this.config.errorHandlers.onTryRefreshToken( + err.message, + request, + response, + userContext + ); + } else if (err.type === error_1.default.TOKEN_THEFT_DETECTED) { logger_1.logDebugMessage("errorHandler: returning TOKEN_THEFT_DETECTED"); logger_1.logDebugMessage("errorHandler: Clearing tokens because of TOKEN_THEFT_DETECTED response"); - cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods(this.config, response, request, userContext); - return await this.config.errorHandlers.onTokenTheftDetected(err.payload.sessionHandle, err.payload.userId, err.payload.recipeUserId, request, response, userContext); - } - else if (err.type === error_1.default.INVALID_CLAIMS) { + cookieAndHeaders_1.clearSessionFromAllTokenTransferMethods( + this.config, + response, + request, + userContext + ); + return await this.config.errorHandlers.onTokenTheftDetected( + err.payload.sessionHandle, + err.payload.userId, + err.payload.recipeUserId, + request, + response, + userContext + ); + } else if (err.type === error_1.default.INVALID_CLAIMS) { return await this.config.errorHandlers.onInvalidClaim(err.payload, request, response, userContext); - } - else if (err.type === error_1.default.CLEAR_DUPLICATE_SESSION_COOKIES) { + } else if (err.type === error_1.default.CLEAR_DUPLICATE_SESSION_COOKIES) { logger_1.logDebugMessage("errorHandler: returning CLEAR_DUPLICATE_SESSION_COOKIES"); // This error occurs in the `refreshPOST` API when multiple session // cookies are found in the request and the user has set `olderCookieDomain`. // We remove session cookies from the olderCookieDomain. The response must return `200 OK` // to avoid logging out the user, allowing the session to continue with the valid cookie. - return await this.config.errorHandlers.onClearDuplicateSessionCookies(err.message, request, response, userContext); - } - else { + return await this.config.errorHandlers.onClearDuplicateSessionCookies( + err.message, + request, + response, + userContext + ); + } else { throw err; } - } - else { + } else { throw err; } }; @@ -158,23 +181,35 @@ class SessionRecipe extends recipeModule_1.default { this.getNormalisedOverwriteSessionDuringSignInUp = (req) => { var _a; const supportsFDI31 = utils_2.hasGreaterThanEqualToFDI(req, "3.1"); - const res = (_a = this.config.overwriteSessionDuringSignInUp) !== null && _a !== void 0 ? _a : supportsFDI31; + const res = + (_a = this.config.overwriteSessionDuringSignInUp) !== null && _a !== void 0 ? _a : supportsFDI31; logger_1.logDebugMessage("getNormalisedOverwriteSessionDuringSignInUp returning: " + res); return res; }; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); - const antiCsrfToLog = typeof this.config.antiCsrfFunctionOrString === "string" - ? this.config.antiCsrfFunctionOrString - : "function"; + const antiCsrfToLog = + typeof this.config.antiCsrfFunctionOrString === "string" + ? this.config.antiCsrfFunctionOrString + : "function"; logger_1.logDebugMessage("session init: antiCsrf: " + antiCsrfToLog); logger_1.logDebugMessage("session init: cookieDomain: " + this.config.cookieDomain); - const sameSiteToPrint = config !== undefined && config.cookieSameSite !== undefined ? config.cookieSameSite : "default function"; + const sameSiteToPrint = + config !== undefined && config.cookieSameSite !== undefined ? config.cookieSameSite : "default function"; logger_1.logDebugMessage("session init: cookieSameSite: " + sameSiteToPrint); logger_1.logDebugMessage("session init: cookieSecure: " + this.config.cookieSecure); - logger_1.logDebugMessage("session init: refreshTokenPath: " + this.config.refreshTokenPath.getAsStringDangerous()); + logger_1.logDebugMessage( + "session init: refreshTokenPath: " + this.config.refreshTokenPath.getAsStringDangerous() + ); logger_1.logDebugMessage("session init: sessionExpiredStatusCode: " + this.config.sessionExpiredStatusCode); this.isInServerlessEnv = isInServerlessEnv; - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, this.getAppInfo(), () => this.recipeInterfaceImpl)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default( + querier_1.Querier.getNewInstanceOrThrowError(recipeId), + this.config, + this.getAppInfo(), + () => this.recipeInterfaceImpl + ) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); { let builder = new supertokens_js_override_1.default(implementation_1.default()); @@ -185,15 +220,16 @@ class SessionRecipe extends recipeModule_1.default { if (SessionRecipe.instance !== undefined) { return SessionRecipe.instance; } - throw new Error("Initialisation not done. Did you forget to call the SuperTokens.init or Session.init function?"); + throw new Error( + "Initialisation not done. Did you forget to call the SuperTokens.init or Session.init function?" + ); } static init(config) { return (appInfo, isInServerlessEnv) => { if (SessionRecipe.instance === undefined) { SessionRecipe.instance = new SessionRecipe(SessionRecipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return SessionRecipe.instance; - } - else { + } else { throw new Error("Session recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/session/recipeImplementation.d.ts b/lib/build/recipe/session/recipeImplementation.d.ts index 3ca3c3147..df95edba8 100644 --- a/lib/build/recipe/session/recipeImplementation.d.ts +++ b/lib/build/recipe/session/recipeImplementation.d.ts @@ -8,4 +8,9 @@ export declare type Helpers = { appInfo: NormalisedAppinfo; getRecipeImpl: () => RecipeInterface; }; -export default function getRecipeInterface(querier: Querier, config: TypeNormalisedInput, appInfo: NormalisedAppinfo, getRecipeImplAfterOverrides: () => RecipeInterface): RecipeInterface; +export default function getRecipeInterface( + querier: Querier, + config: TypeNormalisedInput, + appInfo: NormalisedAppinfo, + getRecipeImplAfterOverrides: () => RecipeInterface +): RecipeInterface; diff --git a/lib/build/recipe/session/recipeImplementation.js b/lib/build/recipe/session/recipeImplementation.js index 22d8144c8..7dbba30ec 100644 --- a/lib/build/recipe/session/recipeImplementation.js +++ b/lib/build/recipe/session/recipeImplementation.js @@ -1,26 +1,45 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const SessionFunctions = __importStar(require("./sessionFunctions")); const cookieAndHeaders_1 = require("./cookieAndHeaders"); @@ -37,33 +56,68 @@ const constants_2 = require("./constants"); const utils_2 = require("../../utils"); function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverrides) { let obj = { - createNewSession: async function ({ recipeUserId, accessTokenPayload = {}, sessionDataInDatabase = {}, disableAntiCsrf, tenantId, userContext, }) { + createNewSession: async function ({ + recipeUserId, + accessTokenPayload = {}, + sessionDataInDatabase = {}, + disableAntiCsrf, + tenantId, + userContext, + }) { logger_1.logDebugMessage("createNewSession: Started"); - let response = await SessionFunctions.createNewSession(helpers, tenantId, recipeUserId, disableAntiCsrf === true, accessTokenPayload, sessionDataInDatabase, userContext); + let response = await SessionFunctions.createNewSession( + helpers, + tenantId, + recipeUserId, + disableAntiCsrf === true, + accessTokenPayload, + sessionDataInDatabase, + userContext + ); logger_1.logDebugMessage("createNewSession: Finished"); const payload = jwt_1.parseJWTWithoutSignatureVerification(response.accessToken.token).payload; - return new sessionClass_1.default(helpers, response.accessToken.token, cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken.expiry, payload), response.refreshToken, response.antiCsrfToken, response.session.handle, response.session.userId, response.session.recipeUserId, payload, undefined, true, tenantId); + return new sessionClass_1.default( + helpers, + response.accessToken.token, + cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken.expiry, payload), + response.refreshToken, + response.antiCsrfToken, + response.session.handle, + response.session.userId, + response.session.recipeUserId, + payload, + undefined, + true, + tenantId + ); }, getGlobalClaimValidators: async function (input) { return input.claimValidatorsAddedByOtherRecipes; }, - getSession: async function ({ accessToken: accessTokenString, antiCsrfToken, options, userContext, }) { - if ((options === null || options === void 0 ? void 0 : options.antiCsrfCheck) !== false && + getSession: async function ({ accessToken: accessTokenString, antiCsrfToken, options, userContext }) { + if ( + (options === null || options === void 0 ? void 0 : options.antiCsrfCheck) !== false && typeof config.antiCsrfFunctionOrString === "string" && - config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER") { - throw new Error("Since the anti-csrf mode is VIA_CUSTOM_HEADER getSession can't check the CSRF token. Please either use VIA_TOKEN or set antiCsrfCheck to false"); + config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER" + ) { + throw new Error( + "Since the anti-csrf mode is VIA_CUSTOM_HEADER getSession can't check the CSRF token. Please either use VIA_TOKEN or set antiCsrfCheck to false" + ); } logger_1.logDebugMessage("getSession: Started"); if (accessTokenString === undefined) { if ((options === null || options === void 0 ? void 0 : options.sessionRequired) === false) { - logger_1.logDebugMessage("getSession: returning undefined because accessToken is undefined and sessionRequired is false"); + logger_1.logDebugMessage( + "getSession: returning undefined because accessToken is undefined and sessionRequired is false" + ); // there is no session that exists here, and the user wants session verification // to be optional. So we return undefined. return undefined; } logger_1.logDebugMessage("getSession: UNAUTHORISED because accessToken in request is undefined"); throw new error_1.default({ - message: "Session does not exist. Are you sending the session tokens in the request with the appropriate token transfer method?", + message: + "Session does not exist. Are you sending the session tokens in the request with the appropriate token transfer method?", type: error_1.default.UNAUTHORISED, payload: { // we do not clear the session here because of a @@ -76,28 +130,56 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride try { accessToken = jwt_1.parseJWTWithoutSignatureVerification(accessTokenString); accessToken_1.validateAccessTokenStructure(accessToken.payload, accessToken.version); - } - catch (error) { + } catch (error) { if ((options === null || options === void 0 ? void 0 : options.sessionRequired) === false) { - logger_1.logDebugMessage("getSession: Returning undefined because parsing failed and sessionRequired is false"); + logger_1.logDebugMessage( + "getSession: Returning undefined because parsing failed and sessionRequired is false" + ); return undefined; } - logger_1.logDebugMessage("getSession: UNAUTHORISED because the accessToken couldn't be parsed or had an invalid structure"); + logger_1.logDebugMessage( + "getSession: UNAUTHORISED because the accessToken couldn't be parsed or had an invalid structure" + ); throw new error_1.default({ message: "Token parsing failed", type: "UNAUTHORISED", payload: { clearTokens: false }, }); } - const response = await SessionFunctions.getSession(helpers, accessToken, antiCsrfToken, (options === null || options === void 0 ? void 0 : options.antiCsrfCheck) !== false, (options === null || options === void 0 ? void 0 : options.checkDatabase) === true, config, userContext); + const response = await SessionFunctions.getSession( + helpers, + accessToken, + antiCsrfToken, + (options === null || options === void 0 ? void 0 : options.antiCsrfCheck) !== false, + (options === null || options === void 0 ? void 0 : options.checkDatabase) === true, + config, + userContext + ); logger_1.logDebugMessage("getSession: Success!"); - const payload = accessToken.version >= 3 - ? response.accessToken !== undefined - ? jwt_1.parseJWTWithoutSignatureVerification(response.accessToken.token).payload - : accessToken.payload - : response.session.userDataInJWT; - const session = new sessionClass_1.default(helpers, response.accessToken !== undefined ? response.accessToken.token : accessTokenString, cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken !== undefined ? response.accessToken.expiry : response.session.expiryTime, payload), undefined, // refresh - antiCsrfToken, response.session.handle, response.session.userId, response.session.recipeUserId, payload, undefined, response.accessToken !== undefined, response.session.tenantId); + const payload = + accessToken.version >= 3 + ? response.accessToken !== undefined + ? jwt_1.parseJWTWithoutSignatureVerification(response.accessToken.token).payload + : accessToken.payload + : response.session.userDataInJWT; + const session = new sessionClass_1.default( + helpers, + response.accessToken !== undefined ? response.accessToken.token : accessTokenString, + cookieAndHeaders_1.buildFrontToken( + response.session.userId, + response.accessToken !== undefined ? response.accessToken.expiry : response.session.expiryTime, + payload + ), + undefined, // refresh + antiCsrfToken, + response.session.handle, + response.session.userId, + response.session.recipeUserId, + payload, + undefined, + response.accessToken !== undefined, + response.session.tenantId + ); return session; }, validateClaims: async function (input) { @@ -108,45 +190,90 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride logger_1.logDebugMessage("updateClaimsInPayloadIfNeeded checking shouldRefetch for " + validator.id); if ("claim" in validator && (await validator.shouldRefetch(accessTokenPayload, input.userContext))) { logger_1.logDebugMessage("updateClaimsInPayloadIfNeeded refetching " + validator.id); - const value = await validator.claim.fetchValue(input.userId, input.recipeUserId, accessTokenPayload.tId === undefined ? constants_1.DEFAULT_TENANT_ID : accessTokenPayload.tId, accessTokenPayload, input.userContext); - logger_1.logDebugMessage("updateClaimsInPayloadIfNeeded " + validator.id + " refetch result " + JSON.stringify(value)); + const value = await validator.claim.fetchValue( + input.userId, + input.recipeUserId, + accessTokenPayload.tId === undefined ? constants_1.DEFAULT_TENANT_ID : accessTokenPayload.tId, + accessTokenPayload, + input.userContext + ); + logger_1.logDebugMessage( + "updateClaimsInPayloadIfNeeded " + validator.id + " refetch result " + JSON.stringify(value) + ); if (value !== undefined) { - accessTokenPayload = validator.claim.addToPayload_internal(accessTokenPayload, value, input.userContext); + accessTokenPayload = validator.claim.addToPayload_internal( + accessTokenPayload, + value, + input.userContext + ); } } } if (JSON.stringify(accessTokenPayload) !== origSessionClaimPayloadJSON) { accessTokenPayloadUpdate = accessTokenPayload; } - const invalidClaims = await utils_1.validateClaimsInPayload(input.claimValidators, accessTokenPayload, input.userContext); + const invalidClaims = await utils_1.validateClaimsInPayload( + input.claimValidators, + accessTokenPayload, + input.userContext + ); return { invalidClaims, accessTokenPayloadUpdate, }; }, - getSessionInformation: async function ({ sessionHandle, userContext, }) { + getSessionInformation: async function ({ sessionHandle, userContext }) { return SessionFunctions.getSessionInformation(helpers, sessionHandle, userContext); }, - refreshSession: async function ({ refreshToken, antiCsrfToken, disableAntiCsrf, userContext, }) { - if (disableAntiCsrf !== true && + refreshSession: async function ({ refreshToken, antiCsrfToken, disableAntiCsrf, userContext }) { + if ( + disableAntiCsrf !== true && typeof config.antiCsrfFunctionOrString === "string" && - config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER") { - throw new Error("Since the anti-csrf mode is VIA_CUSTOM_HEADER getSession can't check the CSRF token. Please either use VIA_TOKEN or set antiCsrfCheck to false"); + config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER" + ) { + throw new Error( + "Since the anti-csrf mode is VIA_CUSTOM_HEADER getSession can't check the CSRF token. Please either use VIA_TOKEN or set antiCsrfCheck to false" + ); } logger_1.logDebugMessage("refreshSession: Started"); - const response = await SessionFunctions.refreshSession(helpers, refreshToken, antiCsrfToken, disableAntiCsrf, config.useDynamicAccessTokenSigningKey, userContext); + const response = await SessionFunctions.refreshSession( + helpers, + refreshToken, + antiCsrfToken, + disableAntiCsrf, + config.useDynamicAccessTokenSigningKey, + userContext + ); logger_1.logDebugMessage("refreshSession: Success!"); const payload = jwt_1.parseJWTWithoutSignatureVerification(response.accessToken.token).payload; - return new sessionClass_1.default(helpers, response.accessToken.token, cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken.expiry, payload), response.refreshToken, response.antiCsrfToken, response.session.handle, response.session.userId, response.session.recipeUserId, payload, undefined, true, payload.tId); + return new sessionClass_1.default( + helpers, + response.accessToken.token, + cookieAndHeaders_1.buildFrontToken(response.session.userId, response.accessToken.expiry, payload), + response.refreshToken, + response.antiCsrfToken, + response.session.handle, + response.session.userId, + response.session.recipeUserId, + payload, + undefined, + true, + payload.tId + ); }, regenerateAccessToken: async function (input) { - let newAccessTokenPayload = input.newAccessTokenPayload === null || input.newAccessTokenPayload === undefined - ? {} - : input.newAccessTokenPayload; - let response = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/session/regenerate"), { - accessToken: input.accessToken, - userDataInJWT: newAccessTokenPayload, - }, input.userContext); + let newAccessTokenPayload = + input.newAccessTokenPayload === null || input.newAccessTokenPayload === undefined + ? {} + : input.newAccessTokenPayload; + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/session/regenerate"), + { + accessToken: input.accessToken, + userDataInJWT: newAccessTokenPayload, + }, + input.userContext + ); if (response.status === "UNAUTHORISED") { return undefined; } @@ -162,22 +289,48 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride accessToken: response.accessToken, }; }, - revokeAllSessionsForUser: function ({ userId, tenantId, revokeAcrossAllTenants, revokeSessionsForLinkedAccounts, userContext, }) { - return SessionFunctions.revokeAllSessionsForUser(helpers, userId, revokeSessionsForLinkedAccounts, tenantId, revokeAcrossAllTenants, userContext); + revokeAllSessionsForUser: function ({ + userId, + tenantId, + revokeAcrossAllTenants, + revokeSessionsForLinkedAccounts, + userContext, + }) { + return SessionFunctions.revokeAllSessionsForUser( + helpers, + userId, + revokeSessionsForLinkedAccounts, + tenantId, + revokeAcrossAllTenants, + userContext + ); }, - getAllSessionHandlesForUser: function ({ userId, fetchSessionsForAllLinkedAccounts, tenantId, fetchAcrossAllTenants, userContext, }) { - return SessionFunctions.getAllSessionHandlesForUser(helpers, userId, fetchSessionsForAllLinkedAccounts, tenantId, fetchAcrossAllTenants, userContext); + getAllSessionHandlesForUser: function ({ + userId, + fetchSessionsForAllLinkedAccounts, + tenantId, + fetchAcrossAllTenants, + userContext, + }) { + return SessionFunctions.getAllSessionHandlesForUser( + helpers, + userId, + fetchSessionsForAllLinkedAccounts, + tenantId, + fetchAcrossAllTenants, + userContext + ); }, - revokeSession: function ({ sessionHandle, userContext, }) { + revokeSession: function ({ sessionHandle, userContext }) { return SessionFunctions.revokeSession(helpers, sessionHandle, userContext); }, - revokeMultipleSessions: function ({ sessionHandles, userContext, }) { + revokeMultipleSessions: function ({ sessionHandles, userContext }) { return SessionFunctions.revokeMultipleSessions(helpers, sessionHandles, userContext); }, - updateSessionDataInDatabase: function ({ sessionHandle, newSessionData, userContext, }) { + updateSessionDataInDatabase: function ({ sessionHandle, newSessionData, userContext }) { return SessionFunctions.updateSessionDataInDatabase(helpers, sessionHandle, newSessionData, userContext); }, - mergeIntoAccessTokenPayload: async function ({ sessionHandle, accessTokenPayloadUpdate, userContext, }) { + mergeIntoAccessTokenPayload: async function ({ sessionHandle, accessTokenPayloadUpdate, userContext }) { const sessionInfo = await this.getSessionInformation({ sessionHandle, userContext }); if (sessionInfo === undefined) { return false; @@ -192,7 +345,12 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride delete newAccessTokenPayload[key]; } } - return SessionFunctions.updateAccessTokenPayload(helpers, sessionHandle, newAccessTokenPayload, userContext); + return SessionFunctions.updateAccessTokenPayload( + helpers, + sessionHandle, + newAccessTokenPayload, + userContext + ); }, fetchAndSetClaim: async function (input) { const sessionInfo = await this.getSessionInformation({ @@ -202,7 +360,13 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride if (sessionInfo === undefined) { return false; } - const accessTokenPayloadUpdate = await input.claim.build(sessionInfo.userId, sessionInfo.recipeUserId, sessionInfo.tenantId, sessionInfo.customClaimsInAccessTokenPayload, input.userContext); + const accessTokenPayloadUpdate = await input.claim.build( + sessionInfo.userId, + sessionInfo.recipeUserId, + sessionInfo.tenantId, + sessionInfo.customClaimsInAccessTokenPayload, + input.userContext + ); return this.mergeIntoAccessTokenPayload({ sessionHandle: input.sessionHandle, accessTokenPayloadUpdate, diff --git a/lib/build/recipe/session/sessionClass.d.ts b/lib/build/recipe/session/sessionClass.d.ts index 2d586ade3..2a1ea04f2 100644 --- a/lib/build/recipe/session/sessionClass.d.ts +++ b/lib/build/recipe/session/sessionClass.d.ts @@ -15,7 +15,20 @@ export default class Session implements SessionContainerInterface { protected reqResInfo: ReqResInfo | undefined; protected accessTokenUpdated: boolean; protected tenantId: string; - constructor(helpers: Helpers, accessToken: string, frontToken: string, refreshToken: TokenInfo | undefined, antiCsrfToken: string | undefined, sessionHandle: string, userId: string, recipeUserId: RecipeUserId, userDataInAccessToken: any, reqResInfo: ReqResInfo | undefined, accessTokenUpdated: boolean, tenantId: string); + constructor( + helpers: Helpers, + accessToken: string, + frontToken: string, + refreshToken: TokenInfo | undefined, + antiCsrfToken: string | undefined, + sessionHandle: string, + userId: string, + recipeUserId: RecipeUserId, + userDataInAccessToken: any, + reqResInfo: ReqResInfo | undefined, + accessTokenUpdated: boolean, + tenantId: string + ); getRecipeUserId(_userContext?: Record): RecipeUserId; revokeSession(userContext?: Record): Promise; getSessionDataFromDatabase(userContext?: Record): Promise; diff --git a/lib/build/recipe/session/sessionClass.js b/lib/build/recipe/session/sessionClass.js index ae6ee0f06..771a39d9f 100644 --- a/lib/build/recipe/session/sessionClass.js +++ b/lib/build/recipe/session/sessionClass.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -25,7 +27,20 @@ const logger_1 = require("../../logger"); const constants_1 = require("./constants"); const utils_2 = require("../../utils"); class Session { - constructor(helpers, accessToken, frontToken, refreshToken, antiCsrfToken, sessionHandle, userId, recipeUserId, userDataInAccessToken, reqResInfo, accessTokenUpdated, tenantId) { + constructor( + helpers, + accessToken, + frontToken, + refreshToken, + antiCsrfToken, + sessionHandle, + userId, + recipeUserId, + userDataInAccessToken, + reqResInfo, + accessTokenUpdated, + tenantId + ) { this.helpers = helpers; this.accessToken = accessToken; this.frontToken = frontToken; @@ -43,9 +58,10 @@ class Session { return this.recipeUserId; } async revokeSession(userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); await this.helpers.getRecipeImpl().revokeSession({ sessionHandle: this.sessionHandle, userContext: ctx, @@ -57,19 +73,28 @@ class Session { // If we instead clear the cookies only when revokeSession // returns true, it can cause this kind of a bug: // https://github.com/supertokens/supertokens-node/issues/343 - cookieAndHeaders_1.clearSession(this.helpers.config, this.reqResInfo.res, this.reqResInfo.transferMethod, this.reqResInfo.req, ctx); + cookieAndHeaders_1.clearSession( + this.helpers.config, + this.reqResInfo.res, + this.reqResInfo.transferMethod, + this.reqResInfo.req, + ctx + ); } } async getSessionDataFromDatabase(userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let sessionInfo = await this.helpers.getRecipeImpl().getSessionInformation({ sessionHandle: this.sessionHandle, userContext: ctx, }); if (sessionInfo === undefined) { - logger_1.logDebugMessage("getSessionDataFromDatabase: Throwing UNAUTHORISED because session does not exist anymore"); + logger_1.logDebugMessage( + "getSessionDataFromDatabase: Throwing UNAUTHORISED because session does not exist anymore" + ); throw new error_1.default({ message: "Session does not exist anymore", type: error_1.default.UNAUTHORISED, @@ -78,15 +103,20 @@ class Session { return sessionInfo.sessionDataInDatabase; } async updateSessionDataInDatabase(newSessionData, userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); - if (!(await this.helpers.getRecipeImpl().updateSessionDataInDatabase({ - sessionHandle: this.sessionHandle, - newSessionData, - userContext: ctx, - }))) { - logger_1.logDebugMessage("updateSessionDataInDatabase: Throwing UNAUTHORISED because session does not exist anymore"); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); + if ( + !(await this.helpers.getRecipeImpl().updateSessionDataInDatabase({ + sessionHandle: this.sessionHandle, + newSessionData, + userContext: ctx, + })) + ) { + logger_1.logDebugMessage( + "updateSessionDataInDatabase: Throwing UNAUTHORISED because session does not exist anymore" + ); throw new error_1.default({ message: "Session does not exist anymore", type: error_1.default.UNAUTHORISED, @@ -120,9 +150,10 @@ class Session { } // Any update to this function should also be reflected in the respective JWT version async mergeIntoAccessTokenPayload(accessTokenPayloadUpdate, userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let newAccessTokenPayload = Object.assign({}, this.getAccessTokenPayload(ctx)); for (const key of constants_1.protectedProps) { delete newAccessTokenPayload[key]; @@ -139,7 +170,9 @@ class Session { userContext: ctx, }); if (response === undefined) { - logger_1.logDebugMessage("mergeIntoAccessTokenPayload: Throwing UNAUTHORISED because session does not exist anymore"); + logger_1.logDebugMessage( + "mergeIntoAccessTokenPayload: Throwing UNAUTHORISED because session does not exist anymore" + ); throw new error_1.default({ message: "Session does not exist anymore", type: error_1.default.UNAUTHORISED, @@ -154,20 +187,31 @@ class Session { this.accessTokenUpdated = true; if (this.reqResInfo !== undefined) { // We need to cast to let TS know that the accessToken in the response is defined (and we don't overwrite it with undefined) - utils_1.setAccessTokenInResponse(this.reqResInfo.res, this.accessToken, this.frontToken, this.helpers.config, this.reqResInfo.transferMethod, this.reqResInfo.req, ctx); + utils_1.setAccessTokenInResponse( + this.reqResInfo.res, + this.accessToken, + this.frontToken, + this.helpers.config, + this.reqResInfo.transferMethod, + this.reqResInfo.req, + ctx + ); } - } - else { + } else { // This case means that the access token has expired between the validation and this update // We can't update the access token on the FE, as it will need to call refresh anyway but we handle this as a successful update during this request. // the changes will be reflected on the FE after refresh is called - this.userDataInAccessToken = Object.assign(Object.assign({}, this.getAccessTokenPayload(ctx)), response.session.userDataInJWT); + this.userDataInAccessToken = Object.assign( + Object.assign({}, this.getAccessTokenPayload(ctx)), + response.session.userDataInJWT + ); } } async getTimeCreated(userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let sessionInfo = await this.helpers.getRecipeImpl().getSessionInformation({ sessionHandle: this.sessionHandle, userContext: ctx, @@ -182,9 +226,10 @@ class Session { return sessionInfo.timeCreated; } async getExpiry(userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let sessionInfo = await this.helpers.getRecipeImpl().getSessionInformation({ sessionHandle: this.sessionHandle, userContext: ctx, @@ -200,9 +245,10 @@ class Session { } // Any update to this function should also be reflected in the respective JWT version async assertClaims(claimValidators, userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); let validateClaimResponse = await this.helpers.getRecipeImpl().validateClaims({ accessTokenPayload: this.getAccessTokenPayload(ctx), userId: this.getUserId(ctx), @@ -226,32 +272,42 @@ class Session { } // Any update to this function should also be reflected in the respective JWT version async fetchAndSetClaim(claim, userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); - const update = await claim.build(this.getUserId(ctx), this.getRecipeUserId(ctx), this.getTenantId(ctx), this.getAccessTokenPayload(ctx), ctx); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); + const update = await claim.build( + this.getUserId(ctx), + this.getRecipeUserId(ctx), + this.getTenantId(ctx), + this.getAccessTokenPayload(ctx), + ctx + ); return this.mergeIntoAccessTokenPayload(update, ctx); } // Any update to this function should also be reflected in the respective JWT version setClaimValue(claim, value, userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); const update = claim.addToPayload_internal({}, value, utils_2.getUserContext(ctx)); return this.mergeIntoAccessTokenPayload(update, ctx); } // Any update to this function should also be reflected in the respective JWT version async getClaimValue(claim, userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); return claim.getValueFromPayload(await this.getAccessTokenPayload(ctx), ctx); } // Any update to this function should also be reflected in the respective JWT version removeClaim(claim, userContext) { - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); const update = claim.removeFromPayloadByMerge_internal({}, ctx); return this.mergeIntoAccessTokenPayload(update, ctx); } @@ -259,12 +315,30 @@ class Session { this.reqResInfo = info; if (this.accessTokenUpdated) { const { res, transferMethod } = info; - const ctx = userContext === undefined && this.reqResInfo !== undefined - ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) - : utils_2.getUserContext(userContext); - utils_1.setAccessTokenInResponse(res, this.accessToken, this.frontToken, this.helpers.config, transferMethod, info.req, ctx); + const ctx = + userContext === undefined && this.reqResInfo !== undefined + ? utils_2.makeDefaultUserContextFromAPI(this.reqResInfo.req) + : utils_2.getUserContext(userContext); + utils_1.setAccessTokenInResponse( + res, + this.accessToken, + this.frontToken, + this.helpers.config, + transferMethod, + info.req, + ctx + ); if (this.refreshToken !== undefined) { - cookieAndHeaders_1.setToken(this.helpers.config, res, "refresh", this.refreshToken.token, this.refreshToken.expiry, transferMethod, info.req, ctx); + cookieAndHeaders_1.setToken( + this.helpers.config, + res, + "refresh", + this.refreshToken.token, + this.refreshToken.expiry, + transferMethod, + info.req, + ctx + ); } if (this.antiCsrfToken !== undefined) { cookieAndHeaders_1.setAntiCsrfTokenInHeaders(res, this.antiCsrfToken); diff --git a/lib/build/recipe/session/sessionFunctions.d.ts b/lib/build/recipe/session/sessionFunctions.d.ts index 366bb8b34..601e51917 100644 --- a/lib/build/recipe/session/sessionFunctions.d.ts +++ b/lib/build/recipe/session/sessionFunctions.d.ts @@ -7,11 +7,27 @@ import { UserContext } from "../../types"; /** * @description call this to "login" a user. */ -export declare function createNewSession(helpers: Helpers, tenantId: string, recipeUserId: RecipeUserId, disableAntiCsrf: boolean, accessTokenPayload: any, sessionDataInDatabase: any, userContext: UserContext): Promise; +export declare function createNewSession( + helpers: Helpers, + tenantId: string, + recipeUserId: RecipeUserId, + disableAntiCsrf: boolean, + accessTokenPayload: any, + sessionDataInDatabase: any, + userContext: UserContext +): Promise; /** * @description authenticates a session. To be used in APIs that require authentication */ -export declare function getSession(helpers: Helpers, parsedAccessToken: ParsedJWTInfo, antiCsrfToken: string | undefined, doAntiCsrfCheck: boolean, alwaysCheckCore: boolean, config: TypeNormalisedInput, userContext: UserContext): Promise<{ +export declare function getSession( + helpers: Helpers, + parsedAccessToken: ParsedJWTInfo, + antiCsrfToken: string | undefined, + doAntiCsrfCheck: boolean, + alwaysCheckCore: boolean, + config: TypeNormalisedInput, + userContext: UserContext +): Promise<{ session: { handle: string; userId: string; @@ -30,33 +46,76 @@ export declare function getSession(helpers: Helpers, parsedAccessToken: ParsedJW * @description Retrieves session information from storage for a given session handle * @returns session data stored in the database, including userData and access token payload, or undefined if sessionHandle is invalid */ -export declare function getSessionInformation(helpers: Helpers, sessionHandle: string, userContext: UserContext): Promise; +export declare function getSessionInformation( + helpers: Helpers, + sessionHandle: string, + userContext: UserContext +): Promise; /** * @description generates new access and refresh tokens for a given refresh token. Called when client's access token has expired. * @sideEffects calls onTokenTheftDetection if token theft is detected. */ -export declare function refreshSession(helpers: Helpers, refreshToken: string, antiCsrfToken: string | undefined, disableAntiCsrf: boolean, useDynamicAccessTokenSigningKey: boolean, userContext: UserContext): Promise; +export declare function refreshSession( + helpers: Helpers, + refreshToken: string, + antiCsrfToken: string | undefined, + disableAntiCsrf: boolean, + useDynamicAccessTokenSigningKey: boolean, + userContext: UserContext +): Promise; /** * @description deletes session info of a user from db. This only invalidates the refresh token. Not the access token. * Access tokens cannot be immediately invalidated. Unless we add a blacklisting method. Or changed the private key to sign them. */ -export declare function revokeAllSessionsForUser(helpers: Helpers, userId: string, revokeSessionsForLinkedAccounts: boolean, tenantId: string | undefined, revokeAcrossAllTenants: boolean | undefined, userContext: UserContext): Promise; +export declare function revokeAllSessionsForUser( + helpers: Helpers, + userId: string, + revokeSessionsForLinkedAccounts: boolean, + tenantId: string | undefined, + revokeAcrossAllTenants: boolean | undefined, + userContext: UserContext +): Promise; /** * @description gets all session handles for current user. Please do not call this unless this user is authenticated. */ -export declare function getAllSessionHandlesForUser(helpers: Helpers, userId: string, fetchSessionsForAllLinkedAccounts: boolean, tenantId: string | undefined, fetchAcrossAllTenants: boolean | undefined, userContext: UserContext): Promise; +export declare function getAllSessionHandlesForUser( + helpers: Helpers, + userId: string, + fetchSessionsForAllLinkedAccounts: boolean, + tenantId: string | undefined, + fetchAcrossAllTenants: boolean | undefined, + userContext: UserContext +): Promise; /** * @description call to destroy one session * @returns true if session was deleted from db. Else false in case there was nothing to delete */ -export declare function revokeSession(helpers: Helpers, sessionHandle: string, userContext: UserContext): Promise; +export declare function revokeSession( + helpers: Helpers, + sessionHandle: string, + userContext: UserContext +): Promise; /** * @description call to destroy multiple sessions * @returns list of sessions revoked */ -export declare function revokeMultipleSessions(helpers: Helpers, sessionHandles: string[], userContext: UserContext): Promise; +export declare function revokeMultipleSessions( + helpers: Helpers, + sessionHandles: string[], + userContext: UserContext +): Promise; /** * @description: It provides no locking mechanism in case other processes are updating session data for this session as well. */ -export declare function updateSessionDataInDatabase(helpers: Helpers, sessionHandle: string, newSessionData: any, userContext: UserContext): Promise; -export declare function updateAccessTokenPayload(helpers: Helpers, sessionHandle: string, newAccessTokenPayload: any, userContext: UserContext): Promise; +export declare function updateSessionDataInDatabase( + helpers: Helpers, + sessionHandle: string, + newSessionData: any, + userContext: UserContext +): Promise; +export declare function updateAccessTokenPayload( + helpers: Helpers, + sessionHandle: string, + newAccessTokenPayload: any, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/session/sessionFunctions.js b/lib/build/recipe/session/sessionFunctions.js index 70f02dca0..f939f0367 100644 --- a/lib/build/recipe/session/sessionFunctions.js +++ b/lib/build/recipe/session/sessionFunctions.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateAccessTokenPayload = exports.updateSessionDataInDatabase = exports.revokeMultipleSessions = exports.revokeSession = exports.getAllSessionHandlesForUser = exports.revokeAllSessionsForUser = exports.refreshSession = exports.getSessionInformation = exports.getSession = exports.createNewSession = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -30,7 +32,15 @@ const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); /** * @description call this to "login" a user. */ -async function createNewSession(helpers, tenantId, recipeUserId, disableAntiCsrf, accessTokenPayload, sessionDataInDatabase, userContext) { +async function createNewSession( + helpers, + tenantId, + recipeUserId, + disableAntiCsrf, + accessTokenPayload, + sessionDataInDatabase, + userContext +) { accessTokenPayload = accessTokenPayload === null || accessTokenPayload === undefined ? {} : accessTokenPayload; sessionDataInDatabase = sessionDataInDatabase === null || sessionDataInDatabase === undefined ? {} : sessionDataInDatabase; @@ -42,7 +52,11 @@ async function createNewSession(helpers, tenantId, recipeUserId, disableAntiCsrf // We dont need to check if anti csrf is a function here because checking for "VIA_TOKEN" is enough enableAntiCsrf: !disableAntiCsrf && helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN", }; - let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/session`), requestBody, userContext); + let response = await helpers.querier.sendPostRequest( + new normalisedURLPath_1.default(`/${tenantId}/recipe/session`), + requestBody, + userContext + ); return { session: { handle: response.session.handle, @@ -68,16 +82,27 @@ exports.createNewSession = createNewSession; /** * @description authenticates a session. To be used in APIs that require authentication */ -async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfCheck, alwaysCheckCore, config, userContext) { +async function getSession( + helpers, + parsedAccessToken, + antiCsrfToken, + doAntiCsrfCheck, + alwaysCheckCore, + config, + userContext +) { var _a, _b; let accessTokenInfo; try { /** * get access token info using jwks */ - accessTokenInfo = await accessToken_1.getInfoFromAccessToken(parsedAccessToken, combinedRemoteJWKSet_1.getCombinedJWKS(config), helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN" && doAntiCsrfCheck); - } - catch (err) { + accessTokenInfo = await accessToken_1.getInfoFromAccessToken( + parsedAccessToken, + combinedRemoteJWKSet_1.getCombinedJWKS(config), + helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN" && doAntiCsrfCheck + ); + } catch (err) { /** * if error type is not TRY_REFRESH_TOKEN, we return the * error to the user @@ -118,8 +143,7 @@ async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfC if (timeCreated <= Date.now() - config.jwksRefreshIntervalSec * 1000) { throw err; } - } - else { + } else { // Since v3 (and above) tokens contain a kid we can trust the cache-refresh mechanism of the jose library. // This means we do not need to call the core since the signature wouldn't pass verification anyway. throw err; @@ -128,7 +152,9 @@ async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfC if (parsedAccessToken.version >= 3) { const tokenUsesDynamicKey = parsedAccessToken.kid.startsWith("d-"); if (tokenUsesDynamicKey !== helpers.config.useDynamicAccessTokenSigningKey) { - logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because the access token doesn't match the useDynamicAccessTokenSigningKey in the config"); + logger_1.logDebugMessage( + "getSession: Returning TRY_REFRESH_TOKEN because the access token doesn't match the useDynamicAccessTokenSigningKey in the config" + ); throw new error_1.default({ message: "The access token doesn't match the useDynamicAccessTokenSigningKey setting", type: error_1.default.TRY_REFRESH_TOKEN, @@ -145,14 +171,18 @@ async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfC if (accessTokenInfo !== undefined) { if (antiCsrfToken === undefined || antiCsrfToken !== accessTokenInfo.antiCsrfToken) { if (antiCsrfToken === undefined) { - logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because antiCsrfToken is missing from request"); + logger_1.logDebugMessage( + "getSession: Returning TRY_REFRESH_TOKEN because antiCsrfToken is missing from request" + ); throw new error_1.default({ - message: "Provided antiCsrfToken is undefined. If you do not want anti-csrf check for this API, please set doAntiCsrfCheck to false for this API", + message: + "Provided antiCsrfToken is undefined. If you do not want anti-csrf check for this API, please set doAntiCsrfCheck to false for this API", type: error_1.default.TRY_REFRESH_TOKEN, }); - } - else { - logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because the passed antiCsrfToken is not the same as in the access token"); + } else { + logger_1.logDebugMessage( + "getSession: Returning TRY_REFRESH_TOKEN because the passed antiCsrfToken is not the same as in the access token" + ); throw new error_1.default({ message: "anti-csrf check failed", type: error_1.default.TRY_REFRESH_TOKEN, @@ -160,9 +190,10 @@ async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfC } } } - } - else if (typeof helpers.config.antiCsrfFunctionOrString === "string" && - helpers.config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER") { + } else if ( + typeof helpers.config.antiCsrfFunctionOrString === "string" && + helpers.config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER" + ) { // The function should never be called by this (we check this outside the function as well) // There we can add a bit more information to the error, so that's the primary check, this is just making sure. throw new Error("Please either use VIA_TOKEN, NONE or call with doAntiCsrfCheck false"); @@ -188,28 +219,36 @@ async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfC enableAntiCsrf: helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN", checkDatabase: alwaysCheckCore, }; - let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/session/verify"), requestBody, userContext); + let response = await helpers.querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/session/verify"), + requestBody, + userContext + ); if (response.status === "OK") { delete response.status; - return Object.assign(Object.assign({}, response), { session: { + return Object.assign(Object.assign({}, response), { + session: { handle: response.session.handle, userId: response.session.userId, recipeUserId: new recipeUserId_1.default(response.session.recipeUserId), - expiryTime: ((_a = response.accessToken) === null || _a === void 0 ? void 0 : _a.expiry) || // if we got a new accesstoken we take the expiry time from there + expiryTime: + ((_a = response.accessToken) === null || _a === void 0 ? void 0 : _a.expiry) || // if we got a new accesstoken we take the expiry time from there (accessTokenInfo === null || accessTokenInfo === void 0 ? void 0 : accessTokenInfo.expiryTime) || // if we didn't get a new access token but could validate the token take that info (alwaysCheckCore === true, or parentRefreshTokenHash1 !== null) parsedAccessToken.payload["expiryTime"], - tenantId: ((_b = response.session) === null || _b === void 0 ? void 0 : _b.tenantId) || (accessTokenInfo === null || accessTokenInfo === void 0 ? void 0 : accessTokenInfo.tenantId) || constants_1.DEFAULT_TENANT_ID, + tenantId: + ((_b = response.session) === null || _b === void 0 ? void 0 : _b.tenantId) || + (accessTokenInfo === null || accessTokenInfo === void 0 ? void 0 : accessTokenInfo.tenantId) || + constants_1.DEFAULT_TENANT_ID, userDataInJWT: response.session.userDataInJWT, - } }); - } - else if (response.status === "UNAUTHORISED") { + }, + }); + } else if (response.status === "UNAUTHORISED") { logger_1.logDebugMessage("getSession: Returning UNAUTHORISED because of core response"); throw new error_1.default({ message: response.message, type: error_1.default.UNAUTHORISED, }); - } - else { + } else { logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because of core response."); throw new error_1.default({ message: response.message, @@ -227,9 +266,13 @@ async function getSessionInformation(helpers, sessionHandle, userContext) { if (utils_1.maxVersion(apiVersion, "2.7") === "2.7") { throw new Error("Please use core version >= 3.5 to call this function."); } - let response = await helpers.querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/session`), { - sessionHandle, - }, userContext); + let response = await helpers.querier.sendGetRequest( + new normalisedURLPath_1.default(`/recipe/session`), + { + sessionHandle, + }, + userContext + ); if (response.status === "OK") { // Change keys to make them more readable return { @@ -242,8 +285,7 @@ async function getSessionInformation(helpers, sessionHandle, userContext) { customClaimsInAccessTokenPayload: response.userDataInJWT, tenantId: response.tenantId, }; - } - else { + } else { return undefined; } } @@ -252,21 +294,34 @@ exports.getSessionInformation = getSessionInformation; * @description generates new access and refresh tokens for a given refresh token. Called when client's access token has expired. * @sideEffects calls onTokenTheftDetection if token theft is detected. */ -async function refreshSession(helpers, refreshToken, antiCsrfToken, disableAntiCsrf, useDynamicAccessTokenSigningKey, userContext) { +async function refreshSession( + helpers, + refreshToken, + antiCsrfToken, + disableAntiCsrf, + useDynamicAccessTokenSigningKey, + userContext +) { let requestBody = { refreshToken, antiCsrfToken, enableAntiCsrf: !disableAntiCsrf && helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN", useDynamicSigningKey: useDynamicAccessTokenSigningKey, }; - if (typeof helpers.config.antiCsrfFunctionOrString === "string" && + if ( + typeof helpers.config.antiCsrfFunctionOrString === "string" && helpers.config.antiCsrfFunctionOrString === "VIA_CUSTOM_HEADER" && - !disableAntiCsrf) { + !disableAntiCsrf + ) { // The function should never be called by this (we check this outside the function as well) // There we can add a bit more information to the error, so that's the primary check, this is just making sure. throw new Error("Please either use VIA_TOKEN, NONE or call with doAntiCsrfCheck false"); } - let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/session/refresh"), requestBody, userContext); + let response = await helpers.querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/session/refresh"), + requestBody, + userContext + ); if (response.status === "OK") { return { session: { @@ -288,15 +343,13 @@ async function refreshSession(helpers, refreshToken, antiCsrfToken, disableAntiC }, antiCsrfToken: response.antiCsrfToken, }; - } - else if (response.status === "UNAUTHORISED") { + } else if (response.status === "UNAUTHORISED") { logger_1.logDebugMessage("refreshSession: Returning UNAUTHORISED because of core response"); throw new error_1.default({ message: response.message, type: error_1.default.UNAUTHORISED, }); - } - else { + } else { logger_1.logDebugMessage("refreshSession: Returning TOKEN_THEFT_DETECTED because of core response"); throw new error_1.default({ message: "Token theft detected", @@ -314,30 +367,56 @@ exports.refreshSession = refreshSession; * @description deletes session info of a user from db. This only invalidates the refresh token. Not the access token. * Access tokens cannot be immediately invalidated. Unless we add a blacklisting method. Or changed the private key to sign them. */ -async function revokeAllSessionsForUser(helpers, userId, revokeSessionsForLinkedAccounts, tenantId, revokeAcrossAllTenants, userContext) { +async function revokeAllSessionsForUser( + helpers, + userId, + revokeSessionsForLinkedAccounts, + tenantId, + revokeAcrossAllTenants, + userContext +) { if (tenantId === undefined) { tenantId = constants_1.DEFAULT_TENANT_ID; } - let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default(revokeAcrossAllTenants ? `/recipe/session/remove` : `/${tenantId}/recipe/session/remove`), { - userId, - revokeSessionsForLinkedAccounts, - revokeAcrossAllTenants, - }, userContext); + let response = await helpers.querier.sendPostRequest( + new normalisedURLPath_1.default( + revokeAcrossAllTenants ? `/recipe/session/remove` : `/${tenantId}/recipe/session/remove` + ), + { + userId, + revokeSessionsForLinkedAccounts, + revokeAcrossAllTenants, + }, + userContext + ); return response.sessionHandlesRevoked; } exports.revokeAllSessionsForUser = revokeAllSessionsForUser; /** * @description gets all session handles for current user. Please do not call this unless this user is authenticated. */ -async function getAllSessionHandlesForUser(helpers, userId, fetchSessionsForAllLinkedAccounts, tenantId, fetchAcrossAllTenants, userContext) { +async function getAllSessionHandlesForUser( + helpers, + userId, + fetchSessionsForAllLinkedAccounts, + tenantId, + fetchAcrossAllTenants, + userContext +) { if (tenantId === undefined) { tenantId = constants_1.DEFAULT_TENANT_ID; } - let response = await helpers.querier.sendGetRequest(new normalisedURLPath_1.default(fetchAcrossAllTenants ? `/recipe/session/user` : `/${tenantId}/recipe/session/user`), { - userId, - fetchSessionsForAllLinkedAccounts, - fetchAcrossAllTenants, - }, userContext); + let response = await helpers.querier.sendGetRequest( + new normalisedURLPath_1.default( + fetchAcrossAllTenants ? `/recipe/session/user` : `/${tenantId}/recipe/session/user` + ), + { + userId, + fetchSessionsForAllLinkedAccounts, + fetchAcrossAllTenants, + }, + userContext + ); return response.sessionHandles; } exports.getAllSessionHandlesForUser = getAllSessionHandlesForUser; @@ -346,9 +425,13 @@ exports.getAllSessionHandlesForUser = getAllSessionHandlesForUser; * @returns true if session was deleted from db. Else false in case there was nothing to delete */ async function revokeSession(helpers, sessionHandle, userContext) { - let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/session/remove"), { - sessionHandles: [sessionHandle], - }, userContext); + let response = await helpers.querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/session/remove"), + { + sessionHandles: [sessionHandle], + }, + userContext + ); return response.sessionHandlesRevoked.length === 1; } exports.revokeSession = revokeSession; @@ -357,9 +440,13 @@ exports.revokeSession = revokeSession; * @returns list of sessions revoked */ async function revokeMultipleSessions(helpers, sessionHandles, userContext) { - let response = await helpers.querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/session/remove`), { - sessionHandles, - }, userContext); + let response = await helpers.querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/session/remove`), + { + sessionHandles, + }, + userContext + ); return response.sessionHandlesRevoked; } exports.revokeMultipleSessions = revokeMultipleSessions; @@ -368,10 +455,15 @@ exports.revokeMultipleSessions = revokeMultipleSessions; */ async function updateSessionDataInDatabase(helpers, sessionHandle, newSessionData, userContext) { newSessionData = newSessionData === null || newSessionData === undefined ? {} : newSessionData; - let response = await helpers.querier.sendPutRequest(new normalisedURLPath_1.default(`/recipe/session/data`), { - sessionHandle, - userDataInDatabase: newSessionData, - }, {}, userContext); + let response = await helpers.querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/session/data`), + { + sessionHandle, + userDataInDatabase: newSessionData, + }, + {}, + userContext + ); if (response.status === "UNAUTHORISED") { return false; } @@ -381,10 +473,15 @@ exports.updateSessionDataInDatabase = updateSessionDataInDatabase; async function updateAccessTokenPayload(helpers, sessionHandle, newAccessTokenPayload, userContext) { newAccessTokenPayload = newAccessTokenPayload === null || newAccessTokenPayload === undefined ? {} : newAccessTokenPayload; - let response = await helpers.querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/jwt/data"), { - sessionHandle, - userDataInJWT: newAccessTokenPayload, - }, {}, userContext); + let response = await helpers.querier.sendPutRequest( + new normalisedURLPath_1.default("/recipe/jwt/data"), + { + sessionHandle, + userDataInJWT: newAccessTokenPayload, + }, + {}, + userContext + ); if (response.status === "UNAUTHORISED") { return false; } diff --git a/lib/build/recipe/session/sessionRequestFunctions.d.ts b/lib/build/recipe/session/sessionRequestFunctions.d.ts index 66be53883..3002f3bd2 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.d.ts +++ b/lib/build/recipe/session/sessionRequestFunctions.d.ts @@ -1,10 +1,23 @@ // @ts-nocheck import Recipe from "./recipe"; -import { VerifySessionOptions, RecipeInterface, TokenTransferMethod, TypeNormalisedInput, SessionContainerInterface } from "./types"; +import { + VerifySessionOptions, + RecipeInterface, + TokenTransferMethod, + TypeNormalisedInput, + SessionContainerInterface, +} from "./types"; import { ParsedJWTInfo } from "./jwt"; import { NormalisedAppinfo, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; -export declare function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, options, userContext, }: { +export declare function getSessionFromRequest({ + req, + res, + config, + recipeInterfaceImpl, + options, + userContext, +}: { req: any; res: any; config: TypeNormalisedInput; @@ -12,19 +25,40 @@ export declare function getSessionFromRequest({ req, res, config, recipeInterfac options?: VerifySessionOptions; userContext: UserContext; }): Promise; -export declare function getAccessTokenFromRequest(req: any, allowedTransferMethod: TokenTransferMethod | "any"): { +export declare function getAccessTokenFromRequest( + req: any, + allowedTransferMethod: TokenTransferMethod | "any" +): { requestTransferMethod: TokenTransferMethod | undefined; accessToken: ParsedJWTInfo | undefined; allowedTransferMethod: TokenTransferMethod | "any"; }; -export declare function refreshSessionInRequest({ res, req, userContext, config, recipeInterfaceImpl, }: { +export declare function refreshSessionInRequest({ + res, + req, + userContext, + config, + recipeInterfaceImpl, +}: { res: any; req: any; userContext: UserContext; config: TypeNormalisedInput; recipeInterfaceImpl: RecipeInterface; }): Promise; -export declare function createNewSessionInRequest({ req, res, userContext, recipeInstance, accessTokenPayload, userId, recipeUserId, config, appInfo, sessionDataInDatabase, tenantId, }: { +export declare function createNewSessionInRequest({ + req, + res, + userContext, + recipeInstance, + accessTokenPayload, + userId, + recipeUserId, + config, + appInfo, + sessionDataInDatabase, + tenantId, +}: { req: any; res: any; userContext: UserContext; diff --git a/lib/build/recipe/session/sessionRequestFunctions.js b/lib/build/recipe/session/sessionRequestFunctions.js index 657e023a7..df0a56f0e 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.js +++ b/lib/build/recipe/session/sessionRequestFunctions.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createNewSessionInRequest = exports.refreshSessionInRequest = exports.getAccessTokenFromRequest = exports.getSessionFromRequest = void 0; const framework_1 = __importDefault(require("../../framework")); @@ -17,7 +19,7 @@ const accessToken_1 = require("./accessToken"); const error_1 = __importDefault(require("./error")); // We are defining this here (and not exporting it) to reduce the scope of legacy code const LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME = "sIdRefreshToken"; -async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, options, userContext, }) { +async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, options, userContext }) { logger_1.logDebugMessage("getSession: Started"); const configuredFramework = supertokens_1.default.getInstanceOrThrowError().framework; if (configuredFramework !== "custom") { @@ -32,7 +34,9 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op logger_1.logDebugMessage("getSession: Wrapping done"); // This token isn't handled by getToken to limit the scope of this legacy/migration code if (req.getCookieValue(LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME) !== undefined) { - logger_1.logDebugMessage("getSession: Throwing TRY_REFRESH_TOKEN because the request is using a legacy session"); + logger_1.logDebugMessage( + "getSession: Throwing TRY_REFRESH_TOKEN because the request is using a legacy session" + ); // This could create a spike on refresh calls during the update of the backend SDK throw new error_1.default({ message: "using legacy session, please call the refresh API", @@ -70,9 +74,12 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op if (doAntiCsrfCheck && antiCsrf === "VIA_CUSTOM_HEADER") { if (antiCsrf === "VIA_CUSTOM_HEADER") { if (utils_2.getRidFromHeader(req) === undefined) { - logger_1.logDebugMessage("getSession: Returning TRY_REFRESH_TOKEN because custom header (rid) was not passed"); + logger_1.logDebugMessage( + "getSession: Returning TRY_REFRESH_TOKEN because custom header (rid) was not passed" + ); throw new error_1.default({ - message: "anti-csrf check failed. Please pass 'rid: \"session\"' header in the request, or set doAntiCsrfCheck to false for this API", + message: + "anti-csrf check failed. Please pass 'rid: \"session\"' header in the request, or set doAntiCsrfCheck to false for this API", type: error_1.default.TRY_REFRESH_TOKEN, }); } @@ -88,22 +95,30 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op userContext, }); if (session !== undefined) { - const claimValidators = await utils_1.getRequiredClaimValidators(session, options === null || options === void 0 ? void 0 : options.overrideGlobalClaimValidators, userContext); + const claimValidators = await utils_1.getRequiredClaimValidators( + session, + options === null || options === void 0 ? void 0 : options.overrideGlobalClaimValidators, + userContext + ); await session.assertClaims(claimValidators, userContext); // requestTransferMethod can only be undefined here if the user overridden getSession // to load the session by a custom method in that (very niche) case they also need to // override how the session is attached to the response. // In that scenario the transferMethod passed to attachToRequestResponse likely doesn't // matter, still, we follow the general fallback logic - await session.attachToRequestResponse({ - req, - res, - transferMethod: requestTransferMethod !== undefined - ? requestTransferMethod - : allowedTransferMethod !== "any" - ? allowedTransferMethod - : "header", - }, userContext); + await session.attachToRequestResponse( + { + req, + res, + transferMethod: + requestTransferMethod !== undefined + ? requestTransferMethod + : allowedTransferMethod !== "any" + ? allowedTransferMethod + : "header", + }, + userContext + ); } return session; } @@ -119,29 +134,35 @@ function getAccessTokenFromRequest(req, allowedTransferMethod) { accessToken_1.validateAccessTokenStructure(info.payload, info.version); logger_1.logDebugMessage("getSession: got access token from " + transferMethod); accessTokens[transferMethod] = info; - } - catch (_a) { - logger_1.logDebugMessage(`getSession: ignoring token in ${transferMethod}, because it doesn't match our access token structure`); + } catch (_a) { + logger_1.logDebugMessage( + `getSession: ignoring token in ${transferMethod}, because it doesn't match our access token structure` + ); } } } let requestTransferMethod; let accessToken; - if ((allowedTransferMethod === "any" || allowedTransferMethod === "header") && - accessTokens["header"] !== undefined) { + if ( + (allowedTransferMethod === "any" || allowedTransferMethod === "header") && + accessTokens["header"] !== undefined + ) { logger_1.logDebugMessage("getSession: using header transfer method"); requestTransferMethod = "header"; accessToken = accessTokens["header"]; - } - else if ((allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && - accessTokens["cookie"] !== undefined) { + } else if ( + (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && + accessTokens["cookie"] !== undefined + ) { logger_1.logDebugMessage("getSession: using cookie transfer method"); // If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN. // This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set). // ensuring outdated token payload isn't used. const hasMultipleAccessTokenCookies = cookieAndHeaders_1.hasMultipleCookiesForTokenType(req, "access"); if (hasMultipleAccessTokenCookies) { - logger_1.logDebugMessage("getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies"); + logger_1.logDebugMessage( + "getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies" + ); throw new error_1.default({ message: "Multiple access tokens present in the request cookies.", type: error_1.default.TRY_REFRESH_TOKEN, @@ -157,7 +178,7 @@ exports.getAccessTokenFromRequest = getAccessTokenFromRequest; In all cases: if sIdRefreshToken token exists (so it's a legacy session) we clear it. Check http://localhost:3002/docs/contribute/decisions/session/0008 for further details and a table of expected behaviours */ -async function refreshSessionInRequest({ res, req, userContext, config, recipeInterfaceImpl, }) { +async function refreshSessionInRequest({ res, req, userContext, config, recipeInterfaceImpl }) { logger_1.logDebugMessage("refreshSession: Started"); const configuredFramework = supertokens_1.default.getInstanceOrThrowError().framework; if (configuredFramework !== "custom") { @@ -188,31 +209,46 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn logger_1.logDebugMessage("refreshSession: getTokenTransferMethod returned " + allowedTransferMethod); let requestTransferMethod; let refreshToken; - if ((allowedTransferMethod === "any" || allowedTransferMethod === "header") && - refreshTokens["header"] !== undefined) { + if ( + (allowedTransferMethod === "any" || allowedTransferMethod === "header") && + refreshTokens["header"] !== undefined + ) { logger_1.logDebugMessage("refreshSession: using header transfer method"); requestTransferMethod = "header"; refreshToken = refreshTokens["header"]; - } - else if ((allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && refreshTokens["cookie"]) { + } else if ((allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && refreshTokens["cookie"]) { logger_1.logDebugMessage("refreshSession: using cookie transfer method"); requestTransferMethod = "cookie"; refreshToken = refreshTokens["cookie"]; - } - else { + } else { // This token isn't handled by getToken/setToken to limit the scope of this legacy/migration code if (req.getCookieValue(LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME) !== undefined) { - logger_1.logDebugMessage("refreshSession: cleared legacy id refresh token because refresh token was not found"); - cookieAndHeaders_1.setCookie(config, res, LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, "", 0, "accessTokenPath", req, userContext); + logger_1.logDebugMessage( + "refreshSession: cleared legacy id refresh token because refresh token was not found" + ); + cookieAndHeaders_1.setCookie( + config, + res, + LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, + "", + 0, + "accessTokenPath", + req, + userContext + ); } // We need to clear the access token cookie if // - the refresh token is not found, and // - the allowedTransferMethod is 'cookie' or 'any', and // - an access token cookie exists (otherwise it'd be a no-op) // See: https://github.com/supertokens/supertokens-node/issues/790 - if ((allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && - cookieAndHeaders_1.getToken(req, "access", "cookie") !== undefined) { - logger_1.logDebugMessage("refreshSession: cleared all session tokens and returning UNAUTHORISED because refresh token in request is undefined"); + if ( + (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && + cookieAndHeaders_1.getToken(req, "access", "cookie") !== undefined + ) { + logger_1.logDebugMessage( + "refreshSession: cleared all session tokens and returning UNAUTHORISED because refresh token in request is undefined" + ); // We're clearing all session tokens instead of just the access token and then throwing an UNAUTHORISED // error with `clearTokens: false`. This approach avoids confusion and we don't want to retain session // tokens on the client in any case if the refresh API is called without a refresh token but with an access token. @@ -243,7 +279,9 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn } if (antiCsrf === "VIA_CUSTOM_HEADER" && !disableAntiCsrf) { if (utils_2.getRidFromHeader(req) === undefined) { - logger_1.logDebugMessage("refreshSession: Returning UNAUTHORISED because custom header (rid) was not passed"); + logger_1.logDebugMessage( + "refreshSession: Returning UNAUTHORISED because custom header (rid) was not passed" + ); throw new error_1.default({ message: "anti-csrf check failed. Please pass 'rid: \"session\"' header in the request.", type: error_1.default.UNAUTHORISED, @@ -262,15 +300,27 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn disableAntiCsrf, userContext, }); - } - catch (ex) { - if (error_1.default.isErrorFromSuperTokens(ex) && - (ex.type === error_1.default.TOKEN_THEFT_DETECTED || ex.payload.clearTokens === true)) { + } catch (ex) { + if ( + error_1.default.isErrorFromSuperTokens(ex) && + (ex.type === error_1.default.TOKEN_THEFT_DETECTED || ex.payload.clearTokens === true) + ) { // We clear the LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME here because we want to limit the scope of this legacy/migration code // so the token clearing functions in the error handlers do not if (req.getCookieValue(LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME) !== undefined) { - logger_1.logDebugMessage("refreshSession: cleared legacy id refresh token because refresh is clearing other tokens"); - cookieAndHeaders_1.setCookie(config, res, LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, "", 0, "accessTokenPath", req, userContext); + logger_1.logDebugMessage( + "refreshSession: cleared legacy id refresh token because refresh is clearing other tokens" + ); + cookieAndHeaders_1.setCookie( + config, + res, + LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, + "", + 0, + "accessTokenPath", + req, + userContext + ); } } throw ex; @@ -282,21 +332,45 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn cookieAndHeaders_1.clearSession(config, res, transferMethod, req, userContext); } } - await session.attachToRequestResponse({ - req, - res, - transferMethod: requestTransferMethod, - }, userContext); + await session.attachToRequestResponse( + { + req, + res, + transferMethod: requestTransferMethod, + }, + userContext + ); logger_1.logDebugMessage("refreshSession: Success!"); // This token isn't handled by getToken/setToken to limit the scope of this legacy/migration code if (req.getCookieValue(LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME) !== undefined) { logger_1.logDebugMessage("refreshSession: cleared legacy id refresh token after successful refresh"); - cookieAndHeaders_1.setCookie(config, res, LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, "", 0, "accessTokenPath", req, userContext); + cookieAndHeaders_1.setCookie( + config, + res, + LEGACY_ID_REFRESH_TOKEN_COOKIE_NAME, + "", + 0, + "accessTokenPath", + req, + userContext + ); } return session; } exports.refreshSessionInRequest = refreshSessionInRequest; -async function createNewSessionInRequest({ req, res, userContext, recipeInstance, accessTokenPayload, userId, recipeUserId, config, appInfo, sessionDataInDatabase, tenantId, }) { +async function createNewSessionInRequest({ + req, + res, + userContext, + recipeInstance, + accessTokenPayload, + userId, + recipeUserId, + config, + appInfo, + sessionDataInDatabase, + tenantId, +}) { logger_1.logDebugMessage("createNewSession: Started"); const configuredFramework = supertokens_1.default.getInstanceOrThrowError().framework; if (configuredFramework !== "custom") { @@ -326,30 +400,37 @@ async function createNewSessionInRequest({ req, res, userContext, recipeInstance // We default to header if we can't "parse" it or if it's undefined if (authModeHeader === "cookie") { outputTransferMethod = authModeHeader; - } - else { + } else { outputTransferMethod = "header"; } } logger_1.logDebugMessage("createNewSession: using transfer method " + outputTransferMethod); - if (outputTransferMethod === "cookie" && + if ( + outputTransferMethod === "cookie" && config.getCookieSameSite({ request: req, userContext, }) === "none" && !config.cookieSecure && - !((appInfo.topLevelAPIDomain === "localhost" || utils_2.isAnIpAddress(appInfo.topLevelAPIDomain)) && + !( + (appInfo.topLevelAPIDomain === "localhost" || utils_2.isAnIpAddress(appInfo.topLevelAPIDomain)) && (appInfo.getTopLevelWebsiteDomain({ request: req, userContext, }) === "localhost" || - utils_2.isAnIpAddress(appInfo.getTopLevelWebsiteDomain({ - request: req, - userContext, - }))))) { + utils_2.isAnIpAddress( + appInfo.getTopLevelWebsiteDomain({ + request: req, + userContext, + }) + )) + ) + ) { // We can allow insecure cookie when both website & API domain are localhost or an IP // When either of them is a different domain, API domain needs to have https and a secure cookie to work - throw new Error("Since your API and website domain are different, for sessions to work, please use https on your apiDomain and dont set cookieSecure to false."); + throw new Error( + "Since your API and website domain are different, for sessions to work, please use https on your apiDomain and dont set cookieSecure to false." + ); } const disableAntiCsrf = outputTransferMethod === "header"; const session = await recipeInstance.recipeInterfaceImpl.createNewSession({ @@ -363,16 +444,22 @@ async function createNewSessionInRequest({ req, res, userContext, recipeInstance }); logger_1.logDebugMessage("createNewSession: Session created in core built"); for (const transferMethod of constants_1.availableTokenTransferMethods) { - if (transferMethod !== outputTransferMethod && cookieAndHeaders_1.getToken(req, "access", transferMethod) !== undefined) { + if ( + transferMethod !== outputTransferMethod && + cookieAndHeaders_1.getToken(req, "access", transferMethod) !== undefined + ) { cookieAndHeaders_1.clearSession(config, res, transferMethod, req, userContext); } } logger_1.logDebugMessage("createNewSession: Cleared old tokens"); - await session.attachToRequestResponse({ - req, - res, - transferMethod: outputTransferMethod, - }, userContext); + await session.attachToRequestResponse( + { + req, + res, + transferMethod: outputTransferMethod, + }, + userContext + ); logger_1.logDebugMessage("createNewSession: Attached new tokens to res"); return session; } diff --git a/lib/build/recipe/session/types.d.ts b/lib/build/recipe/session/types.d.ts index cbfaf78b0..b573925ed 100644 --- a/lib/build/recipe/session/types.d.ts +++ b/lib/build/recipe/session/types.d.ts @@ -57,7 +57,10 @@ export declare type TypeInput = { exposeAccessTokenToFrontendInCookieBasedAuth?: boolean; jwksRefreshIntervalSec?: number; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -75,10 +78,11 @@ export declare type TypeNormalisedInput = { sessionExpiredStatusCode: number; errorHandlers: NormalisedErrorHandlers; overwriteSessionDuringSignInUp: boolean | undefined; - antiCsrfFunctionOrString: "VIA_TOKEN" | "VIA_CUSTOM_HEADER" | "NONE" | ((input: { - request: BaseRequest | undefined; - userContext: UserContext; - }) => "VIA_CUSTOM_HEADER" | "NONE"); + antiCsrfFunctionOrString: + | "VIA_TOKEN" + | "VIA_CUSTOM_HEADER" + | "NONE" + | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => "VIA_CUSTOM_HEADER" | "NONE"); getTokenTransferMethod: (input: { req: BaseRequest; forCreateNewSession: boolean; @@ -88,7 +92,10 @@ export declare type TypeNormalisedInput = { exposeAccessTokenToFrontendInCookieBasedAuth: boolean; jwksRefreshIntervalSec: number; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -99,10 +106,22 @@ export interface ErrorHandlerMiddleware { (message: string, request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; } export interface TokenTheftErrorHandlerMiddleware { - (sessionHandle: string, userId: string, recipeUserId: RecipeUserId, request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; + ( + sessionHandle: string, + userId: string, + recipeUserId: RecipeUserId, + request: BaseRequest, + response: BaseResponse, + userContext: UserContext + ): Promise; } export interface InvalidClaimErrorHandlerMiddleware { - (validatorErrors: ClaimValidationError[], request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; + ( + validatorErrors: ClaimValidationError[], + request: BaseRequest, + response: BaseResponse, + userContext: UserContext + ): Promise; } export interface NormalisedErrorHandlers { onUnauthorised: ErrorHandlerMiddleware; @@ -115,7 +134,11 @@ export interface VerifySessionOptions { antiCsrfCheck?: boolean; sessionRequired?: boolean; checkDatabase?: boolean; - overrideGlobalClaimValidators?: (globalClaimValidators: SessionClaimValidator[], session: SessionContainerInterface, userContext: UserContext) => Promise | SessionClaimValidator[]; + overrideGlobalClaimValidators?: ( + globalClaimValidators: SessionClaimValidator[], + session: SessionContainerInterface, + userContext: UserContext + ) => Promise | SessionClaimValidator[]; } export declare type RecipeInterface = { createNewSession(input: { @@ -171,14 +194,8 @@ export declare type RecipeInterface = { fetchAcrossAllTenants?: boolean; userContext: UserContext; }): Promise; - revokeSession(input: { - sessionHandle: string; - userContext: UserContext; - }): Promise; - revokeMultipleSessions(input: { - sessionHandles: string[]; - userContext: UserContext; - }): Promise; + revokeSession(input: { sessionHandle: string; userContext: UserContext }): Promise; + revokeMultipleSessions(input: { sessionHandles: string[]; userContext: UserContext }): Promise; updateSessionDataInDatabase(input: { sessionHandle: string; newSessionData: any; @@ -196,21 +213,24 @@ export declare type RecipeInterface = { accessToken: string; newAccessTokenPayload?: any; userContext: UserContext; - }): Promise<{ - status: "OK"; - session: { - handle: string; - userId: string; - recipeUserId: RecipeUserId; - userDataInJWT: any; - tenantId: string; - }; - accessToken?: { - token: string; - expiry: number; - createdTime: number; - }; - } | undefined>; + }): Promise< + | { + status: "OK"; + session: { + handle: string; + userId: string; + recipeUserId: RecipeUserId; + userDataInJWT: any; + tenantId: string; + }; + accessToken?: { + token: string; + expiry: number; + createdTime: number; + }; + } + | undefined + >; validateClaims(input: { userId: string; recipeUserId: RecipeUserId; @@ -236,17 +256,16 @@ export declare type RecipeInterface = { sessionHandle: string; claim: SessionClaim; userContext: UserContext; - }): Promise<{ - status: "SESSION_DOES_NOT_EXIST_ERROR"; - } | { - status: "OK"; - value: T | undefined; - }>; - removeClaim(input: { - sessionHandle: string; - claim: SessionClaim; - userContext: UserContext; - }): Promise; + }): Promise< + | { + status: "SESSION_DOES_NOT_EXIST_ERROR"; + } + | { + status: "OK"; + value: T | undefined; + } + >; + removeClaim(input: { sessionHandle: string; claim: SessionClaim; userContext: UserContext }): Promise; }; export interface SessionContainerInterface { revokeSession(userContext?: Record): Promise; @@ -289,17 +308,21 @@ export declare type APIInterface = { * since it's not something that is directly called by the user on the * frontend anyway */ - refreshPOST: undefined | ((input: { - options: APIOptions; - userContext: UserContext; - }) => Promise); - signOutPOST: undefined | ((input: { - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - } | GeneralErrorResponse>); + refreshPOST: + | undefined + | ((input: { options: APIOptions; userContext: UserContext }) => Promise); + signOutPOST: + | undefined + | ((input: { + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + >); verifySession(input: { verifySessionOptions: VerifySessionOptions | undefined; options: APIOptions; @@ -316,25 +339,30 @@ export declare type SessionInformation = { timeCreated: number; tenantId: string; }; -export declare type ClaimValidationResult = { - isValid: true; -} | { - isValid: false; - reason?: JSONValue; -}; +export declare type ClaimValidationResult = + | { + isValid: true; + } + | { + isValid: false; + reason?: JSONValue; + }; export declare type ClaimValidationError = { id: string; reason?: JSONValue; }; -export declare type SessionClaimValidator = (// We split the type like this to express that either both claim and shouldRefetch is defined or neither. -{ - claim: SessionClaim; - /** - * Decides if we need to refetch the claim value before checking the payload with `isValid`. - * E.g.: if the information in the payload is expired, or is not sufficient for this check. - */ - shouldRefetch: (payload: any, userContext: UserContext) => Promise | boolean; -} | {}) & { +export declare type SessionClaimValidator = ( + | // We split the type like this to express that either both claim and shouldRefetch is defined or neither. + { + claim: SessionClaim; + /** + * Decides if we need to refetch the claim value before checking the payload with `isValid`. + * E.g.: if the information in the payload is expired, or is not sufficient for this check. + */ + shouldRefetch: (payload: any, userContext: UserContext) => Promise | boolean; + } + | {} +) & { id: string; /** * Decides if the claim is valid based on the payload (and not checking DB or anything else) @@ -349,7 +377,13 @@ export declare abstract class SessionClaim { * The undefined return value signifies that we don't want to update the claim payload and or the claim value is not present in the database * This can happen for example with a second factor auth claim, where we don't want to add the claim to the session automatically. */ - abstract fetchValue(userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext): Promise | T | undefined; + abstract fetchValue( + userId: string, + recipeUserId: RecipeUserId, + tenantId: string, + currentPayload: JSONObject | undefined, + userContext: UserContext + ): Promise | T | undefined; /** * Saves the provided value into the payload, by cloning and updating the entire object. * @@ -374,7 +408,13 @@ export declare abstract class SessionClaim { * @returns Claim value */ abstract getValueFromPayload(payload: JSONObject, userContext: UserContext): T | undefined; - build(userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext): Promise; + build( + userId: string, + recipeUserId: RecipeUserId, + tenantId: string, + currentPayload: JSONObject | undefined, + userContext: UserContext + ): Promise; } export declare type ReqResInfo = { res: BaseResponse; diff --git a/lib/build/recipe/session/utils.d.ts b/lib/build/recipe/session/utils.d.ts index f48759c19..24ce87cb5 100644 --- a/lib/build/recipe/session/utils.d.ts +++ b/lib/build/recipe/session/utils.d.ts @@ -1,20 +1,76 @@ // @ts-nocheck -import { TypeInput, TypeNormalisedInput, ClaimValidationError, SessionClaimValidator, SessionContainerInterface, VerifySessionOptions, TokenTransferMethod } from "./types"; +import { + TypeInput, + TypeNormalisedInput, + ClaimValidationError, + SessionClaimValidator, + SessionContainerInterface, + VerifySessionOptions, + TokenTransferMethod, +} from "./types"; import SessionRecipe from "./recipe"; import { NormalisedAppinfo, UserContext } from "../../types"; import type { BaseRequest, BaseResponse } from "../../framework"; import RecipeUserId from "../../recipeUserId"; -export declare function sendTryRefreshTokenResponse(recipeInstance: SessionRecipe, _: string, __: BaseRequest, response: BaseResponse, ___: UserContext): Promise; -export declare function sendUnauthorisedResponse(recipeInstance: SessionRecipe, _: string, __: BaseRequest, response: BaseResponse, ___: UserContext): Promise; -export declare function sendInvalidClaimResponse(recipeInstance: SessionRecipe, claimValidationErrors: ClaimValidationError[], __: BaseRequest, response: BaseResponse, ___: UserContext): Promise; -export declare function sendTokenTheftDetectedResponse(recipeInstance: SessionRecipe, sessionHandle: string, _: string, __: RecipeUserId, ___: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; +export declare function sendTryRefreshTokenResponse( + recipeInstance: SessionRecipe, + _: string, + __: BaseRequest, + response: BaseResponse, + ___: UserContext +): Promise; +export declare function sendUnauthorisedResponse( + recipeInstance: SessionRecipe, + _: string, + __: BaseRequest, + response: BaseResponse, + ___: UserContext +): Promise; +export declare function sendInvalidClaimResponse( + recipeInstance: SessionRecipe, + claimValidationErrors: ClaimValidationError[], + __: BaseRequest, + response: BaseResponse, + ___: UserContext +): Promise; +export declare function sendTokenTheftDetectedResponse( + recipeInstance: SessionRecipe, + sessionHandle: string, + _: string, + __: RecipeUserId, + ___: BaseRequest, + response: BaseResponse, + userContext: UserContext +): Promise; export declare function normaliseSessionScopeOrThrowError(sessionScope: string): string; export declare function getURLProtocol(url: string): string; -export declare function validateAndNormaliseUserInput(recipeInstance: SessionRecipe, appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + recipeInstance: SessionRecipe, + appInfo: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; export declare function normaliseSameSiteOrThrowError(sameSite: string): "strict" | "lax" | "none"; -export declare function setAccessTokenInResponse(res: BaseResponse, accessToken: string, frontToken: string, config: TypeNormalisedInput, transferMethod: TokenTransferMethod, req: BaseRequest | undefined, userContext: UserContext): void; -export declare function getRequiredClaimValidators(session: SessionContainerInterface, overrideGlobalClaimValidators: VerifySessionOptions["overrideGlobalClaimValidators"], userContext: UserContext): Promise; -export declare function validateClaimsInPayload(claimValidators: SessionClaimValidator[], newAccessTokenPayload: any, userContext: UserContext): Promise<{ - id: string; - reason: import("../../types").JSONValue; -}[]>; +export declare function setAccessTokenInResponse( + res: BaseResponse, + accessToken: string, + frontToken: string, + config: TypeNormalisedInput, + transferMethod: TokenTransferMethod, + req: BaseRequest | undefined, + userContext: UserContext +): void; +export declare function getRequiredClaimValidators( + session: SessionContainerInterface, + overrideGlobalClaimValidators: VerifySessionOptions["overrideGlobalClaimValidators"], + userContext: UserContext +): Promise; +export declare function validateClaimsInPayload( + claimValidators: SessionClaimValidator[], + newAccessTokenPayload: any, + userContext: UserContext +): Promise< + { + id: string; + reason: import("../../types").JSONValue; + }[] +>; diff --git a/lib/build/recipe/session/utils.js b/lib/build/recipe/session/utils.js index ef0387d79..e4bfc3e2b 100644 --- a/lib/build/recipe/session/utils.js +++ b/lib/build/recipe/session/utils.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateClaimsInPayload = exports.getRequiredClaimValidators = exports.setAccessTokenInResponse = exports.normaliseSameSiteOrThrowError = exports.validateAndNormaliseUserInput = exports.getURLProtocol = exports.normaliseSessionScopeOrThrowError = exports.sendTokenTheftDetectedResponse = exports.sendInvalidClaimResponse = exports.sendUnauthorisedResponse = exports.sendTryRefreshTokenResponse = void 0; const cookieAndHeaders_1 = require("./cookieAndHeaders"); @@ -26,7 +28,11 @@ const utils_1 = require("../../utils"); const utils_2 = require("../../utils"); const logger_1 = require("../../logger"); async function sendTryRefreshTokenResponse(recipeInstance, _, __, response, ___) { - utils_2.sendNon200ResponseWithMessage(response, "try refresh token", recipeInstance.config.sessionExpiredStatusCode); + utils_2.sendNon200ResponseWithMessage( + response, + "try refresh token", + recipeInstance.config.sessionExpiredStatusCode + ); } exports.sendTryRefreshTokenResponse = sendTryRefreshTokenResponse; async function sendUnauthorisedResponse(recipeInstance, _, __, response, ___) { @@ -42,7 +48,11 @@ async function sendInvalidClaimResponse(recipeInstance, claimValidationErrors, _ exports.sendInvalidClaimResponse = sendInvalidClaimResponse; async function sendTokenTheftDetectedResponse(recipeInstance, sessionHandle, _, __, ___, response, userContext) { await recipeInstance.recipeInterfaceImpl.revokeSession({ sessionHandle, userContext }); - utils_2.sendNon200ResponseWithMessage(response, "token theft detected", recipeInstance.config.sessionExpiredStatusCode); + utils_2.sendNon200ResponseWithMessage( + response, + "token theft detected", + recipeInstance.config.sessionExpiredStatusCode + ); } exports.sendTokenTheftDetectedResponse = sendTokenTheftDetectedResponse; function normaliseSessionScopeOrThrowError(sessionScope) { @@ -59,8 +69,7 @@ function normaliseSessionScopeOrThrowError(sessionScope) { let urlObj = new URL(sessionScope); sessionScope = urlObj.hostname; return sessionScope; - } - catch (err) { + } catch (err) { throw new Error("Please provide a valid sessionScope"); } } @@ -81,23 +90,30 @@ function getURLProtocol(url) { exports.getURLProtocol = getURLProtocol; function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { var _a, _b, _c, _d; - let cookieDomain = config === undefined || config.cookieDomain === undefined - ? undefined - : normaliseSessionScopeOrThrowError(config.cookieDomain); - let olderCookieDomain = config === undefined || config.olderCookieDomain === undefined || config.olderCookieDomain === "" - ? config === null || config === void 0 ? void 0 : config.olderCookieDomain - : normaliseSessionScopeOrThrowError(config.olderCookieDomain); - let accessTokenPath = config === undefined || config.accessTokenPath === undefined - ? new normalisedURLPath_1.default("/") - : new normalisedURLPath_1.default(config.accessTokenPath); + let cookieDomain = + config === undefined || config.cookieDomain === undefined + ? undefined + : normaliseSessionScopeOrThrowError(config.cookieDomain); + let olderCookieDomain = + config === undefined || config.olderCookieDomain === undefined || config.olderCookieDomain === "" + ? config === null || config === void 0 + ? void 0 + : config.olderCookieDomain + : normaliseSessionScopeOrThrowError(config.olderCookieDomain); + let accessTokenPath = + config === undefined || config.accessTokenPath === undefined + ? new normalisedURLPath_1.default("/") + : new normalisedURLPath_1.default(config.accessTokenPath); let protocolOfAPIDomain = getURLProtocol(appInfo.apiDomain.getAsStringDangerous()); let cookieSameSite = (input) => { - let protocolOfWebsiteDomain = getURLProtocol(appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous()); + let protocolOfWebsiteDomain = getURLProtocol( + appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + ); return appInfo.topLevelAPIDomain !== appInfo.getTopLevelWebsiteDomain(input) || protocolOfAPIDomain !== protocolOfWebsiteDomain ? "none" @@ -107,11 +123,16 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { let normalisedCookieSameSite = normaliseSameSiteOrThrowError(config.cookieSameSite); cookieSameSite = () => normalisedCookieSameSite; } - let cookieSecure = config === undefined || config.cookieSecure === undefined - ? appInfo.apiDomain.getAsStringDangerous().startsWith("https") - : config.cookieSecure; - let sessionExpiredStatusCode = config === undefined || config.sessionExpiredStatusCode === undefined ? 401 : config.sessionExpiredStatusCode; - const invalidClaimStatusCode = (_a = config === null || config === void 0 ? void 0 : config.invalidClaimStatusCode) !== null && _a !== void 0 ? _a : 403; + let cookieSecure = + config === undefined || config.cookieSecure === undefined + ? appInfo.apiDomain.getAsStringDangerous().startsWith("https") + : config.cookieSecure; + let sessionExpiredStatusCode = + config === undefined || config.sessionExpiredStatusCode === undefined ? 401 : config.sessionExpiredStatusCode; + const invalidClaimStatusCode = + (_a = config === null || config === void 0 ? void 0 : config.invalidClaimStatusCode) !== null && _a !== void 0 + ? _a + : 403; if (sessionExpiredStatusCode === invalidClaimStatusCode) { throw new Error("sessionExpiredStatusCode and sessionExpiredStatusCode must be different"); } @@ -120,7 +141,7 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { throw new Error("antiCsrf config must be one of 'NONE' or 'VIA_CUSTOM_HEADER' or 'VIA_TOKEN'"); } } - let antiCsrf = ({ request, userContext, }) => { + let antiCsrf = ({ request, userContext }) => { const sameSite = cookieSameSite({ request, userContext, @@ -135,7 +156,15 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { } let errorHandlers = { onTokenTheftDetected: async (sessionHandle, userId, recipeUserId, request, response, userContext) => { - return await sendTokenTheftDetectedResponse(recipeInstance, sessionHandle, userId, recipeUserId, request, response, userContext); + return await sendTokenTheftDetectedResponse( + recipeInstance, + sessionHandle, + userId, + recipeUserId, + request, + response, + userContext + ); }, onTryRefreshToken: async (message, request, response, userContext) => { return await sendTryRefreshTokenResponse(recipeInstance, message, request, response, userContext); @@ -167,15 +196,31 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { errorHandlers.onClearDuplicateSessionCookies = config.errorHandlers.onClearDuplicateSessionCookies; } } - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { - useDynamicAccessTokenSigningKey: (_b = config === null || config === void 0 ? void 0 : config.useDynamicAccessTokenSigningKey) !== null && _b !== void 0 ? _b : true, - exposeAccessTokenToFrontendInCookieBasedAuth: (_c = config === null || config === void 0 ? void 0 : config.exposeAccessTokenToFrontendInCookieBasedAuth) !== null && _c !== void 0 ? _c : false, + useDynamicAccessTokenSigningKey: + (_b = config === null || config === void 0 ? void 0 : config.useDynamicAccessTokenSigningKey) !== null && + _b !== void 0 + ? _b + : true, + exposeAccessTokenToFrontendInCookieBasedAuth: + (_c = + config === null || config === void 0 ? void 0 : config.exposeAccessTokenToFrontendInCookieBasedAuth) !== + null && _c !== void 0 + ? _c + : false, refreshTokenPath: appInfo.apiBasePath.appendPath(new normalisedURLPath_1.default(constants_1.REFRESH_API_PATH)), accessTokenPath, - getTokenTransferMethod: (config === null || config === void 0 ? void 0 : config.getTokenTransferMethod) === undefined - ? defaultGetTokenTransferMethod - : config.getTokenTransferMethod, + getTokenTransferMethod: + (config === null || config === void 0 ? void 0 : config.getTokenTransferMethod) === undefined + ? defaultGetTokenTransferMethod + : config.getTokenTransferMethod, cookieDomain, olderCookieDomain, getCookieSameSite: cookieSameSite, @@ -185,8 +230,13 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { antiCsrfFunctionOrString: antiCsrf, override, invalidClaimStatusCode, - overwriteSessionDuringSignInUp: config === null || config === void 0 ? void 0 : config.overwriteSessionDuringSignInUp, - jwksRefreshIntervalSec: (_d = config === null || config === void 0 ? void 0 : config.jwksRefreshIntervalSec) !== null && _d !== void 0 ? _d : 3600 * 4, + overwriteSessionDuringSignInUp: + config === null || config === void 0 ? void 0 : config.overwriteSessionDuringSignInUp, + jwksRefreshIntervalSec: + (_d = config === null || config === void 0 ? void 0 : config.jwksRefreshIntervalSec) !== null && + _d !== void 0 + ? _d + : 3600 * 4, }; } exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; @@ -201,31 +251,51 @@ function normaliseSameSiteOrThrowError(sameSite) { exports.normaliseSameSiteOrThrowError = normaliseSameSiteOrThrowError; function setAccessTokenInResponse(res, accessToken, frontToken, config, transferMethod, req, userContext) { cookieAndHeaders_1.setFrontTokenInHeaders(res, frontToken); - cookieAndHeaders_1.setToken(config, res, "access", accessToken, - // We set the expiration to 1 year, because we can't really access the expiration of the refresh token everywhere we are setting it. - // This should be safe to do, since this is only the validity of the cookie (set here or on the frontend) but we check the expiration of the JWT anyway. - // Even if the token is expired the presence of the token indicates that the user could have a valid refresh token - // Some browsers now cap the maximum expiry at 400 days, so we set it to 1 year, which should suffice. - Date.now() + constants_1.oneYearInMs, transferMethod, req, userContext); - if (config.exposeAccessTokenToFrontendInCookieBasedAuth && transferMethod === "cookie") { - cookieAndHeaders_1.setToken(config, res, "access", accessToken, - // We set the expiration to 1 years, because we can't really access the expiration of the refresh token everywhere we are setting it. + cookieAndHeaders_1.setToken( + config, + res, + "access", + accessToken, + // We set the expiration to 1 year, because we can't really access the expiration of the refresh token everywhere we are setting it. // This should be safe to do, since this is only the validity of the cookie (set here or on the frontend) but we check the expiration of the JWT anyway. // Even if the token is expired the presence of the token indicates that the user could have a valid refresh token // Some browsers now cap the maximum expiry at 400 days, so we set it to 1 year, which should suffice. - Date.now() + constants_1.oneYearInMs, "header", req, userContext); + Date.now() + constants_1.oneYearInMs, + transferMethod, + req, + userContext + ); + if (config.exposeAccessTokenToFrontendInCookieBasedAuth && transferMethod === "cookie") { + cookieAndHeaders_1.setToken( + config, + res, + "access", + accessToken, + // We set the expiration to 1 years, because we can't really access the expiration of the refresh token everywhere we are setting it. + // This should be safe to do, since this is only the validity of the cookie (set here or on the frontend) but we check the expiration of the JWT anyway. + // Even if the token is expired the presence of the token indicates that the user could have a valid refresh token + // Some browsers now cap the maximum expiry at 400 days, so we set it to 1 year, which should suffice. + Date.now() + constants_1.oneYearInMs, + "header", + req, + userContext + ); } } exports.setAccessTokenInResponse = setAccessTokenInResponse; async function getRequiredClaimValidators(session, overrideGlobalClaimValidators, userContext) { - const claimValidatorsAddedByOtherRecipes = recipe_1.default.getInstanceOrThrowError().getClaimValidatorsAddedByOtherRecipes(); - const globalClaimValidators = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getGlobalClaimValidators({ - userId: session.getUserId(userContext), - recipeUserId: session.getRecipeUserId(userContext), - tenantId: session.getTenantId(userContext), - claimValidatorsAddedByOtherRecipes, - userContext, - }); + const claimValidatorsAddedByOtherRecipes = recipe_1.default + .getInstanceOrThrowError() + .getClaimValidatorsAddedByOtherRecipes(); + const globalClaimValidators = await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.getGlobalClaimValidators({ + userId: session.getUserId(userContext), + recipeUserId: session.getRecipeUserId(userContext), + tenantId: session.getTenantId(userContext), + claimValidatorsAddedByOtherRecipes, + userContext, + }); return overrideGlobalClaimValidators !== undefined ? await overrideGlobalClaimValidators(globalClaimValidators, session, userContext) : globalClaimValidators; @@ -235,7 +305,9 @@ async function validateClaimsInPayload(claimValidators, newAccessTokenPayload, u const validationErrors = []; for (const validator of claimValidators) { const claimValidationResult = await validator.validate(newAccessTokenPayload, userContext); - logger_1.logDebugMessage("validateClaimsInPayload " + validator.id + " validation res " + JSON.stringify(claimValidationResult)); + logger_1.logDebugMessage( + "validateClaimsInPayload " + validator.id + " validation res " + JSON.stringify(claimValidationResult) + ); if (!claimValidationResult.isValid) { validationErrors.push({ id: validator.id, @@ -246,7 +318,7 @@ async function validateClaimsInPayload(claimValidators, newAccessTokenPayload, u return validationErrors; } exports.validateClaimsInPayload = validateClaimsInPayload; -function defaultGetTokenTransferMethod({ req, forCreateNewSession, }) { +function defaultGetTokenTransferMethod({ req, forCreateNewSession }) { // We allow fallback (checking headers then cookies) by default when validating if (!forCreateNewSession) { return "any"; diff --git a/lib/build/recipe/thirdparty/api/appleRedirect.d.ts b/lib/build/recipe/thirdparty/api/appleRedirect.d.ts index e1011ab6d..ae0e54770 100644 --- a/lib/build/recipe/thirdparty/api/appleRedirect.d.ts +++ b/lib/build/recipe/thirdparty/api/appleRedirect.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function appleRedirectHandler(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function appleRedirectHandler( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/thirdparty/api/authorisationUrl.d.ts b/lib/build/recipe/thirdparty/api/authorisationUrl.d.ts index 95ae35f51..dac53edea 100644 --- a/lib/build/recipe/thirdparty/api/authorisationUrl.d.ts +++ b/lib/build/recipe/thirdparty/api/authorisationUrl.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function authorisationUrlAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function authorisationUrlAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/thirdparty/api/authorisationUrl.js b/lib/build/recipe/thirdparty/api/authorisationUrl.js index 4e8af7c1a..b5f572f4f 100644 --- a/lib/build/recipe/thirdparty/api/authorisationUrl.js +++ b/lib/build/recipe/thirdparty/api/authorisationUrl.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/thirdparty/api/implementation.js b/lib/build/recipe/thirdparty/api/implementation.js index a2e76c254..1fed7ca95 100644 --- a/lib/build/recipe/thirdparty/api/implementation.js +++ b/lib/build/recipe/thirdparty/api/implementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const emailverification_1 = __importDefault(require("../../emailverification")); const recipe_1 = __importDefault(require("../../emailverification/recipe")); @@ -19,13 +21,19 @@ function getAPIInterface() { }, 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)", + 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)", + 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; @@ -35,11 +43,9 @@ function getAPIInterface() { redirectURIInfo: input.redirectURIInfo, userContext, }); - } - else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { + } else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { oAuthTokensToUse = input.oAuthTokens; - } - else { + } else { throw Error("should never come here"); } const userInfo = await provider.getUserInfo({ oAuthTokens: oAuthTokensToUse, userContext }); @@ -64,19 +70,21 @@ function getAPIInterface() { // We essentially did this above when calling exchangeAuthCodeForOAuthTokens return true; }; - const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ - accountInfo: { - thirdParty: { - userId: userInfo.thirdPartyUserId, - id: provider.id, + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( + { + accountInfo: { + thirdParty: { + userId: userInfo.thirdPartyUserId, + id: provider.id, + }, }, - }, - recipeId, - userContext: input.userContext, - session: input.session, - tenantId, - checkCredentialsOnTenant, - }); + 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 @@ -91,7 +99,11 @@ function getAPIInterface() { // So we just check that as well before calling isEmailChangeAllowed const recipeUserId = authenticatingUser.loginMethod.recipeUserId; if (!emailInfo.isVerified && recipe_1.default.getInstance() !== undefined) { - emailInfo.isVerified = await emailverification_1.default.isEmailVerified(recipeUserId, emailInfo.id, userContext); + emailInfo.isVerified = await emailverification_1.default.isEmailVerified( + recipeUserId, + emailInfo.id, + userContext + ); } } const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ @@ -103,7 +115,8 @@ function getAPIInterface() { id: provider.id, }, }, - authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + authenticatingUser: + authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, factorIds: ["thirdparty"], isSignUp, isVerified: emailInfo.isVerified, @@ -119,10 +132,16 @@ function getAPIInterface() { shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { - logger_1.logDebugMessage("signInUpPOST: erroring out because preAuthChecks returned " + preAuthChecks.status); + 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"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthChecks, + errorCodeMap, + "SIGN_IN_UP_NOT_ALLOWED" + ); } let response = await options.recipeImplementation.signInUp({ thirdPartyId: provider.id, @@ -142,7 +161,11 @@ function getAPIInterface() { } if (response.status !== "OK") { logger_1.logDebugMessage("signInUpPOST: erroring out because signInUp returned " + response.status); - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(response, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); + 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 @@ -162,8 +185,14 @@ function getAPIInterface() { session: input.session, }); if (postAuthChecks.status !== "OK") { - logger_1.logDebugMessage("signInUpPOST: erroring out because postAuthChecks returned " + postAuthChecks.status); - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); + 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", diff --git a/lib/build/recipe/thirdparty/api/signinup.d.ts b/lib/build/recipe/thirdparty/api/signinup.d.ts index 0415d8bdc..06f84bb63 100644 --- a/lib/build/recipe/thirdparty/api/signinup.d.ts +++ b/lib/build/recipe/thirdparty/api/signinup.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function signInUpAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function signInUpAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/thirdparty/api/signinup.js b/lib/build/recipe/thirdparty/api/signinup.js index fb75af068..772fd4bcd 100644 --- a/lib/build/recipe/thirdparty/api/signinup.js +++ b/lib/build/recipe/thirdparty/api/signinup.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../error")); const utils_1 = require("../../../utils"); @@ -43,11 +45,9 @@ async function signInUpAPI(apiImplementation, tenantId, options, userContext) { }); } redirectURIInfo = bodyParams.redirectURIInfo; - } - else if (bodyParams.oAuthTokens !== undefined) { + } else if (bodyParams.oAuthTokens !== undefined) { oAuthTokens = bodyParams.oAuthTokens; - } - else { + } else { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "Please provide one of redirectURIInfo or oAuthTokens in the request body", @@ -66,8 +66,16 @@ async function signInUpAPI(apiImplementation, tenantId, options, userContext) { }); } const provider = providerResponse; - const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, bodyParams); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( + options.req, + bodyParams + ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -82,9 +90,14 @@ async function signInUpAPI(apiImplementation, tenantId, options, userContext) { userContext, }); if (result.status === "OK") { - utils_1.send200Response(options.res, Object.assign({ status: result.status }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); - } - else { + utils_1.send200Response( + options.res, + Object.assign( + { status: result.status }, + utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext) + ) + ); + } else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/thirdparty/error.d.ts b/lib/build/recipe/thirdparty/error.d.ts index c65582222..89e4828d2 100644 --- a/lib/build/recipe/thirdparty/error.d.ts +++ b/lib/build/recipe/thirdparty/error.d.ts @@ -1,14 +1,8 @@ // @ts-nocheck import STError from "../../error"; export default class ThirdPartyError extends STError { - constructor(options: { - type: "BAD_INPUT_ERROR"; - message: string; - }); + constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); } export declare class ClientTypeNotFoundError extends STError { - constructor(options: { - type: "CLIENT_TYPE_NOT_FOUND_ERROR"; - message: string; - }); + constructor(options: { type: "CLIENT_TYPE_NOT_FOUND_ERROR"; message: string }); } diff --git a/lib/build/recipe/thirdparty/error.js b/lib/build/recipe/thirdparty/error.js index 3624b9ffc..8e62fdaab 100644 --- a/lib/build/recipe/thirdparty/error.js +++ b/lib/build/recipe/thirdparty/error.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClientTypeNotFoundError = void 0; const error_1 = __importDefault(require("../../error")); diff --git a/lib/build/recipe/thirdparty/index.d.ts b/lib/build/recipe/thirdparty/index.d.ts index 3e318201b..8fdd56cac 100644 --- a/lib/build/recipe/thirdparty/index.d.ts +++ b/lib/build/recipe/thirdparty/index.d.ts @@ -8,34 +8,68 @@ import RecipeUserId from "../../recipeUserId"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static getProvider(tenantId: string, thirdPartyId: string, clientType: string | undefined, userContext?: Record): Promise; - static manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record): Promise<{ - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - }>; - static manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainerInterface, userContext?: Record): Promise<{ - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } | { - 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"; - }>; + static getProvider( + tenantId: string, + thirdPartyId: string, + clientType: string | undefined, + userContext?: Record + ): Promise; + static manuallyCreateOrUpdateUser( + tenantId: string, + thirdPartyId: string, + thirdPartyUserId: string, + email: string, + isVerified: boolean, + session?: undefined, + userContext?: Record + ): Promise< + | { + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + >; + static manuallyCreateOrUpdateUser( + tenantId: string, + thirdPartyId: string, + thirdPartyUserId: string, + email: string, + isVerified: boolean, + session: SessionContainerInterface, + userContext?: Record + ): Promise< + | { + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + | { + 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"; + } + >; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/thirdparty/index.js b/lib/build/recipe/thirdparty/index.js index 76abeadc6..2ee060ce9 100644 --- a/lib/build/recipe/thirdparty/index.js +++ b/lib/build/recipe/thirdparty/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.manuallyCreateOrUpdateUser = exports.getProvider = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -31,7 +33,15 @@ class Wrapper { userContext: utils_1.getUserContext(userContext), }); } - static async manuallyCreateOrUpdateUser(tenantId, thirdPartyId, thirdPartyUserId, email, isVerified, session, userContext) { + static async manuallyCreateOrUpdateUser( + tenantId, + thirdPartyId, + thirdPartyUserId, + email, + isVerified, + session, + userContext + ) { return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.manuallyCreateOrUpdateUser({ thirdPartyId, thirdPartyUserId, diff --git a/lib/build/recipe/thirdparty/providers/activeDirectory.js b/lib/build/recipe/thirdparty/providers/activeDirectory.js index 2054e76ff..bb48e32c7 100644 --- a/lib/build/recipe/thirdparty/providers/activeDirectory.js +++ b/lib/build/recipe/thirdparty/providers/activeDirectory.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); const utils_1 = require("./utils"); @@ -33,7 +35,9 @@ function ActiveDirectory(input) { } if (config.oidcDiscoveryEndpoint !== undefined) { // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( + config.oidcDiscoveryEndpoint + ); } if (config.scope === undefined) { config.scope = ["openid", "email"]; diff --git a/lib/build/recipe/thirdparty/providers/apple.js b/lib/build/recipe/thirdparty/providers/apple.js index 815f94081..9f86cb31a 100644 --- a/lib/build/recipe/thirdparty/providers/apple.js +++ b/lib/build/recipe/thirdparty/providers/apple.js @@ -13,25 +13,42 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importStar(require("./custom")); const jose = __importStar(require("jose")); @@ -55,7 +72,10 @@ function Apple(input) { if (input.config.oidcDiscoveryEndpoint === undefined) { input.config.oidcDiscoveryEndpoint = "https://appleid.apple.com/.well-known/openid-configuration"; } - input.config.authorizationEndpointQueryParams = Object.assign({ response_mode: "form_post" }, input.config.authorizationEndpointQueryParams); + input.config.authorizationEndpointQueryParams = Object.assign( + { response_mode: "form_post" }, + input.config.authorizationEndpointQueryParams + ); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; @@ -65,16 +85,27 @@ function Apple(input) { config.scope = ["openid", "email"]; } if (config.clientSecret === undefined) { - if (config.additionalConfig === undefined || + if ( + config.additionalConfig === undefined || config.additionalConfig.keyId === undefined || config.additionalConfig.teamId === undefined || - config.additionalConfig.privateKey === undefined) { - throw new Error("Please ensure that keyId, teamId and privateKey are provided in the additionalConfig"); + config.additionalConfig.privateKey === undefined + ) { + throw new Error( + "Please ensure that keyId, teamId and privateKey are provided in the additionalConfig" + ); } - config.clientSecret = await getClientSecret(config.clientId, config.additionalConfig.keyId, config.additionalConfig.teamId, config.additionalConfig.privateKey); + config.clientSecret = await getClientSecret( + config.clientId, + config.additionalConfig.keyId, + config.additionalConfig.teamId, + config.additionalConfig.privateKey + ); } // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( + config.oidcDiscoveryEndpoint + ); return config; }; const oExchangeAuthCodeForOAuthTokens = originalImplementation.exchangeAuthCodeForOAuthTokens; @@ -84,8 +115,7 @@ function Apple(input) { if (user !== undefined) { if (typeof user === "string") { response.user = JSON.parse(user); - } - else if (typeof user === "object") { + } else if (typeof user === "object") { response.user = user; } } @@ -98,10 +128,35 @@ function Apple(input) { const user = input.oAuthTokens.user; if (user !== undefined) { if (typeof user === "string") { - response.rawUserInfoFromProvider = Object.assign(Object.assign({}, response.rawUserInfoFromProvider), { fromIdTokenPayload: Object.assign(Object.assign({}, (_a = response.rawUserInfoFromProvider) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload), { user: JSON.parse(user) }) }); - } - else if (typeof user === "object") { - response.rawUserInfoFromProvider = Object.assign(Object.assign({}, response.rawUserInfoFromProvider), { fromIdTokenPayload: Object.assign(Object.assign({}, (_b = response.rawUserInfoFromProvider) === null || _b === void 0 ? void 0 : _b.fromIdTokenPayload), { user }) }); + response.rawUserInfoFromProvider = Object.assign( + Object.assign({}, response.rawUserInfoFromProvider), + { + fromIdTokenPayload: Object.assign( + Object.assign( + {}, + (_a = response.rawUserInfoFromProvider) === null || _a === void 0 + ? void 0 + : _a.fromIdTokenPayload + ), + { user: JSON.parse(user) } + ), + } + ); + } else if (typeof user === "object") { + response.rawUserInfoFromProvider = Object.assign( + Object.assign({}, response.rawUserInfoFromProvider), + { + fromIdTokenPayload: Object.assign( + Object.assign( + {}, + (_b = response.rawUserInfoFromProvider) === null || _b === void 0 + ? void 0 + : _b.fromIdTokenPayload + ), + { user } + ), + } + ); } } return response; diff --git a/lib/build/recipe/thirdparty/providers/bitbucket.js b/lib/build/recipe/thirdparty/providers/bitbucket.js index b659ce7d2..6c5a60d23 100644 --- a/lib/build/recipe/thirdparty/providers/bitbucket.js +++ b/lib/build/recipe/thirdparty/providers/bitbucket.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); const custom_1 = __importDefault(require("./custom")); @@ -57,16 +59,32 @@ function Bitbucket(input) { fromUserInfoAPI: {}, fromIdTokenPayload: {}, }; - const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest("https://api.bitbucket.org/2.0/user", undefined, headers); + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( + "https://api.bitbucket.org/2.0/user", + undefined, + headers + ); if (userInfoFromAccessToken.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); - throw new Error(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); + throw new Error( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); } rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; - const userInfoFromEmail = await thirdpartyUtils_1.doGetRequest("https://api.bitbucket.org/2.0/user/emails", undefined, headers); + const userInfoFromEmail = await thirdpartyUtils_1.doGetRequest( + "https://api.bitbucket.org/2.0/user/emails", + undefined, + headers + ); if (userInfoFromEmail.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${userInfoFromEmail.status} and body ${userInfoFromEmail.stringResponse}`); - throw new Error(`Received response with status ${userInfoFromEmail.status} and body ${userInfoFromEmail.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${userInfoFromEmail.status} and body ${userInfoFromEmail.stringResponse}` + ); + throw new Error( + `Received response with status ${userInfoFromEmail.status} and body ${userInfoFromEmail.stringResponse}` + ); } rawUserInfoFromProvider.fromUserInfoAPI.email = userInfoFromEmail.jsonResponse; let email = undefined; @@ -79,12 +97,13 @@ function Bitbucket(input) { } return { thirdPartyUserId: rawUserInfoFromProvider.fromUserInfoAPI.uuid, - email: email === undefined - ? undefined - : { - id: email, - isVerified, - }, + email: + email === undefined + ? undefined + : { + id: email, + isVerified, + }, rawUserInfoFromProvider, }; }; diff --git a/lib/build/recipe/thirdparty/providers/boxySaml.js b/lib/build/recipe/thirdparty/providers/boxySaml.js index b42dbdc7c..3aed7692b 100644 --- a/lib/build/recipe/thirdparty/providers/boxySaml.js +++ b/lib/build/recipe/thirdparty/providers/boxySaml.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); function BoxySAML(input) { @@ -23,7 +25,12 @@ function BoxySAML(input) { if (input.config.name === undefined) { input.config.name = "SAML"; } - input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { fromUserInfoAPI: Object.assign({ userId: "id", email: "email" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) }); + input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { + fromUserInfoAPI: Object.assign( + { userId: "id", email: "email" }, + (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI + ), + }); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; diff --git a/lib/build/recipe/thirdparty/providers/configUtils.d.ts b/lib/build/recipe/thirdparty/providers/configUtils.d.ts index 5ec42b1ab..c4e9bbe88 100644 --- a/lib/build/recipe/thirdparty/providers/configUtils.d.ts +++ b/lib/build/recipe/thirdparty/providers/configUtils.d.ts @@ -1,7 +1,25 @@ // @ts-nocheck import { UserContext } from "../../../types"; -import { ProviderClientConfig, ProviderConfig, ProviderConfigForClientType, ProviderInput, TypeProvider } from "../types"; -export declare function getProviderConfigForClient(providerConfig: ProviderConfig, clientConfig: ProviderClientConfig): ProviderConfigForClientType; -export declare function findAndCreateProviderInstance(providers: ProviderInput[], thirdPartyId: string, clientType: string | undefined, userContext: UserContext): Promise; +import { + ProviderClientConfig, + ProviderConfig, + ProviderConfigForClientType, + ProviderInput, + TypeProvider, +} from "../types"; +export declare function getProviderConfigForClient( + providerConfig: ProviderConfig, + clientConfig: ProviderClientConfig +): ProviderConfigForClientType; +export declare function findAndCreateProviderInstance( + providers: ProviderInput[], + thirdPartyId: string, + clientType: string | undefined, + userContext: UserContext +): Promise; export declare function mergeConfig(staticConfig: ProviderConfig, coreConfig: ProviderConfig): ProviderConfig; -export declare function mergeProvidersFromCoreAndStatic(providerConfigsFromCore: ProviderConfig[], providerInputsFromStatic: ProviderInput[], includeAllProviders: boolean): ProviderInput[]; +export declare function mergeProvidersFromCoreAndStatic( + providerConfigsFromCore: ProviderConfig[], + providerInputsFromStatic: ProviderInput[], + includeAllProviders: boolean +): ProviderInput[]; diff --git a/lib/build/recipe/thirdparty/providers/configUtils.js b/lib/build/recipe/thirdparty/providers/configUtils.js index b70052163..f0be656c4 100644 --- a/lib/build/recipe/thirdparty/providers/configUtils.js +++ b/lib/build/recipe/thirdparty/providers/configUtils.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeProvidersFromCoreAndStatic = exports.mergeConfig = exports.findAndCreateProviderInstance = exports.getProviderConfigForClient = void 0; const _1 = require("."); @@ -34,41 +36,29 @@ function createProvider(input) { const clonedInput = Object.assign({}, input); if (clonedInput.config.thirdPartyId.startsWith("active-directory")) { return _1.ActiveDirectory(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("apple")) { + } else if (clonedInput.config.thirdPartyId.startsWith("apple")) { return _1.Apple(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("bitbucket")) { + } else if (clonedInput.config.thirdPartyId.startsWith("bitbucket")) { return _1.Bitbucket(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("discord")) { + } else if (clonedInput.config.thirdPartyId.startsWith("discord")) { return _1.Discord(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("facebook")) { + } else if (clonedInput.config.thirdPartyId.startsWith("facebook")) { return _1.Facebook(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("github")) { + } else if (clonedInput.config.thirdPartyId.startsWith("github")) { return _1.Github(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("gitlab")) { + } else if (clonedInput.config.thirdPartyId.startsWith("gitlab")) { return _1.Gitlab(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("google-workspaces")) { + } else if (clonedInput.config.thirdPartyId.startsWith("google-workspaces")) { return _1.GoogleWorkspaces(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("google")) { + } else if (clonedInput.config.thirdPartyId.startsWith("google")) { return _1.Google(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("okta")) { + } else if (clonedInput.config.thirdPartyId.startsWith("okta")) { return _1.Okta(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("linkedin")) { + } else if (clonedInput.config.thirdPartyId.startsWith("linkedin")) { return _1.Linkedin(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("twitter")) { + } else if (clonedInput.config.thirdPartyId.startsWith("twitter")) { return _1.Twitter(clonedInput); - } - else if (clonedInput.config.thirdPartyId.startsWith("boxy-saml")) { + } else if (clonedInput.config.thirdPartyId.startsWith("boxy-saml")) { return _1.BoxySAML(clonedInput); } return custom_1.default(clonedInput); @@ -86,18 +76,31 @@ async function findAndCreateProviderInstance(providers, thirdPartyId, clientType exports.findAndCreateProviderInstance = findAndCreateProviderInstance; function mergeConfig(staticConfig, coreConfig) { var _a, _b, _c, _d; - const result = Object.assign(Object.assign(Object.assign({}, staticConfig), coreConfig), { userInfoMap: { - fromIdTokenPayload: Object.assign(Object.assign({}, (_a = staticConfig.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload), (_b = coreConfig.userInfoMap) === null || _b === void 0 ? void 0 : _b.fromIdTokenPayload), - fromUserInfoAPI: Object.assign(Object.assign({}, (_c = staticConfig.userInfoMap) === null || _c === void 0 ? void 0 : _c.fromUserInfoAPI), (_d = coreConfig.userInfoMap) === null || _d === void 0 ? void 0 : _d.fromUserInfoAPI), - } }); + const result = Object.assign(Object.assign(Object.assign({}, staticConfig), coreConfig), { + userInfoMap: { + fromIdTokenPayload: Object.assign( + Object.assign( + {}, + (_a = staticConfig.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload + ), + (_b = coreConfig.userInfoMap) === null || _b === void 0 ? void 0 : _b.fromIdTokenPayload + ), + fromUserInfoAPI: Object.assign( + Object.assign( + {}, + (_c = staticConfig.userInfoMap) === null || _c === void 0 ? void 0 : _c.fromUserInfoAPI + ), + (_d = coreConfig.userInfoMap) === null || _d === void 0 ? void 0 : _d.fromUserInfoAPI + ), + }, + }); const mergedClients = staticConfig.clients === undefined ? [] : [...staticConfig.clients]; const coreConfigClients = coreConfig.clients === undefined ? [] : coreConfig.clients; for (const client of coreConfigClients) { const index = mergedClients.findIndex((c) => c.clientType === client.clientType); if (index === -1) { mergedClients.push(client); - } - else { + } else { mergedClients[index] = Object.assign({}, client); } } @@ -108,11 +111,12 @@ exports.mergeConfig = mergeConfig; function mergeProvidersFromCoreAndStatic(providerConfigsFromCore, providerInputsFromStatic, includeAllProviders) { const mergedProviders = []; if (providerConfigsFromCore.length === 0) { - for (const config of providerInputsFromStatic.filter((config) => config.includeInNonPublicTenantsByDefault === true || includeAllProviders === true)) { + for (const config of providerInputsFromStatic.filter( + (config) => config.includeInNonPublicTenantsByDefault === true || includeAllProviders === true + )) { mergedProviders.push(config); } - } - else { + } else { for (const providerConfigFromCore of providerConfigsFromCore) { let mergedProviderInput = { config: providerConfigFromCore, diff --git a/lib/build/recipe/thirdparty/providers/custom.js b/lib/build/recipe/thirdparty/providers/custom.js index 4f9eb9c52..ddd2b7a00 100644 --- a/lib/build/recipe/thirdparty/providers/custom.js +++ b/lib/build/recipe/thirdparty/providers/custom.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getActualClientIdFromDevelopmentClientId = exports.isUsingDevelopmentClientId = exports.DEV_OAUTH_REDIRECT_URL = void 0; const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); @@ -44,14 +46,27 @@ function accessField(obj, key) { function getSupertokensUserInfoResultFromRawUserInfo(config, rawUserInfoResponse) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; let thirdPartyUserId = ""; - if (((_b = (_a = config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.userId) !== undefined) { + if ( + ((_b = (_a = config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) === null || + _b === void 0 + ? void 0 + : _b.userId) !== undefined + ) { const userId = accessField(rawUserInfoResponse.fromUserInfoAPI, config.userInfoMap.fromUserInfoAPI.userId); if (userId !== undefined) { thirdPartyUserId = userId; } } - if (((_d = (_c = config.userInfoMap) === null || _c === void 0 ? void 0 : _c.fromIdTokenPayload) === null || _d === void 0 ? void 0 : _d.userId) !== undefined) { - const userId = accessField(rawUserInfoResponse.fromIdTokenPayload, config.userInfoMap.fromIdTokenPayload.userId); + if ( + ((_d = (_c = config.userInfoMap) === null || _c === void 0 ? void 0 : _c.fromIdTokenPayload) === null || + _d === void 0 + ? void 0 + : _d.userId) !== undefined + ) { + const userId = accessField( + rawUserInfoResponse.fromIdTokenPayload, + config.userInfoMap.fromIdTokenPayload.userId + ); if (userId !== undefined) { thirdPartyUserId = userId; } @@ -63,14 +78,27 @@ function getSupertokensUserInfoResultFromRawUserInfo(config, rawUserInfoResponse thirdPartyUserId, }; let email = ""; - if (((_f = (_e = config.userInfoMap) === null || _e === void 0 ? void 0 : _e.fromUserInfoAPI) === null || _f === void 0 ? void 0 : _f.email) !== undefined) { + if ( + ((_f = (_e = config.userInfoMap) === null || _e === void 0 ? void 0 : _e.fromUserInfoAPI) === null || + _f === void 0 + ? void 0 + : _f.email) !== undefined + ) { const emailVal = accessField(rawUserInfoResponse.fromUserInfoAPI, config.userInfoMap.fromUserInfoAPI.email); if (emailVal !== undefined) { email = emailVal; } } - if (((_h = (_g = config.userInfoMap) === null || _g === void 0 ? void 0 : _g.fromIdTokenPayload) === null || _h === void 0 ? void 0 : _h.email) !== undefined) { - const emailVal = accessField(rawUserInfoResponse.fromIdTokenPayload, config.userInfoMap.fromIdTokenPayload.email); + if ( + ((_h = (_g = config.userInfoMap) === null || _g === void 0 ? void 0 : _g.fromIdTokenPayload) === null || + _h === void 0 + ? void 0 + : _h.email) !== undefined + ) { + const emailVal = accessField( + rawUserInfoResponse.fromIdTokenPayload, + config.userInfoMap.fromIdTokenPayload.email + ); if (emailVal !== undefined) { email = emailVal; } @@ -80,14 +108,30 @@ function getSupertokensUserInfoResultFromRawUserInfo(config, rawUserInfoResponse id: email, isVerified: false, }; - if (((_k = (_j = config.userInfoMap) === null || _j === void 0 ? void 0 : _j.fromUserInfoAPI) === null || _k === void 0 ? void 0 : _k.emailVerified) !== undefined) { - const emailVerifiedVal = accessField(rawUserInfoResponse.fromUserInfoAPI, config.userInfoMap.fromUserInfoAPI.emailVerified); + if ( + ((_k = (_j = config.userInfoMap) === null || _j === void 0 ? void 0 : _j.fromUserInfoAPI) === null || + _k === void 0 + ? void 0 + : _k.emailVerified) !== undefined + ) { + const emailVerifiedVal = accessField( + rawUserInfoResponse.fromUserInfoAPI, + config.userInfoMap.fromUserInfoAPI.emailVerified + ); result.email.isVerified = emailVerifiedVal === true || - (typeof emailVerifiedVal === "string" && emailVerifiedVal.toLowerCase() === "true"); + (typeof emailVerifiedVal === "string" && emailVerifiedVal.toLowerCase() === "true"); } - if (((_m = (_l = config.userInfoMap) === null || _l === void 0 ? void 0 : _l.fromIdTokenPayload) === null || _m === void 0 ? void 0 : _m.emailVerified) !== undefined) { - const emailVerifiedVal = accessField(rawUserInfoResponse.fromIdTokenPayload, config.userInfoMap.fromIdTokenPayload.emailVerified); + if ( + ((_m = (_l = config.userInfoMap) === null || _l === void 0 ? void 0 : _l.fromIdTokenPayload) === null || + _m === void 0 + ? void 0 + : _m.emailVerified) !== undefined + ) { + const emailVerifiedVal = accessField( + rawUserInfoResponse.fromIdTokenPayload, + config.userInfoMap.fromIdTokenPayload.emailVerified + ); result.email.isVerified = emailVerifiedVal === true || emailVerifiedVal === "true"; } } @@ -98,8 +142,14 @@ function NewProvider(input) { // These are safe defaults common to most providers. Each provider implementations override these // as necessary input.config.userInfoMap = { - fromIdTokenPayload: Object.assign({ userId: "sub", email: "email", emailVerified: "email_verified" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload), - fromUserInfoAPI: Object.assign({ userId: "sub", email: "email", emailVerified: "email_verified" }, (_b = input.config.userInfoMap) === null || _b === void 0 ? void 0 : _b.fromUserInfoAPI), + fromIdTokenPayload: Object.assign( + { userId: "sub", email: "email", emailVerified: "email_verified" }, + (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromIdTokenPayload + ), + fromUserInfoAPI: Object.assign( + { userId: "sub", email: "email", emailVerified: "email_verified" }, + (_b = input.config.userInfoMap) === null || _b === void 0 ? void 0 : _b.fromUserInfoAPI + ), }; if (input.config.generateFakeEmail === undefined) { input.config.generateFakeEmail = async function ({ thirdPartyUserId }) { @@ -146,8 +196,7 @@ function NewProvider(input) { for (const [key, value] of Object.entries(impl.config.authorizationEndpointQueryParams)) { if (value === null) { delete queryParams[key]; - } - else { + } else { queryParams[key] = value; } } @@ -192,8 +241,7 @@ function NewProvider(input) { for (const key in impl.config.tokenEndpointBodyParams) { if (impl.config.tokenEndpointBodyParams[key] === null) { delete accessTokenAPIParams[key]; - } - else { + } else { accessTokenAPIParams[key] = impl.config.tokenEndpointBodyParams[key]; } } @@ -205,8 +253,12 @@ function NewProvider(input) { /* Transformation needed for dev keys END */ const tokenResponse = await thirdpartyUtils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); if (tokenResponse.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); - throw new Error(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); + throw new Error( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); } return tokenResponse.jsonResponse; }, @@ -221,9 +273,13 @@ function NewProvider(input) { if (jwks === undefined) { jwks = jose_1.createRemoteJWKSet(new URL(impl.config.jwksURI)); } - rawUserInfoFromProvider.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload(idToken, jwks, { - audience: getActualClientIdFromDevelopmentClientId(impl.config.clientId), - }); + rawUserInfoFromProvider.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( + idToken, + jwks, + { + audience: getActualClientIdFromDevelopmentClientId(impl.config.clientId), + } + ); if (impl.config.validateIdTokenPayload !== undefined) { await impl.config.validateIdTokenPayload({ idTokenPayload: rawUserInfoFromProvider.fromIdTokenPayload, @@ -248,8 +304,7 @@ function NewProvider(input) { for (const [key, value] of Object.entries(impl.config.userInfoEndpointHeaders)) { if (value === null) { delete headers[key]; - } - else { + } else { headers[key] = value; } } @@ -258,16 +313,23 @@ function NewProvider(input) { for (const [key, value] of Object.entries(impl.config.userInfoEndpointQueryParams)) { if (value === null) { delete queryParams[key]; - } - else { + } else { queryParams[key] = value; } } } - const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest(impl.config.userInfoEndpoint, queryParams, headers); + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( + impl.config.userInfoEndpoint, + queryParams, + headers + ); if (userInfoFromAccessToken.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); - throw new Error(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); + throw new Error( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); } rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; } diff --git a/lib/build/recipe/thirdparty/providers/discord.js b/lib/build/recipe/thirdparty/providers/discord.js index 01ae77f9e..cf161c55c 100644 --- a/lib/build/recipe/thirdparty/providers/discord.js +++ b/lib/build/recipe/thirdparty/providers/discord.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); function Discord(input) { @@ -18,7 +20,12 @@ function Discord(input) { if (input.config.userInfoEndpoint === undefined) { input.config.userInfoEndpoint = "https://discord.com/api/users/@me"; } - input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { fromUserInfoAPI: Object.assign({ userId: "id", email: "email", emailVerified: "verified" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) }); + input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { + fromUserInfoAPI: Object.assign( + { userId: "id", email: "email", emailVerified: "verified" }, + (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI + ), + }); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; diff --git a/lib/build/recipe/thirdparty/providers/facebook.js b/lib/build/recipe/thirdparty/providers/facebook.js index bb9cf4f38..0353cc704 100644 --- a/lib/build/recipe/thirdparty/providers/facebook.js +++ b/lib/build/recipe/thirdparty/providers/facebook.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); function Facebook(input) { @@ -18,7 +20,12 @@ function Facebook(input) { if (input.config.userInfoEndpoint === undefined) { input.config.userInfoEndpoint = "https://graph.facebook.com/me"; } - input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { fromUserInfoAPI: Object.assign({ userId: "id" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) }); + input.config.userInfoMap = Object.assign(Object.assign({}, input.config.userInfoMap), { + fromUserInfoAPI: Object.assign( + { userId: "id" }, + (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI + ), + }); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; @@ -56,9 +63,27 @@ function Facebook(input) { user_age_range: ["age_range"], }; const scopeValues = originalImplementation.config.scope; - const fields = (_a = scopeValues === null || scopeValues === void 0 ? void 0 : scopeValues.map((scopeValue) => { var _a; return (_a = fieldsPermissionMap[scopeValue]) !== null && _a !== void 0 ? _a : []; }).flat().join(",")) !== null && _a !== void 0 ? _a : "id,email"; - originalImplementation.config.userInfoEndpointQueryParams = Object.assign({ access_token: input.oAuthTokens.access_token, fields, format: "json" }, originalImplementation.config.userInfoEndpointQueryParams); - originalImplementation.config.userInfoEndpointHeaders = Object.assign(Object.assign({}, originalImplementation.config.userInfoEndpointHeaders), { Authorization: null }); + const fields = + (_a = + scopeValues === null || scopeValues === void 0 + ? void 0 + : scopeValues + .map((scopeValue) => { + var _a; + return (_a = fieldsPermissionMap[scopeValue]) !== null && _a !== void 0 ? _a : []; + }) + .flat() + .join(",")) !== null && _a !== void 0 + ? _a + : "id,email"; + originalImplementation.config.userInfoEndpointQueryParams = Object.assign( + { access_token: input.oAuthTokens.access_token, fields, format: "json" }, + originalImplementation.config.userInfoEndpointQueryParams + ); + originalImplementation.config.userInfoEndpointHeaders = Object.assign( + Object.assign({}, originalImplementation.config.userInfoEndpointHeaders), + { Authorization: null } + ); return await oGetUserInfo(input); }; if (oOverride !== undefined) { diff --git a/lib/build/recipe/thirdparty/providers/github.js b/lib/build/recipe/thirdparty/providers/github.js index 17763a34d..ced201b08 100644 --- a/lib/build/recipe/thirdparty/providers/github.js +++ b/lib/build/recipe/thirdparty/providers/github.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -54,18 +56,26 @@ function Github(input) { } if (input.config.validateAccessToken === undefined) { input.config.validateAccessToken = async ({ accessToken, clientConfig }) => { - const basicAuthToken = utils_1.encodeBase64(`${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}`); - const applicationResponse = await thirdpartyUtils_1.doPostRequest(`https://api.github.com/applications/${clientConfig.clientId}/token`, { - access_token: accessToken, - }, { - Authorization: `Basic ${basicAuthToken}`, - "Content-Type": "application/json", - }); + const basicAuthToken = utils_1.encodeBase64( + `${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}` + ); + const applicationResponse = await thirdpartyUtils_1.doPostRequest( + `https://api.github.com/applications/${clientConfig.clientId}/token`, + { + access_token: accessToken, + }, + { + Authorization: `Basic ${basicAuthToken}`, + "Content-Type": "application/json", + } + ); if (applicationResponse.status !== 200) { throw new Error("Invalid access token"); } - if (applicationResponse.jsonResponse.app === undefined || - applicationResponse.jsonResponse.app.client_id !== clientConfig.clientId) { + if ( + applicationResponse.jsonResponse.app === undefined || + applicationResponse.jsonResponse.app.client_id !== clientConfig.clientId + ) { throw new Error("Access token does not belong to your application"); } }; @@ -86,12 +96,22 @@ function Github(input) { Accept: "application/vnd.github.v3+json", }; const rawResponse = {}; - const emailInfoResp = await thirdpartyUtils_1.doGetRequest("https://api.github.com/user/emails", undefined, headers); + const emailInfoResp = await thirdpartyUtils_1.doGetRequest( + "https://api.github.com/user/emails", + undefined, + headers + ); if (emailInfoResp.status >= 400) { - throw new Error(`Getting userInfo failed with ${emailInfoResp.status}: ${emailInfoResp.stringResponse}`); + throw new Error( + `Getting userInfo failed with ${emailInfoResp.status}: ${emailInfoResp.stringResponse}` + ); } rawResponse.emails = emailInfoResp.jsonResponse; - const userInfoResp = await thirdpartyUtils_1.doGetRequest("https://api.github.com/user", undefined, headers); + const userInfoResp = await thirdpartyUtils_1.doGetRequest( + "https://api.github.com/user", + undefined, + headers + ); if (userInfoResp.status >= 400) { throw new Error(`Getting userInfo failed with ${userInfoResp.status}: ${userInfoResp.stringResponse}`); } diff --git a/lib/build/recipe/thirdparty/providers/gitlab.js b/lib/build/recipe/thirdparty/providers/gitlab.js index 9f6fc1394..2f5bd3967 100644 --- a/lib/build/recipe/thirdparty/providers/gitlab.js +++ b/lib/build/recipe/thirdparty/providers/gitlab.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLDomain_1 = __importDefault(require("../../../normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); @@ -38,12 +40,13 @@ function Gitlab(input) { const oidcDomain = new normalisedURLDomain_1.default(config.additionalConfig.gitlabBaseUrl); const oidcPath = new normalisedURLPath_1.default("/.well-known/openid-configuration"); config.oidcDiscoveryEndpoint = oidcDomain.getAsStringDangerous() + oidcPath.getAsStringDangerous(); - } - else if (config.oidcDiscoveryEndpoint === undefined) { + } else if (config.oidcDiscoveryEndpoint === undefined) { config.oidcDiscoveryEndpoint = "https://gitlab.com/.well-known/openid-configuration"; } // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( + config.oidcDiscoveryEndpoint + ); return config; }; if (oOverride !== undefined) { diff --git a/lib/build/recipe/thirdparty/providers/google.js b/lib/build/recipe/thirdparty/providers/google.js index 5fa11ed12..686604afe 100644 --- a/lib/build/recipe/thirdparty/providers/google.js +++ b/lib/build/recipe/thirdparty/providers/google.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); const utils_1 = require("./utils"); @@ -12,7 +14,10 @@ function Google(input) { if (input.config.oidcDiscoveryEndpoint === undefined) { input.config.oidcDiscoveryEndpoint = "https://accounts.google.com/.well-known/openid-configuration"; } - input.config.authorizationEndpointQueryParams = Object.assign({ included_grant_scopes: "true", access_type: "offline" }, input.config.authorizationEndpointQueryParams); + input.config.authorizationEndpointQueryParams = Object.assign( + { included_grant_scopes: "true", access_type: "offline" }, + input.config.authorizationEndpointQueryParams + ); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; @@ -22,7 +27,9 @@ function Google(input) { config.scope = ["openid", "email"]; } // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( + config.oidcDiscoveryEndpoint + ); return config; }; if (oOverride !== undefined) { diff --git a/lib/build/recipe/thirdparty/providers/googleWorkspaces.js b/lib/build/recipe/thirdparty/providers/googleWorkspaces.js index 001dc1656..b7c9cc6fc 100644 --- a/lib/build/recipe/thirdparty/providers/googleWorkspaces.js +++ b/lib/build/recipe/thirdparty/providers/googleWorkspaces.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const google_1 = __importDefault(require("./google")); function GoogleWorkspaces(input) { @@ -11,22 +13,35 @@ function GoogleWorkspaces(input) { if (input.config.validateIdTokenPayload === undefined) { input.config.validateIdTokenPayload = async function (input) { var _a, _b, _c; - if (((_a = input.clientConfig.additionalConfig) === null || _a === void 0 ? void 0 : _a.hd) !== undefined && - ((_b = input.clientConfig.additionalConfig) === null || _b === void 0 ? void 0 : _b.hd) !== "*") { - if (((_c = input.clientConfig.additionalConfig) === null || _c === void 0 ? void 0 : _c.hd) !== input.idTokenPayload.hd) { - throw new Error("the value for hd claim in the id token does not match the value provided in the config"); + if ( + ((_a = input.clientConfig.additionalConfig) === null || _a === void 0 ? void 0 : _a.hd) !== undefined && + ((_b = input.clientConfig.additionalConfig) === null || _b === void 0 ? void 0 : _b.hd) !== "*" + ) { + if ( + ((_c = input.clientConfig.additionalConfig) === null || _c === void 0 ? void 0 : _c.hd) !== + input.idTokenPayload.hd + ) { + throw new Error( + "the value for hd claim in the id token does not match the value provided in the config" + ); } } }; } - input.config.authorizationEndpointQueryParams = Object.assign({ included_grant_scopes: "true", access_type: "offline" }, input.config.authorizationEndpointQueryParams); + input.config.authorizationEndpointQueryParams = Object.assign( + { included_grant_scopes: "true", access_type: "offline" }, + input.config.authorizationEndpointQueryParams + ); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; originalImplementation.getConfigForClientType = async function (input) { const config = await oGetConfig(input); config.additionalConfig = Object.assign({ hd: "*" }, config.additionalConfig); - config.authorizationEndpointQueryParams = Object.assign(Object.assign({}, config.authorizationEndpointQueryParams), { hd: config.additionalConfig.hd }); + config.authorizationEndpointQueryParams = Object.assign( + Object.assign({}, config.authorizationEndpointQueryParams), + { hd: config.additionalConfig.hd } + ); return config; }; if (oOverride !== undefined) { diff --git a/lib/build/recipe/thirdparty/providers/index.js b/lib/build/recipe/thirdparty/providers/index.js index 869c34251..345d7f130 100644 --- a/lib/build/recipe/thirdparty/providers/index.js +++ b/lib/build/recipe/thirdparty/providers/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Twitter = exports.Gitlab = exports.Bitbucket = exports.Okta = exports.Linkedin = exports.GoogleWorkspaces = exports.Google = exports.Github = exports.Facebook = exports.Discord = exports.Apple = exports.BoxySAML = exports.ActiveDirectory = void 0; const activeDirectory_1 = __importDefault(require("./activeDirectory")); diff --git a/lib/build/recipe/thirdparty/providers/linkedin.js b/lib/build/recipe/thirdparty/providers/linkedin.js index 199a745f6..defa0739c 100644 --- a/lib/build/recipe/thirdparty/providers/linkedin.js +++ b/lib/build/recipe/thirdparty/providers/linkedin.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -54,11 +56,19 @@ function Linkedin(input) { fromIdTokenPayload: {}, }; // https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2?context=linkedin%2Fconsumer%2Fcontext#sample-api-response - const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest("https://api.linkedin.com/v2/userinfo", undefined, headers); + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( + "https://api.linkedin.com/v2/userinfo", + undefined, + headers + ); rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; if (userInfoFromAccessToken.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); - throw new Error(`Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); + throw new Error( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); } return { thirdPartyUserId: rawUserInfoFromProvider.fromUserInfoAPI.sub, diff --git a/lib/build/recipe/thirdparty/providers/okta.js b/lib/build/recipe/thirdparty/providers/okta.js index 6f58939ec..2fdffd98b 100644 --- a/lib/build/recipe/thirdparty/providers/okta.js +++ b/lib/build/recipe/thirdparty/providers/okta.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -37,7 +39,9 @@ function Okta(input) { } if (config.oidcDiscoveryEndpoint !== undefined) { // The config could be coming from core where we didn't add the well-known previously - config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown(config.oidcDiscoveryEndpoint); + config.oidcDiscoveryEndpoint = utils_1.normaliseOIDCEndpointToIncludeWellKnown( + config.oidcDiscoveryEndpoint + ); } if (config.scope === undefined) { config.scope = ["openid", "email"]; diff --git a/lib/build/recipe/thirdparty/providers/twitter.js b/lib/build/recipe/thirdparty/providers/twitter.js index a77dd74ad..ec82e5638 100644 --- a/lib/build/recipe/thirdparty/providers/twitter.js +++ b/lib/build/recipe/thirdparty/providers/twitter.js @@ -1,23 +1,40 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; Object.defineProperty(exports, "__esModule", { value: true }); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -54,7 +71,15 @@ function Twitter(input) { if (input.config.requireEmail === undefined) { input.config.requireEmail = false; } - input.config.userInfoMap = Object.assign({ fromUserInfoAPI: Object.assign({ userId: "data.id" }, (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI) }, input.config.userInfoMap); + input.config.userInfoMap = Object.assign( + { + fromUserInfoAPI: Object.assign( + { userId: "data.id" }, + (_a = input.config.userInfoMap) === null || _a === void 0 ? void 0 : _a.fromUserInfoAPI + ), + }, + input.config.userInfoMap + ); const oOverride = input.override; input.override = function (originalImplementation) { const oGetConfig = originalImplementation.getConfigForClientType; @@ -79,13 +104,30 @@ function Twitter(input) { } /* Transformation needed for dev keys END */ const basicAuthToken = utils_1.encodeBase64(`${clientId}:${originalImplementation.config.clientSecret}`); - const twitterOauthTokenParams = Object.assign({ grant_type: "authorization_code", client_id: clientId, code_verifier: input.redirectURIInfo.pkceCodeVerifier, redirect_uri: redirectUri, code: input.redirectURIInfo.redirectURIQueryParams.code }, originalImplementation.config.tokenEndpointBodyParams); - const tokenResponse = await thirdpartyUtils_1.doPostRequest(originalImplementation.config.tokenEndpoint, twitterOauthTokenParams, { - Authorization: `Basic ${basicAuthToken}`, - }); + const twitterOauthTokenParams = Object.assign( + { + grant_type: "authorization_code", + client_id: clientId, + code_verifier: input.redirectURIInfo.pkceCodeVerifier, + redirect_uri: redirectUri, + code: input.redirectURIInfo.redirectURIQueryParams.code, + }, + originalImplementation.config.tokenEndpointBodyParams + ); + const tokenResponse = await thirdpartyUtils_1.doPostRequest( + originalImplementation.config.tokenEndpoint, + twitterOauthTokenParams, + { + Authorization: `Basic ${basicAuthToken}`, + } + ); if (tokenResponse.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); - throw new Error(`Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); + throw new Error( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); } return tokenResponse.jsonResponse; }; diff --git a/lib/build/recipe/thirdparty/providers/utils.d.ts b/lib/build/recipe/thirdparty/providers/utils.d.ts index 8121bfb8f..e09a4d2bc 100644 --- a/lib/build/recipe/thirdparty/providers/utils.d.ts +++ b/lib/build/recipe/thirdparty/providers/utils.d.ts @@ -1,24 +1,36 @@ // @ts-nocheck import * as jose from "jose"; import { ProviderConfigForClientType } from "../types"; -export declare function doGetRequest(url: string, queryParams?: { - [key: string]: string; -}, headers?: { - [key: string]: string; -}): Promise<{ +export declare function doGetRequest( + url: string, + queryParams?: { + [key: string]: string; + }, + headers?: { + [key: string]: string; + } +): Promise<{ jsonResponse: Record | undefined; status: number; stringResponse: string; }>; -export declare function doPostRequest(url: string, params: { - [key: string]: any; -}, headers?: { - [key: string]: string; -}): Promise<{ +export declare function doPostRequest( + url: string, + params: { + [key: string]: any; + }, + headers?: { + [key: string]: string; + } +): Promise<{ jsonResponse: Record | undefined; status: number; stringResponse: string; }>; -export declare function verifyIdTokenFromJWKSEndpointAndGetPayload(idToken: string, jwks: jose.JWTVerifyGetKey, otherOptions: jose.JWTVerifyOptions): Promise; +export declare function verifyIdTokenFromJWKSEndpointAndGetPayload( + idToken: string, + jwks: jose.JWTVerifyGetKey, + otherOptions: jose.JWTVerifyOptions +): Promise; export declare function discoverOIDCEndpoints(config: ProviderConfigForClientType): Promise; export declare function normaliseOIDCEndpointToIncludeWellKnown(url: string): string; diff --git a/lib/build/recipe/thirdparty/providers/utils.js b/lib/build/recipe/thirdparty/providers/utils.js index 6d3cb090b..bacd7b02f 100644 --- a/lib/build/recipe/thirdparty/providers/utils.js +++ b/lib/build/recipe/thirdparty/providers/utils.js @@ -1,26 +1,45 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.normaliseOIDCEndpointToIncludeWellKnown = exports.discoverOIDCEndpoints = exports.verifyIdTokenFromJWKSEndpointAndGetPayload = exports.doPostRequest = exports.doGetRequest = void 0; const jose = __importStar(require("jose")); @@ -30,7 +49,9 @@ const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath" const logger_1 = require("../../../logger"); const utils_1 = require("../../../utils"); async function doGetRequest(url, queryParams, headers) { - logger_1.logDebugMessage(`GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}`); + logger_1.logDebugMessage( + `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` + ); if ((headers === null || headers === void 0 ? void 0 : headers["Accept"]) === undefined) { headers = Object.assign(Object.assign({}, headers), { Accept: "application/json" }); } @@ -58,7 +79,9 @@ async function doPostRequest(url, params, headers) { } headers["Content-Type"] = "application/x-www-form-urlencoded"; headers["Accept"] = "application/json"; // few providers like github don't send back json response by default - logger_1.logDebugMessage(`POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}`); + logger_1.logDebugMessage( + `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` + ); const body = new URLSearchParams(params).toString(); let response = await utils_1.doFetch(url, { method: "POST", @@ -109,8 +132,10 @@ function normaliseOIDCEndpointToIncludeWellKnown(url) { const normalisedDomain = new normalisedURLDomain_1.default(url); const normalisedPath = new normalisedURLPath_1.default(url); const normalisedWellKnownPath = new normalisedURLPath_1.default("/.well-known/openid-configuration"); - return (normalisedDomain.getAsStringDangerous() + + return ( + normalisedDomain.getAsStringDangerous() + normalisedPath.getAsStringDangerous() + - normalisedWellKnownPath.getAsStringDangerous()); + normalisedWellKnownPath.getAsStringDangerous() + ); } exports.normaliseOIDCEndpointToIncludeWellKnown = normaliseOIDCEndpointToIncludeWellKnown; diff --git a/lib/build/recipe/thirdparty/recipe.d.ts b/lib/build/recipe/thirdparty/recipe.d.ts index bd271c875..cc79859cf 100644 --- a/lib/build/recipe/thirdparty/recipe.d.ts +++ b/lib/build/recipe/thirdparty/recipe.d.ts @@ -13,12 +13,27 @@ export default class Recipe extends RecipeModule { recipeInterfaceImpl: RecipeInterface; apiImpl: APIInterface; isInServerlessEnv: boolean; - constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput | undefined, _recipes: {}, _ingredients: {}); + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput | undefined, + _recipes: {}, + _ingredients: {} + ); static init(config?: TypeInput): RecipeListFunction; static getInstanceOrThrowError(): Recipe; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/thirdparty/recipe.js b/lib/build/recipe/thirdparty/recipe.js index 613764527..d9daed748 100644 --- a/lib/build/recipe/thirdparty/recipe.js +++ b/lib/build/recipe/thirdparty/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const utils_1 = require("./utils"); @@ -71,11 +73,9 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.SIGN_IN_UP_API) { return await signinup_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.AUTHORISATION_API) { + } else if (id === constants_1.AUTHORISATION_API) { return await authorisationUrl_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.APPLE_REDIRECT_HANDLER) { + } else if (id === constants_1.APPLE_REDIRECT_HANDLER) { return await appleRedirect_1.default(this.apiImpl, options, userContext); } return false; @@ -93,7 +93,9 @@ class Recipe extends recipeModule_1.default { this.isInServerlessEnv = isInServerlessEnv; this.providers = this.config.signInAndUpFeature.providers; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.providers)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.providers) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -111,12 +113,18 @@ class Recipe extends recipeModule_1.default { static init(config) { return (appInfo, isInServerlessEnv) => { if (Recipe.instance === undefined) { - Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config, {}, { - emailDelivery: undefined, - }); + Recipe.instance = new Recipe( + Recipe.RECIPE_ID, + appInfo, + isInServerlessEnv, + config, + {}, + { + emailDelivery: undefined, + } + ); return Recipe.instance; - } - else { + } else { throw new Error("ThirdParty recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/thirdparty/recipeImplementation.js b/lib/build/recipe/thirdparty/recipeImplementation.js index 624eae517..dce4ef1e6 100644 --- a/lib/build/recipe/thirdparty/recipeImplementation.js +++ b/lib/build/recipe/thirdparty/recipeImplementation.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const configUtils_1 = require("./providers/configUtils"); @@ -14,9 +16,23 @@ const authUtils_1 = require("../../authUtils"); const constants_1 = require("../multitenancy/constants"); function getRecipeImplementation(querier, providers) { return { - manuallyCreateOrUpdateUser: async function ({ thirdPartyId, thirdPartyUserId, email, isVerified, tenantId, session, shouldTryLinkingWithSessionUser, userContext, }) { + manuallyCreateOrUpdateUser: async function ({ + thirdPartyId, + thirdPartyUserId, + email, + isVerified, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }) { const accountLinking = recipe_1.default.getInstance(); - const users = await __1.listUsersByAccountInfo(tenantId, { thirdParty: { id: thirdPartyId, userId: thirdPartyUserId } }, false, userContext); + const users = await __1.listUsersByAccountInfo( + tenantId, + { thirdParty: { id: thirdPartyId, userId: thirdPartyUserId } }, + false, + userContext + ); const user = users[0]; if (user !== undefined) { const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ @@ -29,17 +45,22 @@ function getRecipeImplementation(querier, providers) { if (!isEmailChangeAllowed.allowed) { return { status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", - reason: isEmailChangeAllowed.reason === "PRIMARY_USER_CONFLICT" - ? "Email already associated with another primary user." - : "New email cannot be applied to existing account because of account takeover risks.", + reason: + isEmailChangeAllowed.reason === "PRIMARY_USER_CONFLICT" + ? "Email already associated with another primary user." + : "New email cannot be applied to existing account because of account takeover risks.", }; } } - let response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId}/recipe/signinup`), { - thirdPartyId, - thirdPartyUserId, - email: { id: email, isVerified }, - }, userContext); + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/${tenantId}/recipe/signinup`), + { + thirdPartyId, + thirdPartyUserId, + email: { id: email, isVerified }, + }, + userContext + ); if (response.status !== "OK") { return response; } @@ -52,15 +73,17 @@ function getRecipeImplementation(querier, providers) { }); // 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 __1.getUser(response.recipeUserId.getAsString(), userContext)); - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ - tenantId, - shouldTryLinkingWithSessionUser, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - userContext, - }); + response.user = await __1.getUser(response.recipeUserId.getAsString(), userContext); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( + { + tenantId, + shouldTryLinkingWithSessionUser, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + userContext, + } + ); if (linkResult.status !== "OK") { return linkResult; } @@ -71,7 +94,18 @@ function getRecipeImplementation(querier, providers) { recipeUserId: response.recipeUserId, }; }, - signInUp: async function ({ thirdPartyId, thirdPartyUserId, email, isVerified, tenantId, userContext, oAuthTokens, session, shouldTryLinkingWithSessionUser, rawUserInfoFromProvider, }) { + signInUp: async function ({ + thirdPartyId, + thirdPartyUserId, + email, + isVerified, + tenantId, + userContext, + oAuthTokens, + session, + shouldTryLinkingWithSessionUser, + rawUserInfoFromProvider, + }) { let response = await this.manuallyCreateOrUpdateUser({ thirdPartyId, thirdPartyUserId, @@ -85,14 +119,14 @@ function getRecipeImplementation(querier, providers) { if (response.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { return { status: "SIGN_IN_UP_NOT_ALLOWED", - reason: response.reason === "Email already associated with another primary user." - ? "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)" - : "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_024)", + reason: + response.reason === "Email already associated with another primary user." + ? "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)" + : "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_024)", }; } if (response.status === "OK") { - return Object.assign(Object.assign({}, response), { oAuthTokens, - rawUserInfoFromProvider }); + return Object.assign(Object.assign({}, response), { oAuthTokens, rawUserInfoFromProvider }); } return response; }, @@ -102,8 +136,17 @@ function getRecipeImplementation(querier, providers) { if (tenantConfig === undefined) { throw new Error("Tenant not found"); } - const mergedProviders = configUtils_1.mergeProvidersFromCoreAndStatic(tenantConfig.thirdParty.providers, providers, tenantId === constants_1.DEFAULT_TENANT_ID); - const provider = await configUtils_1.findAndCreateProviderInstance(mergedProviders, thirdPartyId, clientType, userContext); + const mergedProviders = configUtils_1.mergeProvidersFromCoreAndStatic( + tenantConfig.thirdParty.providers, + providers, + tenantId === constants_1.DEFAULT_TENANT_ID + ); + const provider = await configUtils_1.findAndCreateProviderInstance( + mergedProviders, + thirdPartyId, + clientType, + userContext + ); return provider; }, }; diff --git a/lib/build/recipe/thirdparty/types.d.ts b/lib/build/recipe/thirdparty/types.d.ts index 022a34ad7..8d0198e3b 100644 --- a/lib/build/recipe/thirdparty/types.d.ts +++ b/lib/build/recipe/thirdparty/types.d.ts @@ -115,10 +115,7 @@ export declare type TypeProvider = { }; userContext: UserContext; }) => Promise; - getUserInfo: (input: { - oAuthTokens: any; - userContext: UserContext; - }) => Promise; + getUserInfo: (input: { oAuthTokens: any; userContext: UserContext }) => Promise; }; export declare type ProviderConfig = CommonProviderConfig & { clients?: ProviderClientConfig[]; @@ -137,14 +134,20 @@ export declare type TypeNormalisedInputSignInAndUp = { export declare type TypeInput = { signInAndUpFeature?: TypeInputSignInAndUp; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { signInAndUpFeature: TypeNormalisedInputSignInAndUp; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -175,29 +178,37 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - createdNewRecipeUser: boolean; - recipeUserId: RecipeUserId; - user: User; - oAuthTokens: { - [key: string]: any; - }; - rawUserInfoFromProvider: { - fromIdTokenPayload?: { - [key: string]: any; - }; - fromUserInfoAPI?: { - [key: string]: any; - }; - }; - } | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } | { - 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"; - }>; + }): Promise< + | { + status: "OK"; + createdNewRecipeUser: boolean; + recipeUserId: RecipeUserId; + user: User; + oAuthTokens: { + [key: string]: any; + }; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + | { + 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"; + } + >; manuallyCreateOrUpdateUser(input: { thirdPartyId: string; thirdPartyUserId: string; @@ -207,21 +218,30 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; - reason: string; - } | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } | { - 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"; - }>; + }): Promise< + | { + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR"; + reason: string; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + | { + 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"; + } + >; }; export declare type APIOptions = { recipeImplementation: RecipeInterface; @@ -234,62 +254,81 @@ export declare type APIOptions = { appInfo: NormalisedAppinfo; }; export declare type APIInterface = { - authorisationUrlGET: undefined | ((input: { - provider: TypeProvider; - redirectURIOnProviderDashboard: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - urlWithQueryParams: string; - pkceCodeVerifier?: string; - } | GeneralErrorResponse>); - signInUpPOST: undefined | ((input: { - provider: TypeProvider; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - } & ({ - redirectURIInfo: { - redirectURIOnProviderDashboard: string; - redirectURIQueryParams: any; - pkceCodeVerifier?: string; - }; - } | { - oAuthTokens: { - [key: string]: any; - }; - })) => Promise<{ - status: "OK"; - createdNewRecipeUser: boolean; - user: User; - session: SessionContainerInterface; - oAuthTokens: { - [key: string]: any; - }; - rawUserInfoFromProvider: { - fromIdTokenPayload?: { - [key: string]: any; - }; - fromUserInfoAPI?: { - [key: string]: any; - }; - }; - } | { - status: "NO_EMAIL_GIVEN_BY_PROVIDER"; - } | { - status: "SIGN_IN_UP_NOT_ALLOWED"; - reason: string; - } | GeneralErrorResponse>); - appleRedirectHandlerPOST: undefined | ((input: { - formPostInfoFromProvider: { - [key: string]: any; - }; - options: APIOptions; - userContext: UserContext; - }) => Promise); + authorisationUrlGET: + | undefined + | ((input: { + provider: TypeProvider; + redirectURIOnProviderDashboard: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + urlWithQueryParams: string; + pkceCodeVerifier?: string; + } + | GeneralErrorResponse + >); + signInUpPOST: + | undefined + | (( + input: { + provider: TypeProvider; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + } & ( + | { + redirectURIInfo: { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string; + }; + } + | { + oAuthTokens: { + [key: string]: any; + }; + } + ) + ) => Promise< + | { + status: "OK"; + createdNewRecipeUser: boolean; + user: User; + session: SessionContainerInterface; + oAuthTokens: { + [key: string]: any; + }; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + } + | { + status: "NO_EMAIL_GIVEN_BY_PROVIDER"; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + >); + appleRedirectHandlerPOST: + | undefined + | ((input: { + formPostInfoFromProvider: { + [key: string]: any; + }; + options: APIOptions; + userContext: UserContext; + }) => Promise); }; export {}; diff --git a/lib/build/recipe/thirdparty/utils.d.ts b/lib/build/recipe/thirdparty/utils.d.ts index 7e1bebd95..e4db0684e 100644 --- a/lib/build/recipe/thirdparty/utils.d.ts +++ b/lib/build/recipe/thirdparty/utils.d.ts @@ -1,5 +1,8 @@ // @ts-nocheck import { NormalisedAppinfo } from "../../types"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput(appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + appInfo: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; export declare function isFakeEmail(email: string): boolean; diff --git a/lib/build/recipe/thirdparty/utils.js b/lib/build/recipe/thirdparty/utils.js index 1e14d1ea9..8828f95a5 100644 --- a/lib/build/recipe/thirdparty/utils.js +++ b/lib/build/recipe/thirdparty/utils.js @@ -16,8 +16,17 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.isFakeEmail = exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(appInfo, config) { - let signInAndUpFeature = validateAndNormaliseSignInAndUpConfig(appInfo, config === null || config === void 0 ? void 0 : config.signInAndUpFeature); - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let signInAndUpFeature = validateAndNormaliseSignInAndUpConfig( + appInfo, + config === null || config === void 0 ? void 0 : config.signInAndUpFeature + ); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { signInAndUpFeature, override, diff --git a/lib/build/recipe/totp/api/createDevice.d.ts b/lib/build/recipe/totp/api/createDevice.d.ts index 9d703be7f..aaa135298 100644 --- a/lib/build/recipe/totp/api/createDevice.d.ts +++ b/lib/build/recipe/totp/api/createDevice.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function createDeviceAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function createDeviceAPI( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/totp/api/createDevice.js b/lib/build/recipe/totp/api/createDevice.js index 14d72b950..c821f6e64 100644 --- a/lib/build/recipe/totp/api/createDevice.js +++ b/lib/build/recipe/totp/api/createDevice.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -23,7 +25,12 @@ async function createDeviceAPI(apiImplementation, options, userContext) { if (apiImplementation.createDevicePOST === undefined) { return false; } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [], sessionRequired: true }, + userContext + ); const bodyParams = await options.req.getJSONBody(); const deviceName = bodyParams.deviceName; if (deviceName !== undefined && typeof deviceName !== "string") { diff --git a/lib/build/recipe/totp/api/implementation.js b/lib/build/recipe/totp/api/implementation.js index 87d0dedc8..909546b34 100644 --- a/lib/build/recipe/totp/api/implementation.js +++ b/lib/build/recipe/totp/api/implementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const multifactorauth_1 = __importDefault(require("../../multifactorauth")); const recipe_1 = __importDefault(require("../../multifactorauth/recipe")); @@ -28,7 +30,11 @@ function getAPIInterface() { if (mfaInstance === undefined) { throw new Error("should never come here"); // If TOTP initialised, MFA is auto initialised. This should never happen. } - await multifactorauth_1.default.assertAllowedToSetupFactorElseThrowInvalidClaimError(session, "totp", userContext); + await multifactorauth_1.default.assertAllowedToSetupFactorElseThrowInvalidClaimError( + session, + "totp", + userContext + ); const createDeviceRes = await options.recipeImplementation.createDevice({ userId, deviceName: deviceName, @@ -39,8 +45,7 @@ function getAPIInterface() { type: error_1.default.UNAUTHORISED, message: "Session user not found", }); - } - else { + } else { return createDeviceRes; } }, @@ -66,7 +71,11 @@ function getAPIInterface() { if (mfaInstance === undefined) { throw new Error("should never come here"); // If TOTP initialised, MFA is auto initialised. This should never happen. } - await multifactorauth_1.default.assertAllowedToSetupFactorElseThrowInvalidClaimError(session, "totp", userContext); + await multifactorauth_1.default.assertAllowedToSetupFactorElseThrowInvalidClaimError( + session, + "totp", + userContext + ); const res = await options.recipeImplementation.verifyDevice({ tenantId, userId, diff --git a/lib/build/recipe/totp/api/listDevices.d.ts b/lib/build/recipe/totp/api/listDevices.d.ts index ffec14f56..d6ba65d69 100644 --- a/lib/build/recipe/totp/api/listDevices.d.ts +++ b/lib/build/recipe/totp/api/listDevices.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function listDevicesAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function listDevicesAPI( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/totp/api/listDevices.js b/lib/build/recipe/totp/api/listDevices.js index ea958ca65..39ba7d214 100644 --- a/lib/build/recipe/totp/api/listDevices.js +++ b/lib/build/recipe/totp/api/listDevices.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -23,7 +25,12 @@ async function listDevicesAPI(apiImplementation, options, userContext) { if (apiImplementation.listDevicesGET === undefined) { return false; } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [], sessionRequired: true }, + userContext + ); let response = await apiImplementation.listDevicesGET({ options, session, diff --git a/lib/build/recipe/totp/api/removeDevice.d.ts b/lib/build/recipe/totp/api/removeDevice.d.ts index 057e4d511..047515bef 100644 --- a/lib/build/recipe/totp/api/removeDevice.d.ts +++ b/lib/build/recipe/totp/api/removeDevice.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function removeDeviceAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function removeDeviceAPI( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/totp/api/removeDevice.js b/lib/build/recipe/totp/api/removeDevice.js index 79fc750fa..3893442a1 100644 --- a/lib/build/recipe/totp/api/removeDevice.js +++ b/lib/build/recipe/totp/api/removeDevice.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -23,7 +25,12 @@ async function removeDeviceAPI(apiImplementation, options, userContext) { if (apiImplementation.removeDevicePOST === undefined) { return false; } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [], sessionRequired: true }, + userContext + ); const bodyParams = await options.req.getJSONBody(); const deviceName = bodyParams.deviceName; if (deviceName === undefined || typeof deviceName !== "string" || deviceName.length === 0) { diff --git a/lib/build/recipe/totp/api/verifyDevice.d.ts b/lib/build/recipe/totp/api/verifyDevice.d.ts index 27b92b41b..d4de4bcc9 100644 --- a/lib/build/recipe/totp/api/verifyDevice.d.ts +++ b/lib/build/recipe/totp/api/verifyDevice.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function verifyDeviceAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function verifyDeviceAPI( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/totp/api/verifyDevice.js b/lib/build/recipe/totp/api/verifyDevice.js index c4c78d106..9499c0225 100644 --- a/lib/build/recipe/totp/api/verifyDevice.js +++ b/lib/build/recipe/totp/api/verifyDevice.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -23,7 +25,12 @@ async function verifyDeviceAPI(apiImplementation, options, userContext) { if (apiImplementation.verifyDevicePOST === undefined) { return false; } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [], sessionRequired: true }, + userContext + ); const bodyParams = await options.req.getJSONBody(); const deviceName = bodyParams.deviceName; const totp = bodyParams.totp; diff --git a/lib/build/recipe/totp/api/verifyTOTP.d.ts b/lib/build/recipe/totp/api/verifyTOTP.d.ts index b9e24dc4a..925a9f40a 100644 --- a/lib/build/recipe/totp/api/verifyTOTP.d.ts +++ b/lib/build/recipe/totp/api/verifyTOTP.d.ts @@ -1,4 +1,8 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function verifyTOTPAPI(apiImplementation: APIInterface, options: APIOptions, userContext: UserContext): Promise; +export default function verifyTOTPAPI( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/totp/api/verifyTOTP.js b/lib/build/recipe/totp/api/verifyTOTP.js index 299ed5e9a..1f8d08a05 100644 --- a/lib/build/recipe/totp/api/verifyTOTP.js +++ b/lib/build/recipe/totp/api/verifyTOTP.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); @@ -23,7 +25,12 @@ async function verifyTOTPAPI(apiImplementation, options, userContext) { if (apiImplementation.verifyTOTPPOST === undefined) { return false; } - const session = await session_1.default.getSession(options.req, options.res, { overrideGlobalClaimValidators: () => [], sessionRequired: true }, userContext); + const session = await session_1.default.getSession( + options.req, + options.res, + { overrideGlobalClaimValidators: () => [], sessionRequired: true }, + userContext + ); const bodyParams = await options.req.getJSONBody(); const totp = bodyParams.totp; if (totp === undefined || typeof totp !== "string") { diff --git a/lib/build/recipe/totp/index.d.ts b/lib/build/recipe/totp/index.d.ts index 589bcf99a..9759cdc47 100644 --- a/lib/build/recipe/totp/index.d.ts +++ b/lib/build/recipe/totp/index.d.ts @@ -3,20 +3,39 @@ import Recipe from "./recipe"; import { RecipeInterface, APIOptions, APIInterface } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record): Promise<{ - status: "OK"; - deviceName: string; - secret: string; - qrCodeString: string; - } | { - status: "DEVICE_ALREADY_EXISTS_ERROR"; - } | { - status: "UNKNOWN_USER_ID_ERROR"; - }>; - static updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record): Promise<{ + static createDevice( + userId: string, + userIdentifierInfo?: string, + deviceName?: string, + skew?: number, + period?: number, + userContext?: Record + ): Promise< + | { + status: "OK"; + deviceName: string; + secret: string; + qrCodeString: string; + } + | { + status: "DEVICE_ALREADY_EXISTS_ERROR"; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; + static updateDevice( + userId: string, + existingDeviceName: string, + newDeviceName: string, + userContext?: Record + ): Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR"; }>; - static listDevices(userId: string, userContext?: Record): Promise<{ + static listDevices( + userId: string, + userContext?: Record + ): Promise<{ status: "OK"; devices: { name: string; @@ -25,33 +44,57 @@ export default class Wrapper { verified: boolean; }[]; }>; - static removeDevice(userId: string, deviceName: string, userContext?: Record): Promise<{ + static removeDevice( + userId: string, + deviceName: string, + userContext?: Record + ): Promise<{ status: "OK"; didDeviceExist: boolean; }>; - static verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record): Promise<{ - status: "OK"; - wasAlreadyVerified: boolean; - } | { - status: "UNKNOWN_DEVICE_ERROR"; - } | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - }>; - static verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record): Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR"; - } | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - }>; + static verifyDevice( + tenantId: string, + userId: string, + deviceName: string, + totp: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + wasAlreadyVerified: boolean; + } + | { + status: "UNKNOWN_DEVICE_ERROR"; + } + | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } + | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + } + >; + static verifyTOTP( + tenantId: string, + userId: string, + totp: string, + userContext?: Record + ): Promise< + | { + status: "OK" | "UNKNOWN_USER_ID_ERROR"; + } + | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } + | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + } + >; } export declare let init: typeof Recipe.init; export declare let createDevice: typeof Wrapper.createDevice; diff --git a/lib/build/recipe/totp/index.js b/lib/build/recipe/totp/index.js index c954e65d2..579bb3332 100644 --- a/lib/build/recipe/totp/index.js +++ b/lib/build/recipe/totp/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifyTOTP = exports.verifyDevice = exports.removeDevice = exports.updateDevice = exports.listDevices = exports.createDevice = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/totp/recipe.d.ts b/lib/build/recipe/totp/recipe.d.ts index 28f023fec..53309d471 100644 --- a/lib/build/recipe/totp/recipe.d.ts +++ b/lib/build/recipe/totp/recipe.d.ts @@ -18,7 +18,15 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, _tenantId: string, req: BaseRequest, res: BaseResponse, _: NormalisedURLPath, __: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + _tenantId: string, + req: BaseRequest, + res: BaseResponse, + _: NormalisedURLPath, + __: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _: BaseRequest, __: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/totp/recipe.js b/lib/build/recipe/totp/recipe.js index 5a4619762..fb146942b 100644 --- a/lib/build/recipe/totp/recipe.js +++ b/lib/build/recipe/totp/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -83,17 +85,13 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.CREATE_TOTP_DEVICE) { return await createDevice_1.default(this.apiImpl, options, userContext); - } - else if (id === constants_1.LIST_TOTP_DEVICES) { + } else if (id === constants_1.LIST_TOTP_DEVICES) { return await listDevices_1.default(this.apiImpl, options, userContext); - } - else if (id === constants_1.REMOVE_TOTP_DEVICE) { + } else if (id === constants_1.REMOVE_TOTP_DEVICE) { return await removeDevice_1.default(this.apiImpl, options, userContext); - } - else if (id === constants_1.VERIFY_TOTP_DEVICE) { + } else if (id === constants_1.VERIFY_TOTP_DEVICE) { return await verifyDevice_1.default(this.apiImpl, options, userContext); - } - else if (id === constants_1.VERIFY_TOTP) { + } else if (id === constants_1.VERIFY_TOTP) { return await verifyTOTP_1.default(this.apiImpl, options, userContext); } throw new Error("should never come here"); @@ -110,7 +108,9 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -152,8 +152,7 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } - else { + } else { throw new Error("TOTP recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/totp/recipeImplementation.js b/lib/build/recipe/totp/recipeImplementation.js index fb86eb82b..5ad44874b 100644 --- a/lib/build/recipe/totp/recipeImplementation.js +++ b/lib/build/recipe/totp/recipeImplementation.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const __1 = require("../.."); @@ -28,15 +30,16 @@ function getRecipeInterface(querier, config) { status: "UNKNOWN_USER_ID_ERROR", }; } - const primaryLoginMethod = user.loginMethods.find((method) => method.recipeUserId.getAsString() === user.id); + const primaryLoginMethod = user.loginMethods.find( + (method) => method.recipeUserId.getAsString() === user.id + ); if (primaryLoginMethod !== undefined) { if (primaryLoginMethod.email !== undefined) { return { info: primaryLoginMethod.email, status: "OK", }; - } - else if (primaryLoginMethod.phoneNumber !== undefined) { + } else if (primaryLoginMethod.phoneNumber !== undefined) { return { info: primaryLoginMethod.phoneNumber, status: "OK", @@ -45,8 +48,7 @@ function getRecipeInterface(querier, config) { } if (user.emails.length > 0) { return { info: user.emails[0], status: "OK" }; - } - else if (user.phoneNumbers.length > 0) { + } else if (user.phoneNumbers.length > 0) { return { info: user.phoneNumbers[0], status: "OK" }; } return { @@ -62,55 +64,85 @@ function getRecipeInterface(querier, config) { }); if (emailOrPhoneInfo.status === "OK") { input.userIdentifierInfo = emailOrPhoneInfo.info; - } - else if (emailOrPhoneInfo.status === "UNKNOWN_USER_ID_ERROR") { + } else if (emailOrPhoneInfo.status === "UNKNOWN_USER_ID_ERROR") { return { status: "UNKNOWN_USER_ID_ERROR", }; - } - else { + } else { // Ignore since UserIdentifierInfo is optional } } - const response = await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/totp/device"), { - userId: input.userId, - deviceName: input.deviceName, - skew: (_a = input.skew) !== null && _a !== void 0 ? _a : config.defaultSkew, - period: (_b = input.period) !== null && _b !== void 0 ? _b : config.defaultPeriod, - }, input.userContext); - return Object.assign(Object.assign({}, response), { qrCodeString: `otpauth://totp/${encodeURI(config.issuer)}${input.userIdentifierInfo !== undefined ? ":" + encodeURI(input.userIdentifierInfo) : ""}` + - `?secret=${response.secret}&issuer=${encodeURI(config.issuer)}&digits=6&period=${(_c = input.period) !== null && _c !== void 0 ? _c : config.defaultPeriod}` }); + const response = await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/totp/device"), + { + userId: input.userId, + deviceName: input.deviceName, + skew: (_a = input.skew) !== null && _a !== void 0 ? _a : config.defaultSkew, + period: (_b = input.period) !== null && _b !== void 0 ? _b : config.defaultPeriod, + }, + input.userContext + ); + return Object.assign(Object.assign({}, response), { + qrCodeString: + `otpauth://totp/${encodeURI(config.issuer)}${ + input.userIdentifierInfo !== undefined ? ":" + encodeURI(input.userIdentifierInfo) : "" + }` + + `?secret=${response.secret}&issuer=${encodeURI(config.issuer)}&digits=6&period=${ + (_c = input.period) !== null && _c !== void 0 ? _c : config.defaultPeriod + }`, + }); }, updateDevice: (input) => { - return querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/totp/device"), { - userId: input.userId, - existingDeviceName: input.existingDeviceName, - newDeviceName: input.newDeviceName, - }, {}, input.userContext); + return querier.sendPutRequest( + new normalisedURLPath_1.default("/recipe/totp/device"), + { + userId: input.userId, + existingDeviceName: input.existingDeviceName, + newDeviceName: input.newDeviceName, + }, + {}, + input.userContext + ); }, listDevices: (input) => { - return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/totp/device/list"), { - userId: input.userId, - }, input.userContext); + return querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/totp/device/list"), + { + userId: input.userId, + }, + input.userContext + ); }, removeDevice: (input) => { - return querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/totp/device/remove"), { - userId: input.userId, - deviceName: input.deviceName, - }, input.userContext); + return querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/totp/device/remove"), + { + userId: input.userId, + deviceName: input.deviceName, + }, + input.userContext + ); }, verifyDevice: (input) => { - return querier.sendPostRequest(new normalisedURLPath_1.default(`${input.tenantId}/recipe/totp/device/verify`), { - userId: input.userId, - deviceName: input.deviceName, - totp: input.totp, - }, input.userContext); + return querier.sendPostRequest( + new normalisedURLPath_1.default(`${input.tenantId}/recipe/totp/device/verify`), + { + userId: input.userId, + deviceName: input.deviceName, + totp: input.totp, + }, + input.userContext + ); }, verifyTOTP: (input) => { - return querier.sendPostRequest(new normalisedURLPath_1.default(`${input.tenantId}/recipe/totp/verify`), { - userId: input.userId, - totp: input.totp, - }, input.userContext); + return querier.sendPostRequest( + new normalisedURLPath_1.default(`${input.tenantId}/recipe/totp/verify`), + { + userId: input.userId, + totp: input.totp, + }, + input.userContext + ); }, }; } diff --git a/lib/build/recipe/totp/types.d.ts b/lib/build/recipe/totp/types.d.ts index fc61b60ea..f4016b9e1 100644 --- a/lib/build/recipe/totp/types.d.ts +++ b/lib/build/recipe/totp/types.d.ts @@ -8,7 +8,10 @@ export declare type TypeInput = { defaultSkew?: number; defaultPeriod?: number; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -17,7 +20,10 @@ export declare type TypeNormalisedInput = { defaultSkew: number; defaultPeriod: number; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -25,12 +31,15 @@ export declare type RecipeInterface = { getUserIdentifierInfoForUserId: (input: { userId: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - info: string; - } | { - status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR"; - }>; + }) => Promise< + | { + status: "OK"; + info: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR"; + } + >; createDevice: (input: { userId: string; userIdentifierInfo?: string; @@ -38,16 +47,20 @@ export declare type RecipeInterface = { skew?: number; period?: number; userContext: UserContext; - }) => Promise<{ - status: "OK"; - deviceName: string; - secret: string; - qrCodeString: string; - } | { - status: "DEVICE_ALREADY_EXISTS_ERROR"; - } | { - status: "UNKNOWN_USER_ID_ERROR"; - }>; + }) => Promise< + | { + status: "OK"; + deviceName: string; + secret: string; + qrCodeString: string; + } + | { + status: "DEVICE_ALREADY_EXISTS_ERROR"; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; updateDevice: (input: { userId: string; existingDeviceName: string; @@ -82,34 +95,43 @@ export declare type RecipeInterface = { deviceName: string; totp: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - wasAlreadyVerified: boolean; - } | { - status: "UNKNOWN_DEVICE_ERROR"; - } | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - }>; + }) => Promise< + | { + status: "OK"; + wasAlreadyVerified: boolean; + } + | { + status: "UNKNOWN_DEVICE_ERROR"; + } + | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } + | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + } + >; verifyTOTP: (input: { tenantId: string; userId: string; totp: string; userContext: UserContext; - }) => Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR"; - } | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - }>; + }) => Promise< + | { + status: "OK" | "UNKNOWN_USER_ID_ERROR"; + } + | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } + | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + } + >; }; export declare type APIOptions = { recipeImplementation: RecipeInterface; @@ -120,73 +142,104 @@ export declare type APIOptions = { res: BaseResponse; }; export declare type APIInterface = { - createDevicePOST: undefined | ((input: { - deviceName?: string; - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - deviceName: string; - secret: string; - qrCodeString: string; - } | { - status: "DEVICE_ALREADY_EXISTS_ERROR"; - } | GeneralErrorResponse>); - listDevicesGET: undefined | ((input: { - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - devices: { - name: string; - period: number; - skew: number; - verified: boolean; - }[]; - } | GeneralErrorResponse>); - removeDevicePOST: undefined | ((input: { - deviceName: string; - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - didDeviceExist: boolean; - } | GeneralErrorResponse>); - verifyDevicePOST: undefined | ((input: { - deviceName: string; - totp: string; - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - wasAlreadyVerified: boolean; - } | { - status: "UNKNOWN_DEVICE_ERROR"; - } | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - } | GeneralErrorResponse>); - verifyTOTPPOST: undefined | ((input: { - totp: string; - options: APIOptions; - session: SessionContainerInterface; - userContext: UserContext; - }) => Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR"; - } | { - status: "INVALID_TOTP_ERROR"; - currentNumberOfFailedAttempts: number; - maxNumberOfFailedAttempts: number; - } | { - status: "LIMIT_REACHED_ERROR"; - retryAfterMs: number; - } | GeneralErrorResponse>); + createDevicePOST: + | undefined + | ((input: { + deviceName?: string; + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + deviceName: string; + secret: string; + qrCodeString: string; + } + | { + status: "DEVICE_ALREADY_EXISTS_ERROR"; + } + | GeneralErrorResponse + >); + listDevicesGET: + | undefined + | ((input: { + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + devices: { + name: string; + period: number; + skew: number; + verified: boolean; + }[]; + } + | GeneralErrorResponse + >); + removeDevicePOST: + | undefined + | ((input: { + deviceName: string; + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + didDeviceExist: boolean; + } + | GeneralErrorResponse + >); + verifyDevicePOST: + | undefined + | ((input: { + deviceName: string; + totp: string; + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + wasAlreadyVerified: boolean; + } + | { + status: "UNKNOWN_DEVICE_ERROR"; + } + | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } + | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + } + | GeneralErrorResponse + >); + verifyTOTPPOST: + | undefined + | ((input: { + totp: string; + options: APIOptions; + session: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + status: "OK" | "UNKNOWN_USER_ID_ERROR"; + } + | { + status: "INVALID_TOTP_ERROR"; + currentNumberOfFailedAttempts: number; + maxNumberOfFailedAttempts: number; + } + | { + status: "LIMIT_REACHED_ERROR"; + retryAfterMs: number; + } + | GeneralErrorResponse + >); }; diff --git a/lib/build/recipe/totp/utils.d.ts b/lib/build/recipe/totp/utils.d.ts index 69bb66884..6b5abd280 100644 --- a/lib/build/recipe/totp/utils.d.ts +++ b/lib/build/recipe/totp/utils.d.ts @@ -1,4 +1,7 @@ // @ts-nocheck import { NormalisedAppinfo } from "../../types"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput(appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + appInfo: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; diff --git a/lib/build/recipe/totp/utils.js b/lib/build/recipe/totp/utils.js index fec010fe6..fbc423449 100644 --- a/lib/build/recipe/totp/utils.js +++ b/lib/build/recipe/totp/utils.js @@ -17,11 +17,26 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(appInfo, config) { var _a, _b, _c; - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { - issuer: (_a = config === null || config === void 0 ? void 0 : config.issuer) !== null && _a !== void 0 ? _a : appInfo.appName, - defaultSkew: (_b = config === null || config === void 0 ? void 0 : config.defaultSkew) !== null && _b !== void 0 ? _b : 1, - defaultPeriod: (_c = config === null || config === void 0 ? void 0 : config.defaultPeriod) !== null && _c !== void 0 ? _c : 30, + issuer: + (_a = config === null || config === void 0 ? void 0 : config.issuer) !== null && _a !== void 0 + ? _a + : appInfo.appName, + defaultSkew: + (_b = config === null || config === void 0 ? void 0 : config.defaultSkew) !== null && _b !== void 0 + ? _b + : 1, + defaultPeriod: + (_c = config === null || config === void 0 ? void 0 : config.defaultPeriod) !== null && _c !== void 0 + ? _c + : 30, override, }; } diff --git a/lib/build/recipe/usermetadata/index.d.ts b/lib/build/recipe/usermetadata/index.d.ts index 68b6918ca..17987da81 100644 --- a/lib/build/recipe/usermetadata/index.d.ts +++ b/lib/build/recipe/usermetadata/index.d.ts @@ -4,15 +4,25 @@ import Recipe from "./recipe"; import { RecipeInterface } from "./types"; export default class Wrapper { static init: typeof Recipe.init; - static getUserMetadata(userId: string, userContext?: Record): Promise<{ + static getUserMetadata( + userId: string, + userContext?: Record + ): Promise<{ status: "OK"; metadata: any; }>; - static updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record): Promise<{ + static updateUserMetadata( + userId: string, + metadataUpdate: JSONObject, + userContext?: Record + ): Promise<{ status: "OK"; metadata: JSONObject; }>; - static clearUserMetadata(userId: string, userContext?: Record): Promise<{ + static clearUserMetadata( + userId: string, + userContext?: Record + ): Promise<{ status: "OK"; }>; } diff --git a/lib/build/recipe/usermetadata/index.js b/lib/build/recipe/usermetadata/index.js index 3502273ab..b0884bab7 100644 --- a/lib/build/recipe/usermetadata/index.js +++ b/lib/build/recipe/usermetadata/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.clearUserMetadata = exports.updateUserMetadata = exports.getUserMetadata = exports.init = void 0; const utils_1 = require("../../utils"); diff --git a/lib/build/recipe/usermetadata/recipe.d.ts b/lib/build/recipe/usermetadata/recipe.d.ts index a1b5a2748..53d2c80ad 100644 --- a/lib/build/recipe/usermetadata/recipe.d.ts +++ b/lib/build/recipe/usermetadata/recipe.d.ts @@ -16,7 +16,14 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled(): APIHandled[]; - handleAPIRequest: (_: string, _tenantId: string | undefined, __: BaseRequest, ___: BaseResponse, ____: normalisedURLPath, _____: HTTPMethod) => Promise; + handleAPIRequest: ( + _: string, + _tenantId: string | undefined, + __: BaseRequest, + ___: BaseResponse, + ____: normalisedURLPath, + _____: HTTPMethod + ) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; diff --git a/lib/build/recipe/usermetadata/recipe.js b/lib/build/recipe/usermetadata/recipe.js index 1ada4d79e..600238705 100644 --- a/lib/build/recipe/usermetadata/recipe.js +++ b/lib/build/recipe/usermetadata/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); const querier_1 = require("../../querier"); @@ -34,7 +36,9 @@ class Recipe extends recipeModule_1.default { this.config = utils_2.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId))); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId)) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } } @@ -43,15 +47,16 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance !== undefined) { return Recipe.instance; } - throw new Error("Initialisation not done. Did you forget to call the UserMetadata.init or UserMetadata.init function?"); + throw new Error( + "Initialisation not done. Did you forget to call the UserMetadata.init or UserMetadata.init function?" + ); } static init(config) { return (appInfo, isInServerlessEnv) => { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } - else { + } else { throw new Error("UserMetadata recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/usermetadata/recipeImplementation.js b/lib/build/recipe/usermetadata/recipeImplementation.js index 660e2fce8..2fd78898b 100644 --- a/lib/build/recipe/usermetadata/recipeImplementation.js +++ b/lib/build/recipe/usermetadata/recipeImplementation.js @@ -13,26 +13,41 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); function getRecipeInterface(querier) { return { getUserMetadata: function ({ userId, userContext }) { - return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/user/metadata"), { userId }, userContext); + return querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/user/metadata"), + { userId }, + userContext + ); }, updateUserMetadata: function ({ userId, metadataUpdate, userContext }) { - return querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/user/metadata"), { - userId, - metadataUpdate, - }, {}, userContext); + return querier.sendPutRequest( + new normalisedURLPath_1.default("/recipe/user/metadata"), + { + userId, + metadataUpdate, + }, + {}, + userContext + ); }, clearUserMetadata: function ({ userId, userContext }) { - return querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/user/metadata/remove"), { - userId, - }, userContext); + return querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/user/metadata/remove"), + { + userId, + }, + userContext + ); }, }; } diff --git a/lib/build/recipe/usermetadata/types.d.ts b/lib/build/recipe/usermetadata/types.d.ts index 2a192cfb0..8dd434ac7 100644 --- a/lib/build/recipe/usermetadata/types.d.ts +++ b/lib/build/recipe/usermetadata/types.d.ts @@ -3,13 +3,19 @@ import OverrideableBuilder from "supertokens-js-override"; import { JSONObject, UserContext } from "../../types"; export declare type TypeInput = { override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; export declare type TypeNormalisedInput = { override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; diff --git a/lib/build/recipe/usermetadata/utils.d.ts b/lib/build/recipe/usermetadata/utils.d.ts index 133d4840f..4025b1b44 100644 --- a/lib/build/recipe/usermetadata/utils.d.ts +++ b/lib/build/recipe/usermetadata/utils.d.ts @@ -2,4 +2,8 @@ import { NormalisedAppinfo } from "../../types"; import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput(_: Recipe, __: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + _: Recipe, + __: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; diff --git a/lib/build/recipe/usermetadata/utils.js b/lib/build/recipe/usermetadata/utils.js index 9bdd116e1..74993e81f 100644 --- a/lib/build/recipe/usermetadata/utils.js +++ b/lib/build/recipe/usermetadata/utils.js @@ -16,7 +16,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(_, __, config) { - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { override, }; diff --git a/lib/build/recipe/userroles/index.d.ts b/lib/build/recipe/userroles/index.d.ts index af8d359c2..11a803d66 100644 --- a/lib/build/recipe/userroles/index.d.ts +++ b/lib/build/recipe/userroles/index.d.ts @@ -5,50 +5,99 @@ export default class Wrapper { static init: typeof Recipe.init; static PermissionClaim: import("./permissionClaim").PermissionClaimClass; static UserRoleClaim: import("./userRoleClaim").UserRoleClaimClass; - static addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record): Promise<{ - status: "OK"; - didUserAlreadyHaveRole: boolean; - } | { - status: "UNKNOWN_ROLE_ERROR"; - }>; - static removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record): Promise<{ - status: "OK"; - didUserHaveRole: boolean; - } | { - status: "UNKNOWN_ROLE_ERROR"; - }>; - static getRolesForUser(tenantId: string, userId: string, userContext?: Record): Promise<{ + static addRoleToUser( + tenantId: string, + userId: string, + role: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + didUserAlreadyHaveRole: boolean; + } + | { + status: "UNKNOWN_ROLE_ERROR"; + } + >; + static removeUserRole( + tenantId: string, + userId: string, + role: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + didUserHaveRole: boolean; + } + | { + status: "UNKNOWN_ROLE_ERROR"; + } + >; + static getRolesForUser( + tenantId: string, + userId: string, + userContext?: Record + ): Promise<{ status: "OK"; roles: string[]; }>; - static getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record): Promise<{ - status: "OK"; - users: string[]; - } | { - status: "UNKNOWN_ROLE_ERROR"; - }>; - static createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record): Promise<{ + static getUsersThatHaveRole( + tenantId: string, + role: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + users: string[]; + } + | { + status: "UNKNOWN_ROLE_ERROR"; + } + >; + static createNewRoleOrAddPermissions( + role: string, + permissions: string[], + userContext?: Record + ): Promise<{ status: "OK"; createdNewRole: boolean; }>; - static getPermissionsForRole(role: string, userContext?: Record): Promise<{ - status: "OK"; - permissions: string[]; - } | { - status: "UNKNOWN_ROLE_ERROR"; - }>; - static removePermissionsFromRole(role: string, permissions: string[], userContext?: Record): Promise<{ + static getPermissionsForRole( + role: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + permissions: string[]; + } + | { + status: "UNKNOWN_ROLE_ERROR"; + } + >; + static removePermissionsFromRole( + role: string, + permissions: string[], + userContext?: Record + ): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR"; }>; - static getRolesThatHavePermission(permission: string, userContext?: Record): Promise<{ + static getRolesThatHavePermission( + permission: string, + userContext?: Record + ): Promise<{ status: "OK"; roles: string[]; }>; - static deleteRole(role: string, userContext?: Record): Promise<{ + static deleteRole( + role: string, + userContext?: Record + ): Promise<{ status: "OK"; didRoleExist: boolean; }>; - static getAllRoles(userContext?: Record): Promise<{ + static getAllRoles( + userContext?: Record + ): Promise<{ status: "OK"; roles: string[]; }>; diff --git a/lib/build/recipe/userroles/index.js b/lib/build/recipe/userroles/index.js index fd98b7c01..21e01ff10 100644 --- a/lib/build/recipe/userroles/index.js +++ b/lib/build/recipe/userroles/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PermissionClaim = exports.UserRoleClaim = exports.getAllRoles = exports.deleteRole = exports.getRolesThatHavePermission = exports.removePermissionsFromRole = exports.getPermissionsForRole = exports.createNewRoleOrAddPermissions = exports.getUsersThatHaveRole = exports.getRolesForUser = exports.removeUserRole = exports.addRoleToUser = exports.init = void 0; const utils_1 = require("../../utils"); @@ -107,6 +109,16 @@ exports.getRolesThatHavePermission = Wrapper.getRolesThatHavePermission; exports.deleteRole = Wrapper.deleteRole; exports.getAllRoles = Wrapper.getAllRoles; var userRoleClaim_2 = require("./userRoleClaim"); -Object.defineProperty(exports, "UserRoleClaim", { enumerable: true, get: function () { return userRoleClaim_2.UserRoleClaim; } }); +Object.defineProperty(exports, "UserRoleClaim", { + enumerable: true, + get: function () { + return userRoleClaim_2.UserRoleClaim; + }, +}); var permissionClaim_2 = require("./permissionClaim"); -Object.defineProperty(exports, "PermissionClaim", { enumerable: true, get: function () { return permissionClaim_2.PermissionClaim; } }); +Object.defineProperty(exports, "PermissionClaim", { + enumerable: true, + get: function () { + return permissionClaim_2.PermissionClaim; + }, +}); diff --git a/lib/build/recipe/userroles/permissionClaim.js b/lib/build/recipe/userroles/permissionClaim.js index 16a5cb348..c022b4080 100644 --- a/lib/build/recipe/userroles/permissionClaim.js +++ b/lib/build/recipe/userroles/permissionClaim.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PermissionClaim = exports.PermissionClaimClass = void 0; const recipe_1 = __importDefault(require("./recipe")); diff --git a/lib/build/recipe/userroles/recipe.d.ts b/lib/build/recipe/userroles/recipe.d.ts index a1b5a2748..53d2c80ad 100644 --- a/lib/build/recipe/userroles/recipe.d.ts +++ b/lib/build/recipe/userroles/recipe.d.ts @@ -16,7 +16,14 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled(): APIHandled[]; - handleAPIRequest: (_: string, _tenantId: string | undefined, __: BaseRequest, ___: BaseResponse, ____: normalisedURLPath, _____: HTTPMethod) => Promise; + handleAPIRequest: ( + _: string, + _tenantId: string | undefined, + __: BaseRequest, + ___: BaseResponse, + ____: normalisedURLPath, + _____: HTTPMethod + ) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; diff --git a/lib/build/recipe/userroles/recipe.js b/lib/build/recipe/userroles/recipe.js index a4a3d7748..3f16cf7be 100644 --- a/lib/build/recipe/userroles/recipe.js +++ b/lib/build/recipe/userroles/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); const querier_1 = require("../../querier"); @@ -40,7 +42,9 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; { - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId))); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId)) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } postSuperTokensInitCallbacks_1.PostSuperTokensInitCallbacks.addPostInitCallback(() => { @@ -88,41 +92,43 @@ class Recipe extends recipeModule_1.default { }; recipe_2.default.getInstanceOrThrowError().addAccessTokenBuilderFromOtherRecipe(tokenPayloadBuilder); recipe_2.default.getInstanceOrThrowError().addIdTokenBuilderFromOtherRecipe(tokenPayloadBuilder); - recipe_2.default.getInstanceOrThrowError().addUserInfoBuilderFromOtherRecipe(async (user, _accessTokenPayload, scopes, tenantId, userContext) => { - let userInfo = {}; - let userRoles = []; - if (scopes.includes("roles") || scopes.includes("permissions")) { - const res = await this.recipeInterfaceImpl.getRolesForUser({ - userId: user.id, - tenantId, - userContext, - }); - if (res.status !== "OK") { - throw new Error("Failed to fetch roles for the user"); - } - userRoles = res.roles; - } - if (scopes.includes("roles")) { - userInfo.roles = userRoles; - } - if (scopes.includes("permissions")) { - const userPermissions = new Set(); - for (const role of userRoles) { - const rolePermissions = await this.recipeInterfaceImpl.getPermissionsForRole({ - role, + recipe_2.default + .getInstanceOrThrowError() + .addUserInfoBuilderFromOtherRecipe(async (user, _accessTokenPayload, scopes, tenantId, userContext) => { + let userInfo = {}; + let userRoles = []; + if (scopes.includes("roles") || scopes.includes("permissions")) { + const res = await this.recipeInterfaceImpl.getRolesForUser({ + userId: user.id, + tenantId, userContext, }); - if (rolePermissions.status !== "OK") { - throw new Error("Failed to fetch permissions for the role"); + if (res.status !== "OK") { + throw new Error("Failed to fetch roles for the user"); } - for (const perm of rolePermissions.permissions) { - userPermissions.add(perm); + userRoles = res.roles; + } + if (scopes.includes("roles")) { + userInfo.roles = userRoles; + } + if (scopes.includes("permissions")) { + const userPermissions = new Set(); + for (const role of userRoles) { + const rolePermissions = await this.recipeInterfaceImpl.getPermissionsForRole({ + role, + userContext, + }); + if (rolePermissions.status !== "OK") { + throw new Error("Failed to fetch permissions for the role"); + } + for (const perm of rolePermissions.permissions) { + userPermissions.add(perm); + } } + userInfo.permissions = Array.from(userPermissions); } - userInfo.permissions = Array.from(userPermissions); - } - return userInfo; - }); + return userInfo; + }); }); } /* Init functions */ @@ -130,15 +136,16 @@ class Recipe extends recipeModule_1.default { if (Recipe.instance !== undefined) { return Recipe.instance; } - throw new Error("Initialisation not done. Did you forget to call the UserRoles.init or SuperTokens.init functions?"); + throw new Error( + "Initialisation not done. Did you forget to call the UserRoles.init or SuperTokens.init functions?" + ); } static init(config) { return (appInfo, isInServerlessEnv) => { if (Recipe.instance === undefined) { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; - } - else { + } else { throw new Error("UserRoles recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/userroles/recipeImplementation.js b/lib/build/recipe/userroles/recipeImplementation.js index 9e5c9a81d..47dbdc1ba 100644 --- a/lib/build/recipe/userroles/recipeImplementation.js +++ b/lib/build/recipe/userroles/recipeImplementation.js @@ -13,43 +13,91 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const constants_1 = require("../multitenancy/constants"); function getRecipeInterface(querier) { return { addRoleToUser: function ({ userId, role, tenantId, userContext }) { - return querier.sendPutRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role`), { userId, role }, {}, userContext); + return querier.sendPutRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role` + ), + { userId, role }, + {}, + userContext + ); }, removeUserRole: function ({ userId, role, tenantId, userContext }) { - return querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role/remove`), { userId, role }, userContext); + return querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role/remove` + ), + { userId, role }, + userContext + ); }, getRolesForUser: function ({ userId, tenantId, userContext }) { - return querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/roles`), { userId }, userContext); + return querier.sendGetRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/roles` + ), + { userId }, + userContext + ); }, getUsersThatHaveRole: function ({ role, tenantId, userContext }) { - return querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/role/users`), { role }, userContext); + return querier.sendGetRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/role/users` + ), + { role }, + userContext + ); }, createNewRoleOrAddPermissions: function ({ role, permissions, userContext }) { - return querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/role"), { role, permissions }, {}, userContext); + return querier.sendPutRequest( + new normalisedURLPath_1.default("/recipe/role"), + { role, permissions }, + {}, + userContext + ); }, getPermissionsForRole: function ({ role, userContext }) { - return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/role/permissions"), { role }, userContext); + return querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/role/permissions"), + { role }, + userContext + ); }, removePermissionsFromRole: function ({ role, permissions, userContext }) { - return querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/role/permissions/remove"), { - role, - permissions, - }, userContext); + return querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/role/permissions/remove"), + { + role, + permissions, + }, + userContext + ); }, getRolesThatHavePermission: function ({ permission, userContext }) { - return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/permission/roles"), { permission }, userContext); + return querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/permission/roles"), + { permission }, + userContext + ); }, deleteRole: function ({ role, userContext }) { - return querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/role/remove"), { role }, userContext); + return querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/role/remove"), + { role }, + userContext + ); }, getAllRoles: function ({ userContext }) { return querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/roles"), {}, userContext); diff --git a/lib/build/recipe/userroles/types.d.ts b/lib/build/recipe/userroles/types.d.ts index b08d38c7c..c79ca9b8d 100644 --- a/lib/build/recipe/userroles/types.d.ts +++ b/lib/build/recipe/userroles/types.d.ts @@ -5,7 +5,10 @@ export declare type TypeInput = { skipAddingRolesToAccessToken?: boolean; skipAddingPermissionsToAccessToken?: boolean; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -13,7 +16,10 @@ export declare type TypeNormalisedInput = { skipAddingRolesToAccessToken: boolean; skipAddingPermissionsToAccessToken: boolean; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -24,23 +30,29 @@ export declare type RecipeInterface = { role: string; tenantId: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - didUserAlreadyHaveRole: boolean; - } | { - status: "UNKNOWN_ROLE_ERROR"; - }>; + }) => Promise< + | { + status: "OK"; + didUserAlreadyHaveRole: boolean; + } + | { + status: "UNKNOWN_ROLE_ERROR"; + } + >; removeUserRole: (input: { userId: string; role: string; tenantId: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - didUserHaveRole: boolean; - } | { - status: "UNKNOWN_ROLE_ERROR"; - }>; + }) => Promise< + | { + status: "OK"; + didUserHaveRole: boolean; + } + | { + status: "UNKNOWN_ROLE_ERROR"; + } + >; getRolesForUser: (input: { userId: string; tenantId: string; @@ -53,12 +65,15 @@ export declare type RecipeInterface = { role: string; tenantId: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - users: string[]; - } | { - status: "UNKNOWN_ROLE_ERROR"; - }>; + }) => Promise< + | { + status: "OK"; + users: string[]; + } + | { + status: "UNKNOWN_ROLE_ERROR"; + } + >; createNewRoleOrAddPermissions: (input: { role: string; permissions: string[]; @@ -70,12 +85,15 @@ export declare type RecipeInterface = { getPermissionsForRole: (input: { role: string; userContext: UserContext; - }) => Promise<{ - status: "OK"; - permissions: string[]; - } | { - status: "UNKNOWN_ROLE_ERROR"; - }>; + }) => Promise< + | { + status: "OK"; + permissions: string[]; + } + | { + status: "UNKNOWN_ROLE_ERROR"; + } + >; removePermissionsFromRole: (input: { role: string; permissions: string[]; diff --git a/lib/build/recipe/userroles/userRoleClaim.js b/lib/build/recipe/userroles/userRoleClaim.js index 20254bbd7..ec8bdb305 100644 --- a/lib/build/recipe/userroles/userRoleClaim.js +++ b/lib/build/recipe/userroles/userRoleClaim.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.UserRoleClaim = exports.UserRoleClaimClass = void 0; const recipe_1 = __importDefault(require("./recipe")); diff --git a/lib/build/recipe/userroles/utils.d.ts b/lib/build/recipe/userroles/utils.d.ts index 133d4840f..4025b1b44 100644 --- a/lib/build/recipe/userroles/utils.d.ts +++ b/lib/build/recipe/userroles/utils.d.ts @@ -2,4 +2,8 @@ import { NormalisedAppinfo } from "../../types"; import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; -export declare function validateAndNormaliseUserInput(_: Recipe, __: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; +export declare function validateAndNormaliseUserInput( + _: Recipe, + __: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; diff --git a/lib/build/recipe/userroles/utils.js b/lib/build/recipe/userroles/utils.js index f392db6ee..7023b84d5 100644 --- a/lib/build/recipe/userroles/utils.js +++ b/lib/build/recipe/userroles/utils.js @@ -16,10 +16,18 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndNormaliseUserInput = void 0; function validateAndNormaliseUserInput(_, __, config) { - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); return { - skipAddingRolesToAccessToken: (config === null || config === void 0 ? void 0 : config.skipAddingRolesToAccessToken) === true, - skipAddingPermissionsToAccessToken: (config === null || config === void 0 ? void 0 : config.skipAddingPermissionsToAccessToken) === true, + skipAddingRolesToAccessToken: + (config === null || config === void 0 ? void 0 : config.skipAddingRolesToAccessToken) === true, + skipAddingPermissionsToAccessToken: + (config === null || config === void 0 ? void 0 : config.skipAddingPermissionsToAccessToken) === true, override, }; } diff --git a/lib/build/recipe/webauthn/api/emailExists.d.ts b/lib/build/recipe/webauthn/api/emailExists.d.ts index 478175dec..2f55b6d3b 100644 --- a/lib/build/recipe/webauthn/api/emailExists.d.ts +++ b/lib/build/recipe/webauthn/api/emailExists.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function emailExists(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function emailExists( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/emailExists.js b/lib/build/recipe/webauthn/api/emailExists.js index 1c483fbe1..878fa5c58 100644 --- a/lib/build/recipe/webauthn/api/emailExists.js +++ b/lib/build/recipe/webauthn/api/emailExists.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts index 70044a514..ca836c5b4 100644 --- a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts +++ b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function generateRecoverAccountToken(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function generateRecoverAccountToken( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js index 10dcd5788..0bec60d05 100644 --- a/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js +++ b/lib/build/recipe/webauthn/api/generateRecoverAccountToken.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); diff --git a/lib/build/recipe/webauthn/api/implementation.js b/lib/build/recipe/webauthn/api/implementation.js index 7c17a55f9..6a4c31a84 100644 --- a/lib/build/recipe/webauthn/api/implementation.js +++ b/lib/build/recipe/webauthn/api/implementation.js @@ -1,18 +1,20 @@ "use strict"; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../../accountlinking/recipe")); const recipe_2 = __importDefault(require("../../emailverification/recipe")); @@ -26,7 +28,8 @@ const __1 = require("../../.."); function getAPIImplementation() { return { registerOptionsPOST: async function (_a) { - var { tenantId, options, userContext } = _a, props = __rest(_a, ["tenantId", "options", "userContext"]); + var { tenantId, options, userContext } = _a, + props = __rest(_a, ["tenantId", "options", "userContext"]); const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, request: options.req, @@ -46,16 +49,20 @@ function getAPIImplementation() { const residentKey = constants_1.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY; const userVerification = constants_1.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION; const supportedAlgorithmIds = constants_1.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS; - let response = await options.recipeImplementation.registerOptions(Object.assign(Object.assign({}, props), { attestation, - residentKey, - userVerification, - origin, - relyingPartyId, - relyingPartyName, - timeout, - tenantId, - userContext, - supportedAlgorithmIds })); + let response = await options.recipeImplementation.registerOptions( + Object.assign(Object.assign({}, props), { + attestation, + residentKey, + userVerification, + origin, + relyingPartyId, + relyingPartyName, + timeout, + tenantId, + userContext, + supportedAlgorithmIds, + }) + ); if (response.status !== "OK") { return response; } @@ -74,7 +81,7 @@ function getAPIImplementation() { authenticatorSelection: response.authenticatorSelection, }; }, - signInOptionsPOST: async function ({ email, tenantId, options, userContext, }) { + signInOptionsPOST: async function ({ tenantId, options, userContext }) { const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, request: options.req, @@ -89,7 +96,6 @@ function getAPIImplementation() { const timeout = constants_1.DEFAULT_SIGNIN_OPTIONS_TIMEOUT; const userVerification = constants_1.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION; let response = await options.recipeImplementation.signInOptions({ - email, userVerification, origin, relyingPartyId, @@ -110,19 +116,33 @@ function getAPIImplementation() { userVerification: response.userVerification, }; }, - signUpPOST: async function ({ webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, options, userContext, }) { + signUpPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }) { // TODO update error codes (ERR_CODE_XXX) after final implementation const errorCodeMap = { - SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + SIGN_UP_NOT_ALLOWED: + "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", INVALID_AUTHENTICATOR_ERROR: { - // TODO: add more cases + // TODO: add more cases }, - INVALID_CREDENTIALS_ERROR: "The sign up credentials are incorrect. Please use a different authenticator.", + INVALID_CREDENTIALS_ERROR: + "The sign up credentials are incorrect. Please use a different authenticator.", LINKING_TO_SESSION_USER_FAILED: { - EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", - 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_016)", + EMAIL_VERIFICATION_REQUIRED: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)", + 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_016)", }, }; const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ @@ -138,7 +158,9 @@ function getAPIImplementation() { // check for type is done in a parent function but they are kept // here to be on the safe side. if (!email) { - throw new Error("Should never come here since we already check that the email value is a string in validateEmailAddress"); + throw new Error( + "Should never come here since we already check that the email value is a string in validateEmailAddress" + ); } // todo familiarize with this method const preAuthCheckRes = await authUtils_1.AuthUtils.preAuthChecks({ @@ -158,23 +180,33 @@ function getAPIImplementation() { shouldTryLinkingWithSessionUser, }); if (preAuthCheckRes.status === "SIGN_UP_NOT_ALLOWED") { - const conflictingUsers = await recipe_1.default.getInstance().recipeInterfaceImpl.listUsersByAccountInfo({ - tenantId, - accountInfo: { - email, - }, - doUnionOfAccountInfo: false, - userContext, - }); + const conflictingUsers = await recipe_1.default + .getInstance() + .recipeInterfaceImpl.listUsersByAccountInfo({ + tenantId, + accountInfo: { + email, + }, + doUnionOfAccountInfo: false, + userContext, + }); // this isn't mandatory to - if (conflictingUsers.some((u) => u.loginMethods.some((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)))) { + if ( + conflictingUsers.some((u) => + u.loginMethods.some((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) + ) + ) { return { status: "EMAIL_ALREADY_EXISTS_ERROR", }; } } if (preAuthCheckRes.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthCheckRes, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthCheckRes, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); } if (utils_1.isFakeEmail(email) && preAuthCheckRes.isFirstFactor) { // Fake emails cannot be used as a first factor @@ -195,7 +227,11 @@ function getAPIImplementation() { return signUpResponse; } if (signUpResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(signUpResponse, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + signUpResponse, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); } // todo familiarize with this method // todo check if we need to remove webauthn credential ids from the type - it is not used atm. @@ -214,7 +250,11 @@ function getAPIImplementation() { // It should never actually come here, but we do it cause of consistency. // If it does come here (in case there is a bug), it would make this func throw // anyway, cause there is no SIGN_IN_NOT_ALLOWED in the errorCodeMap. - authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_UP_NOT_ALLOWED"); + authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + postAuthChecks, + errorCodeMap, + "SIGN_UP_NOT_ALLOWED" + ); throw new Error("This should never happen"); } return { @@ -223,14 +263,28 @@ function getAPIImplementation() { user: postAuthChecks.user, }; }, - signInPOST: async function ({ webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, options, userContext, }) { + signInPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }) { + var _a; const errorCodeMap = { - SIGN_IN_NOT_ALLOWED: "Cannot sign in due to security reasons. Please try recovering your account, use a different login method or contact support. (ERR_CODE_008)", + SIGN_IN_NOT_ALLOWED: + "Cannot sign in due to security reasons. Please try recovering your account, use a different login method or contact support. (ERR_CODE_008)", LINKING_TO_SESSION_USER_FAILED: { - EMAIL_VERIFICATION_REQUIRED: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", - RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", - ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", - 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_012)", + EMAIL_VERIFICATION_REQUIRED: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_009)", + RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_010)", + ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR: + "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_011)", + 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_012)", }, }; const recipeId = "webauthn"; @@ -253,7 +307,6 @@ function getAPIImplementation() { status: "INVALID_CREDENTIALS_ERROR", }; } - let email = generatedOptions.email; const checkCredentialsOnTenant = async () => { return true; }; @@ -266,14 +319,17 @@ function getAPIImplementation() { // lm.hasSamePhoneNumberAs(accountInfo.phoneNumber) || // lm.hasSameThirdPartyInfoAs(accountInfo.thirdParty)) // ); - const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({ - accountInfo: { email }, - userContext, - recipeId, - session, - tenantId, - checkCredentialsOnTenant, - }); + const accountInfo = { webauthn: { credentialId: credential.id } }; + const authenticatingUser = await authUtils_1.AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired( + { + accountInfo, + userContext, + recipeId, + session, + tenantId, + checkCredentialsOnTenant, + } + ); const isVerified = authenticatingUser !== undefined && authenticatingUser.loginMethod.verified; // We check this before preAuthChecks, because that function assumes that if isSignUp is false, // then authenticatingUser is defined. While it wouldn't technically cause any problems with @@ -284,6 +340,22 @@ function getAPIImplementation() { status: "INVALID_CREDENTIALS_ERROR", }; } + // we find the email of the user that has the same credentialId as the one we are verifying + const email = + (_a = authenticatingUser.user.loginMethods.find((lm) => { + var _a; + return ( + lm.recipeId === "webauthn" && + ((_a = lm.webauthn) === null || _a === void 0 + ? void 0 + : _a.credentialIds.includes(credential.id)) + ); + })) === null || _a === void 0 + ? void 0 + : _a.email; + if (email === undefined) { + throw new Error("This should never happen: webauthn user has no email"); + } const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ authenticatingAccountInfo: { recipeId, @@ -291,7 +363,8 @@ function getAPIImplementation() { }, factorIds: [recipeId], isSignUp: false, - authenticatingUser: authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, + authenticatingUser: + authenticatingUser === null || authenticatingUser === void 0 ? void 0 : authenticatingUser.user, isVerified, signInVerifiesLoginMethod: false, skipSessionUserUpdateInCore: false, @@ -304,7 +377,11 @@ function getAPIImplementation() { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); } if (preAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(preAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + preAuthChecks, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); } if (utils_1.isFakeEmail(email) && preAuthChecks.isFirstFactor) { // Fake emails cannot be used as a first factor @@ -324,7 +401,11 @@ function getAPIImplementation() { return signInResponse; } if (signInResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(signInResponse, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + signInResponse, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); } const postAuthChecks = await authUtils_1.AuthUtils.postAuthChecks({ authenticatedUser: signInResponse.user, @@ -338,7 +419,11 @@ function getAPIImplementation() { userContext, }); if (postAuthChecks.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(postAuthChecks, errorCodeMap, "SIGN_IN_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + postAuthChecks, + errorCodeMap, + "SIGN_IN_NOT_ALLOWED" + ); } return { status: "OK", @@ -346,7 +431,7 @@ function getAPIImplementation() { user: postAuthChecks.user, }; }, - emailExistsGET: async function ({ email, tenantId, userContext, }) { + emailExistsGET: async function ({ email, tenantId, userContext }) { // even if the above returns true, we still need to check if there // exists an webauthn user with the same email cause the function // above does not check for that. @@ -358,21 +443,26 @@ function getAPIImplementation() { doUnionOfAccountInfo: false, userContext, }); - let webauthnUserExists = users.find((u) => { - return (u.loginMethods.find((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) !== - undefined); - }) !== undefined; + let webauthnUserExists = + users.find((u) => { + return ( + u.loginMethods.find((lm) => lm.recipeId === "webauthn" && lm.hasSameEmailAs(email)) !== + undefined + ); + }) !== undefined; return { status: "OK", exists: webauthnUserExists, }; }, - generateRecoverAccountTokenPOST: async function ({ email, tenantId, options, userContext, }) { + generateRecoverAccountTokenPOST: async function ({ email, tenantId, options, userContext }) { // NOTE: Check for email being a non-string value. This check will likely // never evaluate to `true` as there is an upper-level check for the type // in validation but kept here to be safe. if (typeof email !== "string") - throw new Error("Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError"); + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); // this function will be reused in different parts of the flow below.. async function generateAndSendRecoverAccountToken(primaryUserId, recipeUserId) { // the user ID here can be primary or recipe level. @@ -383,7 +473,11 @@ function getAPIImplementation() { userContext, }); if (response.status === "UNKNOWN_USER_ID_ERROR") { - logger_1.logDebugMessage(`Recover account email not sent, unknown user id: ${recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString()}`); + logger_1.logDebugMessage( + `Recover account email not sent, unknown user id: ${ + recipeUserId === undefined ? primaryUserId : recipeUserId.getAsString() + }` + ); return { status: "OK", }; @@ -426,7 +520,9 @@ function getAPIImplementation() { // for later use. let webauthnAccount = undefined; for (let i = 0; i < users.length; i++) { - let webauthnAccountTmp = users[i].loginMethods.find((l) => l.recipeId === "webauthn" && l.hasSameEmailAs(email)); + let webauthnAccountTmp = users[i].loginMethods.find( + (l) => l.recipeId === "webauthn" && l.hasSameEmailAs(email) + ); if (webauthnAccountTmp !== undefined) { webauthnAccount = webauthnAccountTmp; break; @@ -443,36 +539,50 @@ function getAPIImplementation() { status: "OK", }; } - return await generateAndSendRecoverAccountToken(webauthnAccount.recipeUserId.getAsString(), webauthnAccount.recipeUserId); + return await generateAndSendRecoverAccountToken( + webauthnAccount.recipeUserId.getAsString(), + webauthnAccount.recipeUserId + ); } // Next we check if there is any login method in which the input email is verified. // If that is the case, then it's proven that the user owns the email and we can // trust linking of the webauthn account. - let emailVerified = primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - return lm.hasSameEmailAs(email) && lm.verified; - }) !== undefined; + let emailVerified = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.hasSameEmailAs(email) && lm.verified; + }) !== undefined; // finally, we check if the primary user has any other email / phone number // associated with this account - and if it does, then it means that // there is a risk of account takeover, so we do not allow the token to be generated - let hasOtherEmailOrPhone = primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - // we do the extra undefined check below cause - // hasSameEmailAs returns false if the lm.email is undefined, and - // we want to check that the email is different as opposed to email - // not existing in lm. - return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; - }) !== undefined; + let hasOtherEmailOrPhone = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + }) !== undefined; if (!emailVerified && hasOtherEmailOrPhone) { return { status: "RECOVER_ACCOUNT_NOT_ALLOWED", - reason: "Recover account link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + reason: + "Recover account link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", }; } - let shouldDoAccountLinkingResponse = await recipe_1.default.getInstance().config.shouldDoAutomaticAccountLinking(webauthnAccount !== undefined - ? webauthnAccount - : { - recipeId: "webauthn", - email, - }, primaryUserAssociatedWithEmail, undefined, tenantId, userContext); + let shouldDoAccountLinkingResponse = await recipe_1.default + .getInstance() + .config.shouldDoAutomaticAccountLinking( + webauthnAccount !== undefined + ? webauthnAccount + : { + recipeId: "webauthn", + email, + }, + primaryUserAssociatedWithEmail, + undefined, + tenantId, + userContext + ); // Now we need to check that if there exists any webauthn user at all // for the input email. If not, then it implies that when the token is consumed, // then we will create a new user - so we should only generate the token if @@ -486,7 +596,9 @@ function getAPIImplementation() { // code consume cannot be linked to the primary user - therefore, we should // not generate a recover account reset token if (!shouldDoAccountLinkingResponse.shouldAutomaticallyLink) { - logger_1.logDebugMessage(`Recover account email not sent, since webauthn user didn't exist, and account linking not enabled`); + logger_1.logDebugMessage( + `Recover account email not sent, since webauthn user didn't exist, and account linking not enabled` + ); return { status: "OK", }; @@ -506,9 +618,10 @@ function getAPIImplementation() { // we will be creating a new webauthn account when the token // is consumed and linking it to this primary user. return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, undefined); - } - else { - logger_1.logDebugMessage(`Recover account email not sent, isSignUpAllowed returned false for email: ${email}`); + } else { + logger_1.logDebugMessage( + `Recover account email not sent, isSignUpAllowed returned false for email: ${email}` + ); return { status: "OK", }; @@ -518,11 +631,15 @@ function getAPIImplementation() { // and also some primary user ID exist. We now need to find out if they are linked // together or not. If they are linked together, then we can just generate the token // else we check for more security conditions (since we will be linking them post token generation) - let areTheTwoAccountsLinked = primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - return lm.recipeUserId.getAsString() === webauthnAccount.recipeUserId.getAsString(); - }) !== undefined; + let areTheTwoAccountsLinked = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.recipeUserId.getAsString() === webauthnAccount.recipeUserId.getAsString(); + }) !== undefined; if (areTheTwoAccountsLinked) { - return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, webauthnAccount.recipeUserId); + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); } // Here we know that the two accounts are NOT linked. We now need to check for an // extra security measure here to make sure that the input email in the primary user @@ -545,25 +662,43 @@ function getAPIImplementation() { // here we will go ahead with the token generation cause // even when the token is consumed, we will not be linking the accounts // so no need to check for anything - return await generateAndSendRecoverAccountToken(webauthnAccount.recipeUserId.getAsString(), webauthnAccount.recipeUserId); + return await generateAndSendRecoverAccountToken( + webauthnAccount.recipeUserId.getAsString(), + webauthnAccount.recipeUserId + ); } if (!shouldDoAccountLinkingResponse.shouldRequireVerification) { // the checks below are related to email verification, and if the user // does not care about that, then we should just continue with token generation - return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, webauthnAccount.recipeUserId); - } - return await generateAndSendRecoverAccountToken(primaryUserAssociatedWithEmail.id, webauthnAccount.recipeUserId); + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); + } + return await generateAndSendRecoverAccountToken( + primaryUserAssociatedWithEmail.id, + webauthnAccount.recipeUserId + ); }, - recoverAccountPOST: async function ({ webauthnGeneratedOptionsId, credential, token, tenantId, options, userContext, }) { + recoverAccountPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + token, + tenantId, + options, + userContext, + }) { async function markEmailAsVerified(recipeUserId, email) { const emailVerificationInstance = recipe_2.default.getInstance(); if (emailVerificationInstance) { - const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken({ - tenantId, - recipeUserId, - email, - userContext, - }); + const tokenResponse = await emailVerificationInstance.recipeInterfaceImpl.createEmailVerificationToken( + { + tenantId, + recipeUserId, + email, + userContext, + } + ); if (tokenResponse.status === "OK") { await emailVerificationInstance.recipeInterfaceImpl.verifyEmailUsingToken({ tenantId, @@ -590,13 +725,11 @@ function getAPIImplementation() { status: "INVALID_AUTHENTICATOR_ERROR", reason: updateResponse.reason, }; - } - else if (updateResponse.status === "INVALID_CREDENTIALS_ERROR") { + } else if (updateResponse.status === "INVALID_CREDENTIALS_ERROR") { return { status: "INVALID_CREDENTIALS_ERROR", }; - } - else { + } else { // status: "OK" // If the update was successful, we try to mark the email as verified. // We do this because we assume that the recover account token was delivered by email (and to the appropriate email address) @@ -608,7 +741,10 @@ function getAPIImplementation() { // If we verified (and linked) the existing user with the original credential, User M would get access to the current user and any linked users. await markEmailAsVerified(recipeUserId, emailForWhomTokenWasGenerated); // We refresh the user information here, because the verification status may be updated, which is used during linking. - const updatedUserAfterEmailVerification = await __1.getUser(recipeUserId.getAsString(), userContext); + const updatedUserAfterEmailVerification = await __1.getUser( + recipeUserId.getAsString(), + userContext + ); if (updatedUserAfterEmailVerification === undefined) { throw new Error("Should never happen - user deleted after during recover account"); } @@ -632,7 +768,8 @@ function getAPIImplementation() { session: undefined, userContext, }); - const userAfterWeTriedLinking = linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; + const userAfterWeTriedLinking = + linkRes.status === "OK" ? linkRes.user : updatedUserAfterEmailVerification; return { status: "OK", email: emailForWhomTokenWasGenerated, @@ -666,23 +803,27 @@ function getAPIImplementation() { if (existingUser.isPrimaryUser) { // If this user contains an webauthn account for whom the token was generated, // then we update that user's credential. - let webauthnUserIsLinkedToExistingUser = existingUser.loginMethods.find((lm) => { - // we check based on user ID and not email because the only time - // the primary user ID is used for token generation is if the webauthn - // user did not exist - in which case the value of emailPasswordUserExists will - // resolve to false anyway, and that's what we want. - // there is an edge case where if the webauthn recipe user was created - // after the recover account token generation, and it was linked to the - // primary user id (userIdForWhomTokenWasGenerated), in this case, - // we still don't allow credntials update, cause the user should try again - // and the token should be regenerated for the right recipe user. - return (lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && - lm.recipeId === "webauthn"); - }) !== undefined; + let webauthnUserIsLinkedToExistingUser = + existingUser.loginMethods.find((lm) => { + // we check based on user ID and not email because the only time + // the primary user ID is used for token generation is if the webauthn + // user did not exist - in which case the value of emailPasswordUserExists will + // resolve to false anyway, and that's what we want. + // there is an edge case where if the webauthn recipe user was created + // after the recover account token generation, and it was linked to the + // primary user id (userIdForWhomTokenWasGenerated), in this case, + // we still don't allow credntials update, cause the user should try again + // and the token should be regenerated for the right recipe user. + return ( + lm.recipeUserId.getAsString() === userIdForWhomTokenWasGenerated && + lm.recipeId === "webauthn" + ); + }) !== undefined; if (webauthnUserIsLinkedToExistingUser) { - return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary(new recipeUserId_1.default(userIdForWhomTokenWasGenerated)); - } - else { + return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( + new recipeUserId_1.default(userIdForWhomTokenWasGenerated) + ); + } else { // this means that the existingUser does not have an webauthn user associated // with it. It could now mean that no webauthn user exists, or it could mean that // the the webauthn user exists, but it's not linked to the current account. @@ -703,25 +844,28 @@ function getAPIImplementation() { credential, userContext, }); - if (createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || + if ( + createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || createUserResponse.status === "GENERATED_OPTIONS_NOT_FOUND_ERROR" || createUserResponse.status === "INVALID_GENERATED_OPTIONS_ERROR" || - createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR") { + createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR" + ) { return createUserResponse; - } - else if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { + } else if (createUserResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { // this means that the user already existed and we can just return an invalid // token (see the above comment) return { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", }; - } - else { + } else { // we mark the email as verified because recover account also requires // access to the email to work.. This has a good side effect that // any other login method with the same email in existingAccount will also get marked // as verified. - await markEmailAsVerified(createUserResponse.user.loginMethods[0].recipeUserId, tokenConsumptionResponse.email); + await markEmailAsVerified( + createUserResponse.user.loginMethods[0].recipeUserId, + tokenConsumptionResponse.email + ); const updatedUser = await __1.getUser(createUserResponse.user.id, userContext); if (updatedUser === undefined) { throw new Error("Should never happen - user deleted after during recover account"); @@ -733,12 +877,14 @@ function getAPIImplementation() { // email is shared. // We do not take try linking by session here, since this is supposed to be called without a session // Still, the session object is passed around because it is a required input for shouldDoAutomaticAccountLinking - const linkRes = await recipe_1.default.getInstance().tryLinkingByAccountInfoOrCreatePrimaryUser({ - tenantId, - inputUser: createUserResponse.user, - session: undefined, - userContext, - }); + const linkRes = await recipe_1.default + .getInstance() + .tryLinkingByAccountInfoOrCreatePrimaryUser({ + tenantId, + inputUser: createUserResponse.user, + session: undefined, + userContext, + }); const userAfterLinking = linkRes.status === "OK" ? linkRes.user : createUserResponse.user; if (linkRes.status === "OK" && linkRes.user.id !== existingUser.id) { // this means that the account we just linked to @@ -753,21 +899,30 @@ function getAPIImplementation() { }; } } - } - else { + } else { // This means that the existing user is not a primary account, which implies that // it must be a non linked webauthn account. In this case, we simply update the credential. // Linking to an existing account will be done after the user goes through the email // verification flow once they log in (if applicable). - return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary(new recipeUserId_1.default(userIdForWhomTokenWasGenerated)); + return doRegisterCredentialAndVerifyEmailAndTryLinkIfNotPrimary( + new recipeUserId_1.default(userIdForWhomTokenWasGenerated) + ); } }, - registerCredentialPOST: async function ({ webauthnGeneratedOptionsId, credential, tenantId, options, userContext, session, }) { + registerCredentialPOST: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + options, + userContext, + session, + }) { // TODO update error codes (ERR_CODE_XXX) after final implementation const errorCodeMap = { - REGISTER_CREDENTIAL_NOT_ALLOWED: "Cannot register credential due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", + REGISTER_CREDENTIAL_NOT_ALLOWED: + "Cannot register credential due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", INVALID_AUTHENTICATOR_ERROR: { - // TODO: add more cases + // TODO: add more cases }, INVALID_CREDENTIALS_ERROR: "The credentials are incorrect. Please use a different authenticator.", }; @@ -784,7 +939,9 @@ function getAPIImplementation() { // check for type is done in a parent function but they are kept // here to be on the safe side. if (!email) { - throw new Error("Should never come here since we already check that the email value is a string in validateEmailAddress"); + throw new Error( + "Should never come here since we already check that the email value is a string in validateEmailAddress" + ); } // we are using the email from the register options const registerCredentialResponse = await options.recipeImplementation.registerCredential({ @@ -794,7 +951,11 @@ function getAPIImplementation() { recipeUserId: session.getRecipeUserId(), }); if (registerCredentialResponse.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(registerCredentialResponse, errorCodeMap, "REGISTER_CREDENTIAL_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + registerCredentialResponse, + errorCodeMap, + "REGISTER_CREDENTIAL_NOT_ALLOWED" + ); } return { status: "OK", diff --git a/lib/build/recipe/webauthn/api/recoverAccount.d.ts b/lib/build/recipe/webauthn/api/recoverAccount.d.ts index c0fd31373..a5038fad1 100644 --- a/lib/build/recipe/webauthn/api/recoverAccount.d.ts +++ b/lib/build/recipe/webauthn/api/recoverAccount.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -export default function recoverAccount(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function recoverAccount( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/recoverAccount.js b/lib/build/recipe/webauthn/api/recoverAccount.js index 305fb33df..acb883f39 100644 --- a/lib/build/recipe/webauthn/api/recoverAccount.js +++ b/lib/build/recipe/webauthn/api/recoverAccount.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -25,7 +27,9 @@ async function recoverAccount(apiImplementation, tenantId, options, userContext) return false; } const requestBody = await options.req.getJSONBody(); - let webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError(requestBody.webauthnGeneratedOptionsId); + let webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); let credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); let token = requestBody.token; if (token === undefined) { @@ -48,11 +52,14 @@ async function recoverAccount(apiImplementation, tenantId, options, userContext) options, userContext, }); - utils_1.send200Response(options.res, result.status === "OK" - ? { - status: "OK", - } - : result); + utils_1.send200Response( + options.res, + result.status === "OK" + ? { + status: "OK", + } + : result + ); return true; } exports.default = recoverAccount; diff --git a/lib/build/recipe/webauthn/api/registerCredential.d.ts b/lib/build/recipe/webauthn/api/registerCredential.d.ts index 01bbfa0ae..4a29d2f16 100644 --- a/lib/build/recipe/webauthn/api/registerCredential.d.ts +++ b/lib/build/recipe/webauthn/api/registerCredential.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function registerCredentialAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function registerCredentialAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/registerCredential.js b/lib/build/recipe/webauthn/api/registerCredential.js index 69cc88689..5c8dfdd4c 100644 --- a/lib/build/recipe/webauthn/api/registerCredential.js +++ b/lib/build/recipe/webauthn/api/registerCredential.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -26,9 +28,16 @@ async function registerCredentialAPI(apiImplementation, tenantId, options, userC return false; } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError(requestBody.webauthnGeneratedOptionsId); + const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, undefined, userContext); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + undefined, + userContext + ); if (session === undefined) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, @@ -47,11 +56,9 @@ async function registerCredentialAPI(apiImplementation, tenantId, options, userC utils_1.send200Response(options.res, { status: "OK", }); - } - else if (result.status === "GENERAL_ERROR") { + } else if (result.status === "GENERAL_ERROR") { utils_1.send200Response(options.res, result); - } - else { + } else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/webauthn/api/registerOptions.d.ts b/lib/build/recipe/webauthn/api/registerOptions.d.ts index ee285ec10..6f9f603b6 100644 --- a/lib/build/recipe/webauthn/api/registerOptions.d.ts +++ b/lib/build/recipe/webauthn/api/registerOptions.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function registerOptions(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function registerOptions( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/registerOptions.js b/lib/build/recipe/webauthn/api/registerOptions.js index 91e1ee902..9d6ed7036 100644 --- a/lib/build/recipe/webauthn/api/registerOptions.js +++ b/lib/build/recipe/webauthn/api/registerOptions.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); @@ -27,8 +29,10 @@ async function registerOptions(apiImplementation, tenantId, options, userContext const requestBody = await options.req.getJSONBody(); let email = (_a = requestBody.email) === null || _a === void 0 ? void 0 : _a.trim(); let recoverAccountToken = requestBody.recoverAccountToken; - if ((email === undefined || typeof email !== "string") && - (recoverAccountToken === undefined || typeof recoverAccountToken !== "string")) { + if ( + (email === undefined || typeof email !== "string") && + (recoverAccountToken === undefined || typeof recoverAccountToken !== "string") + ) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "Please provide the email or the recover account token", diff --git a/lib/build/recipe/webauthn/api/signInOptions.d.ts b/lib/build/recipe/webauthn/api/signInOptions.d.ts index 17ae095ee..1e3bc7b5f 100644 --- a/lib/build/recipe/webauthn/api/signInOptions.d.ts +++ b/lib/build/recipe/webauthn/api/signInOptions.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function signInOptions(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function signInOptions( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/signInOptions.js b/lib/build/recipe/webauthn/api/signInOptions.js index cdc4c777f..25034546a 100644 --- a/lib/build/recipe/webauthn/api/signInOptions.js +++ b/lib/build/recipe/webauthn/api/signInOptions.js @@ -13,27 +13,13 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); -const error_1 = __importDefault(require("../error")); async function signInOptions(apiImplementation, tenantId, options, userContext) { - var _a; if (apiImplementation.signInOptionsPOST === undefined) { return false; } - const requestBody = await options.req.getJSONBody(); - let email = (_a = requestBody.email) === null || _a === void 0 ? void 0 : _a.trim(); - if (email === undefined || typeof email !== "string") { - throw new error_1.default({ - type: error_1.default.BAD_INPUT_ERROR, - message: "Please provide the email", - }); - } let result = await apiImplementation.signInOptionsPOST({ - email, tenantId, options, userContext, diff --git a/lib/build/recipe/webauthn/api/signin.d.ts b/lib/build/recipe/webauthn/api/signin.d.ts index 079e9b8fb..72cd6e46b 100644 --- a/lib/build/recipe/webauthn/api/signin.d.ts +++ b/lib/build/recipe/webauthn/api/signin.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function signInAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function signInAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/signin.js b/lib/build/recipe/webauthn/api/signin.js index fa62f569a..7a9c59de5 100644 --- a/lib/build/recipe/webauthn/api/signin.js +++ b/lib/build/recipe/webauthn/api/signin.js @@ -22,10 +22,20 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { return false; } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError(requestBody.webauthnGeneratedOptionsId); + const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); - const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( + options.req, + requestBody + ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -39,9 +49,11 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { userContext, }); if (result.status === "OK") { - utils_1.send200Response(options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); - } - else { + utils_1.send200Response( + options.res, + Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) + ); + } else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/webauthn/api/signup.d.ts b/lib/build/recipe/webauthn/api/signup.d.ts index 6c7c81572..afc748051 100644 --- a/lib/build/recipe/webauthn/api/signup.d.ts +++ b/lib/build/recipe/webauthn/api/signup.d.ts @@ -1,4 +1,9 @@ // @ts-nocheck import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -export default function signUpAPI(apiImplementation: APIInterface, tenantId: string, options: APIOptions, userContext: UserContext): Promise; +export default function signUpAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/webauthn/api/signup.js b/lib/build/recipe/webauthn/api/signup.js index f1921de3c..c196907c7 100644 --- a/lib/build/recipe/webauthn/api/signup.js +++ b/lib/build/recipe/webauthn/api/signup.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); @@ -26,10 +28,20 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { return false; } const requestBody = await options.req.getJSONBody(); - const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError(requestBody.webauthnGeneratedOptionsId); + const webauthnGeneratedOptionsId = await utils_2.validateWebauthnGeneratedOptionsIdOrThrowError( + requestBody.webauthnGeneratedOptionsId + ); const credential = await utils_2.validateCredentialOrThrowError(requestBody.credential); - const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); - const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, shouldTryLinkingWithSessionUser, userContext); + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( + options.req, + requestBody + ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( + options.req, + options.res, + shouldTryLinkingWithSessionUser, + userContext + ); if (session !== undefined) { tenantId = session.getTenantId(); } @@ -43,18 +55,18 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { userContext: userContext, }); if (result.status === "OK") { - utils_1.send200Response(options.res, Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext))); - } - else if (result.status === "GENERAL_ERROR") { + utils_1.send200Response( + options.res, + Object.assign({ status: "OK" }, utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext)) + ); + } else if (result.status === "GENERAL_ERROR") { utils_1.send200Response(options.res, result); - } - else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { + } else if (result.status === "EMAIL_ALREADY_EXISTS_ERROR") { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, message: "This email already exists. Please sign in instead.", }); - } - else { + } else { utils_1.send200Response(options.res, result); } return true; diff --git a/lib/build/recipe/webauthn/api/utils.d.ts b/lib/build/recipe/webauthn/api/utils.d.ts index 66b31a0b0..881337429 100644 --- a/lib/build/recipe/webauthn/api/utils.d.ts +++ b/lib/build/recipe/webauthn/api/utils.d.ts @@ -1,3 +1,5 @@ // @ts-nocheck -export declare function validateWebauthnGeneratedOptionsIdOrThrowError(webauthnGeneratedOptionsId: string): Promise; +export declare function validateWebauthnGeneratedOptionsIdOrThrowError( + webauthnGeneratedOptionsId: string +): Promise; export declare function validateCredentialOrThrowError(credential: T): Promise; diff --git a/lib/build/recipe/webauthn/api/utils.js b/lib/build/recipe/webauthn/api/utils.js index da7c7df34..ae0a5d6fd 100644 --- a/lib/build/recipe/webauthn/api/utils.js +++ b/lib/build/recipe/webauthn/api/utils.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateCredentialOrThrowError = exports.validateWebauthnGeneratedOptionsIdOrThrowError = void 0; /* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. diff --git a/lib/build/recipe/webauthn/core-mock.js b/lib/build/recipe/webauthn/core-mock.js index 78d1ee44b..5a574fb4d 100644 --- a/lib/build/recipe/webauthn/core-mock.js +++ b/lib/build/recipe/webauthn/core-mock.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMockQuerier = void 0; const querier_1 = require("../../querier"); @@ -44,13 +46,25 @@ const getMockQuerier = (recipeId) => { const now = new Date(); const createdAt = now.getTime(); const expiresAt = createdAt + body.timeout * 1000; - writeDb("generatedOptions", id, Object.assign(Object.assign({}, registrationOptions), { id, origin: body.origin, tenantId: body.tenantId, email: body.email, rpId: registrationOptions.rp.id, createdAt, - expiresAt })); + writeDb( + "generatedOptions", + id, + Object.assign(Object.assign({}, registrationOptions), { + id, + origin: body.origin, + tenantId: body.tenantId, + email: body.email, + rpId: registrationOptions.rp.id, + createdAt, + expiresAt, + }) + ); // @ts-ignore - return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id, createdAt, - expiresAt }, registrationOptions); - } - else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { + return Object.assign( + { status: "OK", webauthnGeneratedOptionsId: id, createdAt, expiresAt }, + registrationOptions + ); + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/options/signin")) { const signInOptions = await server_1.generateAuthenticationOptions({ rpID: body.relyingPartyId, timeout: body.timeout, @@ -60,13 +74,20 @@ const getMockQuerier = (recipeId) => { const now = new Date(); const createdAt = now.getTime(); const expiresAt = createdAt + body.timeout * 1000; - writeDb("generatedOptions", id, Object.assign(Object.assign({}, signInOptions), { id, origin: body.origin, tenantId: body.tenantId, email: body.email, createdAt, - expiresAt })); + writeDb( + "generatedOptions", + id, + Object.assign(Object.assign({}, signInOptions), { + id, + origin: body.origin, + tenantId: body.tenantId, + createdAt, + expiresAt, + }) + ); // @ts-ignore - return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id, createdAt, - expiresAt }, signInOptions); - } - else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { + return Object.assign({ status: "OK", webauthnGeneratedOptionsId: id, createdAt, expiresAt }, signInOptions); + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signup")) { const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); if (!options) { // @ts-ignore @@ -93,9 +114,15 @@ const getMockQuerier = (recipeId) => { id: credentialId, userId: recipeUserId, counter: 0, - publicKey: (_a = registrationVerification.registrationInfo) === null || _a === void 0 ? void 0 : _a.credential.publicKey.toString(), + publicKey: + (_a = registrationVerification.registrationInfo) === null || _a === void 0 + ? void 0 + : _a.credential.publicKey.toString(), rpId: options.rpId, - transports: (_b = registrationVerification.registrationInfo) === null || _b === void 0 ? void 0 : _b.credential.transports, + transports: + (_b = registrationVerification.registrationInfo) === null || _b === void 0 + ? void 0 + : _b.credential.transports, createdAt: now.toISOString(), }); const user = { @@ -131,8 +158,7 @@ const getMockQuerier = (recipeId) => { }; // @ts-ignore return response; - } - else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { + } else if (path.getAsStringDangerous().includes("/recipe/webauthn/signin")) { const options = readDb("generatedOptions", body.webauthnGeneratedOptionsId); if (!options) { // @ts-ignore diff --git a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts index 0d74c5679..b4ac552b5 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts +++ b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.d.ts @@ -6,7 +6,9 @@ export default class BackwardCompatibilityService implements EmailDeliveryInterf private isInServerlessEnv; private appInfo; constructor(appInfo: NormalisedAppinfo, isInServerlessEnv: boolean); - sendEmail: (input: TypeWebauthnEmailDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: TypeWebauthnEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js index 90e55746b..8c1477788 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/backwardCompatibility/index.js @@ -5,17 +5,22 @@ async function createAndSendEmailUsingSupertokensService(input) { if (utils_1.isTestEnv()) { return; } - const result = await utils_1.postWithFetch("https://api.supertokens.io/0/st/auth/webauthn/recover", { - "api-version": "0", - "content-type": "application/json; charset=utf-8", - }, { - email: input.user.email, - appName: input.appInfo.appName, - recoverAccountURL: input.recoverAccountLink, - }, { - successLog: `Email sent to ${input.user.email}`, - errorLogHeader: "Error sending webauthn recover account email", - }); + const result = await utils_1.postWithFetch( + "https://api.supertokens.io/0/st/auth/webauthn/recover", + { + "api-version": "0", + "content-type": "application/json; charset=utf-8", + }, + { + email: input.user.email, + appName: input.appInfo.appName, + recoverAccountURL: input.recoverAccountLink, + }, + { + successLog: `Email sent to ${input.user.email}`, + errorLogHeader: "Error sending webauthn recover account email", + } + ); if ("error" in result) { throw result.error; } @@ -26,8 +31,7 @@ async function createAndSendEmailUsingSupertokensService(input) { * will be of type `{err: string}` */ throw new Error(result.resp.body.err); - } - else { + } else { throw new Error(`Request failed with status code ${result.resp.status}`); } } @@ -44,9 +48,8 @@ class BackwardCompatibilityService { appInfo: this.appInfo, user: input.user, recoverAccountLink: input.recoverAccountLink, - }).catch((_) => { }); - } - else { + }).catch((_) => {}); + } else { // see https://github.com/supertokens/supertokens-node/pull/135 await createAndSendEmailUsingSupertokensService({ appInfo: this.appInfo, @@ -54,8 +57,7 @@ class BackwardCompatibilityService { recoverAccountLink: input.recoverAccountLink, }); } - } - catch (_) { } + } catch (_) {} }; this.isInServerlessEnv = isInServerlessEnv; this.appInfo = appInfo; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/index.js b/lib/build/recipe/webauthn/emaildelivery/services/index.js index d648973c6..91700aeaf 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/index.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMTPService = void 0; const smtp_1 = __importDefault(require("./smtp")); diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts index 937bd644e..d792d04cb 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.d.ts @@ -6,7 +6,9 @@ import { UserContext } from "../../../../../types"; export default class SMTPService implements EmailDeliveryInterface { serviceImpl: ServiceInterface; constructor(config: TypeInput); - sendEmail: (input: TypeWebauthnEmailDeliveryInput & { - userContext: UserContext; - }) => Promise; + sendEmail: ( + input: TypeWebauthnEmailDeliveryInput & { + userContext: UserContext; + } + ) => Promise; } diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js index da1ee9c8a..dedcbe33f 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/index.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const nodemailer_1 = require("nodemailer"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); @@ -10,7 +12,9 @@ class SMTPService { constructor(config) { this.sendEmail = async (input) => { let content = await this.serviceImpl.getContent(input); - await this.serviceImpl.sendRawEmail(Object.assign(Object.assign({}, content), { userContext: input.userContext })); + await this.serviceImpl.sendRawEmail( + Object.assign(Object.assign({}, content), { userContext: input.userContext }) + ); }; const transporter = nodemailer_1.createTransport({ host: config.smtpSettings.host, @@ -21,7 +25,9 @@ class SMTPService { }, secure: config.smtpSettings.secure, }); - let builder = new supertokens_js_override_1.default(serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from)); + let builder = new supertokens_js_override_1.default( + serviceImplementation_1.getServiceImplementation(transporter, config.smtpSettings.from) + ); if (config.override !== undefined) { builder = builder.override(config.override); } diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts index 9538c7ea0..3f85c452a 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.d.ts @@ -1,5 +1,7 @@ // @ts-nocheck import { TypeWebauthnRecoverAccountEmailDeliveryInput } from "../../../types"; import { GetContentResult } from "../../../../../ingredients/emaildelivery/services/smtp"; -export default function getRecoverAccountEmailContent(input: TypeWebauthnRecoverAccountEmailDeliveryInput): GetContentResult; +export default function getRecoverAccountEmailContent( + input: TypeWebauthnRecoverAccountEmailDeliveryInput +): GetContentResult; export declare function getRecoverAccountEmailHTML(appName: string, email: string, resetLink: string): string; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js index dc2fde1d6..39185d372 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/recoverAccount.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRecoverAccountEmailHTML = void 0; const supertokens_1 = __importDefault(require("../../../../../supertokens")); diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts index c5c631103..5eec27f1c 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.d.ts @@ -2,7 +2,10 @@ import { TypeWebauthnEmailDeliveryInput } from "../../../../types"; import { Transporter } from "nodemailer"; import { ServiceInterface } from "../../../../../../ingredients/emaildelivery/services/smtp"; -export declare function getServiceImplementation(transporter: Transporter, from: { - name: string; - email: string; -}): ServiceInterface; +export declare function getServiceImplementation( + transporter: Transporter, + from: { + name: string; + email: string; + } +): ServiceInterface; diff --git a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js index 2ded64273..f097d8647 100644 --- a/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js +++ b/lib/build/recipe/webauthn/emaildelivery/services/smtp/serviceImplementation/index.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServiceImplementation = void 0; const recoverAccount_1 = __importDefault(require("../recoverAccount")); @@ -29,8 +31,7 @@ function getServiceImplementation(transporter, from) { subject: input.subject, html: input.body, }); - } - else { + } else { await transporter.sendMail({ from: `${from.name} <${from.email}>`, to: input.toEmail, diff --git a/lib/build/recipe/webauthn/error.d.ts b/lib/build/recipe/webauthn/error.d.ts index d6412505c..486758b61 100644 --- a/lib/build/recipe/webauthn/error.d.ts +++ b/lib/build/recipe/webauthn/error.d.ts @@ -1,8 +1,5 @@ // @ts-nocheck import STError from "../../error"; export default class SessionError extends STError { - constructor(options: { - type: "BAD_INPUT_ERROR"; - message: string; - }); + constructor(options: { type: "BAD_INPUT_ERROR"; message: string }); } diff --git a/lib/build/recipe/webauthn/error.js b/lib/build/recipe/webauthn/error.js index 31debe4fe..7de05e126 100644 --- a/lib/build/recipe/webauthn/error.js +++ b/lib/build/recipe/webauthn/error.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); class SessionError extends error_1.default { diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts index 4d23e7582..c1bb8225a 100644 --- a/lib/build/recipe/webauthn/index.d.ts +++ b/lib/build/recipe/webauthn/index.d.ts @@ -1,7 +1,17 @@ // @ts-nocheck import Recipe from "./recipe"; import SuperTokensError from "./error"; -import { RecipeInterface, APIInterface, APIOptions, TypeWebauthnEmailDeliveryInput, CredentialPayload, UserVerification, ResidentKey, Attestation, AuthenticationPayload } from "./types"; +import { + RecipeInterface, + APIInterface, + APIOptions, + TypeWebauthnEmailDeliveryInput, + CredentialPayload, + UserVerification, + ResidentKey, + Attestation, + AuthenticationPayload, +} from "./types"; import RecipeUserId from "../../recipeUserId"; import { SessionContainerInterface } from "../session/types"; import { User } from "../../types"; @@ -9,7 +19,16 @@ import { BaseRequest } from "../../framework"; export default class Wrapper { static init: typeof Recipe.init; static Error: typeof SuperTokensError; - static registerOptions({ residentKey, userVerification, attestation, supportedAlgorithmIds, timeout, tenantId, userContext, ...rest }: { + static registerOptions({ + residentKey, + userVerification, + attestation, + supportedAlgorithmIds, + timeout, + tenantId, + userContext, + ...rest + }: { residentKey?: ResidentKey; userVerification?: UserVerification; attestation?: Attestation; @@ -17,144 +36,214 @@ export default class Wrapper { timeout?: number; tenantId?: string; userContext?: Record; - } & ({ - relyingPartyId: string; - relyingPartyName: string; - origin: string; - } | { - request: BaseRequest; - relyingPartyId?: string; - relyingPartyName?: string; - origin?: string; - }) & ({ - email: string; - } | { - recoverAccountToken: string; - })): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: "public-key"; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: "public-key"; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: ResidentKey; - userVerification: UserVerification; - }; - } | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } | { - status: "INVALID_EMAIL_ERROR"; - err: string; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - }>; - static signInOptions({ email, tenantId, userVerification, timeout, userContext, ...rest }: { - email: string; + } & ( + | { + relyingPartyId: string; + relyingPartyName: string; + origin: string; + } + | { + request: BaseRequest; + relyingPartyId?: string; + relyingPartyName?: string; + origin?: string; + } + ) & + ( + | { + email: string; + } + | { + recoverAccountToken: string; + } + )): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: ResidentKey; + userVerification: UserVerification; + }; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + >; + static signInOptions({ + tenantId, + userVerification, + timeout, + userContext, + ...rest + }: { timeout?: number; userVerification?: UserVerification; tenantId?: string; userContext?: Record; - } & ({ - relyingPartyId: string; - origin: string; - } | { - request: BaseRequest; - relyingPartyId?: string; - origin?: string; - })): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: UserVerification; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - }>; - static getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId, userContext, }: { + } & ( + | { + relyingPartyId: string; + origin: string; + } + | { + request: BaseRequest; + relyingPartyId?: string; + origin?: string; + } + )): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: UserVerification; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + >; + static getGeneratedOptions({ + webauthnGeneratedOptionsId, + tenantId, + userContext, + }: { webauthnGeneratedOptionsId: string; tenantId?: string; userContext?: Record; - }): Promise<{ - status: "OK"; - id: string; - relyingPartyId: string; - origin: string; - email: string; - timeout: string; - challenge: string; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - }>; - static signUp({ tenantId, webauthnGeneratedOptionsId, credential, session, userContext, }: { + }): Promise< + | { + status: "OK"; + id: string; + relyingPartyId: string; + origin: string; + email: string; + timeout: string; + challenge: string; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + >; + static signUp({ + tenantId, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }: { tenantId?: string; webauthnGeneratedOptionsId: string; credential: CredentialPayload; userContext?: Record; session?: SessionContainerInterface; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } | { - 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"; - }>; - static signIn({ tenantId, webauthnGeneratedOptionsId, credential, session, userContext, }: { + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + | { + 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"; + } + >; + static signIn({ + tenantId, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }: { tenantId?: string; webauthnGeneratedOptionsId: string; credential: AuthenticationPayload; session?: SessionContainerInterface; userContext?: Record; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - 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"; - }>; - static verifyCredentials({ tenantId, webauthnGeneratedOptionsId, credential, userContext, }: { + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + 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"; + } + >; + static verifyCredentials({ + tenantId, + webauthnGeneratedOptionsId, + credential, + userContext, + }: { tenantId?: string; webauthnGeneratedOptionsId: string; credential: AuthenticationPayload; userContext?: Record; - }): Promise<{ - status: "OK"; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + >; /** * We do not make email optional here cause we want to * allow passing in primaryUserId. If we make email optional, @@ -166,77 +255,129 @@ export default class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static generateRecoverAccountToken({ tenantId, userId, email, userContext, }: { + static generateRecoverAccountToken({ + tenantId, + userId, + email, + userContext, + }: { tenantId?: string; userId: string; email: string; userContext?: Record; - }): Promise<{ - status: "OK"; - token: string; - } | { - status: "UNKNOWN_USER_ID_ERROR"; - }>; - static recoverAccount({ tenantId, webauthnGeneratedOptionsId, token, credential, userContext, }: { + }): Promise< + | { + status: "OK"; + token: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; + static recoverAccount({ + tenantId, + webauthnGeneratedOptionsId, + token, + credential, + userContext, + }: { tenantId?: string; webauthnGeneratedOptionsId: string; token: string; credential: CredentialPayload; userContext?: Record; - }): Promise<{ - status: "OK"; - } | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - failureReason: string; - }>; - static consumeRecoverAccountToken({ tenantId, token, userContext, }: { + }): Promise< + | { + status: "OK"; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + failureReason: string; + } + >; + static consumeRecoverAccountToken({ + tenantId, + token, + userContext, + }: { tenantId?: string; token: string; userContext?: Record; - }): Promise<{ - status: "OK"; - email: string; - userId: string; - } | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - }>; - static registerCredential({ recipeUserId, webauthnGeneratedOptionsId, credential, userContext, }: { + }): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + >; + static registerCredential({ + recipeUserId, + webauthnGeneratedOptionsId, + credential, + userContext, + }: { recipeUserId: RecipeUserId; webauthnGeneratedOptionsId: string; credential: CredentialPayload; userContext?: Record; - }): Promise<{ - status: "OK"; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - }>; - static createRecoverAccountLink({ tenantId, userId, email, userContext, }: { + }): Promise< + | { + status: "OK"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + >; + static createRecoverAccountLink({ + tenantId, + userId, + email, + userContext, + }: { tenantId?: string; userId: string; email: string; userContext?: Record; - }): Promise<{ - status: "OK"; - link: string; - } | { - status: "UNKNOWN_USER_ID_ERROR"; - }>; - static sendRecoverAccountEmail({ tenantId, userId, email, userContext, }: { + }): Promise< + | { + status: "OK"; + link: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; + static sendRecoverAccountEmail({ + tenantId, + userId, + email, + userContext, + }: { tenantId?: string; userId: string; email: string; @@ -244,9 +385,11 @@ export default class Wrapper { }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR"; }>; - static sendEmail(input: TypeWebauthnEmailDeliveryInput & { - userContext?: Record; - }): Promise; + static sendEmail( + input: TypeWebauthnEmailDeliveryInput & { + userContext?: Record; + } + ): Promise; } export declare let init: typeof Recipe.init; export declare let Error: typeof SuperTokensError; diff --git a/lib/build/recipe/webauthn/index.js b/lib/build/recipe/webauthn/index.js index d23707847..cd7c99d6e 100644 --- a/lib/build/recipe/webauthn/index.js +++ b/lib/build/recipe/webauthn/index.js @@ -13,20 +13,22 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getGeneratedOptions = exports.sendEmail = exports.sendRecoverAccountEmail = exports.createRecoverAccountLink = exports.registerCredential = exports.consumeRecoverAccountToken = exports.recoverAccount = exports.generateRecoverAccountToken = exports.verifyCredentials = exports.signIn = exports.signInOptions = exports.registerOptions = exports.Error = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); @@ -39,17 +41,32 @@ const utils_2 = require("../../utils"); const constants_2 = require("./constants"); class Wrapper { static async registerOptions(_a) { - var { residentKey = constants_2.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, userVerification = constants_2.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, attestation = constants_2.DEFAULT_REGISTER_OPTIONS_ATTESTATION, supportedAlgorithmIds = constants_2.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, timeout = constants_2.DEFAULT_REGISTER_OPTIONS_TIMEOUT, tenantId = constants_1.DEFAULT_TENANT_ID, userContext } = _a, rest = __rest(_a, ["residentKey", "userVerification", "attestation", "supportedAlgorithmIds", "timeout", "tenantId", "userContext"]); + var { + residentKey = constants_2.DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, + userVerification = constants_2.DEFAULT_REGISTER_OPTIONS_USER_VERIFICATION, + attestation = constants_2.DEFAULT_REGISTER_OPTIONS_ATTESTATION, + supportedAlgorithmIds = constants_2.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, + timeout = constants_2.DEFAULT_REGISTER_OPTIONS_TIMEOUT, + tenantId = constants_1.DEFAULT_TENANT_ID, + userContext, + } = _a, + rest = __rest(_a, [ + "residentKey", + "userVerification", + "attestation", + "supportedAlgorithmIds", + "timeout", + "tenantId", + "userContext", + ]); let emailOrRecoverAccountToken; if ("email" in rest || "recoverAccountToken" in rest) { if ("email" in rest) { emailOrRecoverAccountToken = { email: rest.email }; - } - else { + } else { emailOrRecoverAccountToken = { recoverAccountToken: rest.recoverAccountToken }; } - } - else { + } else { return { status: "INVALID_EMAIL_ERROR", err: "Email is missing" }; } let relyingPartyId; @@ -58,26 +75,25 @@ class Wrapper { if ("request" in rest) { origin = rest.origin || - (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ - request: rest.request, - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); + (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); relyingPartyId = rest.relyingPartyId || - (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ - request: rest.request, - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); relyingPartyName = rest.relyingPartyName || - (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyName({ - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); - } - else { + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyName({ + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); + } else { if (!rest.origin) { throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "Origin missing from the input" }); } @@ -85,43 +101,56 @@ class Wrapper { throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); } if (!rest.relyingPartyName) { - throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyName missing from the input" }); + throw new exports.Error({ + type: "BAD_INPUT_ERROR", + message: "RelyingPartyName missing from the input", + }); } origin = rest.origin; relyingPartyId = rest.relyingPartyId; relyingPartyName = rest.relyingPartyName; } - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions(Object.assign(Object.assign({}, emailOrRecoverAccountToken), { residentKey, - userVerification, - supportedAlgorithmIds, - relyingPartyId, - relyingPartyName, - origin, - timeout, - attestation, - tenantId, userContext: utils_2.getUserContext(userContext) })); + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerOptions( + Object.assign(Object.assign({}, emailOrRecoverAccountToken), { + residentKey, + userVerification, + supportedAlgorithmIds, + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation, + tenantId, + userContext: utils_2.getUserContext(userContext), + }) + ); } static async signInOptions(_a) { - var { email, tenantId = constants_1.DEFAULT_TENANT_ID, userVerification = constants_2.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, timeout = constants_2.DEFAULT_SIGNIN_OPTIONS_TIMEOUT, userContext } = _a, rest = __rest(_a, ["email", "tenantId", "userVerification", "timeout", "userContext"]); + var { + tenantId = constants_1.DEFAULT_TENANT_ID, + userVerification = constants_2.DEFAULT_SIGNIN_OPTIONS_USER_VERIFICATION, + timeout = constants_2.DEFAULT_SIGNIN_OPTIONS_TIMEOUT, + userContext, + } = _a, + rest = __rest(_a, ["tenantId", "userVerification", "timeout", "userContext"]); let origin; let relyingPartyId; if ("request" in rest) { relyingPartyId = rest.relyingPartyId || - (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ - request: rest.request, - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyId({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); origin = rest.origin || - (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ - request: rest.request, - tenantId: tenantId, - userContext: utils_2.getUserContext(userContext), - })); - } - else { + (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ + request: rest.request, + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); + } else { if (!rest.relyingPartyId) { throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); } @@ -132,7 +161,6 @@ class Wrapper { origin = rest.origin; } return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ - email, relyingPartyId, origin, timeout, @@ -141,14 +169,20 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId = constants_1.DEFAULT_TENANT_ID, userContext, }) { + static getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId = constants_1.DEFAULT_TENANT_ID, userContext }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getGeneratedOptions({ webauthnGeneratedOptionsId, tenantId, userContext: utils_2.getUserContext(userContext), }); } - static signUp({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, credential, session, userContext, }) { + static signUp({ + tenantId = constants_1.DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signUp({ webauthnGeneratedOptionsId, credential, @@ -158,7 +192,13 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static signIn({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, credential, session, userContext, }) { + static signIn({ + tenantId = constants_1.DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + session, + userContext, + }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signIn({ webauthnGeneratedOptionsId, credential, @@ -168,7 +208,12 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static async verifyCredentials({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, credential, userContext, }) { + static async verifyCredentials({ + tenantId = constants_1.DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + credential, + userContext, + }) { const resp = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ webauthnGeneratedOptionsId, credential, @@ -191,7 +236,7 @@ class Wrapper { * * And we want to allow primaryUserId being passed in. */ - static generateRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext, }) { + static generateRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.generateRecoverAccountToken({ userId, email, @@ -199,7 +244,13 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static async recoverAccount({ tenantId = constants_1.DEFAULT_TENANT_ID, webauthnGeneratedOptionsId, token, credential, userContext, }) { + static async recoverAccount({ + tenantId = constants_1.DEFAULT_TENANT_ID, + webauthnGeneratedOptionsId, + token, + credential, + userContext, + }) { const consumeResp = await Wrapper.consumeRecoverAccountToken({ tenantId, token, userContext }); if (consumeResp.status !== "OK") { return consumeResp; @@ -220,14 +271,14 @@ class Wrapper { status: result.status, }; } - static consumeRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, token, userContext, }) { + static consumeRecoverAccountToken({ tenantId = constants_1.DEFAULT_TENANT_ID, token, userContext }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeRecoverAccountToken({ token, tenantId, userContext: utils_2.getUserContext(userContext), }); } - static registerCredential({ recipeUserId, webauthnGeneratedOptionsId, credential, userContext, }) { + static registerCredential({ recipeUserId, webauthnGeneratedOptionsId, credential, userContext }) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.registerCredential({ recipeUserId, webauthnGeneratedOptionsId, @@ -235,7 +286,7 @@ class Wrapper { userContext: utils_2.getUserContext(userContext), }); } - static async createRecoverAccountLink({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext, }) { + static async createRecoverAccountLink({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { let token = await this.generateRecoverAccountToken({ tenantId, userId, email, userContext }); if (token.status === "UNKNOWN_USER_ID_ERROR") { return token; @@ -253,7 +304,7 @@ class Wrapper { }), }; } - static async sendRecoverAccountEmail({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext, }) { + static async sendRecoverAccountEmail({ tenantId = constants_1.DEFAULT_TENANT_ID, userId, email, userContext }) { const user = await __1.getUser(userId, userContext); if (!user) { return { status: "UNKNOWN_USER_ID_ERROR" }; @@ -283,7 +334,12 @@ class Wrapper { } static async sendEmail(input) { let recipeInstance = recipe_1.default.getInstanceOrThrowError(); - return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail(Object.assign(Object.assign({}, input), { tenantId: input.tenantId || constants_1.DEFAULT_TENANT_ID, userContext: utils_2.getUserContext(input.userContext) })); + return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail( + Object.assign(Object.assign({}, input), { + tenantId: input.tenantId || constants_1.DEFAULT_TENANT_ID, + userContext: utils_2.getUserContext(input.userContext), + }) + ); } } exports.default = Wrapper; diff --git a/lib/build/recipe/webauthn/recipe.d.ts b/lib/build/recipe/webauthn/recipe.d.ts index b82fe1e38..1eecfffbe 100644 --- a/lib/build/recipe/webauthn/recipe.d.ts +++ b/lib/build/recipe/webauthn/recipe.d.ts @@ -15,14 +15,28 @@ export default class Recipe extends RecipeModule { apiImpl: APIInterface; isInServerlessEnv: boolean; emailDelivery: EmailDeliveryIngredient; - constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config: TypeInput | undefined, ingredients: { - emailDelivery: EmailDeliveryIngredient | undefined; - }); + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput | undefined, + ingredients: { + emailDelivery: EmailDeliveryIngredient | undefined; + } + ); static getInstanceOrThrowError(): Recipe; static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled: () => APIHandled[]; - handleAPIRequest: (id: string, tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, userContext: UserContext) => Promise; + handleAPIRequest: ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ) => Promise; handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise; getAllCORSHeaders: () => string[]; isErrorFromThisRecipe: (err: any) => err is STError; diff --git a/lib/build/recipe/webauthn/recipe.js b/lib/build/recipe/webauthn/recipe.js index 8d3ccc339..6d76efb0d 100644 --- a/lib/build/recipe/webauthn/recipe.js +++ b/lib/build/recipe/webauthn/recipe.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipeModule_1 = __importDefault(require("../../recipeModule")); const error_1 = __importDefault(require("./error")); @@ -72,7 +74,9 @@ class Recipe extends recipeModule_1.default { }, { method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API), + pathWithoutApiBasePath: new normalisedURLPath_1.default( + constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API + ), id: constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API, disabled: this.apiImpl.generateRecoverAccountTokenPOST === undefined, }, @@ -103,33 +107,24 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.REGISTER_OPTIONS_API) { return await registerOptions_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.SIGNIN_OPTIONS_API) { + } else if (id === constants_1.SIGNIN_OPTIONS_API) { return await signInOptions_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.SIGN_UP_API) { + } else if (id === constants_1.SIGN_UP_API) { return await signup_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.SIGN_IN_API) { + } else if (id === constants_1.SIGN_IN_API) { return await signin_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API) { + } else if (id === constants_1.GENERATE_RECOVER_ACCOUNT_TOKEN_API) { return await generateRecoverAccountToken_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.RECOVER_ACCOUNT_API) { + } else if (id === constants_1.RECOVER_ACCOUNT_API) { return await recoverAccount_1.default(this.apiImpl, tenantId, options, userContext); - } - else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API) { + } else if (id === constants_1.SIGNUP_EMAIL_EXISTS_API) { return await emailExists_1.default(this.apiImpl, tenantId, options, userContext); - } - else - return false; + } else return false; }; this.handleError = async (err, _request, _response) => { if (err.fromRecipe === Recipe.RECIPE_ID) { throw err; - } - else { + } else { throw err; } }; @@ -145,7 +140,9 @@ class Recipe extends recipeModule_1.default { const getWebauthnConfig = () => this.config; // const querier = Querier.getNewInstanceOrThrowError(recipeId); const querier = core_mock_1.getMockQuerier(recipeId); - let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(querier, getWebauthnConfig)); + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier, getWebauthnConfig) + ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } { @@ -197,7 +194,9 @@ class Recipe extends recipeModule_1.default { return a.timeJoined - b.timeJoined; }); // Then we take the ones that belong to this recipe - const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter((lm) => lm.recipeId === Recipe.RECIPE_ID); + const recipeLoginMethodsOrderedByTimeJoinedOldestFirst = orderedLoginMethodsByTimeJoinedOldestFirst.filter( + (lm) => lm.recipeId === Recipe.RECIPE_ID + ); let result; if (recipeLoginMethodsOrderedByTimeJoinedOldestFirst.length !== 0) { // If there are login methods belonging to this recipe, the factor is set up @@ -224,16 +223,18 @@ class Recipe extends recipeModule_1.default { .map((lm) => lm.email), ]; // We handle moving the session email to the top of the list later - } - else { + } else { // This factor hasn't been set up, we list all emails belonging to the user - if (orderedLoginMethodsByTimeJoinedOldestFirst.some((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email))) { + if ( + orderedLoginMethodsByTimeJoinedOldestFirst.some( + (lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email) + ) + ) { // If there is at least one real email address linked to the user, we only suggest real addresses result = orderedLoginMethodsByTimeJoinedOldestFirst .filter((lm) => lm.email !== undefined && !utils_3.isFakeEmail(lm.email)) .map((lm) => lm.email); - } - else { + } else { // Else we use the fake ones result = orderedLoginMethodsByTimeJoinedOldestFirst .filter((lm) => lm.email !== undefined && utils_3.isFakeEmail(lm.email)) @@ -287,8 +288,7 @@ class Recipe extends recipeModule_1.default { emailDelivery: undefined, }); return Recipe.instance; - } - else { + } else { throw new Error("Webauthn recipe has already been initialised. Please check your code for bugs."); } }; diff --git a/lib/build/recipe/webauthn/recipeImplementation.d.ts b/lib/build/recipe/webauthn/recipeImplementation.d.ts index d1514956f..b6421a667 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.d.ts +++ b/lib/build/recipe/webauthn/recipeImplementation.d.ts @@ -1,4 +1,7 @@ // @ts-nocheck import { RecipeInterface, TypeNormalisedInput } from "./types"; import { Querier } from "../../querier"; -export default function getRecipeInterface(querier: Querier, getWebauthnConfig: () => TypeNormalisedInput): RecipeInterface; +export default function getRecipeInterface( + querier: Querier, + getWebauthnConfig: () => TypeNormalisedInput +): RecipeInterface; diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js index 3847372d7..58bfeb881 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.js +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -1,37 +1,56 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../accountlinking/recipe")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); @@ -45,20 +64,41 @@ const utils_1 = require("../thirdparty/utils"); function getRecipeInterface(querier, getWebauthnConfig) { return { registerOptions: async function (_a) { - var { relyingPartyId, relyingPartyName, origin, timeout, attestation = "none", tenantId, userContext, supportedAlgorithmIds, userVerification, residentKey } = _a, rest = __rest(_a, ["relyingPartyId", "relyingPartyName", "origin", "timeout", "attestation", "tenantId", "userContext", "supportedAlgorithmIds", "userVerification", "residentKey"]); + var { + relyingPartyId, + relyingPartyName, + origin, + timeout, + attestation = "none", + tenantId, + userContext, + supportedAlgorithmIds, + userVerification, + residentKey, + } = _a, + rest = __rest(_a, [ + "relyingPartyId", + "relyingPartyName", + "origin", + "timeout", + "attestation", + "tenantId", + "userContext", + "supportedAlgorithmIds", + "userVerification", + "residentKey", + ]); const emailInput = "email" in rest ? rest.email : undefined; const recoverAccountTokenInput = "recoverAccountToken" in rest ? rest.recoverAccountToken : undefined; let email; if (emailInput !== undefined) { email = emailInput; - } - else if (recoverAccountTokenInput !== undefined) { + } else if (recoverAccountTokenInput !== undefined) { // the actual validation of the token will be done during consumeRecoverAccountToken let decoded; try { decoded = await jose.decodeJwt(recoverAccountTokenInput); - } - catch (e) { + } catch (e) { console.error(e); return { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR", @@ -84,38 +124,58 @@ function getRecipeInterface(querier, getWebauthnConfig) { let displayName; if (rest.displayName) { displayName = rest.displayName; - } - else { + } else { if (utils_1.isFakeEmail(email)) { displayName = email.split("@")[0]; - } - else { + } else { displayName = email; } } - return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/register`), { - email, - displayName, - relyingPartyName, - relyingPartyId, - origin, - timeout, - attestation, - supportedAlgorithmIds, - userVerification, - residentKey, - }, userContext); + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/register` + ), + { + email, + displayName, + relyingPartyName, + relyingPartyId, + origin, + timeout, + attestation, + supportedAlgorithmIds, + userVerification, + residentKey, + }, + userContext + ); }, - signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext, email }) { + signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. - return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/signin`), { - email, - relyingPartyId, - origin, - timeout, - }, userContext); + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/signin` + ), + { + relyingPartyId, + origin, + timeout, + }, + userContext + ); }, - signUp: async function ({ webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) { + signUp: async function ({ + webauthnGeneratedOptionsId, + credential, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }) { const response = await this.createNewRecipeUser({ credential, webauthnGeneratedOptionsId, @@ -126,14 +186,16 @@ function getRecipeInterface(querier, getWebauthnConfig) { return response; } let updatedUser = response.user; - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( + { + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + } + ); if (linkResult.status != "OK") { return linkResult; } @@ -144,7 +206,14 @@ function getRecipeInterface(querier, getWebauthnConfig) { recipeUserId: response.recipeUserId, }; }, - signIn: async function ({ credential, webauthnGeneratedOptionsId, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) { + signIn: async function ({ + credential, + webauthnGeneratedOptionsId, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + }) { const response = await this.verifyCredentials({ credential, webauthnGeneratedOptionsId, @@ -154,7 +223,9 @@ function getRecipeInterface(querier, getWebauthnConfig) { if (response.status !== "OK") { return response; } - const loginMethod = response.user.loginMethods.find((lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString()); + const loginMethod = response.user.loginMethods.find( + (lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString() + ); if (!loginMethod.verified) { await recipe_1.default.getInstance().verifyEmailForRecipeUserIfLinkedAccountsAreVerified({ user: response.user, @@ -172,16 +243,18 @@ function getRecipeInterface(querier, getWebauthnConfig) { // 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 __1.getUser(response.recipeUserId.getAsString(), userContext)); + response.user = await __1.getUser(response.recipeUserId.getAsString(), userContext); } - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ - tenantId, - inputUser: response.user, - recipeUserId: response.recipeUserId, - session, - shouldTryLinkingWithSessionUser, - userContext, - }); + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( + { + tenantId, + inputUser: response.user, + recipeUserId: response.recipeUserId, + session, + shouldTryLinkingWithSessionUser, + userContext, + } + ); if (linkResult.status === "LINKING_TO_SESSION_USER_FAILED") { return linkResult; } @@ -189,10 +262,16 @@ function getRecipeInterface(querier, getWebauthnConfig) { return response; }, verifyCredentials: async function ({ credential, webauthnGeneratedOptionsId, tenantId, userContext }) { - const response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/signin`), { - credential, - webauthnGeneratedOptionsId, - }, userContext); + const response = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/signin` + ), + { + credential, + webauthnGeneratedOptionsId, + }, + userContext + ); if (response.status === "OK") { return { status: "OK", @@ -205,10 +284,18 @@ function getRecipeInterface(querier, getWebauthnConfig) { }; }, createNewRecipeUser: async function (input) { - const resp = await querier.sendPostRequest(new normalisedURLPath_1.default(`/${input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId}/recipe/webauthn/signup`), { - webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId, - credential: input.credential, - }, input.userContext); + const resp = await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + input.tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : input.tenantId + }/recipe/webauthn/signup` + ), + { + webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId, + credential: input.credential, + }, + input.userContext + ); if (resp.status === "OK") { return { status: "OK", @@ -220,26 +307,50 @@ function getRecipeInterface(querier, getWebauthnConfig) { }, generateRecoverAccountToken: async function ({ userId, email, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. - return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token`), { - userId, - email, - }, userContext); + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/user/recover/token` + ), + { + userId, + email, + }, + userContext + ); }, consumeRecoverAccountToken: async function ({ token, tenantId, userContext }) { - return await querier.sendPostRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token/consume`), { - token, - }, userContext); + return await querier.sendPostRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/user/recover/token/consume` + ), + { + token, + }, + userContext + ); }, registerCredential: async function ({ webauthnGeneratedOptionsId, credential, userContext, recipeUserId }) { - return await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/register`), { - webauthnGeneratedOptionsId, - credential, - }, userContext); + return await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/register`), + { + webauthnGeneratedOptionsId, + credential, + }, + userContext + ); }, decodeCredential: async function ({ credential, userContext }) { - const response = await querier.sendPostRequest(new normalisedURLPath_1.default(`/recipe/webauthn/credential/decode`), { - credential, - }, userContext); + const response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/webauthn/credential/decode`), + { + credential, + }, + userContext + ); if (response.status === "OK") { return response; } @@ -248,22 +359,64 @@ function getRecipeInterface(querier, getWebauthnConfig) { }; }, getUserFromRecoverAccountToken: async function ({ token, tenantId, userContext }) { - return await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/recover/token/${token}`), {}, userContext); + return await querier.sendGetRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/user/recover/token/${token}` + ), + {}, + userContext + ); }, removeCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { - return await querier.sendDeleteRequest(new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}`), {}, {}, userContext); + return await querier.sendDeleteRequest( + new normalisedURLPath_1.default( + `/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}` + ), + {}, + {}, + userContext + ); }, getCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { - return await querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}`), {}, userContext); + return await querier.sendGetRequest( + new normalisedURLPath_1.default( + `/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}` + ), + {}, + userContext + ); }, listCredentials: async function ({ recipeUserId, userContext }) { - return await querier.sendGetRequest(new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/list`), {}, userContext); + return await querier.sendGetRequest( + new normalisedURLPath_1.default(`/recipe/webauthn/user/${recipeUserId}/credential/list`), + {}, + userContext + ); }, removeGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { - return await querier.sendDeleteRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/${webauthnGeneratedOptionsId}`), {}, {}, userContext); + return await querier.sendDeleteRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + ), + {}, + {}, + userContext + ); }, getGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { - return await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options/${webauthnGeneratedOptionsId}`), {}, userContext); + return await querier.sendGetRequest( + new normalisedURLPath_1.default( + `/${ + tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId + }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + ), + {}, + userContext + ); }, }; } diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts index af4631de5..4b468adc8 100644 --- a/lib/build/recipe/webauthn/types.d.ts +++ b/lib/build/recipe/webauthn/types.d.ts @@ -2,7 +2,10 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; import { SessionContainerInterface } from "../session/types"; -import { TypeInput as EmailDeliveryTypeInput, TypeInputWithService as EmailDeliveryTypeInputWithService } from "../../ingredients/emaildelivery/types"; +import { + TypeInput as EmailDeliveryTypeInput, + TypeInputWithService as EmailDeliveryTypeInputWithService, +} from "../../ingredients/emaildelivery/types"; import EmailDeliveryIngredient from "../../ingredients/emaildelivery"; import { GeneralErrorResponse, NormalisedAppinfo, User, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; @@ -10,10 +13,15 @@ export declare type TypeNormalisedInput = { getRelyingPartyId: TypeNormalisedInputRelyingPartyId; getRelyingPartyName: TypeNormalisedInputRelyingPartyName; getOrigin: TypeNormalisedInputGetOrigin; - getEmailDeliveryConfig: (isInServerlessEnv: boolean) => EmailDeliveryTypeInputWithService; + getEmailDeliveryConfig: ( + isInServerlessEnv: boolean + ) => EmailDeliveryTypeInputWithService; validateEmailAddress: TypeNormalisedInputValidateEmailAddress; override: { - functions: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; @@ -31,7 +39,10 @@ export declare type TypeNormalisedInputGetOrigin = (input: { request: BaseRequest; userContext: UserContext; }) => Promise; -export declare type TypeNormalisedInputValidateEmailAddress = (email: string, tenantId: string) => Promise | string | undefined; +export declare type TypeNormalisedInputValidateEmailAddress = ( + email: string, + tenantId: string +) => Promise | string | undefined; export declare type TypeInput = { emailDelivery?: EmailDeliveryTypeInput; getRelyingPartyId?: TypeInputRelyingPartyId; @@ -39,104 +50,119 @@ export declare type TypeInput = { validateEmailAddress?: TypeInputValidateEmailAddress; getOrigin?: TypeInputGetOrigin; override?: { - functions?: (originalImplementation: RecipeInterface, builder?: OverrideableBuilder) => RecipeInterface; + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; }; }; -export declare type TypeInputRelyingPartyId = string | ((input: { - tenantId: string; - request: BaseRequest | undefined; - userContext: UserContext; -}) => Promise); -export declare type TypeInputRelyingPartyName = string | ((input: { - tenantId: string; - userContext: UserContext; -}) => Promise); +export declare type TypeInputRelyingPartyId = + | string + | ((input: { tenantId: string; request: BaseRequest | undefined; userContext: UserContext }) => Promise); +export declare type TypeInputRelyingPartyName = + | string + | ((input: { tenantId: string; userContext: UserContext }) => Promise); export declare type TypeInputGetOrigin = (input: { tenantId: string; request: BaseRequest; userContext: UserContext; }) => Promise; -export declare type TypeInputValidateEmailAddress = (email: string, tenantId: string) => Promise | string | undefined; +export declare type TypeInputValidateEmailAddress = ( + email: string, + tenantId: string +) => Promise | string | undefined; declare type Base64URLString = string; export declare type ResidentKey = "required" | "preferred" | "discouraged"; export declare type UserVerification = "required" | "preferred" | "discouraged"; export declare type Attestation = "none" | "indirect" | "direct" | "enterprise"; export declare type RecipeInterface = { - registerOptions(input: { - relyingPartyId: string; - relyingPartyName: string; - displayName?: string; - origin: string; - residentKey: ResidentKey | undefined; - userVerification: UserVerification | undefined; - attestation: Attestation | undefined; - supportedAlgorithmIds: number[] | undefined; - timeout: number | undefined; - tenantId: string; - userContext: UserContext; - } & ({ - recoverAccountToken: string; - } | { - email: string; - })): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - createdAt: string; - expiresAt: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: "public-key"; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: Attestation; - pubKeyCredParams: { - alg: number; - type: "public-key"; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: ResidentKey; - userVerification: UserVerification; - }; - } | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } | { - status: "INVALID_EMAIL_ERROR"; - err: string; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - }>; + registerOptions( + input: { + relyingPartyId: string; + relyingPartyName: string; + displayName?: string; + origin: string; + residentKey: ResidentKey | undefined; + userVerification: UserVerification | undefined; + attestation: Attestation | undefined; + supportedAlgorithmIds: number[] | undefined; + timeout: number | undefined; + tenantId: string; + userContext: UserContext; + } & ( + | { + recoverAccountToken: string; + } + | { + email: string; + } + ) + ): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: Attestation; + pubKeyCredParams: { + alg: number; + type: "public-key"; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: ResidentKey; + userVerification: UserVerification; + }; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + >; signInOptions(input: { - email: string; relyingPartyId: string; origin: string; userVerification: UserVerification | undefined; timeout: number | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - createdAt: string; - expiresAt: string; - challenge: string; - timeout: number; - userVerification: UserVerification; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; + challenge: string; + timeout: number; + userVerification: UserVerification; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + >; signUp(input: { webauthnGeneratedOptionsId: string; credential: RegistrationPayload; @@ -144,25 +170,37 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } | { - 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"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + | { + 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"; + } + >; signIn(input: { webauthnGeneratedOptionsId: string; credential: AuthenticationPayload; @@ -170,49 +208,67 @@ export declare type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - 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"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + 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"; + } + >; verifyCredentials(input: { webauthnGeneratedOptionsId: string; credential: AuthenticationPayload; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + >; createNewRecipeUser(input: { webauthnGeneratedOptionsId: string; credential: RegistrationPayload; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + >; /** * We pass in the email as well to this function cause the input userId * may not be associated with an webauthn account. In this case, we @@ -223,132 +279,156 @@ export declare type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - token: string; - } | { - status: "UNKNOWN_USER_ID_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + token: string; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + >; consumeRecoverAccountToken(input: { token: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - email: string; - userId: string; - } | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + email: string; + userId: string; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + >; registerCredential(input: { webauthnGeneratedOptionsId: string; credential: RegistrationPayload; userContext: UserContext; recipeUserId: RecipeUserId; - }): Promise<{ - status: "OK"; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - }>; + }): Promise< + | { + status: "OK"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + >; decodeCredential(input: { credential: CredentialPayload; userContext: UserContext; - }): Promise<{ - status: "OK"; - credential: { - id: string; - rawId: string; - response: { - clientDataJSON: { - type: string; - challenge: string; - origin: string; - crossOrigin?: boolean; - tokenBinding?: { - id?: string; - status: "present" | "supported" | "not-supported"; - }; - }; - attestationObject: { - fmt: "packed" | "tpm" | "android-key" | "android-safetynet" | "fido-u2f" | "none"; - authData: { - rpIdHash: string; - flags: { - up: boolean; - uv: boolean; - be: boolean; - bs: boolean; - at: boolean; - ed: boolean; - flagsInt: number; - }; - counter: number; - aaguid?: string; - credentialID?: string; - credentialPublicKey?: string; - extensionsData?: unknown; - }; - attStmt: { - sig?: Base64URLString; - x5c?: Base64URLString[]; - response?: Base64URLString; - alg?: number; - ver?: string; - certInfo?: Base64URLString; - pubArea?: Base64URLString; - size: number; - }; - }; - transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; - userHandle: string; - }; - authenticatorAttachment: "platform" | "cross-platform"; - clientExtensionResults: Record; - type: string; - }; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + credential: { + id: string; + rawId: string; + response: { + clientDataJSON: { + type: string; + challenge: string; + origin: string; + crossOrigin?: boolean; + tokenBinding?: { + id?: string; + status: "present" | "supported" | "not-supported"; + }; + }; + attestationObject: { + fmt: "packed" | "tpm" | "android-key" | "android-safetynet" | "fido-u2f" | "none"; + authData: { + rpIdHash: string; + flags: { + up: boolean; + uv: boolean; + be: boolean; + bs: boolean; + at: boolean; + ed: boolean; + flagsInt: number; + }; + counter: number; + aaguid?: string; + credentialID?: string; + credentialPublicKey?: string; + extensionsData?: unknown; + }; + attStmt: { + sig?: Base64URLString; + x5c?: Base64URLString[]; + response?: Base64URLString; + alg?: number; + ver?: string; + certInfo?: Base64URLString; + pubArea?: Base64URLString; + size: number; + }; + }; + transports?: ("ble" | "cable" | "hybrid" | "internal" | "nfc" | "smart-card" | "usb")[]; + userHandle: string; + }; + authenticatorAttachment: "platform" | "cross-platform"; + clientExtensionResults: Record; + type: string; + }; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + >; getUserFromRecoverAccountToken(input: { token: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - user: User; - recipeUserId: RecipeUserId; - } | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + user: User; + recipeUserId: RecipeUserId; + } + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + >; removeCredential(input: { webauthnCredentialId: string; recipeUserId: RecipeUserId; userContext: UserContext; - }): Promise<{ - status: "OK"; - } | { - status: "CREDENTIAL_NOT_FOUND_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + } + | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + } + >; getCredential(input: { webauthnCredentialId: string; recipeUserId: RecipeUserId; userContext: UserContext; - }): Promise<{ - status: "OK"; - id: string; - relyingPartyId: string; - recipeUserId: RecipeUserId; - createdAt: number; - } | { - status: "CREDENTIAL_NOT_FOUND_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + id: string; + relyingPartyId: string; + recipeUserId: RecipeUserId; + createdAt: number; + } + | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + } + >; listCredentials(input: { recipeUserId: RecipeUserId; userContext: UserContext; @@ -364,26 +444,32 @@ export declare type RecipeInterface = { webauthnGeneratedOptionsId: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + >; getGeneratedOptions(input: { webauthnGeneratedOptionsId: string; tenantId: string; userContext: UserContext; - }): Promise<{ - status: "OK"; - id: string; - relyingPartyId: string; - origin: string; - email: string; - timeout: string; - challenge: string; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - }>; + }): Promise< + | { + status: "OK"; + id: string; + relyingPartyId: string; + origin: string; + email: string; + timeout: string; + challenge: string; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + >; }; export declare type APIOptions = { recipeImplementation: RecipeInterface; @@ -396,179 +482,246 @@ export declare type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; export declare type APIInterface = { - registerOptionsPOST: undefined | ((input: { - tenantId: string; - options: APIOptions; - userContext: UserContext; - } & ({ - email: string; - } | { - recoverAccountToken: string; - })) => Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - createdAt: string; - expiresAt: string; - rp: { - id: string; - name: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - timeout: number; - excludeCredentials: { - id: string; - type: "public-key"; - transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; - }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; - pubKeyCredParams: { - alg: number; - type: string; - }[]; - authenticatorSelection: { - requireResidentKey: boolean; - residentKey: ResidentKey; - userVerification: UserVerification; - }; - } | GeneralErrorResponse | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } | { - status: "INVALID_EMAIL_ERROR"; - err: string; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - }>); - signInOptionsPOST: undefined | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - webauthnGeneratedOptionsId: string; - createdAt: string; - expiresAt: string; - challenge: string; - timeout: number; - userVerification: UserVerification; - } | GeneralErrorResponse | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - }>); - signUpPOST: undefined | ((input: { - webauthnGeneratedOptionsId: string; - credential: RegistrationPayload; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - user: User; - session: SessionContainerInterface; - } | GeneralErrorResponse | { - status: "SIGN_UP_NOT_ALLOWED"; - reason: string; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - } | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - }>); - signInPOST: undefined | ((input: { - webauthnGeneratedOptionsId: string; - credential: AuthenticationPayload; - tenantId: string; - session: SessionContainerInterface | undefined; - shouldTryLinkingWithSessionUser: boolean | undefined; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - user: User; - session: SessionContainerInterface; - } | GeneralErrorResponse | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - }>); - generateRecoverAccountTokenPOST: undefined | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - } | GeneralErrorResponse | { - status: "RECOVER_ACCOUNT_NOT_ALLOWED"; - reason: string; - }>); - recoverAccountPOST: undefined | ((input: { - token: string; - webauthnGeneratedOptionsId: string; - credential: RegistrationPayload; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - user: User; - email: string; - } | GeneralErrorResponse | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - }>); - registerCredentialPOST: undefined | ((input: { - webauthnGeneratedOptionsId: string; - credential: CredentialPayload; - tenantId: string; - session: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - } | GeneralErrorResponse | { - status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; - reason: string; - } | { - status: "INVALID_CREDENTIALS_ERROR"; - } | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - reason: string; - }>); - emailExistsGET: undefined | ((input: { - email: string; - tenantId: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ - status: "OK"; - exists: boolean; - } | GeneralErrorResponse>); + registerOptionsPOST: + | undefined + | (( + input: { + tenantId: string; + options: APIOptions; + userContext: UserContext; + } & ( + | { + email: string; + } + | { + recoverAccountToken: string; + } + ) + ) => Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; + rp: { + id: string; + name: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + timeout: number; + excludeCredentials: { + id: string; + type: "public-key"; + transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; + }[]; + attestation: "none" | "indirect" | "direct" | "enterprise"; + pubKeyCredParams: { + alg: number; + type: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + residentKey: ResidentKey; + userVerification: UserVerification; + }; + } + | GeneralErrorResponse + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + >); + signInOptionsPOST: + | undefined + | ((input: { + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; + challenge: string; + timeout: number; + userVerification: UserVerification; + } + | GeneralErrorResponse + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + >); + signUpPOST: + | undefined + | ((input: { + webauthnGeneratedOptionsId: string; + credential: RegistrationPayload; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + } + | GeneralErrorResponse + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + >); + signInPOST: + | undefined + | ((input: { + webauthnGeneratedOptionsId: string; + credential: AuthenticationPayload; + tenantId: string; + session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + } + | GeneralErrorResponse + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + >); + generateRecoverAccountTokenPOST: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + | { + status: "RECOVER_ACCOUNT_NOT_ALLOWED"; + reason: string; + } + >); + recoverAccountPOST: + | undefined + | ((input: { + token: string; + webauthnGeneratedOptionsId: string; + credential: RegistrationPayload; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + user: User; + email: string; + } + | GeneralErrorResponse + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + >); + registerCredentialPOST: + | undefined + | ((input: { + webauthnGeneratedOptionsId: string; + credential: CredentialPayload; + tenantId: string; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + | { + status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; + reason: string; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_GENERATED_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + >); + emailExistsGET: + | undefined + | ((input: { + email: string; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + exists: boolean; + } + | GeneralErrorResponse + >); }; export declare type TypeWebauthnRecoverAccountEmailDeliveryInput = { type: "RECOVER_ACCOUNT"; diff --git a/lib/build/recipe/webauthn/utils.d.ts b/lib/build/recipe/webauthn/utils.d.ts index 06e8b66db..b37ca89ff 100644 --- a/lib/build/recipe/webauthn/utils.d.ts +++ b/lib/build/recipe/webauthn/utils.d.ts @@ -3,8 +3,14 @@ import Recipe from "./recipe"; import { TypeInput, TypeNormalisedInput } from "./types"; import { NormalisedAppinfo, UserContext } from "../../types"; import { BaseRequest } from "../../framework"; -export declare function validateAndNormaliseUserInput(_: Recipe, appInfo: NormalisedAppinfo, config?: TypeInput): TypeNormalisedInput; -export declare function defaultEmailValidator(value: any): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; +export declare function validateAndNormaliseUserInput( + _: Recipe, + appInfo: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; +export declare function defaultEmailValidator( + value: any +): Promise<"Development bug: Please make sure the email field yields a string" | "Email is invalid" | undefined>; export declare function getRecoverAccountLink(input: { appInfo: NormalisedAppinfo; token: string; diff --git a/lib/build/recipe/webauthn/utils.js b/lib/build/recipe/webauthn/utils.js index afa11eca4..ac552d09f 100644 --- a/lib/build/recipe/webauthn/utils.js +++ b/lib/build/recipe/webauthn/utils.js @@ -13,21 +13,43 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRecoverAccountLink = exports.defaultEmailValidator = exports.validateAndNormaliseUserInput = void 0; const backwardCompatibility_1 = __importDefault(require("./emaildelivery/services/backwardCompatibility")); function validateAndNormaliseUserInput(_, appInfo, config) { - let getRelyingPartyId = validateAndNormaliseRelyingPartyIdConfig(appInfo, config === null || config === void 0 ? void 0 : config.getRelyingPartyId); - let getRelyingPartyName = validateAndNormaliseRelyingPartyNameConfig(appInfo, config === null || config === void 0 ? void 0 : config.getRelyingPartyName); - let getOrigin = validateAndNormaliseGetOriginConfig(appInfo, config === null || config === void 0 ? void 0 : config.getOrigin); - let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig(config === null || config === void 0 ? void 0 : config.validateEmailAddress); - let override = Object.assign({ functions: (originalImplementation) => originalImplementation, apis: (originalImplementation) => originalImplementation }, config === null || config === void 0 ? void 0 : config.override); + let getRelyingPartyId = validateAndNormaliseRelyingPartyIdConfig( + appInfo, + config === null || config === void 0 ? void 0 : config.getRelyingPartyId + ); + let getRelyingPartyName = validateAndNormaliseRelyingPartyNameConfig( + appInfo, + config === null || config === void 0 ? void 0 : config.getRelyingPartyName + ); + let getOrigin = validateAndNormaliseGetOriginConfig( + appInfo, + config === null || config === void 0 ? void 0 : config.getOrigin + ); + let validateEmailAddress = validateAndNormaliseValidateEmailAddressConfig( + config === null || config === void 0 ? void 0 : config.validateEmailAddress + ); + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); function getEmailDeliveryConfig(isInServerlessEnv) { var _a; - let emailService = (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 ? void 0 : _a.service; + let emailService = + (_a = config === null || config === void 0 ? void 0 : config.emailDelivery) === null || _a === void 0 + ? void 0 + : _a.service; /** * If the user has not passed even that config, we use the default * createAndSendCustomEmail implementation which calls our supertokens API @@ -35,7 +57,7 @@ function validateAndNormaliseUserInput(_, appInfo, config) { if (emailService === undefined) { emailService = new backwardCompatibility_1.default(appInfo, isInServerlessEnv); } - return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { + return Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.emailDelivery), { /** * if we do * let emailDelivery = { @@ -48,7 +70,8 @@ function validateAndNormaliseUserInput(_, appInfo, config) { * set service at the end */ // todo implemenet this - service: emailService }); + service: emailService, + }); } return { override, @@ -64,11 +87,9 @@ function validateAndNormaliseRelyingPartyIdConfig(normalisedAppinfo, relyingPart return (props) => { if (typeof relyingPartyIdConfig === "string") { return Promise.resolve(relyingPartyIdConfig); - } - else if (typeof relyingPartyIdConfig === "function") { + } else if (typeof relyingPartyIdConfig === "function") { return relyingPartyIdConfig(props); - } - else { + } else { const urlString = normalisedAppinfo.apiDomain.getAsStringDangerous(); // should let this throw if the url is invalid const url = new URL(urlString); @@ -81,11 +102,9 @@ function validateAndNormaliseRelyingPartyNameConfig(normalisedAppInfo, relyingPa return (props) => { if (typeof relyingPartyNameConfig === "string") { return Promise.resolve(relyingPartyNameConfig); - } - else if (typeof relyingPartyNameConfig === "function") { + } else if (typeof relyingPartyNameConfig === "function") { return relyingPartyNameConfig(props); - } - else { + } else { return Promise.resolve(normalisedAppInfo.appName); } }; @@ -94,11 +113,12 @@ function validateAndNormaliseGetOriginConfig(normalisedAppinfo, getOriginConfig) return (props) => { if (typeof getOriginConfig === "function") { return getOriginConfig(props); - } - else { - return Promise.resolve(normalisedAppinfo - .getOrigin({ request: props.request, userContext: props.userContext }) - .getAsStringDangerous()); + } else { + return Promise.resolve( + normalisedAppinfo + .getOrigin({ request: props.request, userContext: props.userContext }) + .getAsStringDangerous() + ); } }; } @@ -106,8 +126,7 @@ function validateAndNormaliseValidateEmailAddressConfig(validateEmailAddressConf return (email, tenantId) => { if (typeof validateEmailAddressConfig === "function") { return validateEmailAddressConfig(email, tenantId); - } - else { + } else { return defaultEmailValidator(email); } }; @@ -119,23 +138,29 @@ async function defaultEmailValidator(value) { if (typeof value !== "string") { return "Development bug: Please make sure the email field yields a string"; } - if (value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) === null) { + if ( + value.match( + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ) === null + ) { return "Email is invalid"; } return undefined; } exports.defaultEmailValidator = defaultEmailValidator; function getRecoverAccountLink(input) { - return (input.appInfo - .getOrigin({ - request: input.request, - userContext: input.userContext, - }) - .getAsStringDangerous() + + return ( + input.appInfo + .getOrigin({ + request: input.request, + userContext: input.userContext, + }) + .getAsStringDangerous() + input.appInfo.websiteBasePath.getAsStringDangerous() + "/recover-account?token=" + input.token + "&tenantId=" + - input.tenantId); + input.tenantId + ); } exports.getRecoverAccountLink = getRecoverAccountLink; diff --git a/lib/build/recipeModule.d.ts b/lib/build/recipeModule.d.ts index eea95cbb4..e277192a9 100644 --- a/lib/build/recipeModule.d.ts +++ b/lib/build/recipeModule.d.ts @@ -9,14 +9,34 @@ export default abstract class RecipeModule { constructor(recipeId: string, appInfo: NormalisedAppinfo); getRecipeId: () => string; getAppInfo: () => NormalisedAppinfo; - returnAPIIdIfCanHandleRequest: (path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext) => Promise<{ - id: string; - tenantId: string; - exactMatch: boolean; - } | undefined>; + returnAPIIdIfCanHandleRequest: ( + path: NormalisedURLPath, + method: HTTPMethod, + userContext: UserContext + ) => Promise< + | { + id: string; + tenantId: string; + exactMatch: boolean; + } + | undefined + >; abstract getAPIsHandled(): APIHandled[]; - abstract handleAPIRequest(id: string, tenantId: string, req: BaseRequest, response: BaseResponse, path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext): Promise; - abstract handleError(error: STError, request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise; + abstract handleAPIRequest( + id: string, + tenantId: string, + req: BaseRequest, + response: BaseResponse, + path: NormalisedURLPath, + method: HTTPMethod, + userContext: UserContext + ): Promise; + abstract handleError( + error: STError, + request: BaseRequest, + response: BaseResponse, + userContext: UserContext + ): Promise; abstract getAllCORSHeaders(): string[]; abstract isErrorFromThisRecipe(err: any): err is STError; } diff --git a/lib/build/recipeModule.js b/lib/build/recipeModule.js index 1f3dfb145..75c3219ae 100644 --- a/lib/build/recipeModule.js +++ b/lib/build/recipeModule.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); const constants_1 = require("./recipe/multitenancy/constants"); @@ -53,11 +55,12 @@ class RecipeModule { userContext, }); return { id: currAPI.id, tenantId: finalTenantId, exactMatch: true }; - } - else if (remainingPath !== undefined && + } else if ( + remainingPath !== undefined && this.appInfo.apiBasePath .appendPath(currAPI.pathWithoutApiBasePath) - .equals(this.appInfo.apiBasePath.appendPath(remainingPath))) { + .equals(this.appInfo.apiBasePath.appendPath(remainingPath)) + ) { const finalTenantId = await mtRecipe.recipeInterfaceImpl.getTenantId({ tenantIdFromFrontend: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, userContext, diff --git a/lib/build/supertokens.d.ts b/lib/build/supertokens.d.ts index e4dfeb78b..e4f7f6777 100644 --- a/lib/build/supertokens.d.ts +++ b/lib/build/supertokens.d.ts @@ -16,34 +16,53 @@ export default class SuperTokens { static init(config: TypeInput): void; static reset(): void; static getInstanceOrThrowError(): SuperTokens; - handleAPI: (matchedRecipe: RecipeModule, id: string, tenantId: string, request: BaseRequest, response: BaseResponse, path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext) => Promise; + handleAPI: ( + matchedRecipe: RecipeModule, + id: string, + tenantId: string, + request: BaseRequest, + response: BaseResponse, + path: NormalisedURLPath, + method: HTTPMethod, + userContext: UserContext + ) => Promise; getAllCORSHeaders: () => string[]; - getUserCount: (includeRecipeIds: string[] | undefined, tenantId: string | undefined, userContext: UserContext) => Promise; + getUserCount: ( + includeRecipeIds: string[] | undefined, + tenantId: string | undefined, + userContext: UserContext + ) => Promise; createUserIdMapping: (input: { superTokensUserId: string; externalUserId: string; externalUserIdInfo?: string; force?: boolean; userContext: UserContext; - }) => Promise<{ - status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"; - } | { - status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR"; - doesSuperTokensUserIdExist: boolean; - doesExternalUserIdExist: boolean; - }>; + }) => Promise< + | { + status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"; + } + | { + status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR"; + doesSuperTokensUserIdExist: boolean; + doesExternalUserIdExist: boolean; + } + >; getUserIdMapping: (input: { userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"; userContext: UserContext; - }) => Promise<{ - status: "OK"; - superTokensUserId: string; - externalUserId: string; - externalUserIdInfo: string | undefined; - } | { - status: "UNKNOWN_MAPPING_ERROR"; - }>; + }) => Promise< + | { + status: "OK"; + superTokensUserId: string; + externalUserId: string; + externalUserIdInfo: string | undefined; + } + | { + status: "UNKNOWN_MAPPING_ERROR"; + } + >; deleteUserIdMapping: (input: { userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"; diff --git a/lib/build/supertokens.js b/lib/build/supertokens.js index 8a5101416..db7ceb9ef 100644 --- a/lib/build/supertokens.js +++ b/lib/build/supertokens.js @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("./utils"); const querier_1 = require("./querier"); @@ -48,16 +50,24 @@ class SuperTokens { let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); let apiVersion = await querier.getAPIVersion(userContext); if (utils_1.maxVersion(apiVersion, "2.7") === "2.7") { - throw new Error("Please use core version >= 3.5 to call this function. Otherwise, you can call .getUserCount() instead (for example, EmailPassword.getUserCount())"); + throw new Error( + "Please use core version >= 3.5 to call this function. Otherwise, you can call .getUserCount() instead (for example, EmailPassword.getUserCount())" + ); } let includeRecipeIdsStr = undefined; if (includeRecipeIds !== undefined) { includeRecipeIdsStr = includeRecipeIds.join(","); } - let response = await querier.sendGetRequest(new normalisedURLPath_1.default(`/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/users/count`), { - includeRecipeIds: includeRecipeIdsStr, - includeAllTenants: tenantId === undefined, - }, userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_2.DEFAULT_TENANT_ID : tenantId}/users/count` + ), + { + includeRecipeIds: includeRecipeIdsStr, + includeAllTenants: tenantId === undefined, + }, + userContext + ); return Number(response.count); }; this.createUserIdMapping = async function (input) { @@ -65,14 +75,17 @@ class SuperTokens { let cdiVersion = await querier.getAPIVersion(input.userContext); if (utils_1.maxVersion("2.15", cdiVersion) === cdiVersion) { // create userId mapping is only available >= CDI 2.15 - return await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/userid/map"), { - superTokensUserId: input.superTokensUserId, - externalUserId: input.externalUserId, - externalUserIdInfo: input.externalUserIdInfo, - force: input.force, - }, input.userContext); - } - else { + return await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/userid/map"), + { + superTokensUserId: input.superTokensUserId, + externalUserId: input.externalUserId, + externalUserIdInfo: input.externalUserIdInfo, + force: input.force, + }, + input.userContext + ); + } else { throw new global.Error("Please upgrade the SuperTokens core to >= 3.15.0"); } }; @@ -81,13 +94,16 @@ class SuperTokens { let cdiVersion = await querier.getAPIVersion(input.userContext); if (utils_1.maxVersion("2.15", cdiVersion) === cdiVersion) { // create userId mapping is only available >= CDI 2.15 - let response = await querier.sendGetRequest(new normalisedURLPath_1.default("/recipe/userid/map"), { - userId: input.userId, - userIdType: input.userIdType, - }, input.userContext); + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/userid/map"), + { + userId: input.userId, + userIdType: input.userIdType, + }, + input.userContext + ); return response; - } - else { + } else { throw new global.Error("Please upgrade the SuperTokens core to >= 3.15.0"); } }; @@ -95,13 +111,16 @@ class SuperTokens { let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); let cdiVersion = await querier.getAPIVersion(input.userContext); if (utils_1.maxVersion("2.15", cdiVersion) === cdiVersion) { - return await querier.sendPostRequest(new normalisedURLPath_1.default("/recipe/userid/map/remove"), { - userId: input.userId, - userIdType: input.userIdType, - force: input.force, - }, input.userContext); - } - else { + return await querier.sendPostRequest( + new normalisedURLPath_1.default("/recipe/userid/map/remove"), + { + userId: input.userId, + userIdType: input.userIdType, + force: input.force, + }, + input.userContext + ); + } else { throw new global.Error("Please upgrade the SuperTokens core to >= 3.15.0"); } }; @@ -109,24 +128,32 @@ class SuperTokens { let querier = querier_1.Querier.getNewInstanceOrThrowError(undefined); let cdiVersion = await querier.getAPIVersion(input.userContext); if (utils_1.maxVersion("2.15", cdiVersion) === cdiVersion) { - return await querier.sendPutRequest(new normalisedURLPath_1.default("/recipe/userid/external-user-id-info"), { - userId: input.userId, - userIdType: input.userIdType, - externalUserIdInfo: input.externalUserIdInfo, - }, {}, input.userContext); - } - else { + return await querier.sendPutRequest( + new normalisedURLPath_1.default("/recipe/userid/external-user-id-info"), + { + userId: input.userId, + userIdType: input.userIdType, + externalUserIdInfo: input.externalUserIdInfo, + }, + {}, + input.userContext + ); + } else { throw new global.Error("Please upgrade the SuperTokens core to >= 3.15.0"); } }; this.middleware = async (request, response, userContext) => { logger_1.logDebugMessage("middleware: Started"); - let path = this.appInfo.apiGatewayPath.appendPath(new normalisedURLPath_1.default(request.getOriginalURL())); + let path = this.appInfo.apiGatewayPath.appendPath( + new normalisedURLPath_1.default(request.getOriginalURL()) + ); let method = utils_1.normaliseHttpMethod(request.getMethod()); // if the prefix of the URL doesn't match the base path, we skip if (!path.startsWith(this.appInfo.apiBasePath)) { - logger_1.logDebugMessage("middleware: Not handling because request path did not start with config path. Request path: " + - path.getAsStringDangerous()); + logger_1.logDebugMessage( + "middleware: Not handling because request path did not start with config path. Request path: " + + path.getAsStringDangerous() + ); return false; } let requestRID = utils_1.getRidFromHeader(request); @@ -138,12 +165,14 @@ class SuperTokens { async function handleWithoutRid(recipeModules) { let bestMatch = undefined; for (let i = 0; i < recipeModules.length; i++) { - logger_1.logDebugMessage("middleware: Checking recipe ID for match: " + - recipeModules[i].getRecipeId() + - " with path: " + - path.getAsStringDangerous() + - " and method: " + - method); + logger_1.logDebugMessage( + "middleware: Checking recipe ID for match: " + + recipeModules[i].getRecipeId() + + " with path: " + + path.getAsStringDangerous() + + " and method: " + + method + ); let idResult = await recipeModules[i].returnAPIIdIfCanHandleRequest(path, method, userContext); if (idResult !== undefined) { // The request path may or may not include the tenantId. `returnAPIIdIfCanHandleRequest` handles both cases. @@ -162,9 +191,19 @@ class SuperTokens { if (bestMatch !== undefined) { const { idResult, recipeModule } = bestMatch; logger_1.logDebugMessage("middleware: Request being handled by recipe. ID is: " + idResult.id); - let requestHandled = await recipeModule.handleAPIRequest(idResult.id, idResult.tenantId, request, response, path, method, userContext); + let requestHandled = await recipeModule.handleAPIRequest( + idResult.id, + idResult.tenantId, + request, + response, + path, + method, + userContext + ); if (!requestHandled) { - logger_1.logDebugMessage("middleware: Not handled because API returned requestHandled as false"); + logger_1.logDebugMessage( + "middleware: Not handled because API returned requestHandled as false" + ); return false; } logger_1.logDebugMessage("middleware: Ended"); @@ -180,19 +219,23 @@ class SuperTokens { let matchedRecipe = []; // we loop through all recipe modules to find the one with the matching rId for (let i = 0; i < this.recipeModules.length; i++) { - logger_1.logDebugMessage("middleware: Checking recipe ID for match: " + this.recipeModules[i].getRecipeId()); + logger_1.logDebugMessage( + "middleware: Checking recipe ID for match: " + this.recipeModules[i].getRecipeId() + ); if (this.recipeModules[i].getRecipeId() === requestRID) { matchedRecipe.push(this.recipeModules[i]); - } - else if (requestRID === "thirdpartyemailpassword") { - if (this.recipeModules[i].getRecipeId() === "thirdparty" || - this.recipeModules[i].getRecipeId() === "emailpassword") { + } else if (requestRID === "thirdpartyemailpassword") { + if ( + this.recipeModules[i].getRecipeId() === "thirdparty" || + this.recipeModules[i].getRecipeId() === "emailpassword" + ) { matchedRecipe.push(this.recipeModules[i]); } - } - else if (requestRID === "thirdpartypasswordless") { - if (this.recipeModules[i].getRecipeId() === "thirdparty" || - this.recipeModules[i].getRecipeId() === "passwordless") { + } else if (requestRID === "thirdpartypasswordless") { + if ( + this.recipeModules[i].getRecipeId() === "thirdparty" || + this.recipeModules[i].getRecipeId() === "passwordless" + ) { matchedRecipe.push(this.recipeModules[i]); } } @@ -211,15 +254,18 @@ class SuperTokens { // the path and methods of the APIs exposed via those recipes is unique. let currIdResult = await matchedRecipe[i].returnAPIIdIfCanHandleRequest(path, method, userContext); if (currIdResult !== undefined) { - if (idResult === undefined || + if ( + idResult === undefined || // The request path may or may not include the tenantId. `returnAPIIdIfCanHandleRequest` handles both cases. // If one recipe matches with tenantId and another matches exactly, we prefer the exact match. - (currIdResult.exactMatch === true && idResult.exactMatch === false)) { + (currIdResult.exactMatch === true && idResult.exactMatch === false) + ) { finalMatchedRecipe = matchedRecipe[i]; idResult = currIdResult; - } - else { - throw new Error("Two recipes have matched the same API path and method! This is a bug in the SDK. Please contact support."); + } else { + throw new Error( + "Two recipes have matched the same API path and method! This is a bug in the SDK. Please contact support." + ); } } } @@ -228,15 +274,22 @@ class SuperTokens { } logger_1.logDebugMessage("middleware: Request being handled by recipe. ID is: " + idResult.id); // give task to the matched recipe - let requestHandled = await finalMatchedRecipe.handleAPIRequest(idResult.id, idResult.tenantId, request, response, path, method, userContext); + let requestHandled = await finalMatchedRecipe.handleAPIRequest( + idResult.id, + idResult.tenantId, + request, + response, + path, + method, + userContext + ); if (!requestHandled) { logger_1.logDebugMessage("middleware: Not handled because API returned requestHandled as false"); return false; } logger_1.logDebugMessage("middleware: Ended"); return true; - } - else { + } else { return handleWithoutRid(this.recipeModules); } }; @@ -249,9 +302,13 @@ class SuperTokens { return utils_1.sendNon200ResponseWithMessage(response, err.message, 400); } for (let i = 0; i < this.recipeModules.length; i++) { - logger_1.logDebugMessage("errorHandler: Checking recipe for match: " + this.recipeModules[i].getRecipeId()); + logger_1.logDebugMessage( + "errorHandler: Checking recipe for match: " + this.recipeModules[i].getRecipeId() + ); if (this.recipeModules[i].isErrorFromThisRecipe(err)) { - logger_1.logDebugMessage("errorHandler: Matched with recipeID: " + this.recipeModules[i].getRecipeId()); + logger_1.logDebugMessage( + "errorHandler: Matched with recipeID: " + this.recipeModules[i].getRecipeId() + ); return await this.recipeModules[i].handleError(err, request, response, userContext); } } @@ -277,23 +334,35 @@ class SuperTokens { logger_1.enableDebugLogs(); } logger_1.logDebugMessage("Started SuperTokens with debug logging (supertokens.init called)"); - const originToPrint = config.appInfo.origin === undefined - ? undefined - : typeof config.appInfo.origin === "string" + const originToPrint = + config.appInfo.origin === undefined + ? undefined + : typeof config.appInfo.origin === "string" ? config.appInfo.origin : "function"; - logger_1.logDebugMessage("appInfo: " + - JSON.stringify(Object.assign(Object.assign({}, config.appInfo), { origin: originToPrint }))); + logger_1.logDebugMessage( + "appInfo: " + JSON.stringify(Object.assign(Object.assign({}, config.appInfo), { origin: originToPrint })) + ); this.framework = config.framework !== undefined ? config.framework : "express"; logger_1.logDebugMessage("framework: " + this.framework); this.appInfo = utils_1.normaliseInputAppInfoOrThrowError(config.appInfo); this.supertokens = config.supertokens; - querier_1.Querier.init((_a = config.supertokens) === null || _a === void 0 ? void 0 : _a.connectionURI.split(";").filter((h) => h !== "").map((h) => { - return { - domain: new normalisedURLDomain_1.default(h.trim()), - basePath: new normalisedURLPath_1.default(h.trim()), - }; - }), (_b = config.supertokens) === null || _b === void 0 ? void 0 : _b.apiKey, (_c = config.supertokens) === null || _c === void 0 ? void 0 : _c.networkInterceptor, (_d = config.supertokens) === null || _d === void 0 ? void 0 : _d.disableCoreCallCache); + querier_1.Querier.init( + (_a = config.supertokens) === null || _a === void 0 + ? void 0 + : _a.connectionURI + .split(";") + .filter((h) => h !== "") + .map((h) => { + return { + domain: new normalisedURLDomain_1.default(h.trim()), + basePath: new normalisedURLPath_1.default(h.trim()), + }; + }), + (_b = config.supertokens) === null || _b === void 0 ? void 0 : _b.apiKey, + (_c = config.supertokens) === null || _c === void 0 ? void 0 : _c.networkInterceptor, + (_d = config.supertokens) === null || _d === void 0 ? void 0 : _d.disableCoreCallCache + ); if (config.recipeList === undefined || config.recipeList.length === 0) { throw new Error("Please provide at least one recipe to the supertokens.init function call"); } @@ -324,23 +393,17 @@ class SuperTokens { const recipeModule = func(this.appInfo, this.isInServerlessEnv); if (recipeModule.getRecipeId() === MultitenancyRecipe.RECIPE_ID) { multitenancyFound = true; - } - else if (recipeModule.getRecipeId() === UserMetadataRecipe.RECIPE_ID) { + } else if (recipeModule.getRecipeId() === UserMetadataRecipe.RECIPE_ID) { userMetadataFound = true; - } - else if (recipeModule.getRecipeId() === MultiFactorAuthRecipe.RECIPE_ID) { + } else if (recipeModule.getRecipeId() === MultiFactorAuthRecipe.RECIPE_ID) { multiFactorAuthFound = true; - } - else if (recipeModule.getRecipeId() === TotpRecipe.RECIPE_ID) { + } else if (recipeModule.getRecipeId() === TotpRecipe.RECIPE_ID) { totpFound = true; - } - else if (recipeModule.getRecipeId() === OAuth2ProviderRecipe.RECIPE_ID) { + } else if (recipeModule.getRecipeId() === OAuth2ProviderRecipe.RECIPE_ID) { oauth2Found = true; - } - else if (recipeModule.getRecipeId() === OpenIdRecipe.RECIPE_ID) { + } else if (recipeModule.getRecipeId() === OpenIdRecipe.RECIPE_ID) { openIdFound = true; - } - else if (recipeModule.getRecipeId() === jwtRecipe.RECIPE_ID) { + } else if (recipeModule.getRecipeId() === jwtRecipe.RECIPE_ID) { jwtFound = true; } return recipeModule; diff --git a/lib/build/thirdpartyUtils.d.ts b/lib/build/thirdpartyUtils.d.ts index 8db4b25a7..84517d5e2 100644 --- a/lib/build/thirdpartyUtils.d.ts +++ b/lib/build/thirdpartyUtils.d.ts @@ -1,22 +1,34 @@ // @ts-nocheck import * as jose from "jose"; -export declare function doGetRequest(url: string, queryParams?: { - [key: string]: string; -}, headers?: { - [key: string]: string; -}): Promise<{ +export declare function doGetRequest( + url: string, + queryParams?: { + [key: string]: string; + }, + headers?: { + [key: string]: string; + } +): Promise<{ jsonResponse: Record | undefined; status: number; stringResponse: string; }>; -export declare function doPostRequest(url: string, params: { - [key: string]: any; -}, headers?: { - [key: string]: string; -}): Promise<{ +export declare function doPostRequest( + url: string, + params: { + [key: string]: any; + }, + headers?: { + [key: string]: string; + } +): Promise<{ jsonResponse: Record | undefined; status: number; stringResponse: string; }>; -export declare function verifyIdTokenFromJWKSEndpointAndGetPayload(idToken: string, jwks: jose.JWTVerifyGetKey, otherOptions: jose.JWTVerifyOptions): Promise; +export declare function verifyIdTokenFromJWKSEndpointAndGetPayload( + idToken: string, + jwks: jose.JWTVerifyGetKey, + otherOptions: jose.JWTVerifyOptions +): Promise; export declare function getOIDCDiscoveryInfo(issuer: string): Promise; diff --git a/lib/build/thirdpartyUtils.js b/lib/build/thirdpartyUtils.js index da6ca5c0c..83cc3d405 100644 --- a/lib/build/thirdpartyUtils.js +++ b/lib/build/thirdpartyUtils.js @@ -1,26 +1,45 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getOIDCDiscoveryInfo = exports.verifyIdTokenFromJWKSEndpointAndGetPayload = exports.doPostRequest = exports.doGetRequest = void 0; const jose = __importStar(require("jose")); @@ -29,7 +48,9 @@ const utils_1 = require("./utils"); const normalisedURLDomain_1 = __importDefault(require("./normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); async function doGetRequest(url, queryParams, headers) { - logger_1.logDebugMessage(`GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}`); + logger_1.logDebugMessage( + `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` + ); if ((headers === null || headers === void 0 ? void 0 : headers["Accept"]) === undefined) { headers = Object.assign(Object.assign({}, headers), { Accept: "application/json" }); } @@ -57,7 +78,9 @@ async function doPostRequest(url, params, headers) { } headers["Content-Type"] = "application/x-www-form-urlencoded"; headers["Accept"] = "application/json"; - logger_1.logDebugMessage(`POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}`); + logger_1.logDebugMessage( + `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` + ); const body = new URLSearchParams(params).toString(); let response = await utils_1.doFetch(url, { method: "POST", @@ -90,9 +113,13 @@ async function getOIDCDiscoveryInfo(issuer) { if (oidcInfoMap[issuer] !== undefined) { return oidcInfoMap[issuer]; } - const oidcInfo = await doGetRequest(normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous()); + const oidcInfo = await doGetRequest( + normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous() + ); if (oidcInfo.status >= 400) { - logger_1.logDebugMessage(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); + logger_1.logDebugMessage( + `Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}` + ); throw new Error(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); } oidcInfoMap[issuer] = oidcInfo.jsonResponse; diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 138add514..c1abe0c67 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -17,10 +17,7 @@ export declare type UserContext = Branded, "UserContext">; export declare type AppInfo = { appName: string; websiteDomain?: string; - origin?: string | ((input: { - request: BaseRequest | undefined; - userContext: UserContext; - }) => string); + origin?: string | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => string); websiteBasePath?: string; apiDomain: string; apiBasePath?: string; @@ -28,16 +25,10 @@ export declare type AppInfo = { }; export declare type NormalisedAppinfo = { appName: string; - getOrigin: (input: { - request: BaseRequest | undefined; - userContext: UserContext; - }) => NormalisedURLDomain; + getOrigin: (input: { request: BaseRequest | undefined; userContext: UserContext }) => NormalisedURLDomain; apiDomain: NormalisedURLDomain; topLevelAPIDomain: string; - getTopLevelWebsiteDomain: (input: { - request: BaseRequest | undefined; - userContext: UserContext; - }) => string; + getTopLevelWebsiteDomain: (input: { request: BaseRequest | undefined; userContext: UserContext }) => string; apiBasePath: NormalisedURLPath; apiGatewayPath: NormalisedURLPath; websiteBasePath: NormalisedURLPath; @@ -103,10 +94,7 @@ export declare type User = { verified: boolean; hasSameEmailAs: (email: string | undefined) => boolean; hasSamePhoneNumberAs: (phoneNumber: string | undefined) => boolean; - hasSameThirdPartyInfoAs: (thirdParty?: { - id: string; - userId: string; - }) => boolean; + hasSameThirdPartyInfoAs: (thirdParty?: { id: string; userId: string }) => boolean; toJson: () => any; })[]; toJson: () => any; diff --git a/lib/build/user.d.ts b/lib/build/user.d.ts index 176114d1d..9bc4835a5 100644 --- a/lib/build/user.d.ts +++ b/lib/build/user.d.ts @@ -15,10 +15,7 @@ export declare class LoginMethod implements RecipeLevelUser { constructor(loginMethod: UserWithoutHelperFunctions["loginMethods"][number]); hasSameEmailAs(email: string | undefined): boolean; hasSamePhoneNumberAs(phoneNumber: string | undefined): boolean; - hasSameThirdPartyInfoAs(thirdParty?: { - id: string; - userId: string; - }): boolean; + hasSameThirdPartyInfoAs(thirdParty?: { id: string; userId: string }): boolean; toJson(): JSONObject; } export declare class User implements UserType { diff --git a/lib/build/user.js b/lib/build/user.js index 57e0ef149..fe045dde2 100644 --- a/lib/build/user.js +++ b/lib/build/user.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.User = exports.LoginMethod = void 0; const recipeUserId_1 = __importDefault(require("./recipeUserId")); @@ -35,8 +37,7 @@ class LoginMethod { // this means that the phone number is not valid according to the E.164 standard. // but we still just trim it. phoneNumber = phoneNumber.trim(); - } - else { + } else { phoneNumber = parsedPhoneNumber.format("E.164"); } return this.phoneNumber !== undefined && this.phoneNumber === phoneNumber; @@ -47,9 +48,11 @@ class LoginMethod { } thirdParty.id = thirdParty.id.trim(); thirdParty.userId = thirdParty.userId.trim(); - return (this.thirdParty !== undefined && + return ( + this.thirdParty !== undefined && this.thirdParty.id === thirdParty.id && - this.thirdParty.userId === thirdParty.userId); + this.thirdParty.userId === thirdParty.userId + ); } toJson() { return { diff --git a/lib/build/utils.d.ts b/lib/build/utils.d.ts index fafd36454..b681642e1 100644 --- a/lib/build/utils.d.ts +++ b/lib/build/utils.d.ts @@ -13,11 +13,15 @@ export declare function sendNon200Response(res: BaseResponse, statusCode: number export declare function send200Response(res: BaseResponse, responseJson: any): void; export declare function isAnIpAddress(ipaddress: string): boolean; export declare function getNormalisedShouldTryLinkingWithSessionUserFlag(req: BaseRequest, body: any): any; -export declare function getBackwardsCompatibleUserInfo(req: BaseRequest, result: { - user: User; - session: SessionContainer; - createdNewRecipeUser?: boolean; -}, userContext: UserContext): JSONObject; +export declare function getBackwardsCompatibleUserInfo( + req: BaseRequest, + result: { + user: User; + session: SessionContainer; + createdNewRecipeUser?: boolean; + }, + userContext: UserContext +): JSONObject; export declare function getLatestFDIVersionFromFDIList(fdiHeaderValue: string): string; export declare function hasGreaterThanEqualToFDI(req: BaseRequest, version: string): boolean; export declare function getRidFromHeader(req: BaseRequest): string | undefined; @@ -25,26 +29,43 @@ export declare function frontendHasInterceptor(req: BaseRequest): boolean; export declare function humaniseMilliseconds(ms: number): string; export declare function makeDefaultUserContextFromAPI(request: BaseRequest): UserContext; export declare function getUserContext(inputUserContext?: Record): UserContext; -export declare function setRequestInUserContextIfNotDefined(userContext: UserContext | undefined, request: BaseRequest): UserContext; +export declare function setRequestInUserContextIfNotDefined( + userContext: UserContext | undefined, + request: BaseRequest +): UserContext; export declare function getTopLevelDomainForSameSiteResolution(url: string): string; export declare function getFromObjectCaseInsensitive(key: string, object: Record): T | undefined; -export declare function postWithFetch(url: string, headers: Record, body: any, { successLog, errorLogHeader }: { - successLog: string; - errorLogHeader: string; -}): Promise<{ - resp: { - status: number; - body: any; - }; -} | { - error: any; -}>; +export declare function postWithFetch( + url: string, + headers: Record, + body: any, + { + successLog, + errorLogHeader, + }: { + successLog: string; + errorLogHeader: string; + } +): Promise< + | { + resp: { + status: number; + body: any; + }; + } + | { + error: any; + } +>; export declare function normaliseEmail(email: string): string; export declare function toCamelCase(str: string): string; export declare function toSnakeCase(str: string): string; -export declare function transformObjectKeys(obj: { - [key: string]: any; -}, caseType: "snake-case" | "camelCase"): T; +export declare function transformObjectKeys( + obj: { + [key: string]: any; + }, + caseType: "snake-case" | "camelCase" +): T; export declare const getProcess: () => any; export declare const getBuffer: () => any; export declare const isTestEnv: () => boolean; diff --git a/lib/build/utils.js b/lib/build/utils.js index 29b751f98..0f0ddadde 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isBuffer = exports.decodeBase64 = exports.encodeBase64 = exports.isTestEnv = exports.getBuffer = exports.getProcess = exports.transformObjectKeys = exports.toSnakeCase = exports.toCamelCase = exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.getUserContext = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.hasGreaterThanEqualToFDI = exports.getLatestFDIVersionFromFDIList = exports.getBackwardsCompatibleUserInfo = exports.getNormalisedShouldTryLinkingWithSessionUserFlag = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; const tldts_1 = require("tldts"); @@ -15,15 +17,18 @@ const doFetch = async (input, init) => { // frameworks like nextJS cache fetch GET requests (https://nextjs.org/docs/app/building-your-application/caching#data-cache) // we don't want that because it may lead to weird behaviour when querying the core. if (init === undefined) { - processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH); + processState_1.ProcessState.getInstance().addState( + processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH + ); init = { cache: "no-cache", redirect: "manual", }; - } - else { + } else { if (init.cache === undefined) { - processState_1.ProcessState.getInstance().addState(processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH); + processState_1.ProcessState.getInstance().addState( + processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH + ); init.cache = "no-cache"; init.redirect = "manual"; } @@ -31,18 +36,17 @@ const doFetch = async (input, init) => { const fetchFunction = typeof fetch !== "undefined" ? fetch : cross_fetch_1.default; try { return await fetchFunction(input, init); - } - catch (e) { + } catch (e) { // Cloudflare Workers don't support the 'cache' field in RequestInit. // To work around this, we delete the 'cache' field and retry the fetch if the error is due to the missing 'cache' field. // Remove this workaround once the 'cache' field is supported. // More info: https://github.com/cloudflare/workerd/issues/698 - const unimplementedCacheError = e && + const unimplementedCacheError = + e && typeof e === "object" && "message" in e && e.message === "The 'cache' field on 'RequestInitializerDict' is not implemented."; - if (!unimplementedCacheError) - throw e; + if (!unimplementedCacheError) throw e; const newOpts = Object.assign({}, init); delete newOpts.cache; return await fetchFunction(input, newOpts); @@ -70,8 +74,7 @@ function maxVersion(version1, version2) { let v2 = Number(splittedv2[i]); if (v1 > v2) { return version1; - } - else if (v2 > v1) { + } else if (v2 > v1) { return version2; } } @@ -91,11 +94,14 @@ function normaliseInputAppInfoOrThrowError(appInfo) { if (appInfo.appName === undefined) { throw new Error("Please provide your appName inside the appInfo object when calling supertokens.init"); } - let apiGatewayPath = appInfo.apiGatewayPath !== undefined - ? new normalisedURLPath_1.default(appInfo.apiGatewayPath) - : new normalisedURLPath_1.default(""); + let apiGatewayPath = + appInfo.apiGatewayPath !== undefined + ? new normalisedURLPath_1.default(appInfo.apiGatewayPath) + : new normalisedURLPath_1.default(""); if (appInfo.origin === undefined && appInfo.websiteDomain === undefined) { - throw new Error("Please provide either origin or websiteDomain inside the appInfo object when calling supertokens.init"); + throw new Error( + "Please provide either origin or websiteDomain inside the appInfo object when calling supertokens.init" + ); } let websiteDomainFunction = (input) => { let origin = appInfo.origin; @@ -120,12 +126,15 @@ function normaliseInputAppInfoOrThrowError(appInfo) { appName: appInfo.appName, getOrigin: websiteDomainFunction, apiDomain, - apiBasePath: apiGatewayPath.appendPath(appInfo.apiBasePath === undefined - ? new normalisedURLPath_1.default("/auth") - : new normalisedURLPath_1.default(appInfo.apiBasePath)), - websiteBasePath: appInfo.websiteBasePath === undefined - ? new normalisedURLPath_1.default("/auth") - : new normalisedURLPath_1.default(appInfo.websiteBasePath), + apiBasePath: apiGatewayPath.appendPath( + appInfo.apiBasePath === undefined + ? new normalisedURLPath_1.default("/auth") + : new normalisedURLPath_1.default(appInfo.apiBasePath) + ), + websiteBasePath: + appInfo.websiteBasePath === undefined + ? new normalisedURLPath_1.default("/auth") + : new normalisedURLPath_1.default(appInfo.websiteBasePath), apiGatewayPath, topLevelAPIDomain, getTopLevelWebsiteDomain: topLevelWebsiteDomain, @@ -165,18 +174,18 @@ function deepTransform(obj) { let val = obj[key]; if (val && typeof val === "object" && val["toJson"] !== undefined && typeof val["toJson"] === "function") { out[key] = val.toJson(); - } - else if (val && typeof val === "object") { + } else if (val && typeof val === "object") { out[key] = deepTransform(val); - } - else { + } else { out[key] = val; } } return out; } function isAnIpAddress(ipaddress) { - return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress); + return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( + ipaddress + ); } exports.isAnIpAddress = isAnIpAddress; function getNormalisedShouldTryLinkingWithSessionUserFlag(req, body) { @@ -191,8 +200,10 @@ function getBackwardsCompatibleUserInfo(req, result, userContext) { let resp; // (>= 1.18 && < 2.0) || >= 3.0: This is because before 1.18, and between 2 and 3, FDI does not // support account linking. - if ((hasGreaterThanEqualToFDI(req, "1.18") && !hasGreaterThanEqualToFDI(req, "2.0")) || - hasGreaterThanEqualToFDI(req, "3.0")) { + if ( + (hasGreaterThanEqualToFDI(req, "1.18") && !hasGreaterThanEqualToFDI(req, "2.0")) || + hasGreaterThanEqualToFDI(req, "3.0") + ) { resp = { user: result.user.toJson(), }; @@ -200,9 +211,10 @@ function getBackwardsCompatibleUserInfo(req, result, userContext) { resp.createdNewRecipeUser = result.createdNewRecipeUser; } return resp; - } - else { - let loginMethod = result.user.loginMethods.find((lm) => lm.recipeUserId.getAsString() === result.session.getRecipeUserId(userContext).getAsString()); + } else { + let loginMethod = result.user.loginMethods.find( + (lm) => lm.recipeUserId.getAsString() === result.session.getRecipeUserId(userContext).getAsString() + ); if (loginMethod === undefined) { // we pick the oldest login method here for the user. // this can happen in case the user is implementing something like @@ -210,8 +222,7 @@ function getBackwardsCompatibleUserInfo(req, result, userContext) { for (let i = 0; i < result.user.loginMethods.length; i++) { if (loginMethod === undefined) { loginMethod = result.user.loginMethods[i]; - } - else if (loginMethod.timeJoined > result.user.loginMethods[i].timeJoined) { + } else if (loginMethod.timeJoined > result.user.loginMethods[i].timeJoined) { loginMethod = result.user.loginMethods[i]; } } @@ -276,20 +287,15 @@ function humaniseMilliseconds(ms) { let t = Math.floor(ms / 1000); let suffix = ""; if (t < 60) { - if (t > 1) - suffix = "s"; + if (t > 1) suffix = "s"; return `${t} second${suffix}`; - } - else if (t < 3600) { + } else if (t < 3600) { const m = Math.floor(t / 60); - if (m > 1) - suffix = "s"; + if (m > 1) suffix = "s"; return `${m} minute${suffix}`; - } - else { + } else { const h = Math.floor(t / 360) / 10; - if (h > 1) - suffix = "s"; + if (h > 1) suffix = "s"; return `${h} hour${suffix}`; } } @@ -299,7 +305,7 @@ function makeDefaultUserContextFromAPI(request) { } exports.makeDefaultUserContextFromAPI = makeDefaultUserContextFromAPI; function getUserContext(inputUserContext) { - return (inputUserContext !== null && inputUserContext !== void 0 ? inputUserContext : {}); + return inputUserContext !== null && inputUserContext !== void 0 ? inputUserContext : {}; } exports.getUserContext = getUserContext; function setRequestInUserContextIfNotDefined(userContext, request) { @@ -376,14 +382,12 @@ async function postWithFetch(url, headers, body, { successLog, errorLogHeader }) logger_1.logDebugMessage(errorLogHeader); logger_1.logDebugMessage(`Error status: ${fetchResp.status}`); logger_1.logDebugMessage(`Error response: ${respText}`); - } - catch (caught) { + } catch (caught) { error = caught; logger_1.logDebugMessage(errorLogHeader); if (error instanceof Error) { logger_1.logDebugMessage(`Error: ${error.message}`); - } - else { + } else { logger_1.logDebugMessage(`Error: ${JSON.stringify(error)}`); } } @@ -431,8 +435,7 @@ const getProcess = () => { * to one that is compatible where process may not be available * (like `edge` runtime). */ - if (typeof process !== "undefined") - return process; + if (typeof process !== "undefined") return process; const ponyFilledProcess = require("process"); return ponyFilledProcess; }; @@ -443,8 +446,7 @@ const getBuffer = () => { * to one that is compatible where it may not be available * (like `edge` runtime). */ - if (typeof Buffer !== "undefined") - return Buffer; + if (typeof Buffer !== "undefined") return Buffer; const ponyFilledBuffer = require("buffer").Buffer; return ponyFilledBuffer; }; From 813bd39695a5506b48dc788095b25c9d22ced6c2 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Wed, 29 Jan 2025 13:24:55 +0200 Subject: [PATCH 35/36] added support for backend-sdk-testing --- package-lock.json | 6511 +++++++++++++++++++- test/test-server/package-lock.json | 1 - test/test-server/src/index.ts | 42 + test/test-server/src/testFunctionMapper.ts | 40 + test/test-server/src/webauthn.ts | 22 +- 5 files changed, 6599 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 35d08da5e..11653bdca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,10 @@ { "name": "supertokens-node", "version": "21.0.0", - "lockfileVersion": 3, + "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "supertokens-node", "version": "21.0.0", "license": "Apache-2.0", "dependencies": { @@ -8304,5 +8303,6513 @@ "url": "https://github.com/sponsors/sindresorhus" } } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "requires": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + } + }, + "@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true + }, + "@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "dev": true, + "requires": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "requires": { + "@babel/types": "^7.24.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "dev": true, + "requires": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + } + }, + "@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "dev": true + }, + "@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + } + }, + "@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@extra-number/significant-digits": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@extra-number/significant-digits/-/significant-digits-1.3.9.tgz", + "integrity": "sha512-E5PY/bCwrNqEHh4QS6AQBinLZ+sxM1lT8tsSVYk8VwhWIPp6fCU/BMRVq0V8iJ8LwS3FHmaA4vUzb78s4BIIyA==", + "dev": true + }, + "@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "dev": true, + "requires": { + "ajv": "^6.12.6" + } + }, + "@hapi/accept": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-5.0.2.tgz", + "integrity": "sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/ammo": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-5.0.1.tgz", + "integrity": "sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA==", + "dev": true, + "requires": { + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/b64": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-5.0.0.tgz", + "integrity": "sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw==", + "dev": true, + "requires": { + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "dev": true, + "requires": { + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/bounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-2.0.0.tgz", + "integrity": "sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/bourne": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz", + "integrity": "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q==", + "dev": true + }, + "@hapi/call": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@hapi/call/-/call-8.0.1.tgz", + "integrity": "sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/catbox": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-11.1.1.tgz", + "integrity": "sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/podium": "4.x.x", + "@hapi/validate": "1.x.x" + } + }, + "@hapi/catbox-memory": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-5.0.1.tgz", + "integrity": "sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/content": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@hapi/content/-/content-5.0.2.tgz", + "integrity": "sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x" + } + }, + "@hapi/cryptiles": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-5.1.0.tgz", + "integrity": "sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x" + } + }, + "@hapi/file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/file/-/file-2.0.0.tgz", + "integrity": "sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ==", + "dev": true + }, + "@hapi/hapi": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-20.3.0.tgz", + "integrity": "sha512-zvPSRvaQyF3S6Nev9aiAzko2/hIFZmNSJNcs07qdVaVAvj8dGJSV4fVUuQSnufYJAGiSau+U5LxMLhx79se5WA==", + "dev": true, + "requires": { + "@hapi/accept": "^5.0.1", + "@hapi/ammo": "^5.0.1", + "@hapi/boom": "^9.1.0", + "@hapi/bounce": "^2.0.0", + "@hapi/call": "^8.0.0", + "@hapi/catbox": "^11.1.1", + "@hapi/catbox-memory": "^5.0.0", + "@hapi/heavy": "^7.0.1", + "@hapi/hoek": "^9.0.4", + "@hapi/mimos": "^6.0.0", + "@hapi/podium": "^4.1.1", + "@hapi/shot": "^5.0.5", + "@hapi/somever": "^3.0.0", + "@hapi/statehood": "^7.0.3", + "@hapi/subtext": "^7.1.0", + "@hapi/teamwork": "^5.1.0", + "@hapi/topo": "^5.0.0", + "@hapi/validate": "^1.1.1" + } + }, + "@hapi/heavy": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-7.0.1.tgz", + "integrity": "sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/validate": "1.x.x" + } + }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "@hapi/iron": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-6.0.0.tgz", + "integrity": "sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw==", + "dev": true, + "requires": { + "@hapi/b64": "5.x.x", + "@hapi/boom": "9.x.x", + "@hapi/bourne": "2.x.x", + "@hapi/cryptiles": "5.x.x", + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/mimos": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-6.0.0.tgz", + "integrity": "sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg==", + "dev": true, + "requires": { + "@hapi/hoek": "9.x.x", + "mime-db": "1.x.x" + } + }, + "@hapi/nigel": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-4.0.2.tgz", + "integrity": "sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.4", + "@hapi/vise": "^4.0.0" + } + }, + "@hapi/pez": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-5.1.0.tgz", + "integrity": "sha512-YfB0btnkLB3lb6Ry/1KifnMPBm5ZPfaAHWFskzOMAgDgXgcBgA+zjpIynyEiBfWEz22DBT8o1e2tAaBdlt8zbw==", + "dev": true, + "requires": { + "@hapi/b64": "5.x.x", + "@hapi/boom": "9.x.x", + "@hapi/content": "^5.0.2", + "@hapi/hoek": "9.x.x", + "@hapi/nigel": "4.x.x" + } + }, + "@hapi/podium": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-4.1.3.tgz", + "integrity": "sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g==", + "dev": true, + "requires": { + "@hapi/hoek": "9.x.x", + "@hapi/teamwork": "5.x.x", + "@hapi/validate": "1.x.x" + } + }, + "@hapi/shot": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-5.0.5.tgz", + "integrity": "sha512-x5AMSZ5+j+Paa8KdfCoKh+klB78otxF+vcJR/IoN91Vo2e5ulXIW6HUsFTCU+4W6P/Etaip9nmdAx2zWDimB2A==", + "dev": true, + "requires": { + "@hapi/hoek": "9.x.x", + "@hapi/validate": "1.x.x" + } + }, + "@hapi/somever": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-3.0.1.tgz", + "integrity": "sha512-4ZTSN3YAHtgpY/M4GOtHUXgi6uZtG9nEZfNI6QrArhK0XN/RDVgijlb9kOmXwCR5VclDSkBul9FBvhSuKXx9+w==", + "dev": true, + "requires": { + "@hapi/bounce": "2.x.x", + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/statehood": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-7.0.4.tgz", + "integrity": "sha512-Fia6atroOVmc5+2bNOxF6Zv9vpbNAjEXNcUbWXavDqhnJDlchwUUwKS5LCi5mGtCTxRhUKKHwuxuBZJkmLZ7fw==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/bounce": "2.x.x", + "@hapi/bourne": "2.x.x", + "@hapi/cryptiles": "5.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/iron": "6.x.x", + "@hapi/validate": "1.x.x" + } + }, + "@hapi/subtext": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-7.1.0.tgz", + "integrity": "sha512-n94cU6hlvsNRIpXaROzBNEJGwxC+HA69q769pChzej84On8vsU14guHDub7Pphr/pqn5b93zV3IkMPDU5AUiXA==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/bourne": "2.x.x", + "@hapi/content": "^5.0.2", + "@hapi/file": "2.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/pez": "^5.1.0", + "@hapi/wreck": "17.x.x" + } + }, + "@hapi/teamwork": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-5.1.1.tgz", + "integrity": "sha512-1oPx9AE5TIv+V6Ih54RP9lTZBso3rP8j4Xhb6iSVwPXtAM+sDopl5TFMv5Paw73UnpZJ9gjcrTE1BXrWt9eQrg==", + "dev": true + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@hapi/validate": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-1.1.3.tgz", + "integrity": "sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0" + } + }, + "@hapi/vise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-4.0.0.tgz", + "integrity": "sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg==", + "dev": true, + "requires": { + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/wreck": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-17.2.0.tgz", + "integrity": "sha512-pJ5kjYoRPYDv+eIuiLQqhGon341fr2bNIYZjuotuPJG/3Ilzr/XtI+JAp0A86E2bYfsS3zBPABuS2ICkaXFT8g==", + "dev": true, + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/bourne": "2.x.x", + "@hapi/hoek": "9.x.x" + } + }, + "@hexagon/base64": { + "version": "1.1.28", + "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", + "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@koa/router": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-10.1.1.tgz", + "integrity": "sha512-ORNjq5z4EmQPriKbR0ER3k4Gh7YGNhWDL7JBW+8wXDrHLbWYKYSJaOJ9aN06npF5tbTxe2JBOsurpJDAvjiXKw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.1.0" + } + }, + "@levischuck/tiny-cbor": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.2.tgz", + "integrity": "sha512-f5CnPw997Y2GQ8FAvtuVVC19FX8mwNNC+1XJcIi16n/LTJifKO6QBgGLgN3YEmqtGMk17SKSuoWES3imJVxAVw==", + "dev": true + }, + "@loopback/context": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/@loopback/context/-/context-3.18.0.tgz", + "integrity": "sha512-PKx0rTguqBj6mUHBbEHLF031MnP6KiSkMLE4E8Hpy2KPJxG97HUT2ZUACHCP6qm8yS9spWQQ6g72VYAWxDrN+g==", + "dev": true, + "requires": { + "@loopback/metadata": "^3.3.4", + "@types/debug": "^4.1.7", + "debug": "^4.3.2", + "hyperid": "^2.3.1", + "p-event": "^4.2.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + } + }, + "@loopback/core": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@loopback/core/-/core-2.16.2.tgz", + "integrity": "sha512-KtkNv6HIh8TFBOxTkfPp/BQbVqjDsGef/DtbNHH1ZHs3gSbofhkZs3IqQdYQzpkUq71mQjz5RJ/yUYY5Sqva9w==", + "dev": true, + "requires": { + "@loopback/context": "^3.17.1", + "debug": "^4.3.1", + "tslib": "^2.3.0" + } + }, + "@loopback/filter": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@loopback/filter/-/filter-1.5.4.tgz", + "integrity": "sha512-kfdCgSy0YoAFNYXJOpag5uJnlErYcROIeJqeAglkwOa3hSw2BYIudurU8hoqsiOBIGhI5BF4A3S8u4q089xWlg==", + "dev": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@loopback/http-server": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@loopback/http-server/-/http-server-2.5.4.tgz", + "integrity": "sha512-M7w+4AEhwDn7q00soCe8yYQDUS+n87ppuXQ1rJ9a1b9TdnEh+7nPFVrVpwiEKBGyVGIJWDq5BMSZYo1zMIPFUA==", + "dev": true, + "requires": { + "debug": "^4.3.2", + "stoppable": "^1.1.0", + "tslib": "^2.3.1" + } + }, + "@loopback/metadata": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@loopback/metadata/-/metadata-3.3.4.tgz", + "integrity": "sha512-FISs8OVYKB+wmL0VZdsDZzMOc/KC6anOf3ORpFRO2Mgl9dKCOD8IELKc8r/nr2kyD4r7/pjr5GfLy4nirS1vnQ==", + "dev": true, + "requires": { + "debug": "^4.3.2", + "lodash": "^4.17.21", + "reflect-metadata": "^0.1.13", + "tslib": "^2.3.1" + } + }, + "@loopback/openapi-v3": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@loopback/openapi-v3/-/openapi-v3-5.3.1.tgz", + "integrity": "sha512-MBVamgxDDbgQQlQjIxSOnaVXLx6plxzn3e8CW8YbNc3TNiS1P8EFa5vNBp8wIzSDTeEd3ic6qzUxCUZIICiFNA==", + "dev": true, + "requires": { + "@loopback/repository-json-schema": "^3.4.1", + "debug": "^4.3.1", + "http-status": "^1.5.0", + "json-merge-patch": "^1.0.1", + "lodash": "^4.17.21", + "openapi3-ts": "^2.0.1", + "tslib": "^2.2.0" + }, + "dependencies": { + "@loopback/repository-json-schema": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@loopback/repository-json-schema/-/repository-json-schema-3.4.1.tgz", + "integrity": "sha512-E9UKegav+8Bp0MLPQu33c7tWUmWbnKARy0Uu2m7nvP3e3t3WOwB8U9hMjX/wBOhJ4UFJCXAXlq1MulQ/R3dyTw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.7", + "debug": "^4.3.1", + "tslib": "^2.2.0" + } + } + } + }, + "@loopback/repository": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@loopback/repository/-/repository-3.7.1.tgz", + "integrity": "sha512-q9vpgQ5MSZqI/ww2TTuDy0Y34NZaapS0+4ZKcwVgwH0XJFxgGwznc5W0l1Esu1lplijejpza4ItKwnlGmvYcJg==", + "dev": true, + "requires": { + "@loopback/filter": "^1.5.2", + "@types/debug": "^4.1.5", + "debug": "^4.3.1", + "lodash": "^4.17.21", + "loopback-datasource-juggler": "^4.26.0", + "tslib": "^2.3.0" + } + }, + "@loopback/rest": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@loopback/rest/-/rest-9.3.0.tgz", + "integrity": "sha512-Qwn5WXctQ2AV6Duze/x7ks9Tb/Oy6FSs0Hhyuhzz7aassKbu1m/GiJLB7bvpJVUN+qVZ2+vQZZ6Y3F+znzH4og==", + "dev": true, + "requires": { + "@loopback/express": "^3.3.0", + "@loopback/http-server": "^2.5.0", + "@loopback/openapi-v3": "^5.3.0", + "@openapi-contrib/openapi-schema-to-json-schema": "^3.1.0", + "@types/body-parser": "^1.19.0", + "@types/cors": "^2.8.10", + "@types/express": "^4.17.11", + "@types/express-serve-static-core": "^4.17.19", + "@types/http-errors": "^1.8.0", + "@types/on-finished": "^2.3.1", + "@types/serve-static": "1.13.9", + "@types/type-is": "^1.6.3", + "ajv": "^6.12.6", + "ajv-errors": "^1.0.1", + "ajv-keywords": "^3.5.2", + "body-parser": "^1.19.0", + "cors": "^2.8.5", + "debug": "^4.3.1", + "express": "^4.17.1", + "http-errors": "^1.8.0", + "js-yaml": "^4.1.0", + "json-schema-compare": "^0.2.2", + "lodash": "^4.17.21", + "on-finished": "^2.3.0", + "path-to-regexp": "^6.2.0", + "qs": "^6.10.1", + "strong-error-handler": "^4.0.0", + "tslib": "^2.2.0", + "type-is": "^1.6.18", + "validator": "^13.6.0" + }, + "dependencies": { + "@loopback/express": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@loopback/express/-/express-3.3.4.tgz", + "integrity": "sha512-y+7fu/aXGp7+5QhEnKhwmI/vKLAQtsyvEXTiT8stbj4VHWvNbUbYVwfud5IOeH7d+lhxlNQ5aEJ7IDwoM8xD6Q==", + "dev": true, + "requires": { + "@loopback/http-server": "^2.5.4", + "@types/body-parser": "^1.19.1", + "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.24", + "@types/http-errors": "^1.8.1", + "body-parser": "^1.19.0", + "debug": "^4.3.2", + "express": "^4.17.1", + "http-errors": "^1.8.0", + "on-finished": "^2.3.0", + "toposort": "^2.0.2", + "tslib": "^2.3.1" + } + }, + "@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + } + } + }, + "@next/env": { + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.4.tgz", + "integrity": "sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==", + "dev": true + }, + "@next/swc-linux-x64-gnu": { + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.4.tgz", + "integrity": "sha512-BapIFZ3ZRnvQ1uWbmqEGJuPT9cgLwvKtxhK/L2t4QYO7l+/DxXuIGjvp1x8rvfa/x1FFSsipERZK70pewbtJtw==", + "dev": true, + "optional": true + }, + "@next/swc-linux-x64-musl": { + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.4.tgz", + "integrity": "sha512-mqVxTwk4XuBl49qn2A5UmzFImoL1iLm0KQQwtdRJRKl21ylQwwGCxJtIYo2rbfkZHoSKlh/YgztY0qH3wG1xIg==", + "dev": true, + "optional": true + }, + "@openapi-contrib/openapi-schema-to-json-schema": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-3.3.2.tgz", + "integrity": "sha512-aqyc5iEZsUF8qYNxwJNkHYoFxqdoPkqVTnDsj5gqhU+arG4QqLaIDcEOaG0EtKlFBGmSLsQbFYsINiladCJb3g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "@peculiar/asn1-android": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.13.tgz", + "integrity": "sha512-0VTNazDGKrLS6a3BwTDZanqq6DR/I3SbvmDMuS8Be+OYpvM6x1SRDh9AGDsHVnaCOIztOspCPc6N1m+iUv1Xxw==", + "dev": true, + "requires": { + "@peculiar/asn1-schema": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "@peculiar/asn1-ecc": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.14.tgz", + "integrity": "sha512-zWPyI7QZto6rnLv6zPniTqbGaLh6zBpJyI46r1yS/bVHJXT2amdMHCRRnbV5yst2H8+ppXG6uXu/M6lKakiQ8w==", + "dev": true, + "requires": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/asn1-x509": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "@peculiar/asn1-rsa": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.13.tgz", + "integrity": "sha512-wBNQqCyRtmqvXkGkL4DR3WxZhHy8fDiYtOjTeCd7SFE5F6GBeafw3EJ94PX/V0OJJrjQ40SkRY2IZu3ZSyBqcg==", + "dev": true, + "requires": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/asn1-x509": "^2.3.13", + "asn1js": "^3.0.5", + "tslib": "^2.6.2" + } + }, + "@peculiar/asn1-schema": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", + "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", + "dev": true, + "requires": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "@peculiar/asn1-x509": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.13.tgz", + "integrity": "sha512-PfeLQl2skXmxX2/AFFCVaWU8U6FKW1Db43mgBhShCOFS1bVxqtvusq1hVjfuEcuSQGedrLdCSvTgabluwN/M9A==", + "dev": true, + "requires": { + "@peculiar/asn1-schema": "^2.3.13", + "asn1js": "^3.0.5", + "ipaddr.js": "^2.1.0", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + }, + "dependencies": { + "ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true + } + } + }, + "@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, + "@simplewebauthn/server": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-11.0.0.tgz", + "integrity": "sha512-zu8dxKcPiRUNSN2kmrnNOzNbRI8VaR/rL4ENCHUfC6PEE7SAAdIql9g5GBOd/wOVZolIsaZz3ccFxuGoVP0iaw==", + "dev": true, + "requires": { + "@hexagon/base64": "^1.1.27", + "@levischuck/tiny-cbor": "^0.2.2", + "@peculiar/asn1-android": "^2.3.10", + "@peculiar/asn1-ecc": "^2.3.8", + "@peculiar/asn1-rsa": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "@simplewebauthn/types": "^11.0.0", + "cross-fetch": "^4.0.0" + }, + "dependencies": { + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "requires": { + "node-fetch": "^2.6.12" + } + } + } + }, + "@simplewebauthn/types": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-11.0.0.tgz", + "integrity": "sha512-b2o0wC5u2rWts31dTgBkAtSNKGX0cvL6h8QedNsKmj8O4QoLFQFR3DBVBUlpyVEhYKA+mXGUaXbcOc4JdQ3HzA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "@sinonjs/samsam": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "@swc/helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/aws-lambda": { + "version": "8.10.77", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.77.tgz", + "integrity": "sha512-n0EMFJU/7u3KvHrR83l/zrKOVURXl5pUJPNED/Bzjah89QKCHwCiKCBoVUXRwTGRfCYGIDdinJaAlKDHZdp/Ng==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/brotli": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/brotli/-/brotli-1.3.4.tgz", + "integrity": "sha512-cKYjgaS2DMdCKF7R0F5cgx1nfBYObN2ihIuPGQ4/dlIY6RpV7OWNwe9L8V4tTVKL2eZqOkNM9FM/rgTvLf4oXw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/co-body": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-5.1.1.tgz", + "integrity": "sha512-0/6AjTfQc5OJUchOS4OHiXNPZVuk+5XvEC2vdcizw/bwx0yb0xY7TKSf8JYvQYZ/OJDiAEjWzxnMjGPnSVlPmA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/content-disposition": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", + "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==", + "dev": true + }, + "@types/content-type": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.8.tgz", + "integrity": "sha512-1tBhmVUeso3+ahfyaKluXe38p+94lovUZdoVfQ3OnJo9uJC42JT7CBoN3k9HYhAae+GwiBYmHu+N9FZhOG+2Pg==", + "dev": true + }, + "@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true + }, + "@types/cookies": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", + "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "requires": { + "@types/ms": "*" + } + }, + "@types/express": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz", + "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/hapi__catbox": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/@types/hapi__catbox/-/hapi__catbox-10.2.6.tgz", + "integrity": "sha512-qdMHk4fBlwRfnBBDJaoaxb+fU9Ewi2xqkXD3mNjSPl2v/G/8IJbDpVRBuIcF7oXrcE8YebU5M8cCeKh1NXEn0w==", + "dev": true + }, + "@types/hapi__hapi": { + "version": "20.0.8", + "resolved": "https://registry.npmjs.org/@types/hapi__hapi/-/hapi__hapi-20.0.8.tgz", + "integrity": "sha512-NNslrYq2XQwm4uOqNcSWKpYtaeMr4DkQdrFzSB7p9rKB9ppJLh3mgP2wak9vBZl7/Cnhhb+JVBcUZCOUcW0JPA==", + "dev": true, + "requires": { + "@hapi/boom": "^9.0.0", + "@hapi/iron": "^6.0.0", + "@hapi/podium": "^4.1.3", + "@types/hapi__catbox": "*", + "@types/hapi__mimos": "*", + "@types/hapi__shot": "*", + "@types/node": "*", + "joi": "^17.3.0" + } + }, + "@types/hapi__mimos": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/hapi__mimos/-/hapi__mimos-4.1.4.tgz", + "integrity": "sha512-i9hvJpFYTT/qzB5xKWvDYaSXrIiNqi4ephi+5Lo6+DoQdwqPXQgmVVOZR+s3MBiHoFqsCZCX9TmVWG3HczmTEQ==", + "dev": true, + "requires": { + "@types/mime-db": "*" + } + }, + "@types/hapi__shot": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/hapi__shot/-/hapi__shot-4.1.6.tgz", + "integrity": "sha512-h33NBjx2WyOs/9JgcFeFhkxnioYWQAZxOHdmqDuoJ1Qjxpcs+JGvSjEEoDeWfcrF+1n47kKgqph5IpfmPOnzbg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/http-assert": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz", + "integrity": "sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==", + "dev": true + }, + "@types/http-errors": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.2.tgz", + "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==", + "dev": true + }, + "@types/inflation": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/inflation/-/inflation-2.0.4.tgz", + "integrity": "sha512-daFI2atltBXImBIKT1FaETUQlEX3wAluTN0O4F0ukPA4CaK1DrYdGmqRU1CfWcyu/B7985DZ/28/Jk00R9pPOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "dev": true + }, + "@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "dev": true, + "requires": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "@types/koa-bodyparser": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@types/koa-bodyparser/-/koa-bodyparser-4.3.12.tgz", + "integrity": "sha512-hKMmRMVP889gPIdLZmmtou/BijaU1tHPyMNmcK7FAHAdATnRcGQQy78EqTTxLH1D4FTsrxIzklAQCso9oGoebQ==", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, + "@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "@types/mime-db": { + "version": "1.43.5", + "resolved": "https://registry.npmjs.org/@types/mime-db/-/mime-db-1.43.5.tgz", + "integrity": "sha512-/bfTiIUTNPUBnwnYvUxXAre5MhD88jgagLEQiQtIASjU+bwxd8kS/ASDA4a8ufd8m0Lheu6eeMJHEUpLHoJ28A==", + "dev": true + }, + "@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true + }, + "@types/node": { + "version": "20.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", + "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/nodemailer": { + "version": "6.4.14", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.14.tgz", + "integrity": "sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/on-finished": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/on-finished/-/on-finished-2.3.4.tgz", + "integrity": "sha512-Ld4UQD3udYcKPaAWlI1EYXKhefkZcTlpqOLkQRmN3u5Ml/tUypMivUHbNH8LweP4H4FlhGGO+uBjJI1Y1dkE1g==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", + "dev": true + }, + "@types/qs": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", + "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/set-cookie-parser": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.9.tgz", + "integrity": "sha512-bCorlULvl0xTdjj4BPUHX4cqs9I+go2TfW/7Do1nnFYWS0CPP429Qr1AY42kiFhCwLpvAkWFr1XIBHd8j6/MCQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/type-is": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.6.tgz", + "integrity": "sha512-fs1KHv/f9OvmTMsu4sBNaUu32oyda9Y9uK25naJG8gayxNrfqGIjPQsbLIYyfe7xFkppnPlJB+BuTldOaX9bXw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-i1aY7RKb6HmQIEnK0cBmUZUp1URx0riIHw/GYNoZ46Su0GWfLiDmMI8zMRmaauMnOTg2bQag0qfwcyUFC9Tn+A==", + "dev": true + }, + "@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "@wdio/reporter": { + "version": "8.32.4", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.32.4.tgz", + "integrity": "sha512-kZXbyNuZSSpk4kBavDb+ac25ODu9NVZED6WwZafrlMSnBHcDkoMt26Q0Jp3RKUj+FTyuKH0HvfeLrwVkk6QKDw==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.32.4", + "diff": "^5.0.0", + "object-inspect": "^1.12.0" + } + }, + "@wdio/types": { + "version": "8.32.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.32.4.tgz", + "integrity": "sha512-pDPGcCvq0MQF8u0sjw9m4aMI2gAKn6vphyBB2+1IxYriL777gbbxd7WQ+PygMBvYVprCYIkLPvhUFwF85WakmA==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "dev": true + }, + "accept-language": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", + "integrity": "sha512-sUofgqBPzgfcF20sPoBYGQ1IhQLt2LSkxTnlQSuLF3n5gPEqd5AimbvOvHEi0T1kLMiGVqPWzI5a9OteBRth3A==", + "dev": true, + "requires": { + "bcp47": "^1.1.2", + "stable": "^0.1.6" + } + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "requires": {} + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "peer": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "dev": true + }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dev": true, + "requires": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "avvio": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.5.tgz", + "integrity": "sha512-AOhBxyLVdpOad3TujtC9kL/9r3HnTkxwQ5ggOsYrvvZP1cCFvzHWJd5XxZDFuTn+IN8vkKSG5SEJrd27vCSbeA==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1", + "queue-microtask": "^1.1.2" + } + }, + "aws-sdk": { + "version": "2.1592.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1592.0.tgz", + "integrity": "sha512-iwmS46jOEHMNodfrpNBJ5eHwjKAY05t/xYV2cp+KyzMX2yGgt2/EtWWnlcoMGBKR31qKTsjMj5ZPouC9/VeDOA==", + "dev": true, + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.6.2" + }, + "dependencies": { + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "dev": true + } + } + }, + "aws-sdk-mock": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/aws-sdk-mock/-/aws-sdk-mock-5.9.0.tgz", + "integrity": "sha512-kTUXaQQ1CTn3Cwxa2g1XqtCDq+FTEbPl/zgaYCok357f7gbWkeYEegqa5RziTRb11oNIUHrLp9DSHwZT3XdBkA==", + "dev": true, + "requires": { + "aws-sdk": "^2.1231.0", + "sinon": "^17.0.0", + "traverse": "^0.6.6" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "axios": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", + "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bcp47": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", + "integrity": "sha512-JnkkL4GUpOvvanH9AZPX38CxhiLsXMBicBY2IAtqiVN8YulGDQybUydWA4W6yAMtw6iShtw+8HEF6cfrTHU+UQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true + }, + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dev": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + }, + "dependencies": { + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + } + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "requires": { + "streamsearch": "^1.1.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001605", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", + "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", + "dev": true + }, + "capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "peer": true + }, + "change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "requires": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true + }, + "check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cldrjs": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/cldrjs/-/cldrjs-0.5.5.tgz", + "integrity": "sha512-KDwzwbmLIPfCgd8JERVDpQKrUUM1U4KpFJJg2IROv89rF172lLufoJnqJ/Wea6fXL5bO6WjuLMzY8V52UWPvkA==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dev": true, + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "dev": true + } + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, + "cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + } + }, + "core-js": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz", + "integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "requires": { + "node-fetch": "^2.6.12" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "dev": true + }, + "crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true + }, + "dotenv-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dotenv-json/-/dotenv-json-1.0.0.tgz", + "integrity": "sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ==", + "dev": true + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dev": true, + "requires": { + "jake": "^10.8.5" + } + }, + "electron-to-chromium": { + "version": "1.4.726", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.726.tgz", + "integrity": "sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "dev": true + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-json-stringify": { + "version": "2.7.13", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz", + "integrity": "sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA==", + "dev": true, + "requires": { + "ajv": "^6.11.0", + "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", + "string-similarity": "^4.0.1" + } + }, + "fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "dev": true + }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "fast-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", + "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", + "dev": true + }, + "fastify": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.18.1.tgz", + "integrity": "sha512-OA0imy/bQCMzf7LUCb/1JI3ZSoA0Jo0MLpYULxV7gpppOpJ8NBxDp2PQoQ0FDqJevZPb7tlZf5JacIQft8x9yw==", + "dev": true, + "requires": { + "@fastify/ajv-compiler": "^1.0.0", + "abstract-logging": "^2.0.0", + "avvio": "^7.1.2", + "fast-json-stringify": "^2.5.2", + "fastify-error": "^0.3.0", + "fastify-warning": "^0.2.0", + "find-my-way": "^4.0.0", + "flatstr": "^1.0.12", + "light-my-request": "^4.2.0", + "pino": "^6.2.1", + "proxy-addr": "^2.0.7", + "readable-stream": "^3.4.0", + "rfdc": "^1.1.4", + "secure-json-parse": "^2.0.0", + "semver": "^7.3.2", + "tiny-lru": "^7.0.0" + } + }, + "fastify-error": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", + "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==", + "dev": true + }, + "fastify-warning": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", + "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==", + "dev": true + }, + "fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-my-way": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.5.1.tgz", + "integrity": "sha512-kE0u7sGoUFbMXcOG/xpkmz4sRLCklERnBcg7Ftuu1iAxsfEt2S46RLJ3Sq7vshsEy2wJT2hZxE58XZK27qa8kg==", + "dev": true, + "requires": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0", + "semver-store": "^0.3.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flatstr": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", + "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "dev": true + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globalize": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/globalize/-/globalize-1.7.0.tgz", + "integrity": "sha512-faR46vTIbFCeAemyuc9E6/d7Wrx9k2ae2L60UhakztFg6VuE42gENVJNuPFtt7Sdjrk9m2w8+py7Jj+JTNy59w==", + "dev": true, + "requires": { + "cldrjs": "^0.5.4" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, + "requires": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + } + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, + "http-status": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.7.4.tgz", + "integrity": "sha512-c2qSwNtTlHVYAhMj9JpGdyo0No/+DiKXCJ9pHtZ2Yf3QmPnBIytKSRT7BuyIiQ7icXLynavGmxUqkOjSrAuMuA==", + "dev": true + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "hyperid": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-2.3.1.tgz", + "integrity": "sha512-mIbI7Ymn6MCdODaW1/6wdf5lvvXzmPsARN4zTLakMmcziBOuP4PxCBJvHF6kbAIHX6H4vAELx/pDmt0j6Th5RQ==", + "dev": true, + "requires": { + "uuid": "^8.3.2", + "uuid-parse": "^1.1.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "invert-kv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-3.0.1.tgz", + "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.14" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + } + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dev": true, + "requires": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "dev": true + }, + "joi": { + "version": "17.12.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.3.tgz", + "integrity": "sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "jose": { + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", + "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.4" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-merge-patch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-merge-patch/-/json-merge-patch-1.0.2.tgz", + "integrity": "sha512-M6Vp2GN9L7cfuMXiWOmHj9bEFbeC250iVtcKQbqVgEsDVYnIsrNsbU+h/Y/PkbBQCtEa4Bez+Ebv0zfbC8ObLg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-compare": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz", + "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + } + }, + "jssha": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", + "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", + "dev": true + }, + "just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "requires": { + "tsscmp": "1.0.6" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "koa": { + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.2.tgz", + "integrity": "sha512-MXTeZH3M6AJ8ukW2QZ8wqO3Dcdfh2WRRmjCBkEP+NhKNCiqlO5RDqHmSnsyNrbRJrdjyvIGSJho4vQiWgQJSVA==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "dependencies": { + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + } + }, + "lambda-event-mock": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lambda-event-mock/-/lambda-event-mock-1.5.0.tgz", + "integrity": "sha512-vx1d+vZqi7FF6B3+mAfHnY/6Tlp6BheL2ta0MJS0cIRB3Rc4I5cviHTkiJxHdE156gXx3ZjlQRJrS4puXvtrhA==", + "dev": true, + "requires": { + "@extra-number/significant-digits": "^1.1.1", + "clone-deep": "^4.0.1", + "uuid": "^3.3.3", + "vandium-utils": "^1.2.0" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "vandium-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vandium-utils/-/vandium-utils-1.2.0.tgz", + "integrity": "sha512-yxYUDZz4BNo0CW/z5w4mvclitt5zolY7zjW97i6tTE+sU63cxYs1A6Bl9+jtIQa3+0hkeqY87k+7ptRvmeHe3g==", + "dev": true + } + } + }, + "lambda-leak": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lambda-leak/-/lambda-leak-2.0.0.tgz", + "integrity": "sha512-2c9jwUN3ZLa2GEiOhObbx2BMGQplEUCDHSIkhDtYwUjsTfiV/3jCF6ThIuEXfsvqbUK+0QpZcugIKB8YMbSevQ==", + "dev": true + }, + "lambda-tester": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lambda-tester/-/lambda-tester-4.0.1.tgz", + "integrity": "sha512-ft6XHk84B6/dYEzyI3anKoGWz08xQ5allEHiFYDUzaYTymgVK7tiBkCEbuWx+MFvH7OpFNsJXVtjXm0X8iH3Iw==", + "dev": true, + "requires": { + "app-root-path": "^3.0.0", + "dotenv": "^8.0.0", + "dotenv-json": "^1.0.0", + "lambda-event-mock": "^1.5.0", + "lambda-leak": "^2.0.0", + "semver": "^6.1.1", + "uuid": "^3.3.3", + "vandium-utils": "^2.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "lcid": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-3.1.1.tgz", + "integrity": "sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==", + "dev": true, + "requires": { + "invert-kv": "^3.0.0" + } + }, + "libphonenumber-js": { + "version": "1.10.59", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.59.tgz", + "integrity": "sha512-HeTsOrDF/hWhEiKqZVwg9Cqlep5x2T+IYDENvT2VRj3iX8JQ7Y+omENv+AIn0vC8m6GYhivfCed5Cgfw27r5SA==" + }, + "light-my-request": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.12.0.tgz", + "integrity": "sha512-0y+9VIfJEsPVzK5ArSIJ8Dkxp8QMP7/aCuxCUtG/tr9a2NoOf/snATE/OUc05XUplJCEnRh6gTkH7xh9POt1DQ==", + "dev": true, + "requires": { + "ajv": "^8.1.0", + "cookie": "^0.5.0", + "process-warning": "^1.0.0", + "set-cookie-parser": "^2.4.1" + }, + "dependencies": { + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "dev": true, + "peer": true + }, + "loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true, + "peer": true + }, + "loopback-connector": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-5.3.3.tgz", + "integrity": "sha512-ZYULfy5W7+R2A3I9TILWZOdfMVcZ2qEQT/tye0Fy7Ju3zQ6Gv1bmroARGPGVDAneFt+5YaiaieLdoJ1t02hLpg==", + "dev": true, + "requires": { + "async": "^3.2.4", + "bluebird": "^3.7.2", + "debug": "^4.3.4", + "msgpack5": "^4.5.1", + "strong-globalize": "^6.0.5", + "uuid": "^9.0.0" + }, + "dependencies": { + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true + } + } + }, + "loopback-datasource-juggler": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/loopback-datasource-juggler/-/loopback-datasource-juggler-4.28.9.tgz", + "integrity": "sha512-vBwqQaSa2GpCqS/zevAGG6zRgzsQ/KhB4xUaBSbGxNMD6GwTbS60GuD4yKSN2t4pwx4Qca2x3YUAXhumO1bN2Q==", + "dev": true, + "requires": { + "async": "^3.2.4", + "change-case": "^4.1.2", + "debug": "^4.3.4", + "depd": "^2.0.0", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "loopback-connector": "^5.3.3", + "minimatch": "^5.1.6", + "nanoid": "^3.3.6", + "qs": "^6.11.2", + "strong-globalize": "^6.0.5", + "traverse": "^0.6.7", + "uuid": "^9.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true + } + } + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.1" + } + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true + }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dev": true, + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true + }, + "mem": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-5.1.1.tgz", + "integrity": "sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^2.1.0", + "p-is-promise": "^2.1.0" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "mocha": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "8.1.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "mocha-split-tests": { + "version": "git+ssh://git@github.com/rishabhpoddar/mocha-split-tests.git#b0bd99a7d5870493dbe921dbdd5195b47e555035", + "dev": true, + "from": "mocha-split-tests@github:rishabhpoddar/mocha-split-tests", + "requires": { + "commander": "^7.0.0", + "glob": "^7.1.6" + } + }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "msgpack5": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.5.1.tgz", + "integrity": "sha512-zC1vkcliryc4JGlL6OfpHumSYUHWFGimSI+OgfRCjTFLmKA2/foR9rMTOhWiqfOrfxJOctrpWPvrppf8XynJxw==", + "dev": true, + "requires": { + "bl": "^2.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.3.6", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "next": { + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.4.tgz", + "integrity": "sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ==", + "dev": true, + "requires": { + "@next/env": "14.1.4", + "@next/swc-darwin-arm64": "14.1.4", + "@next/swc-darwin-x64": "14.1.4", + "@next/swc-linux-arm64-gnu": "14.1.4", + "@next/swc-linux-arm64-musl": "14.1.4", + "@next/swc-linux-x64-gnu": "14.1.4", + "@next/swc-linux-x64-musl": "14.1.4", + "@next/swc-win32-arm64-msvc": "14.1.4", + "@next/swc-win32-ia32-msvc": "14.1.4", + "@next/swc-win32-x64-msvc": "14.1.4", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + } + }, + "next-test-api-route-handler": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/next-test-api-route-handler/-/next-test-api-route-handler-3.2.0.tgz", + "integrity": "sha512-gEev0YpErOjcfGY6Vj50xKAFBYCymYTdVCQuid1rqY2NIbA99GrTIEs79j6FF7+6j2R6ruQPcbwt+z0f9Z1J9w==", + "dev": true, + "requires": { + "cookie": "^0.6.0", + "core-js": "^3.35.0", + "node-fetch": "^2.6.7" + }, + "dependencies": { + "cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true + } + } + }, + "nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + } + } + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "nock": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-11.7.0.tgz", + "integrity": "sha512-7c1jhHew74C33OBeRYyQENT+YXQiejpwIrEjinh6dRurBae+Ei4QjeUaPlkptIF0ZacEiVCnw8dWaxqepkiihg==", + "dev": true, + "requires": { + "chai": "^4.1.2", + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.13", + "mkdirp": "^0.5.0", + "propagate": "^2.0.0" + } + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "nodemailer": { + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, + "openapi3-ts": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.2.tgz", + "integrity": "sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==", + "dev": true, + "requires": { + "yaml": "^1.10.2" + } + }, + "os-locale": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-5.0.0.tgz", + "integrity": "sha512-tqZcNEDAIZKBEPnHPlVDvKrp7NzgLi7jRmhKiUoa2NUmhl13FtkAGLUVR+ZsYvApBQdBfYm43A4tXXQ4IrYLBA==", + "dev": true, + "requires": { + "execa": "^4.0.0", + "lcid": "^3.0.0", + "mem": "^5.0.0" + } + }, + "otpauth": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.1.5.tgz", + "integrity": "sha512-mnic91MZxvj04Ir7FN8Xi6wF3FU8D+s6M5p6FQaSS91/csKswoOI9Dk7kKSnGFAoBYgGTTO+OWScV0nJuzrbPg==", + "dev": true, + "requires": { + "jssha": "~3.3.1" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", + "dev": true + }, + "p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "requires": { + "p-timeout": "^3.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pino": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", + "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", + "dev": true, + "requires": { + "fast-redact": "^3.0.0", + "fast-safe-stringify": "^2.0.8", + "flatstr": "^1.0.12", + "pino-std-serializers": "^3.1.0", + "process-warning": "^1.0.0", + "quick-format-unescaped": "^4.0.3", + "sonic-boom": "^1.0.2" + } + }, + "pino-std-serializers": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", + "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==", + "dev": true + }, + "pkce-challenge": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-3.1.0.tgz", + "integrity": "sha512-bQ/0XPZZ7eX+cdAkd61uYWpfMhakH3NeteUF1R8GNa+LMqX8QFAkbCLqq+AYAns1/ueACBu/BMWhrlKGrdvGZg==", + "requires": { + "crypto-js": "^4.1.1" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true + }, + "postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, + "pretty-quick": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-3.3.1.tgz", + "integrity": "sha512-3b36UXfYQ+IXXqex6mCca89jC8u0mYLqFAN5eTQKoXO6oCQYcIVYZEB/5AlBHI7JPYygReM2Vv6Vom/Gln7fBg==", + "dev": true, + "requires": { + "execa": "^4.1.0", + "find-up": "^4.1.0", + "ignore": "^5.3.0", + "mri": "^1.2.0", + "picocolors": "^1.0.0", + "picomatch": "^3.0.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "dev": true + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, + "process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", + "dev": true + }, + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dev": true, + "requires": { + "tslib": "^2.6.1" + } + }, + "pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "dev": true + }, + "qs": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", + "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", + "requires": { + "side-channel": "^1.0.6" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + } + } + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, + "peer": true, + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "dev": true + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "dev": true, + "requires": { + "ret": "~0.2.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "dev": true + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, + "peer": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, + "scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" + }, + "secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shiki": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", + "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", + "dev": true, + "requires": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "5.2.0" + } + }, + "side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sinon": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", + "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.0.0", + "nise": "^5.1.2", + "supports-color": "^7.2.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "sonic-boom": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", + "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", + "dev": true, + "requires": { + "atomic-sleep": "^1.0.0", + "flatstr": "^1.0.12" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true + }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true + }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "strong-error-handler": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/strong-error-handler/-/strong-error-handler-4.0.8.tgz", + "integrity": "sha512-8C4DoE7/0YTKcrhVcT1Wz/aIXXxBWi4H0WOKTKabuv2q1wSgdkPOgBUSsyp8U34e+aEOtX0CqIkUK/JaNG94QA==", + "dev": true, + "requires": { + "accepts": "^1.3.8", + "debug": "^4.3.4", + "ejs": "^3.1.9", + "fast-safe-stringify": "^2.1.1", + "http-status": "^1.6.2", + "js2xmlparser": "^4.0.2", + "strong-globalize": "^6.0.5" + } + }, + "strong-globalize": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-6.0.6.tgz", + "integrity": "sha512-+mN0wTXBg9rLiKBk7jsyfXFWsg08q160XQcmJ3gNxSQ8wrC668dzR8JUp/wcK3NZ2eQ5h5tvc8O6Y+FC0D61lw==", + "dev": true, + "requires": { + "accept-language": "^3.0.18", + "debug": "^4.2.0", + "globalize": "^1.6.0", + "lodash": "^4.17.20", + "md5": "^2.3.0", + "mkdirp": "^1.0.4", + "os-locale": "^5.0.0", + "yamljs": "^0.3.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dev": true, + "requires": { + "client-only": "0.0.1" + } + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "supertest": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", + "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supertokens-js-override": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/supertokens-js-override/-/supertokens-js-override-0.0.4.tgz", + "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "tiny-lru": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", + "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==", + "dev": true + }, + "tldts": { + "version": "6.1.48", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.48.tgz", + "integrity": "sha512-SPbnh1zaSzi/OsmHb1vrPNnYuwJbdWjwo5TbBYYMlTtH3/1DSb41t8bcSxkwDmmbG2q6VLPVvQc7Yf23T+1EEw==", + "requires": { + "tldts-core": "^6.1.48" + } + }, + "tldts-core": { + "version": "6.1.48", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.48.tgz", + "integrity": "sha512-3gD9iKn/n2UuFH1uilBviK9gvTNT6iYwdqrj1Vr5mh8FuelvpRNaYVH4pNYqUgOGU4aAdL9X35eLuuj0gRsx+A==" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "dev": true + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "dev": true + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true + }, + "twilio": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.23.0.tgz", + "integrity": "sha512-LdNBQfOe0dY2oJH2sAsrxazpgfFQo5yXGxe96QA8UWB5uu+433PrUbkv8gQ5RmrRCqUTPQ0aOrIyAdBr1aB03Q==", + "requires": { + "axios": "^1.6.0", + "dayjs": "^1.11.9", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^9.0.0", + "qs": "^6.9.4", + "scmp": "^2.1.0", + "url-parse": "^1.5.9", + "xmlbuilder": "^13.0.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typedoc": { + "version": "0.22.18", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.18.tgz", + "integrity": "sha512-NK9RlLhRUGMvc6Rw5USEYgT4DVAUFk7IF7Q6MYfpJ88KnTZP7EneEa4RcP+tX1auAcz7QT1Iy0bUSZBYYHdoyA==", + "dev": true, + "requires": { + "glob": "^8.0.3", + "lunr": "^2.3.9", + "marked": "^4.0.16", + "minimatch": "^5.1.0", + "shiki": "^0.10.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "typescript": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, + "upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "dev": true + } + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "uuid-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", + "dev": true + }, + "validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "dev": true + }, + "vandium-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vandium-utils/-/vandium-utils-2.0.0.tgz", + "integrity": "sha512-XWbQ/0H03TpYDXk8sLScBEZpE7TbA0CHDL6/Xjt37IBYKLsHUQuBlL44ttAUs9zoBOLFxsW7HehXcuWCNyqOxQ==", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", + "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, + "which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + } + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "dependencies": { + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true + } + } + }, + "xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==" + }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + } + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + }, + "ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } } } diff --git a/test/test-server/package-lock.json b/test/test-server/package-lock.json index 6e069597b..cc57e1de4 100644 --- a/test/test-server/package-lock.json +++ b/test/test-server/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "test-server", "version": "1.0.0", "dependencies": { "debug": "^4.3.5", diff --git a/test/test-server/src/index.ts b/test/test-server/src/index.ts index e41f2f41e..de7c2cb26 100644 --- a/test/test-server/src/index.ts +++ b/test/test-server/src/index.ts @@ -12,6 +12,7 @@ import { TypeInput as EmailVerificationTypeInput } from "../../../lib/build/reci import MultiFactorAuthRecipe from "../../../lib/build/recipe/multifactorauth/recipe"; import MultitenancyRecipe from "../../../lib/build/recipe/multitenancy/recipe"; import PasswordlessRecipe from "../../../lib/build/recipe/passwordless/recipe"; +import WebauthnRecipe from "../../../lib/build/recipe/webauthn/recipe"; import { TypeInput as PasswordlessTypeInput } from "../../../lib/build/recipe/passwordless/types"; import SessionRecipe from "../../../lib/build/recipe/session/recipe"; import { TypeInput as SessionTypeInput } from "../../../lib/build/recipe/session/types"; @@ -29,6 +30,7 @@ import SuperTokensRecipe from "../../../lib/build/supertokens"; import { RecipeListFunction } from "../../../lib/build/types"; import AccountLinking from "../../../recipe/accountlinking"; import EmailPassword from "../../../recipe/emailpassword"; +import WebAuthn from "../../../recipe/webauthn"; import EmailVerification from "../../../recipe/emailverification"; import MultiFactorAuth from "../../../recipe/multifactorauth"; import Multitenancy from "../../../recipe/multitenancy"; @@ -46,6 +48,7 @@ import { logger } from "./logger"; import multiFactorAuthRoutes from "./multifactorauth"; import multitenancyRoutes from "./multitenancy"; import passwordlessRoutes from "./passwordless"; +import webauthnRoutes from "./webauthn"; import OAuth2ProviderRoutes from "./oauth2provider"; import sessionRoutes from "./session"; import supertokensRoutes from "./supertokens"; @@ -57,6 +60,7 @@ import OverrideableBuilder from "supertokens-js-override"; import { resetOverrideLogs, logOverrideEvent, getOverrideLogs } from "./overrideLogging"; import Dashboard from "../../../recipe/dashboard"; import DashboardRecipe from "../../../lib/build/recipe/dashboard/recipe"; +import { TypeInput as WebauthnTypeInput } from "../../../lib/build/recipe/webauthn/types"; const { logDebugMessage } = logger("com.supertokens:node-test-server"); @@ -103,6 +107,7 @@ function STReset() { OAuth2ClientRecipe.reset(); SuperTokensRecipe.reset(); DashboardRecipe.reset(); + WebauthnRecipe.reset(); } function initST(config: any) { @@ -343,6 +348,42 @@ function initST(config: any) { } recipeList.push(OAuth2Client.init(initConfig)); } + + if (recipe.recipeId === "webauthn") { + let init: WebauthnTypeInput = { + ...config, + }; + + recipeList.push( + WebAuthn.init({ + ...init, + getRelyingPartyName: config?.getRelyingPartyName + ? callbackWithLog("WebAuthn.getRelyingPartyName", config?.getRelyingPartyName) + : undefined, + getRelyingPartyId: config?.getRelyingPartyId + ? callbackWithLog("WebAuthn.getRelyingPartyId", config?.getRelyingPartyId) + : undefined, + validateEmailAddress: config?.validateEmailAddress + ? callbackWithLog("WebAuthn.validateEmailAddress", config?.validateEmailAddress) + : undefined, + getOrigin: config?.getOrigin ? callbackWithLog("WebAuthn.getOrigin", config?.getOrigin) : undefined, + emailDelivery: { + ...config?.emailDelivery, + override: overrideBuilderWithLogging( + "WebAuthn.emailDelivery.override", + config?.emailDelivery?.override + ), + }, + override: { + apis: overrideBuilderWithLogging("WebAuthn.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "WebAuthn.override.functions", + config?.override?.functions + ), + }, + }) + ); + } }); init.recipeList = recipeList; @@ -435,6 +476,7 @@ app.use("/test/thirdparty", thirdPartyRoutes); app.use("/test/totp", TOTPRoutes); app.use("/test/usermetadata", userMetadataRoutes); app.use("/test/oauth2provider", OAuth2ProviderRoutes); +app.use("/test/webauthn", webauthnRoutes); // *** Custom routes to help with session tests *** app.post("/create", async (req, res, next) => { diff --git a/test/test-server/src/testFunctionMapper.ts b/test/test-server/src/testFunctionMapper.ts index c342ac37f..fa3444fde 100644 --- a/test/test-server/src/testFunctionMapper.ts +++ b/test/test-server/src/testFunctionMapper.ts @@ -542,5 +542,45 @@ export function getFunc(evalStr: string): (...args: any[]) => any { } } + if (evalStr.startsWith("webauthn.init.getRelyingPartyName")) { + return async (R) => { + if (evalStr.includes("testName")) { + return "testName"; + } + + return "SuperTokens"; + }; + } + + if (evalStr.startsWith("webauthn.init.getRelyingPartyId")) { + return async (R) => { + if (evalStr.includes("testId.com")) { + return "testId.com"; + } + + return "api.supertokens.io"; + }; + } + + if (evalStr.startsWith("webauthn.init.getOrigin")) { + return async (R) => { + if (evalStr.includes("testOrigin.com")) { + return "testOrigin.com"; + } + + return "api.supertokens.io"; + }; + } + + if (evalStr.startsWith("webauthn.init.validateEmailAddress")) { + return async (email) => { + if (evalStr.includes("test@example.com")) { + return email === "test@example.com" ? undefined : "Invalid email"; + } + + return undefined; + }; + } + throw new Error("Unknown eval string: " + evalStr); } diff --git a/test/test-server/src/webauthn.ts b/test/test-server/src/webauthn.ts index d2747ea2b..afd0c0f3e 100644 --- a/test/test-server/src/webauthn.ts +++ b/test/test-server/src/webauthn.ts @@ -5,23 +5,17 @@ import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeR import * as supertokens from "../../../lib/build"; import { logger } from "./logger"; -const namespace = "com.supertokens:node-test-server:emailpassword"; +const namespace = "com.supertokens:node-test-server:webauthn"; const { logDebugMessage } = logger(namespace); -const router = Router().post("/registeroptions", async (req, res, next) => { +const router = Router().post("/getgeneratedoptions", async (req, res, next) => { try { - logDebugMessage("Webauthn:registerOptions %j", req.body); - const response = await Webauthn.registerOptions( - req.body.email, - req.body.recoverAccountToken, - req.body.relyingPartyId, - req.body.relyingPartyName, - req.body.origin, - req.body.timeout, - req.body.attestation, - req.body.tenantId || "public", - req.body.userContext - ); + logDebugMessage("Webauthn:getGeneratedOptions %j", req.body); + const response = await Webauthn.getGeneratedOptions({ + webauthnGeneratedOptionsId: req.body.webauthnGeneratedOptionsId, + tenantId: req.body.tenantId, + userContext: req.body.userContext, + }); res.json(response); } catch (e) { next(e); From c0cc3bc1963e812c902bbcd7899eed7b0f154b3a Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Fri, 31 Jan 2025 11:25:09 +0200 Subject: [PATCH 36/36] test fixes --- .../recipe/webauthn/api/implementation.js | 5 +++ lib/build/recipe/webauthn/index.d.ts | 2 ++ lib/build/recipe/webauthn/index.js | 15 ++++++++ lib/build/recipe/webauthn/recipe.js | 7 ++-- .../recipe/webauthn/recipeImplementation.js | 9 +++-- lib/build/recipe/webauthn/types.d.ts | 1 + lib/ts/recipe/webauthn/api/implementation.ts | 5 +++ lib/ts/recipe/webauthn/index.ts | 17 +++++++-- lib/ts/recipe/webauthn/recipe.ts | 7 ++-- .../recipe/webauthn/recipeImplementation.ts | 9 +++-- lib/ts/recipe/webauthn/types.ts | 1 + test/webauthn/apis.test.js | 36 ++++++++++++------- 12 files changed, 83 insertions(+), 31 deletions(-) diff --git a/lib/build/recipe/webauthn/api/implementation.js b/lib/build/recipe/webauthn/api/implementation.js index 6a4c31a84..300079813 100644 --- a/lib/build/recipe/webauthn/api/implementation.js +++ b/lib/build/recipe/webauthn/api/implementation.js @@ -87,6 +87,10 @@ function getAPIImplementation() { request: options.req, userContext, }); + const relyingPartyName = await options.config.getRelyingPartyName({ + tenantId, + userContext, + }); // use this to get the full url instead of only the domain url const origin = await options.config.getOrigin({ tenantId, @@ -99,6 +103,7 @@ function getAPIImplementation() { userVerification, origin, relyingPartyId, + relyingPartyName, timeout, tenantId, userContext, diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts index c1bb8225a..ba3034530 100644 --- a/lib/build/recipe/webauthn/index.d.ts +++ b/lib/build/recipe/webauthn/index.d.ts @@ -112,11 +112,13 @@ export default class Wrapper { } & ( | { relyingPartyId: string; + relyingPartyName: string; origin: string; } | { request: BaseRequest; relyingPartyId?: string; + relyingPartyName?: string; origin?: string; } )): Promise< diff --git a/lib/build/recipe/webauthn/index.js b/lib/build/recipe/webauthn/index.js index cd7c99d6e..df6965d71 100644 --- a/lib/build/recipe/webauthn/index.js +++ b/lib/build/recipe/webauthn/index.js @@ -135,6 +135,7 @@ class Wrapper { rest = __rest(_a, ["tenantId", "userVerification", "timeout", "userContext"]); let origin; let relyingPartyId; + let relyingPartyName; if ("request" in rest) { relyingPartyId = rest.relyingPartyId || @@ -143,6 +144,12 @@ class Wrapper { tenantId: tenantId, userContext: utils_2.getUserContext(userContext), })); + relyingPartyName = + rest.relyingPartyName || + (await recipe_1.default.getInstanceOrThrowError().config.getRelyingPartyName({ + tenantId: tenantId, + userContext: utils_2.getUserContext(userContext), + })); origin = rest.origin || (await recipe_1.default.getInstanceOrThrowError().config.getOrigin({ @@ -154,14 +161,22 @@ class Wrapper { if (!rest.relyingPartyId) { throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); } + if (!rest.relyingPartyName) { + throw new exports.Error({ + type: "BAD_INPUT_ERROR", + message: "RelyingPartyName missing from the input", + }); + } if (!rest.origin) { throw new exports.Error({ type: "BAD_INPUT_ERROR", message: "Origin missing from the input" }); } relyingPartyId = rest.relyingPartyId; + relyingPartyName = rest.relyingPartyName; origin = rest.origin; } return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ relyingPartyId, + relyingPartyName, origin, timeout, tenantId, diff --git a/lib/build/recipe/webauthn/recipe.js b/lib/build/recipe/webauthn/recipe.js index 6d76efb0d..4320b6689 100644 --- a/lib/build/recipe/webauthn/recipe.js +++ b/lib/build/recipe/webauthn/recipe.js @@ -41,7 +41,8 @@ const recipe_1 = __importDefault(require("../multifactorauth/recipe")); const recipe_2 = __importDefault(require("../multitenancy/recipe")); const utils_3 = require("../thirdparty/utils"); const multifactorauth_1 = require("../multifactorauth"); -const core_mock_1 = require("./core-mock"); +// import { getMockQuerier } from "./core-mock"; +const querier_1 = require("../../querier"); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config, ingredients) { super(recipeId, appInfo); @@ -138,8 +139,8 @@ class Recipe extends recipeModule_1.default { this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); { const getWebauthnConfig = () => this.config; - // const querier = Querier.getNewInstanceOrThrowError(recipeId); - const querier = core_mock_1.getMockQuerier(recipeId); + const querier = querier_1.Querier.getNewInstanceOrThrowError(recipeId); + // const querier = getMockQuerier(recipeId); let builder = new supertokens_js_override_1.default( recipeImplementation_1.default(querier, getWebauthnConfig) ); diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js index 58bfeb881..7f85f6bd8 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.js +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -152,7 +152,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { userContext ); }, - signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext }) { + signInOptions: async function ({ relyingPartyId, relyingPartyName, origin, timeout, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest( new normalisedURLPath_1.default( @@ -162,6 +162,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { ), { relyingPartyId, + relyingPartyName, origin, timeout, }, @@ -410,11 +411,9 @@ function getRecipeInterface(querier, getWebauthnConfig) { getGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { return await querier.sendGetRequest( new normalisedURLPath_1.default( - `/${ - tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options` ), - {}, + { webauthnGeneratedOptionsId }, userContext ); }, diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts index 4b468adc8..1bf8a520a 100644 --- a/lib/build/recipe/webauthn/types.d.ts +++ b/lib/build/recipe/webauthn/types.d.ts @@ -144,6 +144,7 @@ export declare type RecipeInterface = { >; signInOptions(input: { relyingPartyId: string; + relyingPartyName: string; origin: string; userVerification: UserVerification | undefined; timeout: number | undefined; diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index 41eb38c1f..284976aba 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -152,6 +152,10 @@ export default function getAPIImplementation(): APIInterface { request: options.req, userContext, }); + const relyingPartyName = await options.config.getRelyingPartyName({ + tenantId, + userContext, + }); // use this to get the full url instead of only the domain url const origin = await options.config.getOrigin({ @@ -167,6 +171,7 @@ export default function getAPIImplementation(): APIInterface { userVerification, origin, relyingPartyId, + relyingPartyName, timeout, tenantId, userContext, diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index d93979448..eda27102a 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -188,8 +188,8 @@ export default class Wrapper { tenantId?: string; userContext?: Record; } & ( - | { relyingPartyId: string; origin: string } - | { request: BaseRequest; relyingPartyId?: string; origin?: string } + | { relyingPartyId: string; relyingPartyName: string; origin: string } + | { request: BaseRequest; relyingPartyId?: string; relyingPartyName?: string; origin?: string } )): Promise< | { status: "OK"; @@ -202,6 +202,7 @@ export default class Wrapper { > { let origin: string; let relyingPartyId: string; + let relyingPartyName: string; if ("request" in rest) { relyingPartyId = rest.relyingPartyId || @@ -210,7 +211,12 @@ export default class Wrapper { tenantId: tenantId, userContext: getUserContext(userContext), })); - + relyingPartyName = + rest.relyingPartyName || + (await Recipe.getInstanceOrThrowError().config.getRelyingPartyName({ + tenantId: tenantId, + userContext: getUserContext(userContext), + })); origin = rest.origin || (await Recipe.getInstanceOrThrowError().config.getOrigin({ @@ -222,15 +228,20 @@ export default class Wrapper { if (!rest.relyingPartyId) { throw new Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyId missing from the input" }); } + if (!rest.relyingPartyName) { + throw new Error({ type: "BAD_INPUT_ERROR", message: "RelyingPartyName missing from the input" }); + } if (!rest.origin) { throw new Error({ type: "BAD_INPUT_ERROR", message: "Origin missing from the input" }); } relyingPartyId = rest.relyingPartyId; + relyingPartyName = rest.relyingPartyName; origin = rest.origin; } return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signInOptions({ relyingPartyId, + relyingPartyName, origin, timeout, tenantId, diff --git a/lib/ts/recipe/webauthn/recipe.ts b/lib/ts/recipe/webauthn/recipe.ts index 8620c3fde..abcfd4b1d 100644 --- a/lib/ts/recipe/webauthn/recipe.ts +++ b/lib/ts/recipe/webauthn/recipe.ts @@ -48,7 +48,8 @@ import MultitenancyRecipe from "../multitenancy/recipe"; import { User } from "../../user"; import { isFakeEmail } from "../thirdparty/utils"; import { FactorIds } from "../multifactorauth"; -import { getMockQuerier } from "./core-mock"; +// import { getMockQuerier } from "./core-mock"; +import { Querier } from "../../querier"; export default class Recipe extends RecipeModule { private static instance: Recipe | undefined = undefined; @@ -78,8 +79,8 @@ export default class Recipe extends RecipeModule { this.config = validateAndNormaliseUserInput(this, appInfo, config); { const getWebauthnConfig = () => this.config; - // const querier = Querier.getNewInstanceOrThrowError(recipeId); - const querier = getMockQuerier(recipeId); + const querier = Querier.getNewInstanceOrThrowError(recipeId); + // const querier = getMockQuerier(recipeId); let builder = new OverrideableBuilder(RecipeImplementation(querier, getWebauthnConfig)); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index c2456e85f..6f229dc84 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -98,7 +98,7 @@ export default function getRecipeInterface( ); }, - signInOptions: async function ({ relyingPartyId, origin, timeout, tenantId, userContext }) { + signInOptions: async function ({ relyingPartyId, relyingPartyName, origin, timeout, tenantId, userContext }) { // the input user ID can be a recipe or a primary user ID. return await querier.sendPostRequest( new NormalisedURLPath( @@ -106,6 +106,7 @@ export default function getRecipeInterface( ), { relyingPartyId, + relyingPartyName, origin, timeout, }, @@ -364,11 +365,9 @@ export default function getRecipeInterface( getGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) { return await querier.sendGetRequest( new NormalisedURLPath( - `/${ - tenantId === undefined ? DEFAULT_TENANT_ID : tenantId - }/recipe/webauthn/options/${webauthnGeneratedOptionsId}` + `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/options` ), - {}, + { webauthnGeneratedOptionsId }, userContext ); }, diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 08216ae18..fbc31ca1f 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -219,6 +219,7 @@ export type RecipeInterface = { signInOptions(input: { relyingPartyId: string; + relyingPartyName: string; origin: string; userVerification: UserVerification | undefined; // see register options timeout: number | undefined; diff --git a/test/webauthn/apis.test.js b/test/webauthn/apis.test.js index 1bb80bf4d..cd040313c 100644 --- a/test/webauthn/apis.test.js +++ b/test/webauthn/apis.test.js @@ -52,7 +52,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }, appInfo: { apiDomain: "api.supertokens.io", - appName: "SuperTokensplm", + appName: "SuperTokens", websiteDomain: "supertokens.io", }, recipeList: [WebAuthn.init()], @@ -84,12 +84,14 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }) ); + console.log("test registerOptions with default values", registerOptionsResponse); + assert(registerOptionsResponse.status === "OK"); assert(typeof registerOptionsResponse.challenge === "string"); assert(registerOptionsResponse.attestation === "none"); - assert(registerOptionsResponse.rp.id === "supertokens.io"); - assert(registerOptionsResponse.rp.name === "SuperTokensplm"); + assert(registerOptionsResponse.rp.id === "api.supertokens.io"); + assert(registerOptionsResponse.rp.name === "SuperTokens"); assert(registerOptionsResponse.user.name === "test@example.com"); assert(registerOptionsResponse.user.displayName === "test@example.com"); assert(Number.isInteger(registerOptionsResponse.timeout)); @@ -100,6 +102,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( { webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + userContext: {}, } ); @@ -115,7 +118,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }, appInfo: { apiDomain: "api.supertokens.io", - appName: "SuperTokensplm", + appName: "SuperTokens", websiteDomain: "supertokens.io", }, recipeList: [ @@ -176,6 +179,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); + console.log("test registerOptions with custom values", registerOptionsResponse); assert(registerOptionsResponse.status === "OK"); @@ -193,8 +197,10 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( { webauthnGeneratedOptionsId: registerOptionsResponse.webauthnGeneratedOptionsId, + userContext: {}, } ); + console.log("generatedOptions", generatedOptions); assert(generatedOptions.origin === "testOrigin.com"); }); }); @@ -209,7 +215,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }, appInfo: { apiDomain: "api.supertokens.io", - appName: "SuperTokensplm", + appName: "SuperTokens", websiteDomain: "supertokens.io", }, recipeList: [WebAuthn.init()], @@ -238,6 +244,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); + console.log("test signInOptions with default values", signInOptionsResponse); assert(signInOptionsResponse.status === "OK"); @@ -248,8 +255,10 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( { webauthnGeneratedOptionsId: signInOptionsResponse.webauthnGeneratedOptionsId, + userContext: {}, } ); + console.log("generatedOptions", generatedOptions); assert(generatedOptions.rpId === "supertokens.io"); assert(generatedOptions.origin === "https://supertokens.io"); @@ -264,7 +273,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }, appInfo: { apiDomain: "api.supertokens.io", - appName: "SuperTokensplm", + appName: "SuperTokens", websiteDomain: "supertokens.io", }, recipeList: [ @@ -305,6 +314,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function } }) ); + console.log("test signInOptions with custom values", signInOptionsResponse); assert(signInOptionsResponse.status === "OK"); @@ -315,8 +325,10 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function const generatedOptions = await SuperTokens.getInstanceOrThrowError().recipeModules[0].recipeInterfaceImpl.getGeneratedOptions( { webauthnGeneratedOptionsId: signInOptionsResponse.webauthnGeneratedOptionsId, + userContext: {}, } ); + console.log("generatedOptions", generatedOptions); assert(generatedOptions.rpId === "testId.com"); assert(generatedOptions.origin === "testOrigin.com"); @@ -329,7 +341,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function const origin = "https://supertokens.io"; const rpId = "supertokens.io"; - const rpName = "SuperTokensplm"; + const rpName = "SuperTokens"; STExpress.init({ supertokens: { @@ -337,7 +349,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }, appInfo: { apiDomain: "api.supertokens.io", - appName: "SuperTokensplm", + appName: "SuperTokens", websiteDomain: "supertokens.io", }, recipeList: [ @@ -427,7 +439,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function const origin = "https://supertokens.io"; const rpId = "supertokens.io"; - const rpName = "SuperTokensplm"; + const rpName = "SuperTokens"; STExpress.init({ supertokens: { @@ -435,7 +447,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }, appInfo: { apiDomain: "api.supertokens.io", - appName: "SuperTokensplm", + appName: "SuperTokens", websiteDomain: "supertokens.io", }, recipeList: [ @@ -574,7 +586,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function const origin = "https://supertokens.io"; const rpId = "supertokens.io"; - const rpName = "SuperTokensplm"; + const rpName = "SuperTokens"; STExpress.init({ supertokens: { @@ -582,7 +594,7 @@ describe(`apisFunctions: ${printPath("[test/webauthn/apis.test.js]")}`, function }, appInfo: { apiDomain: "api.supertokens.io", - appName: "SuperTokensplm", + appName: "SuperTokens", websiteDomain: "supertokens.io", }, recipeList: [