From 1c350bee3ed7272a28a42369707cd7df34a644b3 Mon Sep 17 00:00:00 2001 From: Victor Bojica Date: Thu, 13 Feb 2025 11:38:26 +0200 Subject: [PATCH] error handling cleanup and general cleanup --- .../recipe/webauthn/api/implementation.js | 7 +- lib/build/recipe/webauthn/index.d.ts | 228 +++++---- lib/build/recipe/webauthn/index.js | 9 +- .../recipe/webauthn/recipeImplementation.js | 33 +- lib/build/recipe/webauthn/types.d.ts | 457 +++++++++--------- lib/ts/recipe/webauthn/api/implementation.ts | 36 +- lib/ts/recipe/webauthn/index.ts | 139 +----- .../recipe/webauthn/recipeImplementation.ts | 35 +- lib/ts/recipe/webauthn/types.ts | 421 ++++++---------- 9 files changed, 568 insertions(+), 797 deletions(-) diff --git a/lib/build/recipe/webauthn/api/implementation.js b/lib/build/recipe/webauthn/api/implementation.js index ceee39c15..246c0a53e 100644 --- a/lib/build/recipe/webauthn/api/implementation.js +++ b/lib/build/recipe/webauthn/api/implementation.js @@ -52,6 +52,7 @@ function getAPIImplementation() { const supportedAlgorithmIds = constants_1.DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS; let response = await options.recipeImplementation.registerOptions( Object.assign(Object.assign({}, props), { + displayName: "displayName" in props ? props.displayName : undefined, attestation, residentKey, userVerification, @@ -304,7 +305,7 @@ function getAPIImplementation() { userContext, }); if (verifyResult.status !== "OK") { - return verifyResult; + return { status: "INVALID_CREDENTIALS_ERROR" }; } const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ webauthnGeneratedOptionsId, @@ -855,8 +856,8 @@ function getAPIImplementation() { }); if ( createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || - createUserResponse.status === "GENERATED_OPTIONS_NOT_FOUND_ERROR" || - createUserResponse.status === "INVALID_GENERATED_OPTIONS_ERROR" || + createUserResponse.status === "OPTIONS_NOT_FOUND_ERROR" || + createUserResponse.status === "INVALID_OPTIONS_ERROR" || createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR" ) { return createUserResponse; diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts index fc707ae8a..e4bece567 100644 --- a/lib/build/recipe/webauthn/index.d.ts +++ b/lib/build/recipe/webauthn/index.d.ts @@ -12,9 +12,7 @@ import { Attestation, AuthenticationPayload, } 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; @@ -54,14 +52,23 @@ export default class Wrapper { ( | { email: string; + displayName?: string; } | { recoverAccountToken: string; } )): Promise< + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } | { status: "OK"; webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; rp: { id: string; name: string; @@ -78,7 +85,7 @@ export default class Wrapper { type: "public-key"; transports: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; }[]; - attestation: "none" | "indirect" | "direct" | "enterprise"; + attestation: Attestation; pubKeyCredParams: { alg: number; type: "public-key"; @@ -90,15 +97,9 @@ export default class Wrapper { }; } | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - | { - status: "INVALID_EMAIL_ERROR"; + status: string; err: string; } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } >; static signInOptions({ tenantId, @@ -126,16 +127,18 @@ export default class Wrapper { origin?: string; } )): Promise< + | { + status: "INVALID_OPTIONS_ERROR"; + } | { status: "OK"; webauthnGeneratedOptionsId: string; + createdAt: string; + expiresAt: string; challenge: string; timeout: number; userVerification: UserVerification; } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } >; static getGeneratedOptions({ webauthnGeneratedOptionsId, @@ -146,6 +149,9 @@ export default class Wrapper { tenantId?: string; userContext?: Record; }): Promise< + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } | { status: "OK"; webauthnGeneratedOptionsId: string; @@ -160,9 +166,6 @@ export default class Wrapper { createdAt: number; expiresAt: number; } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } >; static signUp({ tenantId, @@ -177,34 +180,38 @@ export default class Wrapper { userContext?: Record; session?: SessionContainerInterface; }): Promise< + | ( + | ( + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + ) + | { + status: "LINKING_TO_SESSION_USER_FAILED"; + reason: + | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + | "EMAIL_VERIFICATION_REQUIRED" + | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + } + ) | { 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"; + user: import("../../types").User; + recipeUserId: import("../..").RecipeUserId; } >; static signIn({ @@ -220,21 +227,40 @@ export default class Wrapper { session?: SessionContainerInterface; userContext?: Record; }): Promise< + | ( + | ( + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + } + | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + ) + | { + status: "LINKING_TO_SESSION_USER_FAILED"; + reason: + | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + | "EMAIL_VERIFICATION_REQUIRED" + | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + } + ) | { 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"; + user: import("../../types").User; + recipeUserId: import("../..").RecipeUserId; } >; static verifyCredentials({ @@ -247,14 +273,16 @@ export default class Wrapper { webauthnGeneratedOptionsId: string; credential: AuthenticationPayload; userContext?: Record; - }): Promise< - | { - status: "OK"; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } - >; + }): Promise<{ + status: + | "OK" + | "UNKNOWN_USER_ID_ERROR" + | "INVALID_CREDENTIALS_ERROR" + | "INVALID_OPTIONS_ERROR" + | "OPTIONS_NOT_FOUND_ERROR" + | "INVALID_AUTHENTICATOR_ERROR" + | "CREDENTIAL_NOT_FOUND_ERROR"; + }>; /** * We do not make email optional here cause we want to * allow passing in primaryUserId. If we make email optional, @@ -278,11 +306,11 @@ export default class Wrapper { userContext?: Record; }): Promise< | { - status: "OK"; - token: string; + status: "UNKNOWN_USER_ID_ERROR"; } | { - status: "UNKNOWN_USER_ID_ERROR"; + status: "OK"; + token: string; } >; static recoverAccount({ @@ -298,24 +326,16 @@ export default class Wrapper { 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: string; + failureReason: string; } | { - status: "INVALID_AUTHENTICATOR_ERROR"; - failureReason: string; + status: "OK" | "INVALID_CREDENTIALS_ERROR" | "INVALID_OPTIONS_ERROR" | "OPTIONS_NOT_FOUND_ERROR"; + failureReason?: undefined; } >; static consumeRecoverAccountToken({ @@ -327,14 +347,14 @@ export default class Wrapper { token: string; userContext?: Record; }): Promise< + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } | { status: "OK"; email: string; userId: string; } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } >; static registerCredential({ recipeUserId, @@ -347,22 +367,24 @@ export default class Wrapper { credential: CredentialPayload; userContext?: Record; }): Promise< + | ( + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + } + ) | { 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, @@ -376,11 +398,11 @@ export default class Wrapper { userContext?: Record; }): Promise< | { - status: "OK"; - link: string; + status: "UNKNOWN_USER_ID_ERROR"; } | { - status: "UNKNOWN_USER_ID_ERROR"; + status: string; + link: string; } >; static sendRecoverAccountEmail({ @@ -393,9 +415,15 @@ export default class Wrapper { userId: string; email: string; userContext?: Record; - }): Promise<{ - status: "OK" | "UNKNOWN_USER_ID_ERROR"; - }>; + }): Promise< + | { + status: string; + link: string; + } + | { + status: string; + } + >; static sendEmail( input: TypeWebauthnEmailDeliveryInput & { userContext?: Record; diff --git a/lib/build/recipe/webauthn/index.js b/lib/build/recipe/webauthn/index.js index fac25ce2b..425f92c2b 100644 --- a/lib/build/recipe/webauthn/index.js +++ b/lib/build/recipe/webauthn/index.js @@ -63,7 +63,10 @@ class Wrapper { let emailOrRecoverAccountToken; if ("email" in rest || "recoverAccountToken" in rest) { if ("email" in rest) { - emailOrRecoverAccountToken = { email: rest.email }; + emailOrRecoverAccountToken = { + email: rest.email, + displayName: rest.displayName, + }; } else { emailOrRecoverAccountToken = { recoverAccountToken: rest.recoverAccountToken }; } @@ -333,9 +336,7 @@ class Wrapper { return { status: "UNKNOWN_USER_ID_ERROR" }; } let link = await this.createRecoverAccountLink({ tenantId, userId, email, userContext }); - if (link.status === "UNKNOWN_USER_ID_ERROR") { - return link; - } + if (link.status !== "OK") return link; await exports.sendEmail({ recoverAccountLink: link.link, type: "RECOVER_ACCOUNT", diff --git a/lib/build/recipe/webauthn/recipeImplementation.js b/lib/build/recipe/webauthn/recipeImplementation.js index 8b4a2e685..96c6f295a 100644 --- a/lib/build/recipe/webauthn/recipeImplementation.js +++ b/lib/build/recipe/webauthn/recipeImplementation.js @@ -91,7 +91,7 @@ function getRecipeInterface(querier, getWebauthnConfig) { // 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) { + if ("displayName" in rest && rest.displayName !== undefined) { displayName = rest.displayName; } else { if (utils_1.isFakeEmail(email)) { @@ -327,21 +327,6 @@ function getRecipeInterface(querier, getWebauthnConfig) { 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: "INVALID_CREDENTIALS_ERROR", - }; - }, getUserFromRecoverAccountToken: async function ({ token, tenantId, userContext }) { return await querier.sendGetRequest( new normalisedURLPath_1.default( @@ -361,10 +346,8 @@ function getRecipeInterface(querier, getWebauthnConfig) { }, getCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { return await querier.sendGetRequest( - new normalisedURLPath_1.default( - `/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}` - ), - {}, + new normalisedURLPath_1.default(`/recipe/webauthn/user/credential`), + { webauthnCredentialId, recipeUserId }, userContext ); }, @@ -396,6 +379,16 @@ function getRecipeInterface(querier, getWebauthnConfig) { userContext ); }, + updateUserEmail: async function ({ email, recipeUserId, tenantId, userContext }) { + return await querier.sendPutRequest( + new normalisedURLPath_1.default( + `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/email` + ), + { email, recipeUserId }, + {}, + userContext + ); + }, }; } exports.default = getRecipeInterface; diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts index 5a5d37d65..96472bbb2 100644 --- a/lib/build/recipe/webauthn/types.d.ts +++ b/lib/build/recipe/webauthn/types.d.ts @@ -72,6 +72,118 @@ export declare type TypeInputValidateEmailAddress = ( email: string, tenantId: string ) => Promise | string | undefined; +declare type RegisterOptionsErrorResponse = + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } + | { + status: "INVALID_OPTIONS_ERROR"; + }; +declare type SignInOptionsErrorResponse = { + status: "INVALID_OPTIONS_ERROR"; +}; +declare type CreateNewRecipeUserErrorResponse = + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }; +declare type SignUpErrorResponse = + | CreateNewRecipeUserErrorResponse + | { + 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"; + }; +declare type VerifyCredentialsErrorResponse = + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + } + | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + }; +declare type SignInErrorResponse = + | VerifyCredentialsErrorResponse + | { + 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"; + }; +declare type GenerateRecoverAccountTokenErrorResponse = { + status: "UNKNOWN_USER_ID_ERROR"; +}; +declare type ConsumeRecoverAccountTokenErrorResponse = { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; +}; +declare type RegisterCredentialErrorResponse = + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }; +declare type GetUserFromRecoverAccountTokenErrorResponse = { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; +}; +declare type RemoveCredentialErrorResponse = { + status: "CREDENTIAL_NOT_FOUND_ERROR"; +}; +declare type GetCredentialErrorResponse = { + status: "CREDENTIAL_NOT_FOUND_ERROR"; +}; +declare type RemoveGeneratedOptionsErrorResponse = { + status: "OPTIONS_NOT_FOUND_ERROR"; +}; +declare type GetGeneratedOptionsErrorResponse = { + status: "OPTIONS_NOT_FOUND_ERROR"; +}; +declare type UpdateUserEmailErrorResponse = + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "UNKNOWN_USER_ID_ERROR"; + }; declare type Base64URLString = string; export declare type ResidentKey = "required" | "preferred" | "discouraged"; export declare type UserVerification = "required" | "preferred" | "discouraged"; @@ -81,7 +193,6 @@ export declare type RecipeInterface = { input: { relyingPartyId: string; relyingPartyName: string; - displayName?: string; origin: string; residentKey: ResidentKey | undefined; userVerification: UserVerification | undefined; @@ -96,6 +207,7 @@ export declare type RecipeInterface = { recoverAccountToken: string; } | { + displayName: string | undefined; email: string; } ) @@ -132,16 +244,7 @@ export declare type RecipeInterface = { userVerification: UserVerification; }; } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - | { - status: "INVALID_EMAIL_ERROR"; - err: string; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } + | RegisterOptionsErrorResponse >; signInOptions(input: { relyingPartyId: string; @@ -162,9 +265,7 @@ export declare type RecipeInterface = { timeout: number; userVerification: UserVerification; } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } + | SignInOptionsErrorResponse >; signUp(input: { webauthnGeneratedOptionsId: string; @@ -179,30 +280,7 @@ export declare type RecipeInterface = { 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"; - } + | SignUpErrorResponse >; signIn(input: { webauthnGeneratedOptionsId: string; @@ -217,17 +295,7 @@ export declare type RecipeInterface = { 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"; - } + | SignInErrorResponse >; verifyCredentials(input: { webauthnGeneratedOptionsId: string; @@ -240,9 +308,7 @@ export declare type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } + | VerifyCredentialsErrorResponse >; createNewRecipeUser(input: { webauthnGeneratedOptionsId: string; @@ -255,22 +321,7 @@ export declare type RecipeInterface = { 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"; - } + | CreateNewRecipeUserErrorResponse >; /** * We pass in the email as well to this function cause the input userId @@ -287,9 +338,7 @@ export declare type RecipeInterface = { status: "OK"; token: string; } - | { - status: "UNKNOWN_USER_ID_ERROR"; - } + | GenerateRecoverAccountTokenErrorResponse >; consumeRecoverAccountToken(input: { token: string; @@ -301,9 +350,7 @@ export declare type RecipeInterface = { email: string; userId: string; } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } + | ConsumeRecoverAccountTokenErrorResponse >; registerCredential(input: { webauthnGeneratedOptionsId: string; @@ -314,81 +361,7 @@ export declare type RecipeInterface = { | { 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"; - } + | RegisterCredentialErrorResponse >; getUserFromRecoverAccountToken(input: { token: string; @@ -400,9 +373,7 @@ export declare type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } + | GetUserFromRecoverAccountTokenErrorResponse >; removeCredential(input: { webauthnCredentialId: string; @@ -412,25 +383,21 @@ export declare type RecipeInterface = { | { status: "OK"; } - | { - status: "CREDENTIAL_NOT_FOUND_ERROR"; - } + | RemoveCredentialErrorResponse >; getCredential(input: { webauthnCredentialId: string; - recipeUserId: RecipeUserId; + recipeUserId: string; userContext: UserContext; }): Promise< | { status: "OK"; - id: string; + webauthnCredentialId: string; relyingPartyId: string; recipeUserId: RecipeUserId; createdAt: number; } - | { - status: "CREDENTIAL_NOT_FOUND_ERROR"; - } + | GetCredentialErrorResponse >; listCredentials(input: { recipeUserId: string; @@ -440,6 +407,7 @@ export declare type RecipeInterface = { credentials: { webauthnCredentialId: string; relyingPartyId: string; + recipeUserId: string; createdAt: number; }[]; }>; @@ -451,9 +419,7 @@ export declare type RecipeInterface = { | { status: "OK"; } - | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; - } + | RemoveGeneratedOptionsErrorResponse >; getGeneratedOptions(input: { webauthnGeneratedOptionsId: string; @@ -474,9 +440,18 @@ export declare type RecipeInterface = { createdAt: number; expiresAt: number; } + | GetGeneratedOptionsErrorResponse + >; + updateUserEmail(input: { + recipeUserId: string; + email: string; + tenantId: string; + userContext: UserContext; + }): Promise< | { - status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; + status: "OK"; } + | UpdateUserEmailErrorResponse >; }; export declare type APIOptions = { @@ -489,6 +464,76 @@ export declare type APIOptions = { res: BaseResponse; emailDelivery: EmailDeliveryIngredient; }; +declare type RegisterOptionsPOSTErrorResponse = RegisterOptionsErrorResponse; +declare type SignInOptionsPOSTErrorResponse = SignInOptionsErrorResponse; +declare type SignUpPOSTErrorResponse = + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }; +declare type SignInPOSTErrorResponse = + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + }; +declare type GenerateRecoverAccountTokenPOSTErrorResponse = { + status: "RECOVER_ACCOUNT_NOT_ALLOWED"; + reason: string; +}; +declare type RecoverAccountPOSTErrorResponse = + | { + status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }; +declare type RegisterCredentialPOSTErrorResponse = + | { + status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; + reason: string; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason: string; + }; export declare type APIInterface = { registerOptionsPOST: | undefined @@ -539,16 +584,7 @@ export declare type APIInterface = { }; } | GeneralErrorResponse - | { - status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; - } - | { - status: "INVALID_EMAIL_ERROR"; - err: string; - } - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } + | RegisterOptionsPOSTErrorResponse >); signInOptionsPOST: | undefined @@ -567,9 +603,7 @@ export declare type APIInterface = { userVerification: UserVerification; } | GeneralErrorResponse - | { - status: "INVALID_GENERATED_OPTIONS_ERROR"; - } + | SignInOptionsPOSTErrorResponse >); signUpPOST: | undefined @@ -588,26 +622,7 @@ export declare type APIInterface = { 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"; - } + | SignUpPOSTErrorResponse >); signInPOST: | undefined @@ -626,13 +641,7 @@ export declare type APIInterface = { session: SessionContainerInterface; } | GeneralErrorResponse - | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } - | { - status: "INVALID_CREDENTIALS_ERROR"; - } + | SignInPOSTErrorResponse >); generateRecoverAccountTokenPOST: | undefined @@ -646,10 +655,7 @@ export declare type APIInterface = { status: "OK"; } | GeneralErrorResponse - | { - status: "RECOVER_ACCOUNT_NOT_ALLOWED"; - reason: string; - } + | GenerateRecoverAccountTokenPOSTErrorResponse >); recoverAccountPOST: | undefined @@ -667,22 +673,7 @@ export declare type APIInterface = { 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; - } + | RecoverAccountPOSTErrorResponse >); registerCredentialPOST: | undefined @@ -698,23 +689,7 @@ export declare type APIInterface = { 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; - } + | RegisterCredentialPOSTErrorResponse >); emailExistsGET: | undefined diff --git a/lib/ts/recipe/webauthn/api/implementation.ts b/lib/ts/recipe/webauthn/api/implementation.ts index a923c2c7b..482556aa3 100644 --- a/lib/ts/recipe/webauthn/api/implementation.ts +++ b/lib/ts/recipe/webauthn/api/implementation.ts @@ -34,8 +34,7 @@ export default function getAPIImplementation(): APIInterface { tenantId: string; options: APIOptions; userContext: UserContext; - displayName?: string; - } & ({ email: string } | { recoverAccountToken: string })): Promise< + } & ({ email: string; displayName?: string } | { recoverAccountToken: string })): Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; @@ -70,7 +69,7 @@ export default function getAPIImplementation(): APIInterface { } | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } | { status: "INVALID_EMAIL_ERROR"; err: string } - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | { status: "INVALID_OPTIONS_ERROR" } > { const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, @@ -97,6 +96,7 @@ export default function getAPIImplementation(): APIInterface { let response = await options.recipeImplementation.registerOptions({ ...props, + displayName: "displayName" in props ? props.displayName : undefined, attestation, residentKey, userVerification, @@ -149,7 +149,7 @@ export default function getAPIImplementation(): APIInterface { userVerification: UserVerification; } | GeneralErrorResponse - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | { status: "INVALID_OPTIONS_ERROR" } > { const relyingPartyId = await options.config.getRelyingPartyId({ tenantId, @@ -227,8 +227,8 @@ export default function getAPIImplementation(): APIInterface { reason: string; } | { status: "INVALID_CREDENTIALS_ERROR" } - | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | { status: "OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_OPTIONS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } > { @@ -422,7 +422,7 @@ export default function getAPIImplementation(): APIInterface { userContext, }); if (verifyResult.status !== "OK") { - return verifyResult; + return { status: "INVALID_CREDENTIALS_ERROR" }; } const generatedOptions = await options.recipeImplementation.getGeneratedOptions({ @@ -618,13 +618,9 @@ export default function getAPIImplementation(): APIInterface { async function generateAndSendRecoverAccountToken( primaryUserId: string, recipeUserId: RecipeUserId | undefined - ): Promise< - | { - status: "OK"; - } - | { status: "RECOVER_ACCOUNT_NOT_ALLOWED"; reason: string } - | GeneralErrorResponse - > { + ): Promise<{ + status: "OK"; + }> { // the user ID here can be primary or recipe level. let response = await options.recipeImplementation.generateRecoverAccountToken({ tenantId, @@ -884,8 +880,8 @@ export default function getAPIImplementation(): APIInterface { | GeneralErrorResponse | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_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: "OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found + | { status: "INVALID_OPTIONS_ERROR" } // i.e. timeout expired | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } > { async function markEmailAsVerified(recipeUserId: RecipeUserId, email: string) { @@ -1075,8 +1071,8 @@ export default function getAPIImplementation(): APIInterface { if ( createUserResponse.status === "INVALID_CREDENTIALS_ERROR" || - createUserResponse.status === "GENERATED_OPTIONS_NOT_FOUND_ERROR" || - createUserResponse.status === "INVALID_GENERATED_OPTIONS_ERROR" || + createUserResponse.status === "OPTIONS_NOT_FOUND_ERROR" || + createUserResponse.status === "INVALID_OPTIONS_ERROR" || createUserResponse.status === "INVALID_AUTHENTICATOR_ERROR" ) { return createUserResponse; @@ -1162,8 +1158,8 @@ export default function getAPIImplementation(): APIInterface { reason: string; } | { status: "INVALID_CREDENTIALS_ERROR" } - | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | { status: "OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_OPTIONS_ERROR" } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string } > { // TODO update error codes (ERR_CODE_XXX) after final implementation diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index b4c6cfbc1..f972ab21f 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -26,13 +26,11 @@ import { Attestation, AuthenticationPayload, } from "./types"; -import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; import { getRecoverAccountLink } from "./utils"; import { getRequestFromUserContext, getUser } from "../.."; import { getUserContext } from "../../utils"; import { SessionContainerInterface } from "../session/types"; -import { User } from "../../types"; import { DEFAULT_REGISTER_OPTIONS_RESIDENT_KEY, DEFAULT_REGISTER_OPTIONS_SUPPORTED_ALGORITHM_IDS, @@ -77,47 +75,19 @@ export default class Wrapper { ( | { email: string; + displayName?: 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" } - > { - let emailOrRecoverAccountToken: { email: string } | { recoverAccountToken: string }; + )) { + let emailOrRecoverAccountToken: + | { email: string; displayName: string | undefined } + | { recoverAccountToken: string }; if ("email" in rest || "recoverAccountToken" in rest) { if ("email" in rest) { - emailOrRecoverAccountToken = { email: rest.email }; + emailOrRecoverAccountToken = { + email: rest.email, + displayName: rest.displayName, + }; } else { emailOrRecoverAccountToken = { recoverAccountToken: rest.recoverAccountToken }; } @@ -197,16 +167,7 @@ export default class Wrapper { } & ( | { relyingPartyId: string; relyingPartyName: string; origin: string } | { request: BaseRequest; relyingPartyId?: string; relyingPartyName?: string; origin?: string } - )): Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: UserVerification; - } - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } - > { + )) { let origin: string; let relyingPartyId: string; let relyingPartyName: string; @@ -286,26 +247,7 @@ export default class Wrapper { 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"; - } - > { + }) { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signUp({ webauthnGeneratedOptionsId, credential, @@ -328,18 +270,7 @@ export default class Wrapper { 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"; - } - > { + }) { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.signIn({ webauthnGeneratedOptionsId, credential, @@ -360,7 +291,7 @@ export default class Wrapper { webauthnGeneratedOptionsId: string; credential: AuthenticationPayload; userContext?: Record; - }): Promise<{ status: "OK" } | { status: "INVALID_CREDENTIALS_ERROR" }> { + }) { const resp = await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.verifyCredentials({ webauthnGeneratedOptionsId, credential, @@ -395,7 +326,7 @@ export default class Wrapper { userId: string; email: string; userContext?: Record; - }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { + }) { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.generateRecoverAccountToken({ userId, email, @@ -416,16 +347,7 @@ export default class Wrapper { 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 } - > { + }) { const consumeResp = await Wrapper.consumeRecoverAccountToken({ tenantId, token, userContext }); if (consumeResp.status !== "OK") { @@ -458,14 +380,7 @@ export default class Wrapper { 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, @@ -483,15 +398,7 @@ export default class Wrapper { 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 } - > { + }) { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.registerCredential({ recipeUserId, webauthnGeneratedOptionsId, @@ -510,7 +417,7 @@ export default class Wrapper { 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; @@ -540,7 +447,7 @@ export default class Wrapper { 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" }; @@ -552,9 +459,7 @@ export default class Wrapper { } let link = await this.createRecoverAccountLink({ tenantId, userId, email, userContext }); - if (link.status === "UNKNOWN_USER_ID_ERROR") { - return link; - } + if (link.status !== "OK") return link; await sendEmail({ recoverAccountLink: link.link, @@ -573,9 +478,7 @@ export default class Wrapper { }; } - static async sendEmail( - input: TypeWebauthnEmailDeliveryInput & { userContext?: Record } - ): Promise { + static async sendEmail(input: TypeWebauthnEmailDeliveryInput & { userContext?: Record }) { let recipeInstance = Recipe.getInstanceOrThrowError(); return await recipeInstance.emailDelivery.ingredientInterfaceImpl.sendEmail({ ...input, diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index d7a137858..9e8eaba38 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -67,7 +67,7 @@ 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) { + if ("displayName" in rest && rest.displayName !== undefined) { displayName = rest.displayName; } else { if (isFakeEmail(email)) { @@ -307,24 +307,6 @@ export default function getRecipeInterface( ); }, - 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: "INVALID_CREDENTIALS_ERROR", - }; - }, - getUserFromRecoverAccountToken: async function ({ token, tenantId, userContext }) { return await querier.sendGetRequest( new NormalisedURLPath( @@ -346,8 +328,8 @@ export default function getRecipeInterface( getCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) { return await querier.sendGetRequest( - new NormalisedURLPath(`/recipe/webauthn/user/${recipeUserId}/credential/${webauthnCredentialId}`), - {}, + new NormalisedURLPath(`/recipe/webauthn/user/credential`), + { webauthnCredentialId, recipeUserId }, userContext ); }, @@ -382,5 +364,16 @@ export default function getRecipeInterface( userContext ); }, + + updateUserEmail: async function ({ email, recipeUserId, tenantId, userContext }) { + return await querier.sendPutRequest( + new NormalisedURLPath( + `/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/webauthn/user/email` + ), + { email, recipeUserId }, + {}, + userContext + ); + }, }; } diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 8bce1f01c..703e0de5f 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -98,50 +98,71 @@ 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"; err: string } | { status: "INVALID_GENERATED_OPTIONS_ERROR" }; - -// type SignInOptionsErrorResponse = { status: "INVALID_GENERATED_OPTIONS_ERROR" }; - -// type SignUpErrorResponse = -// | { 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 }; - -// type SignInErrorResponse = { status: "INVALID_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: "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: "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: "INVALID_CREDENTIALS_ERROR" }; - -// type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; - -// type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; - -// type GetCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; - -// type RemoveGeneratedOptionsErrorResponse = { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; - -// type GetGeneratedOptionsErrorResponse = { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" }; +type RegisterOptionsErrorResponse = + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } // test yes + | { status: "INVALID_EMAIL_ERROR"; err: string } // test yes + | { status: "INVALID_OPTIONS_ERROR" }; // test no - no validation on the core yet + +type SignInOptionsErrorResponse = { status: "INVALID_OPTIONS_ERROR" }; // test no - no validation on the core yet + +type CreateNewRecipeUserErrorResponse = + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } // test yes + | { status: "INVALID_CREDENTIALS_ERROR" } // test yes - possibly wrong implementation + | { status: "OPTIONS_NOT_FOUND_ERROR" } // test yes + | { status: "INVALID_OPTIONS_ERROR" } // test no - no validation on the core yet + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; // test no - might drop this in favor of invalid credentials if not possible to distinguish + +type SignUpErrorResponse = + | CreateNewRecipeUserErrorResponse + | { + status: "LINKING_TO_SESSION_USER_FAILED"; // test no + 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"; + }; + +type VerifyCredentialsErrorResponse = + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "INVALID_OPTIONS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR" } + | { status: "CREDENTIAL_NOT_FOUND_ERROR" } + | { status: "UNKNOWN_USER_ID_ERROR" } + | { status: "OPTIONS_NOT_FOUND_ERROR" }; + +type SignInErrorResponse = + | VerifyCredentialsErrorResponse + | { + 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"; + }; + +type GenerateRecoverAccountTokenErrorResponse = { status: "UNKNOWN_USER_ID_ERROR" }; + +type ConsumeRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; + +type RegisterCredentialErrorResponse = + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_OPTIONS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; + +type GetUserFromRecoverAccountTokenErrorResponse = { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" }; + +type RemoveCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; + +type GetCredentialErrorResponse = { status: "CREDENTIAL_NOT_FOUND_ERROR" }; + +type RemoveGeneratedOptionsErrorResponse = { status: "OPTIONS_NOT_FOUND_ERROR" }; + +type GetGeneratedOptionsErrorResponse = { status: "OPTIONS_NOT_FOUND_ERROR" }; + +type UpdateUserEmailErrorResponse = { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }; type Base64URLString = string; @@ -154,7 +175,6 @@ export type RecipeInterface = { input: { relyingPartyId: string; relyingPartyName: string; - displayName?: string; origin: string; // default to 'required' in order store the private key locally on the device and not on the server residentKey: ResidentKey | undefined; @@ -174,6 +194,7 @@ export type RecipeInterface = { recoverAccountToken: string; } | { + displayName: string | undefined; email: string; } ) @@ -212,10 +233,7 @@ export type RecipeInterface = { userVerification: UserVerification; }; } - // | RegisterOptionsErrorResponse - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "INVALID_EMAIL_ERROR"; err: string } - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | RegisterOptionsErrorResponse >; signInOptions(input: { @@ -237,8 +255,7 @@ export type RecipeInterface = { timeout: number; userVerification: UserVerification; } - // | SignInOptionsErrorResponse - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | SignInOptionsErrorResponse >; signUp(input: { @@ -254,20 +271,7 @@ export type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - // | SignUpErrorResponse - | { 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"; - } + | SignUpErrorResponse >; signIn(input: { @@ -277,30 +281,14 @@ export type RecipeInterface = { shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; - }): Promise< - | { status: "OK"; user: User; recipeUserId: RecipeUserId } - // | SignInErrorResponse - | { 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 } | SignInErrorResponse>; verifyCredentials(input: { webauthnGeneratedOptionsId: string; credential: AuthenticationPayload; tenantId: string; userContext: UserContext; - }): Promise< - | { status: "OK"; user: User; recipeUserId: RecipeUserId } - // | VerifyCredentialsErrorResponse - | { status: "INVALID_CREDENTIALS_ERROR" } - >; + }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | VerifyCredentialsErrorResponse>; // 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 @@ -317,12 +305,7 @@ export type RecipeInterface = { user: User; recipeUserId: RecipeUserId; } - // | CreateNewRecipeUserErrorResponse - | { 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" } + | CreateNewRecipeUserErrorResponse >; /** @@ -335,11 +318,7 @@ export type RecipeInterface = { email: string; tenantId: string; userContext: UserContext; - }): Promise< - | { status: "OK"; token: string } - // | GenerateRecoverAccountTokenErrorResponse - | { status: "UNKNOWN_USER_ID_ERROR" } - >; + }): Promise<{ status: "OK"; token: string } | GenerateRecoverAccountTokenErrorResponse>; // make sure the email maps to options email consumeRecoverAccountToken(input: { @@ -352,8 +331,7 @@ export type RecipeInterface = { email: string; userId: string; } - // | ConsumeRecoverAccountTokenErrorResponse - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | ConsumeRecoverAccountTokenErrorResponse >; // used internally for creating a credential during recover account flow or when adding a credential to an existing user @@ -369,73 +347,7 @@ export type RecipeInterface = { | { status: "OK"; } - // | RegisterCredentialErrorResponse - | { 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; - }; - } - // | DecodeCredentialErrorResponse - | { status: "INVALID_CREDENTIALS_ERROR" } + | RegisterCredentialErrorResponse >; // used for retrieving the user details (email) from the recover account token @@ -444,11 +356,7 @@ export type RecipeInterface = { token: string; tenantId: string; userContext: UserContext; - }): Promise< - | { status: "OK"; user: User; recipeUserId: RecipeUserId } - // | GetUserFromRecoverAccountTokenErrorResponse - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - >; + }): Promise<{ status: "OK"; user: User; recipeUserId: RecipeUserId } | GetUserFromRecoverAccountTokenErrorResponse>; // credentials CRUD removeCredential(input: { @@ -459,24 +367,22 @@ export type RecipeInterface = { | { status: "OK"; } - // | RemoveCredentialErrorResponse - | { status: "CREDENTIAL_NOT_FOUND_ERROR" } + | RemoveCredentialErrorResponse >; getCredential(input: { webauthnCredentialId: string; - recipeUserId: RecipeUserId; + recipeUserId: string; userContext: UserContext; }): Promise< | { status: "OK"; - id: string; + webauthnCredentialId: string; relyingPartyId: string; recipeUserId: RecipeUserId; createdAt: number; } - // | GetCredentialErrorResponse - | { status: "CREDENTIAL_NOT_FOUND_ERROR" } + | GetCredentialErrorResponse >; listCredentials(input: { @@ -487,6 +393,7 @@ export type RecipeInterface = { credentials: { webauthnCredentialId: string; relyingPartyId: string; + recipeUserId: string; createdAt: number; }[]; }>; @@ -496,11 +403,7 @@ export type RecipeInterface = { webauthnGeneratedOptionsId: string; tenantId: string; userContext: UserContext; - }): Promise< - | { status: "OK" } - // | RemoveGeneratedOptionsErrorResponse - | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } - >; + }): Promise<{ status: "OK" } | RemoveGeneratedOptionsErrorResponse>; getGeneratedOptions(input: { webauthnGeneratedOptionsId: string; @@ -521,9 +424,24 @@ export type RecipeInterface = { createdAt: number; expiresAt: number; } - // | GetGeneratedOptionsErrorResponse - | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR" } + | GetGeneratedOptionsErrorResponse + >; + + updateUserEmail(input: { + recipeUserId: string; + email: string; + tenantId: string; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + } + | UpdateUserEmailErrorResponse >; + + // todo add update user email + // throws UNKNOWN_USER_ID_ERROR + // throws email_already_exists_error }; export type APIOptions = { @@ -537,53 +455,51 @@ export type APIOptions = { emailDelivery: EmailDeliveryIngredient; }; -// type RegisterOptionsPOSTErrorResponse = -// | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } -// | { status: "INVALID_EMAIL_ERROR"; err: string } -// | { status: "INVALID_GENERATED_OPTIONS_ERROR" }; - -// type SignInOptionsPOSTErrorResponse = -// | { status: "INVALID_GENERATED_OPTIONS_ERROR" }; - -// type SignUpPOSTErrorResponse = -// | { -// status: "SIGN_UP_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 } -// | { status: "EMAIL_ALREADY_EXISTS_ERROR" }; - -// type SignInPOSTErrorResponse = -// | { status: "INVALID_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: "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 }; - -// 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 }; +type RegisterOptionsPOSTErrorResponse = RegisterOptionsErrorResponse; + +type SignInOptionsPOSTErrorResponse = SignInOptionsErrorResponse; + +type SignUpPOSTErrorResponse = + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_OPTIONS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; + +type SignInPOSTErrorResponse = + | { status: "INVALID_CREDENTIALS_ERROR" } // we should not disclose any other errors + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + }; + +type GenerateRecoverAccountTokenPOSTErrorResponse = + // we should not disclose any other errors + { + status: "RECOVER_ACCOUNT_NOT_ALLOWED"; + reason: string; + }; + +type RecoverAccountPOSTErrorResponse = + | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } + | { status: "INVALID_CREDENTIALS_ERROR" } // the credential is not valid for various reasons - will discover this during implementation + | { status: "OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found + | { status: "INVALID_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: "OPTIONS_NOT_FOUND_ERROR" } // i.e. options not found + | { status: "INVALID_OPTIONS_ERROR" } // i.e. timeout expired + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string }; export type APIInterface = { registerOptionsPOST: @@ -628,10 +544,7 @@ export type APIInterface = { }; } | GeneralErrorResponse - // | RegisterOptionsPOSTErrorResponse - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" } - | { status: "INVALID_EMAIL_ERROR"; err: string } - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | RegisterOptionsPOSTErrorResponse >); signInOptionsPOST: @@ -651,8 +564,7 @@ export type APIInterface = { userVerification: UserVerification; } | GeneralErrorResponse - // | SignInOptionsPOSTErrorResponse - | { status: "INVALID_GENERATED_OPTIONS_ERROR" } + | SignInOptionsPOSTErrorResponse >); signUpPOST: @@ -673,16 +585,7 @@ export type APIInterface = { session: SessionContainerInterface; } | GeneralErrorResponse - // | SignUpPOSTErrorResponse - | { - status: "SIGN_UP_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 } - | { status: "EMAIL_ALREADY_EXISTS_ERROR" } + | SignUpPOSTErrorResponse >); signInPOST: @@ -702,12 +605,7 @@ export type APIInterface = { session: SessionContainerInterface; } | GeneralErrorResponse - // | SignInPOSTErrorResponse - | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } - | { status: "INVALID_CREDENTIALS_ERROR" } + | SignInPOSTErrorResponse >); generateRecoverAccountTokenPOST: @@ -722,11 +620,7 @@ export type APIInterface = { status: "OK"; } | GeneralErrorResponse - // | GenerateRecoverAccountTokenPOSTErrorResponse - | { - status: "RECOVER_ACCOUNT_NOT_ALLOWED"; - reason: string; - } + | GenerateRecoverAccountTokenPOSTErrorResponse >); recoverAccountPOST: @@ -745,12 +639,7 @@ export type APIInterface = { email: string; } | GeneralErrorResponse - // | RecoverAccountPOSTErrorResponse - | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_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 } + | RecoverAccountPOSTErrorResponse >); registerCredentialPOST: @@ -767,15 +656,7 @@ export type APIInterface = { 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 } + | RegisterCredentialPOSTErrorResponse >); // used for checking if the email already exists before generating the credential