From 439fe7cd35c2071a5587858a2417ffbe5719a848 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 17 Dec 2024 09:13:38 +0530 Subject: [PATCH 1/6] Update function names to be more informative --- lib/ts/recipe/webauthn/index.ts | 52 ++++++++++--------- .../recipe/webauthn/recipeImplementation.ts | 18 +++---- lib/ts/recipe/webauthn/types.ts | 16 +++--- 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index e3dd279..54246c7 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -39,7 +39,7 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along a description of the created webauthn details (challenge, etc.) */ - static registerOptions( + static getRegisterOptions( input: { options?: RecipeFunctionOptions; userContext: any } & ( | { email: string } | { recoverAccountToken: string } @@ -90,7 +90,7 @@ export default class RecipeWrapper { fetchResponse: Response; } > { - return Recipe.getInstanceOrThrow().recipeImplementation.registerOptions({ + return Recipe.getInstanceOrThrow().recipeImplementation.getRegisterOptions({ ...input, userContext: getNormalisedUserContext(input?.userContext), }); @@ -108,7 +108,7 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along a description of the webauthn options (challenge, etc.) */ - static signInOptions(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise< + static getSignInOptions(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; @@ -123,7 +123,7 @@ export default class RecipeWrapper { } | GeneralErrorResponse > { - return Recipe.getInstanceOrThrow().recipeImplementation.signInOptions({ + return Recipe.getInstanceOrThrow().recipeImplementation.getSignInOptions({ ...input, userContext: getNormalisedUserContext(input?.userContext), }); @@ -217,14 +217,14 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along with a boolean indicating existence */ - static emailExists(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise< + static getEmailExists(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise< | { status: "OK"; exists: boolean; } | GeneralErrorResponse > { - return Recipe.getInstanceOrThrow().recipeImplementation.emailExists({ + return Recipe.getInstanceOrThrow().recipeImplementation.getEmailExists({ ...input, userContext: input?.userContext, }); @@ -311,7 +311,7 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email */ - static registerAndSignUp(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise< + static registerUserWithSignUp(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise< | { status: "OK"; user: User; @@ -339,7 +339,7 @@ export default class RecipeWrapper { | { status: "EMAIL_ALREADY_EXISTS_ERROR"; fetchResponse: Response } | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } > { - return Recipe.getInstanceOrThrow().recipeImplementation.registerAndSignUp({ + return Recipe.getInstanceOrThrow().recipeImplementation.registerUserWithSignUp({ ...input, userContext: input?.userContext, }); @@ -358,7 +358,11 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email */ - static authenticateAndSignIn(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise< + static authenticateUserWithSignIn(input: { + email: string; + options?: RecipeFunctionOptions; + userContext: any; + }): Promise< | { status: "OK"; user: User; @@ -376,7 +380,7 @@ export default class RecipeWrapper { } | GeneralErrorResponse > { - return Recipe.getInstanceOrThrow().recipeImplementation.authenticateAndSignIn({ + return Recipe.getInstanceOrThrow().recipeImplementation.authenticateUserWithSignIn({ ...input, userContext: input?.userContext, }); @@ -395,7 +399,7 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email */ - static registerAndRecoverAccount(input: { + static registerUserWithRecoverAccount(input: { recoverAccountToken: string; options?: RecipeFunctionOptions; userContext: any; @@ -422,7 +426,7 @@ export default class RecipeWrapper { | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } > { - return Recipe.getInstanceOrThrow().recipeImplementation.registerAndRecoverAccount({ + return Recipe.getInstanceOrThrow().recipeImplementation.registerUserWithRecoverAccount({ ...input, userContext: input?.userContext, }); @@ -430,27 +434,27 @@ export default class RecipeWrapper { } const init = RecipeWrapper.init; -const registerOptions = RecipeWrapper.registerOptions; -const signInOptions = RecipeWrapper.signInOptions; +const getRegisterOptions = RecipeWrapper.getRegisterOptions; +const getSignInOptions = RecipeWrapper.getSignInOptions; const signUp = RecipeWrapper.signUp; const signIn = RecipeWrapper.signIn; -const emailExists = RecipeWrapper.emailExists; +const getEmailExists = RecipeWrapper.getEmailExists; const generateRecoverAccountToken = RecipeWrapper.generateRecoverAccountToken; const recoverAccount = RecipeWrapper.recoverAccount; -const registerAndSignup = RecipeWrapper.registerAndSignUp; -const authenticateAndSignIn = RecipeWrapper.authenticateAndSignIn; -const registerAndRecoverAccount = RecipeWrapper.registerAndRecoverAccount; +const registerUserWithSignUp = RecipeWrapper.registerUserWithSignUp; +const authenticateUserWithSignIn = RecipeWrapper.authenticateUserWithSignIn; +const registerUserWithRecoverAccount = RecipeWrapper.registerUserWithRecoverAccount; export { init, - registerOptions, - signInOptions, + getRegisterOptions, + getSignInOptions, signUp, signIn, - emailExists, + getEmailExists, generateRecoverAccountToken, recoverAccount, - registerAndSignup, - authenticateAndSignIn, - registerAndRecoverAccount, + registerUserWithSignUp, + authenticateUserWithSignIn, + registerUserWithRecoverAccount, }; diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 32040fa..a27b1c1 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -32,7 +32,7 @@ export default function getRecipeImplementation( const querier = new Querier(recipeImplInput.recipeId, recipeImplInput.appInfo); return { - registerOptions: async function ({ + getRegisterOptions: async function ({ options, userContext, email, @@ -115,7 +115,7 @@ export default function getRecipeImplementation( fetchResponse, }; }, - signInOptions: async function ({ email, options, userContext }) { + getSignInOptions: async function ({ email, options, userContext }) { const { jsonBody, fetchResponse } = await querier.post< | { status: "OK"; @@ -244,7 +244,7 @@ export default function getRecipeImplementation( fetchResponse, }; }, - emailExists: async function ({ email, options, userContext }) { + getEmailExists: async function ({ email, options, userContext }) { const { jsonBody, fetchResponse } = await querier.get< | { status: "OK"; @@ -354,9 +354,9 @@ export default function getRecipeImplementation( fetchResponse, }; }, - registerAndSignUp: async function ({ email, options, userContext }) { + registerUserWithSignUp: async function ({ email, options, userContext }) { // Get the registration options by using the passed email ID. - const registrationOptions = await this.registerOptions({ options, userContext, email }); + const registrationOptions = await this.getRegisterOptions({ options, userContext, email }); if (registrationOptions?.status !== "OK") { // If we did not get an OK status, we need to return the error as is. @@ -391,9 +391,9 @@ export default function getRecipeImplementation( userContext, }); }, - authenticateAndSignIn: async function ({ email, options, userContext }) { + authenticateUserWithSignIn: async function ({ email, options, userContext }) { // Make a call to get the sign in options using the entered email ID. - const signInOptions = await this.signInOptions({ email, options, userContext }); + const signInOptions = await this.getSignInOptions({ email, options, userContext }); if (signInOptions?.status !== "OK") { // We want to return the error as is if status was not "OK" return signInOptions; @@ -417,10 +417,10 @@ export default function getRecipeImplementation( userContext: userContext, }); }, - registerAndRecoverAccount: async function ({ recoverAccountToken, options, userContext }) { + registerUserWithRecoverAccount: async function ({ recoverAccountToken, options, userContext }) { // Get the registration options based on the recoverAccountToken and // register the device against the user. - const registrationOptions = await this.registerOptions({ options, userContext, recoverAccountToken }); + const registrationOptions = await this.getRegisterOptions({ options, userContext, recoverAccountToken }); if (registrationOptions?.status !== "OK") { // If we did not get an OK status, we need to return the error as is. diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 4b5873e..6c2767e 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -77,7 +77,7 @@ export type CredentialPayload = { }; export type RecipeInterface = { - registerOptions: ( + getRegisterOptions: ( input: { options?: RecipeFunctionOptions; userContext: any } & ( | { email: string } | { recoverAccountToken: string } @@ -128,7 +128,7 @@ export type RecipeInterface = { fetchResponse: Response; } >; - signInOptions: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< + getSignInOptions: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< | { status: "OK"; webauthnGeneratedOptionsId: string; @@ -185,7 +185,7 @@ export type RecipeInterface = { } | GeneralErrorResponse >; - emailExists: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< + getEmailExists: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< | { status: "OK"; exists: boolean; @@ -225,7 +225,7 @@ export type RecipeInterface = { | { status: "INVALID_GENERATED_OPTIONS_ERROR"; fetchResponse: Response } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } >; - registerAndSignUp: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< + registerUserWithSignUp: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< | { status: "OK"; user: User; @@ -252,7 +252,11 @@ export type RecipeInterface = { | { status: "EMAIL_ALREADY_EXISTS_ERROR"; fetchResponse: Response } | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } >; - authenticateAndSignIn: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< + authenticateUserWithSignIn: (input: { + email: string; + options?: RecipeFunctionOptions; + userContext: any; + }) => Promise< | { status: "OK"; user: User; @@ -270,7 +274,7 @@ export type RecipeInterface = { } | GeneralErrorResponse >; - registerAndRecoverAccount: (input: { + registerUserWithRecoverAccount: (input: { recoverAccountToken: string; options?: RecipeFunctionOptions; userContext: any; From 05a3e370d12c206e0564cdaeb367a777d300905b Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 17 Dec 2024 11:41:21 +0530 Subject: [PATCH 2/6] Refactor native registrater user functionality into separate function --- lib/ts/recipe/webauthn/index.ts | 2 + .../recipe/webauthn/recipeImplementation.ts | 48 +++++++----- lib/ts/recipe/webauthn/types.ts | 74 +++++++++++-------- 3 files changed, 73 insertions(+), 51 deletions(-) diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 54246c7..99041d8 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -338,6 +338,7 @@ export default class RecipeWrapper { | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } | { status: "EMAIL_ALREADY_EXISTS_ERROR"; fetchResponse: Response } | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } + | { status: "FAILED_TO_REGISTER_USER"; error: any } > { return Recipe.getInstanceOrThrow().recipeImplementation.registerUserWithSignUp({ ...input, @@ -425,6 +426,7 @@ export default class RecipeWrapper { | { status: "INVALID_GENERATED_OPTIONS_ERROR"; fetchResponse: Response } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } + | { status: "FAILED_TO_REGISTER_USER"; error: any } > { return Recipe.getInstanceOrThrow().recipeImplementation.registerUserWithRecoverAccount({ ...input, diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index a27b1c1..846fcf2 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -354,6 +354,26 @@ export default function getRecipeImplementation( fetchResponse, }; }, + registerUser: async function ({ registrationOptions }) { + let registrationResponse: RegistrationResponseJSON; + try { + registrationResponse = await startRegistration({ optionsJSON: registrationOptions }); + } catch (error: any) { + if (error.name === "InvalidStateError") { + return { status: "AUTHENTICATOR_ALREADY_REGISTERED" }; + } + + return { + status: "FAILED_TO_REGISTER_USER", + error: error, + }; + } + + return { + status: "OK", + registrationResponse, + }; + }, registerUserWithSignUp: async function ({ email, options, userContext }) { // Get the registration options by using the passed email ID. const registrationOptions = await this.getRegisterOptions({ options, userContext, email }); @@ -371,22 +391,16 @@ export default function getRecipeImplementation( } // We should have received a valid registration options response. - let registrationResponse: RegistrationResponseJSON; - try { - registrationResponse = await startRegistration({ optionsJSON: registrationOptions }); - } catch (error: any) { - if (error.name === "InvalidStateError") { - return { status: "AUTHENTICATOR_ALREADY_REGISTERED" }; - } - - throw error; + const registerUserResponse = await this.registerUser({ registrationOptions }); + if (registerUserResponse.status !== "OK") { + return registerUserResponse; } // We should have a valid registration response for the passed credentials // and we are good to go ahead and verify them. return await this.signUp({ webauthnGeneratedOptionsId: registrationOptions.webauthnGeneratedOptionsId, - credential: registrationResponse, + credential: registerUserResponse.registrationResponse, options, userContext, }); @@ -435,21 +449,15 @@ export default function getRecipeImplementation( } // We should have received a valid registration options response. - let registrationResponse: RegistrationResponseJSON; - try { - registrationResponse = await startRegistration({ optionsJSON: registrationOptions }); - } catch (error: any) { - if (error.name === "InvalidStateError") { - return { status: "AUTHENTICATOR_ALREADY_REGISTERED" }; - } - - throw error; + const registerUserResponse = await this.registerUser({ registrationOptions }); + if (registerUserResponse.status !== "OK") { + return registerUserResponse; } return await this.recoverAccount({ token: recoverAccountToken, webauthnGeneratedOptionsId: registrationOptions.webauthnGeneratedOptionsId, - credential: registrationResponse, + credential: registerUserResponse.registrationResponse, options, userContext, }); diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 6c2767e..4f9a846 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -76,6 +76,38 @@ export type CredentialPayload = { type: "public-key"; }; +export type RegisterOptions = { + 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; + }; + fetchResponse: Response; +}; + export type RecipeInterface = { getRegisterOptions: ( input: { options?: RecipeFunctionOptions; userContext: any } & ( @@ -83,37 +115,7 @@ export type RecipeInterface = { | { 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; - }; - fetchResponse: Response; - } + | RegisterOptions | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; fetchResponse: Response; @@ -225,6 +227,14 @@ export type RecipeInterface = { | { status: "INVALID_GENERATED_OPTIONS_ERROR"; fetchResponse: Response } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } >; + registerUser: (input: { registrationOptions: RegisterOptions }) => Promise< + | { + status: "OK"; + registrationResponse: RegistrationResponseJSON; + } + | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } + | { status: "FAILED_TO_REGISTER_USER"; error: any } + >; registerUserWithSignUp: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< | { status: "OK"; @@ -251,6 +261,7 @@ export type RecipeInterface = { | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } | { status: "EMAIL_ALREADY_EXISTS_ERROR"; fetchResponse: Response } | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } + | { status: "FAILED_TO_REGISTER_USER"; error: any } >; authenticateUserWithSignIn: (input: { email: string; @@ -299,5 +310,6 @@ export type RecipeInterface = { | { status: "GENERATED_OPTIONS_NOT_FOUND_ERROR"; fetchResponse: Response } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } + | { status: "FAILED_TO_REGISTER_USER"; error: any } >; }; From 4d88f225a088295c0c8a300a505aeb83a6d39d5b Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 17 Dec 2024 11:47:23 +0530 Subject: [PATCH 3/6] Refactor authentication into it's separate function for overriding --- lib/ts/recipe/webauthn/index.ts | 1 + .../recipe/webauthn/recipeImplementation.ts | 27 ++++++++++++---- lib/ts/recipe/webauthn/types.ts | 32 ++++++++++++------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 99041d8..3b2a042 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -379,6 +379,7 @@ export default class RecipeWrapper { reason: string; fetchResponse: Response; } + | { status: "FAILED_TO_AUTHENTICATE_USER"; error: any } | GeneralErrorResponse > { return Recipe.getInstanceOrThrow().recipeImplementation.authenticateUserWithSignIn({ diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index 846fcf2..f62f002 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -405,6 +405,22 @@ export default function getRecipeImplementation( userContext, }); }, + authenticateUser: async function ({ authenticationOptions }) { + let authenticationResponse: AuthenticationResponseJSON; + try { + authenticationResponse = await startAuthentication({ optionsJSON: authenticationOptions }); + } catch (error: any) { + return { + status: "FAILED_TO_AUTHENTICATE_USER", + error: error, + }; + } + + return { + status: "OK", + authenticationResponse: authenticationResponse, + }; + }, authenticateUserWithSignIn: async function ({ email, options, userContext }) { // Make a call to get the sign in options using the entered email ID. const signInOptions = await this.getSignInOptions({ email, options, userContext }); @@ -414,19 +430,16 @@ export default function getRecipeImplementation( } // We should have the options ready and are good to start the authentication - let authenticationResponse: AuthenticationResponseJSON; - try { - authenticationResponse = await startAuthentication({ optionsJSON: signInOptions }); - } catch (error: any) { - // TODO: Do we need to do something with the error besides throwing it? - throw error; + const authenticateUserResponse = await this.authenticateUser({ authenticationOptions: signInOptions }); + if (authenticateUserResponse.status !== "OK") { + return authenticateUserResponse; } // We should have a valid authentication response at this point so we can // go ahead and sign in the user. return await this.signIn({ webauthnGeneratedOptionsId: signInOptions.webauthnGeneratedOptionsId, - credential: authenticationResponse, + credential: authenticateUserResponse.authenticationResponse, options: options, userContext: userContext, }); diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 4f9a846..5a8ac1c 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -76,7 +76,7 @@ export type CredentialPayload = { type: "public-key"; }; -export type RegisterOptions = { +export type RegistrationOptions = { status: "OK"; webauthnGeneratedOptionsId: string; rp: { @@ -108,6 +108,15 @@ export type RegisterOptions = { fetchResponse: Response; }; +export type AuthenticationOptions = { + status: "OK"; + webauthnGeneratedOptionsId: string; + challenge: string; + timeout: number; + userVerification: UserVerification; + fetchResponse: Response; +}; + export type RecipeInterface = { getRegisterOptions: ( input: { options?: RecipeFunctionOptions; userContext: any } & ( @@ -115,7 +124,7 @@ export type RecipeInterface = { | { recoverAccountToken: string } ) ) => Promise< - | RegisterOptions + | RegistrationOptions | { status: "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR"; fetchResponse: Response; @@ -131,14 +140,7 @@ export type RecipeInterface = { } >; getSignInOptions: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< - | { - status: "OK"; - webauthnGeneratedOptionsId: string; - challenge: string; - timeout: number; - userVerification: UserVerification; - fetchResponse: Response; - } + | AuthenticationOptions | { status: "INVALID_GENERATED_OPTIONS_ERROR"; fetchResponse: Response; @@ -227,7 +229,7 @@ export type RecipeInterface = { | { status: "INVALID_GENERATED_OPTIONS_ERROR"; fetchResponse: Response } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } >; - registerUser: (input: { registrationOptions: RegisterOptions }) => Promise< + registerUser: (input: { registrationOptions: RegistrationOptions }) => Promise< | { status: "OK"; registrationResponse: RegistrationResponseJSON; @@ -235,6 +237,13 @@ export type RecipeInterface = { | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } | { status: "FAILED_TO_REGISTER_USER"; error: any } >; + authenticateUser: (input: { authenticationOptions: AuthenticationOptions }) => Promise< + | { + status: "OK"; + authenticationResponse: AuthenticationResponseJSON; + } + | { status: "FAILED_TO_AUTHENTICATE_USER"; error: any } + >; registerUserWithSignUp: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< | { status: "OK"; @@ -283,6 +292,7 @@ export type RecipeInterface = { reason: string; fetchResponse: Response; } + | { status: "FAILED_TO_AUTHENTICATE_USER"; error: any } | GeneralErrorResponse >; registerUserWithRecoverAccount: (input: { From f7099301fd79d905ed04cf844b7207eb66e557a8 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 17 Dec 2024 11:58:15 +0530 Subject: [PATCH 4/6] Make registerUser an exported function of webauthn recipe --- lib/ts/recipe/webauthn/index.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 3b2a042..a8af191 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -18,7 +18,7 @@ import { GeneralErrorResponse, User } from "../../types"; import { getNormalisedUserContext } from "../../utils"; import { RecipeFunctionOptions } from "../recipeModule/types"; import Recipe from "./recipe"; -import { CredentialPayload, ResidentKey, UserInput, UserVerification } from "./types"; +import { CredentialPayload, ResidentKey, UserInput, UserVerification, RegistrationOptions } from "./types"; export default class RecipeWrapper { static init(config?: UserInput) { @@ -298,6 +298,26 @@ export default class RecipeWrapper { }); } + /** + * Register user with the passed options by using native webauthn functions. + * + * It uses `@simplewebauthn/browser` to make the webauthn calls. + * + * @param registrationOptions Options to pass for the registration. + * + * @returns `{ status: "OK", ...}` if successful along with registration response received + */ + static registerUser(input: { registrationOptions: RegistrationOptions }): Promise< + | { + status: "OK"; + registrationResponse: RegistrationResponseJSON; + } + | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } + | { status: "FAILED_TO_REGISTER_USER"; error: any } + > { + return Recipe.getInstanceOrThrow().recipeImplementation.registerUser(input); + } + /** * Register the new device and signup the user with the passed email ID. * @@ -447,6 +467,7 @@ const recoverAccount = RecipeWrapper.recoverAccount; const registerUserWithSignUp = RecipeWrapper.registerUserWithSignUp; const authenticateUserWithSignIn = RecipeWrapper.authenticateUserWithSignIn; const registerUserWithRecoverAccount = RecipeWrapper.registerUserWithRecoverAccount; +const registerUser = RecipeWrapper.registerUser; export { init, @@ -460,4 +481,5 @@ export { registerUserWithSignUp, authenticateUserWithSignIn, registerUserWithRecoverAccount, + registerUser, }; From e38fff89138e30d28ca7d363c51010de2d87b565 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 17 Dec 2024 12:01:58 +0530 Subject: [PATCH 5/6] Expose function for authentication through webauthn recipe --- lib/ts/recipe/webauthn/index.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index a8af191..36b3860 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -18,7 +18,14 @@ import { GeneralErrorResponse, User } from "../../types"; import { getNormalisedUserContext } from "../../utils"; import { RecipeFunctionOptions } from "../recipeModule/types"; import Recipe from "./recipe"; -import { CredentialPayload, ResidentKey, UserInput, UserVerification, RegistrationOptions } from "./types"; +import { + CredentialPayload, + ResidentKey, + UserInput, + UserVerification, + RegistrationOptions, + AuthenticationOptions, +} from "./types"; export default class RecipeWrapper { static init(config?: UserInput) { @@ -318,6 +325,25 @@ export default class RecipeWrapper { return Recipe.getInstanceOrThrow().recipeImplementation.registerUser(input); } + /** + * Authenticate the user with the passed options by using native webauthn functions. + * + * It uses `@simplewebauthn/browser` to make the webauthn calls. + * + * @param authenticationOptions Options to pass for the authentication. + * + * @returns `{ status: "OK", ...}` if successful along with authentication response received + */ + static authenticateUser(input: { authenticationOptions: AuthenticationOptions }): Promise< + | { + status: "OK"; + authenticationResponse: AuthenticationResponseJSON; + } + | { status: "FAILED_TO_AUTHENTICATE_USER"; error: any } + > { + return Recipe.getInstanceOrThrow().recipeImplementation.authenticateUser(input); + } + /** * Register the new device and signup the user with the passed email ID. * @@ -468,6 +494,7 @@ const registerUserWithSignUp = RecipeWrapper.registerUserWithSignUp; const authenticateUserWithSignIn = RecipeWrapper.authenticateUserWithSignIn; const registerUserWithRecoverAccount = RecipeWrapper.registerUserWithRecoverAccount; const registerUser = RecipeWrapper.registerUser; +const authenticateUser = RecipeWrapper.authenticateUser; export { init, @@ -482,4 +509,5 @@ export { authenticateUserWithSignIn, registerUserWithRecoverAccount, registerUser, + authenticateUser, }; From dbe0aa283fa41be87c6367df46167582359c54ab Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Wed, 18 Dec 2024 09:19:10 +0530 Subject: [PATCH 6/6] Rename some functions to more align with the actual functionality --- lib/ts/recipe/webauthn/index.ts | 48 ++++++++++--------- .../recipe/webauthn/recipeImplementation.ts | 36 +++++++------- lib/ts/recipe/webauthn/types.ts | 14 ++++-- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 36b3860..d943f20 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -306,7 +306,7 @@ export default class RecipeWrapper { } /** - * Register user with the passed options by using native webauthn functions. + * Register credential with the passed options by using native webauthn functions. * * It uses `@simplewebauthn/browser` to make the webauthn calls. * @@ -314,7 +314,7 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along with registration response received */ - static registerUser(input: { registrationOptions: RegistrationOptions }): Promise< + static registerCredential(input: { registrationOptions: RegistrationOptions }): Promise< | { status: "OK"; registrationResponse: RegistrationResponseJSON; @@ -322,11 +322,11 @@ export default class RecipeWrapper { | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } | { status: "FAILED_TO_REGISTER_USER"; error: any } > { - return Recipe.getInstanceOrThrow().recipeImplementation.registerUser(input); + return Recipe.getInstanceOrThrow().recipeImplementation.registerCredential(input); } /** - * Authenticate the user with the passed options by using native webauthn functions. + * Authenticate the credential with the passed options by using native webauthn functions. * * It uses `@simplewebauthn/browser` to make the webauthn calls. * @@ -334,14 +334,14 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along with authentication response received */ - static authenticateUser(input: { authenticationOptions: AuthenticationOptions }): Promise< + static authenticateCredential(input: { authenticationOptions: AuthenticationOptions }): Promise< | { status: "OK"; authenticationResponse: AuthenticationResponseJSON; } | { status: "FAILED_TO_AUTHENTICATE_USER"; error: any } > { - return Recipe.getInstanceOrThrow().recipeImplementation.authenticateUser(input); + return Recipe.getInstanceOrThrow().recipeImplementation.authenticateCredential(input); } /** @@ -357,7 +357,11 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email */ - static registerUserWithSignUp(input: { email: string; options?: RecipeFunctionOptions; userContext: any }): Promise< + static registerCredentialWithSignUp(input: { + email: string; + options?: RecipeFunctionOptions; + userContext: any; + }): Promise< | { status: "OK"; user: User; @@ -386,7 +390,7 @@ export default class RecipeWrapper { | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } | { status: "FAILED_TO_REGISTER_USER"; error: any } > { - return Recipe.getInstanceOrThrow().recipeImplementation.registerUserWithSignUp({ + return Recipe.getInstanceOrThrow().recipeImplementation.registerCredentialWithSignUp({ ...input, userContext: input?.userContext, }); @@ -405,7 +409,7 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email */ - static authenticateUserWithSignIn(input: { + static authenticateCredentialWithSignIn(input: { email: string; options?: RecipeFunctionOptions; userContext: any; @@ -428,7 +432,7 @@ export default class RecipeWrapper { | { status: "FAILED_TO_AUTHENTICATE_USER"; error: any } | GeneralErrorResponse > { - return Recipe.getInstanceOrThrow().recipeImplementation.authenticateUserWithSignIn({ + return Recipe.getInstanceOrThrow().recipeImplementation.authenticateCredentialWithSignIn({ ...input, userContext: input?.userContext, }); @@ -447,7 +451,7 @@ export default class RecipeWrapper { * * @returns `{ status: "OK", ...}` if successful along a description of the user details (id, etc.) and email */ - static registerUserWithRecoverAccount(input: { + static registerCredentialWithRecoverAccount(input: { recoverAccountToken: string; options?: RecipeFunctionOptions; userContext: any; @@ -475,7 +479,7 @@ export default class RecipeWrapper { | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } | { status: "FAILED_TO_REGISTER_USER"; error: any } > { - return Recipe.getInstanceOrThrow().recipeImplementation.registerUserWithRecoverAccount({ + return Recipe.getInstanceOrThrow().recipeImplementation.registerCredentialWithRecoverAccount({ ...input, userContext: input?.userContext, }); @@ -490,11 +494,11 @@ const signIn = RecipeWrapper.signIn; const getEmailExists = RecipeWrapper.getEmailExists; const generateRecoverAccountToken = RecipeWrapper.generateRecoverAccountToken; const recoverAccount = RecipeWrapper.recoverAccount; -const registerUserWithSignUp = RecipeWrapper.registerUserWithSignUp; -const authenticateUserWithSignIn = RecipeWrapper.authenticateUserWithSignIn; -const registerUserWithRecoverAccount = RecipeWrapper.registerUserWithRecoverAccount; -const registerUser = RecipeWrapper.registerUser; -const authenticateUser = RecipeWrapper.authenticateUser; +const registerCredentialWithSignUp = RecipeWrapper.registerCredentialWithSignUp; +const authenticateCredentialWithSignIn = RecipeWrapper.authenticateCredentialWithSignIn; +const registerCredentialWithRecoverAccount = RecipeWrapper.registerCredentialWithRecoverAccount; +const registerCredential = RecipeWrapper.registerCredential; +const authenticateCredential = RecipeWrapper.authenticateCredential; export { init, @@ -505,9 +509,9 @@ export { getEmailExists, generateRecoverAccountToken, recoverAccount, - registerUserWithSignUp, - authenticateUserWithSignIn, - registerUserWithRecoverAccount, - registerUser, - authenticateUser, + registerCredentialWithSignUp, + authenticateCredentialWithSignIn, + registerCredentialWithRecoverAccount, + registerCredential, + authenticateCredential, }; diff --git a/lib/ts/recipe/webauthn/recipeImplementation.ts b/lib/ts/recipe/webauthn/recipeImplementation.ts index f62f002..40fe27c 100644 --- a/lib/ts/recipe/webauthn/recipeImplementation.ts +++ b/lib/ts/recipe/webauthn/recipeImplementation.ts @@ -354,7 +354,7 @@ export default function getRecipeImplementation( fetchResponse, }; }, - registerUser: async function ({ registrationOptions }) { + registerCredential: async function ({ registrationOptions }) { let registrationResponse: RegistrationResponseJSON; try { registrationResponse = await startRegistration({ optionsJSON: registrationOptions }); @@ -374,7 +374,7 @@ export default function getRecipeImplementation( registrationResponse, }; }, - registerUserWithSignUp: async function ({ email, options, userContext }) { + registerCredentialWithSignUp: async function ({ email, options, userContext }) { // Get the registration options by using the passed email ID. const registrationOptions = await this.getRegisterOptions({ options, userContext, email }); if (registrationOptions?.status !== "OK") { @@ -391,21 +391,21 @@ export default function getRecipeImplementation( } // We should have received a valid registration options response. - const registerUserResponse = await this.registerUser({ registrationOptions }); - if (registerUserResponse.status !== "OK") { - return registerUserResponse; + const registerCredentialResponse = await this.registerCredential({ registrationOptions }); + if (registerCredentialResponse.status !== "OK") { + return registerCredentialResponse; } // We should have a valid registration response for the passed credentials // and we are good to go ahead and verify them. return await this.signUp({ webauthnGeneratedOptionsId: registrationOptions.webauthnGeneratedOptionsId, - credential: registerUserResponse.registrationResponse, + credential: registerCredentialResponse.registrationResponse, options, userContext, }); }, - authenticateUser: async function ({ authenticationOptions }) { + authenticateCredential: async function ({ authenticationOptions }) { let authenticationResponse: AuthenticationResponseJSON; try { authenticationResponse = await startAuthentication({ optionsJSON: authenticationOptions }); @@ -421,7 +421,7 @@ export default function getRecipeImplementation( authenticationResponse: authenticationResponse, }; }, - authenticateUserWithSignIn: async function ({ email, options, userContext }) { + authenticateCredentialWithSignIn: async function ({ email, options, userContext }) { // Make a call to get the sign in options using the entered email ID. const signInOptions = await this.getSignInOptions({ email, options, userContext }); if (signInOptions?.status !== "OK") { @@ -430,21 +430,23 @@ export default function getRecipeImplementation( } // We should have the options ready and are good to start the authentication - const authenticateUserResponse = await this.authenticateUser({ authenticationOptions: signInOptions }); - if (authenticateUserResponse.status !== "OK") { - return authenticateUserResponse; + const authenticateCredentialResponse = await this.authenticateCredential({ + authenticationOptions: signInOptions, + }); + if (authenticateCredentialResponse.status !== "OK") { + return authenticateCredentialResponse; } // We should have a valid authentication response at this point so we can // go ahead and sign in the user. return await this.signIn({ webauthnGeneratedOptionsId: signInOptions.webauthnGeneratedOptionsId, - credential: authenticateUserResponse.authenticationResponse, + credential: authenticateCredentialResponse.authenticationResponse, options: options, userContext: userContext, }); }, - registerUserWithRecoverAccount: async function ({ recoverAccountToken, options, userContext }) { + registerCredentialWithRecoverAccount: async function ({ recoverAccountToken, options, userContext }) { // Get the registration options based on the recoverAccountToken and // register the device against the user. const registrationOptions = await this.getRegisterOptions({ options, userContext, recoverAccountToken }); @@ -462,15 +464,15 @@ export default function getRecipeImplementation( } // We should have received a valid registration options response. - const registerUserResponse = await this.registerUser({ registrationOptions }); - if (registerUserResponse.status !== "OK") { - return registerUserResponse; + const registerCredentialResponse = await this.registerCredential({ registrationOptions }); + if (registerCredentialResponse.status !== "OK") { + return registerCredentialResponse; } return await this.recoverAccount({ token: recoverAccountToken, webauthnGeneratedOptionsId: registrationOptions.webauthnGeneratedOptionsId, - credential: registerUserResponse.registrationResponse, + credential: registerCredentialResponse.registrationResponse, options, userContext, }); diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index 5a8ac1c..75e3169 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -229,7 +229,7 @@ export type RecipeInterface = { | { status: "INVALID_GENERATED_OPTIONS_ERROR"; fetchResponse: Response } | { status: "INVALID_AUTHENTICATOR_ERROR"; reason: string; fetchResponse: Response } >; - registerUser: (input: { registrationOptions: RegistrationOptions }) => Promise< + registerCredential: (input: { registrationOptions: RegistrationOptions }) => Promise< | { status: "OK"; registrationResponse: RegistrationResponseJSON; @@ -237,14 +237,18 @@ export type RecipeInterface = { | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } | { status: "FAILED_TO_REGISTER_USER"; error: any } >; - authenticateUser: (input: { authenticationOptions: AuthenticationOptions }) => Promise< + authenticateCredential: (input: { authenticationOptions: AuthenticationOptions }) => Promise< | { status: "OK"; authenticationResponse: AuthenticationResponseJSON; } | { status: "FAILED_TO_AUTHENTICATE_USER"; error: any } >; - registerUserWithSignUp: (input: { email: string; options?: RecipeFunctionOptions; userContext: any }) => Promise< + registerCredentialWithSignUp: (input: { + email: string; + options?: RecipeFunctionOptions; + userContext: any; + }) => Promise< | { status: "OK"; user: User; @@ -272,7 +276,7 @@ export type RecipeInterface = { | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } | { status: "FAILED_TO_REGISTER_USER"; error: any } >; - authenticateUserWithSignIn: (input: { + authenticateCredentialWithSignIn: (input: { email: string; options?: RecipeFunctionOptions; userContext: any; @@ -295,7 +299,7 @@ export type RecipeInterface = { | { status: "FAILED_TO_AUTHENTICATE_USER"; error: any } | GeneralErrorResponse >; - registerUserWithRecoverAccount: (input: { + registerCredentialWithRecoverAccount: (input: { recoverAccountToken: string; options?: RecipeFunctionOptions; userContext: any;