diff --git a/CHANGELOG.md b/CHANGELOG.md index aacc5e850..58e9f13ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.50] - 2025-08-11 + +- Add WebAuthn credential management methods: `listCredentials`, `removeCredential` +- Added `createAndRegisterCredentialWithUser` method that creates and registers a credential with a user + +### Breaking changes + +- The `registerCredential` method has been renamed to `createCredential`. This was done to better represent the creation of a credential and not the actual registration of it with the backend API. The new `registerCredential` implementation now calls the backend API. + ## [0.49.1] - 2025-03-27 - Fixed a type issue making the WebauthnPreBuitlUI not produce a type error when added to the prebuiltUIList diff --git a/lib/build/recipe/webauthn/index.d.ts b/lib/build/recipe/webauthn/index.d.ts index e47ec016e..e97066873 100644 --- a/lib/build/recipe/webauthn/index.d.ts +++ b/lib/build/recipe/webauthn/index.d.ts @@ -212,7 +212,7 @@ export default class Wrapper { fetchResponse: Response; } >; - static registerCredential(input: { + static createCredential(input: { registrationOptions: Omit; userContext: any; }): Promise< @@ -389,6 +389,100 @@ export default class Wrapper { error: any; } >; + static listCredentials(input: { options?: RecipeFunctionOptions; userContext: any }): Promise< + | { + status: "OK"; + credentials: { + webauthnCredentialId: string; + relyingPartyId: string; + recipeUserId: string; + createdAt: number; + }[]; + } + | GeneralErrorResponse + >; + static removeCredential(input: { webauthnCredentialId: string; userContext: any }): Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + | { + status: "CREDENTIAL_NOT_FOUND_ERROR"; + fetchResponse: Response; + } + >; + static createAndRegisterCredentialForSessionUser(input: { + recipeUserId: string; + email: string; + options?: RecipeFunctionOptions; + userContext: any; + }): Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + | { + status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; + reason?: string; + } + | { + status: "INVALID_EMAIL_ERROR"; + err: string; + } + | { + status: "INVALID_CREDENTIALS_ERROR"; + } + | { + status: "OPTIONS_NOT_FOUND_ERROR"; + } + | { + status: "INVALID_OPTIONS_ERROR"; + } + | { + status: "INVALID_AUTHENTICATOR_ERROR"; + reason?: string; + } + | { + status: "AUTHENTICATOR_ALREADY_REGISTERED"; + } + | { + status: "FAILED_TO_REGISTER_USER"; + error: any; + } + | { + status: "WEBAUTHN_NOT_SUPPORTED"; + error: any; + } + >; + static registerCredential(input: { + recipeUserId: string; + webauthnGeneratedOptionsId: string; + credential: RegistrationResponseJSON; + options?: RecipeFunctionOptions; + userContext: any; + }): Promise< + | { + status: "OK"; + } + | GeneralErrorResponse + | { + 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; + } + >; static doesBrowserSupportWebAuthn(input: { userContext: any }): Promise< | { status: "OK"; @@ -414,11 +508,15 @@ declare const signIn: typeof Wrapper.signIn; declare const getEmailExists: typeof Wrapper.getEmailExists; declare const generateRecoverAccountToken: typeof Wrapper.generateRecoverAccountToken; declare const recoverAccount: typeof Wrapper.recoverAccount; -declare const registerCredential: typeof Wrapper.registerCredential; +declare const createCredential: typeof Wrapper.createCredential; declare const authenticateCredential: typeof Wrapper.authenticateCredential; declare const registerCredentialWithSignUp: typeof Wrapper.registerCredentialWithSignUp; declare const authenticateCredentialWithSignIn: typeof Wrapper.authenticateCredentialWithSignIn; declare const registerCredentialWithRecoverAccount: typeof Wrapper.registerCredentialWithRecoverAccount; +declare const createAndRegisterCredentialForSessionUser: typeof Wrapper.createAndRegisterCredentialForSessionUser; +declare const listCredentials: typeof Wrapper.listCredentials; +declare const removeCredential: typeof Wrapper.removeCredential; +declare const registerCredential: typeof Wrapper.registerCredential; declare const doesBrowserSupportWebAuthn: typeof Wrapper.doesBrowserSupportWebAuthn; declare const WebauthnComponentsOverrideProvider: import("react").FC< import("react").PropsWithChildren<{ @@ -434,11 +532,15 @@ export { getEmailExists, generateRecoverAccountToken, recoverAccount, - registerCredential, + createCredential, authenticateCredential, registerCredentialWithSignUp, authenticateCredentialWithSignIn, registerCredentialWithRecoverAccount, + createAndRegisterCredentialForSessionUser, doesBrowserSupportWebAuthn, WebauthnComponentsOverrideProvider, + listCredentials, + removeCredential, + registerCredential, }; diff --git a/lib/build/recipe/webauthn/types.d.ts b/lib/build/recipe/webauthn/types.d.ts index 2fdb40499..cace33386 100644 --- a/lib/build/recipe/webauthn/types.d.ts +++ b/lib/build/recipe/webauthn/types.d.ts @@ -38,7 +38,10 @@ export declare type PreAndPostAPIHookAction = | "SIGN_IN" | "EMAIL_EXISTS" | "GENERATE_RECOVER_ACCOUNT_TOKEN" - | "RECOVER_ACCOUNT"; + | "RECOVER_ACCOUNT" + | "REGISTER_CREDENTIAL" + | "REMOVE_CREDENTIAL" + | "LIST_CREDENTIALS"; export declare type OnHandleEventContext = | { action: "SUCCESS"; diff --git a/lib/build/webauthn.js b/lib/build/webauthn.js index a412b3e76..a89208f5e 100644 --- a/lib/build/webauthn.js +++ b/lib/build/webauthn.js @@ -64,8 +64,8 @@ var Wrapper = /** @class */ (function () { Wrapper.recoverAccount = function (input) { return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.recoverAccount(input); }; - Wrapper.registerCredential = function (input) { - return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.registerCredential(input); + Wrapper.createCredential = function (input) { + return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.createCredential(input); }; Wrapper.authenticateCredential = function (input) { return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.authenticateCredential(input); @@ -79,6 +79,18 @@ var Wrapper = /** @class */ (function () { Wrapper.registerCredentialWithRecoverAccount = function (input) { return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.registerCredentialWithRecoverAccount(input); }; + Wrapper.listCredentials = function (input) { + return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.listCredentials(input); + }; + Wrapper.removeCredential = function (input) { + return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.removeCredential(input); + }; + Wrapper.createAndRegisterCredentialForSessionUser = function (input) { + return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.createAndRegisterCredentialForSessionUser(input); + }; + Wrapper.registerCredential = function (input) { + return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.registerCredential(input); + }; Wrapper.doesBrowserSupportWebAuthn = function (input) { return recipe.Webauthn.getInstanceOrThrow().webJSRecipe.doesBrowserSupportWebAuthn(input); }; @@ -93,17 +105,23 @@ var signIn = Wrapper.signIn; var getEmailExists = Wrapper.getEmailExists; var generateRecoverAccountToken = Wrapper.generateRecoverAccountToken; var recoverAccount = Wrapper.recoverAccount; -var registerCredential = Wrapper.registerCredential; +var createCredential = Wrapper.createCredential; var authenticateCredential = Wrapper.authenticateCredential; var registerCredentialWithSignUp = Wrapper.registerCredentialWithSignUp; var authenticateCredentialWithSignIn = Wrapper.authenticateCredentialWithSignIn; var registerCredentialWithRecoverAccount = Wrapper.registerCredentialWithRecoverAccount; +var createAndRegisterCredentialForSessionUser = Wrapper.createAndRegisterCredentialForSessionUser; +var listCredentials = Wrapper.listCredentials; +var removeCredential = Wrapper.removeCredential; +var registerCredential = Wrapper.registerCredential; var doesBrowserSupportWebAuthn = Wrapper.doesBrowserSupportWebAuthn; var WebauthnComponentsOverrideProvider = Wrapper.ComponentsOverrideProvider; exports.WebauthnComponentsOverrideProvider = WebauthnComponentsOverrideProvider; exports.authenticateCredential = authenticateCredential; exports.authenticateCredentialWithSignIn = authenticateCredentialWithSignIn; +exports.createAndRegisterCredentialForSessionUser = createAndRegisterCredentialForSessionUser; +exports.createCredential = createCredential; exports.default = Wrapper; exports.doesBrowserSupportWebAuthn = doesBrowserSupportWebAuthn; exports.generateRecoverAccountToken = generateRecoverAccountToken; @@ -111,9 +129,11 @@ exports.getEmailExists = getEmailExists; exports.getRegisterOptions = getRegisterOptions; exports.getSignInOptions = getSignInOptions; exports.init = init; +exports.listCredentials = listCredentials; exports.recoverAccount = recoverAccount; exports.registerCredential = registerCredential; exports.registerCredentialWithRecoverAccount = registerCredentialWithRecoverAccount; exports.registerCredentialWithSignUp = registerCredentialWithSignUp; +exports.removeCredential = removeCredential; exports.signIn = signIn; exports.signUp = signUp; diff --git a/lib/build/webauthnprebuiltui.js b/lib/build/webauthnprebuiltui.js index 23f1c7da4..229dd011e 100644 --- a/lib/build/webauthnprebuiltui.js +++ b/lib/build/webauthnprebuiltui.js @@ -804,7 +804,7 @@ var RecoverAccountUsingToken = function (props) { } return [ 4 /*yield*/, - props.recipe.webJSRecipe.registerCredential({ + props.recipe.webJSRecipe.createCredential({ registrationOptions: registerOptions, userContext: props.userContext, }), diff --git a/lib/ts/recipe/webauthn/components/features/recoverAccountWithToken/index.tsx b/lib/ts/recipe/webauthn/components/features/recoverAccountWithToken/index.tsx index 30a0aa5a1..d31faf419 100644 --- a/lib/ts/recipe/webauthn/components/features/recoverAccountWithToken/index.tsx +++ b/lib/ts/recipe/webauthn/components/features/recoverAccountWithToken/index.tsx @@ -131,7 +131,7 @@ export const RecoverAccountUsingToken: React.FC = // Use the register options to register the credential and recover the account. // We should have received a valid registration options response. - const registerCredentialResponse = await props.recipe.webJSRecipe.registerCredential({ + const registerCredentialResponse = await props.recipe.webJSRecipe.createCredential({ registrationOptions: registerOptions, userContext: props.userContext, }); diff --git a/lib/ts/recipe/webauthn/index.ts b/lib/ts/recipe/webauthn/index.ts index 4dfa3fbdb..c7b1a4f86 100644 --- a/lib/ts/recipe/webauthn/index.ts +++ b/lib/ts/recipe/webauthn/index.ts @@ -247,7 +247,7 @@ export default class Wrapper { return Webauthn.getInstanceOrThrow().webJSRecipe.recoverAccount(input); } - static registerCredential(input: { + static createCredential(input: { registrationOptions: Omit; userContext: any; }): Promise< @@ -267,7 +267,7 @@ export default class Wrapper { error: any; } > { - return Webauthn.getInstanceOrThrow().webJSRecipe.registerCredential(input); + return Webauthn.getInstanceOrThrow().webJSRecipe.createCredential(input); } static authenticateCredential(input: { @@ -439,6 +439,69 @@ export default class Wrapper { return Webauthn.getInstanceOrThrow().webJSRecipe.registerCredentialWithRecoverAccount(input); } + static listCredentials(input: { options?: RecipeFunctionOptions; userContext: any }): Promise< + | { + status: "OK"; + credentials: { + webauthnCredentialId: string; + relyingPartyId: string; + recipeUserId: string; + createdAt: number; + }[]; + } + | GeneralErrorResponse + > { + return Webauthn.getInstanceOrThrow().webJSRecipe.listCredentials(input); + } + + static removeCredential(input: { + webauthnCredentialId: string; + userContext: any; + }): Promise< + { status: "OK" } | GeneralErrorResponse | { status: "CREDENTIAL_NOT_FOUND_ERROR"; fetchResponse: Response } + > { + return Webauthn.getInstanceOrThrow().webJSRecipe.removeCredential(input); + } + + static createAndRegisterCredentialForSessionUser(input: { + recipeUserId: string; + email: string; + options?: RecipeFunctionOptions; + userContext: any; + }): Promise< + | { status: "OK" } + | GeneralErrorResponse + | { status: "REGISTER_CREDENTIAL_NOT_ALLOWED"; reason?: string } + | { status: "INVALID_EMAIL_ERROR"; err: string } + | { status: "INVALID_CREDENTIALS_ERROR" } + | { status: "OPTIONS_NOT_FOUND_ERROR" } + | { status: "INVALID_OPTIONS_ERROR" } + | { status: "INVALID_AUTHENTICATOR_ERROR"; reason?: string } + | { status: "AUTHENTICATOR_ALREADY_REGISTERED" } + | { status: "FAILED_TO_REGISTER_USER"; error: any } + | { status: "WEBAUTHN_NOT_SUPPORTED"; error: any } + > { + return Webauthn.getInstanceOrThrow().webJSRecipe.createAndRegisterCredentialForSessionUser(input); + } + + static registerCredential(input: { + recipeUserId: string; + webauthnGeneratedOptionsId: string; + credential: RegistrationResponseJSON; + options?: RecipeFunctionOptions; + userContext: any; + }): Promise< + | { status: "OK" } + | GeneralErrorResponse + | { 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 } + > { + return Webauthn.getInstanceOrThrow().webJSRecipe.registerCredential(input); + } + static doesBrowserSupportWebAuthn(input: { userContext: any }): Promise< | { status: "OK"; @@ -464,11 +527,15 @@ const signIn = Wrapper.signIn; const getEmailExists = Wrapper.getEmailExists; const generateRecoverAccountToken = Wrapper.generateRecoverAccountToken; const recoverAccount = Wrapper.recoverAccount; -const registerCredential = Wrapper.registerCredential; +const createCredential = Wrapper.createCredential; const authenticateCredential = Wrapper.authenticateCredential; const registerCredentialWithSignUp = Wrapper.registerCredentialWithSignUp; const authenticateCredentialWithSignIn = Wrapper.authenticateCredentialWithSignIn; const registerCredentialWithRecoverAccount = Wrapper.registerCredentialWithRecoverAccount; +const createAndRegisterCredentialForSessionUser = Wrapper.createAndRegisterCredentialForSessionUser; +const listCredentials = Wrapper.listCredentials; +const removeCredential = Wrapper.removeCredential; +const registerCredential = Wrapper.registerCredential; const doesBrowserSupportWebAuthn = Wrapper.doesBrowserSupportWebAuthn; const WebauthnComponentsOverrideProvider = Wrapper.ComponentsOverrideProvider; @@ -481,11 +548,15 @@ export { getEmailExists, generateRecoverAccountToken, recoverAccount, - registerCredential, + createCredential, authenticateCredential, registerCredentialWithSignUp, authenticateCredentialWithSignIn, registerCredentialWithRecoverAccount, + createAndRegisterCredentialForSessionUser, doesBrowserSupportWebAuthn, WebauthnComponentsOverrideProvider, + listCredentials, + removeCredential, + registerCredential, }; diff --git a/lib/ts/recipe/webauthn/types.ts b/lib/ts/recipe/webauthn/types.ts index ec4b28081..6da6d01e1 100644 --- a/lib/ts/recipe/webauthn/types.ts +++ b/lib/ts/recipe/webauthn/types.ts @@ -57,7 +57,10 @@ export type PreAndPostAPIHookAction = | "SIGN_IN" | "EMAIL_EXISTS" | "GENERATE_RECOVER_ACCOUNT_TOKEN" - | "RECOVER_ACCOUNT"; + | "RECOVER_ACCOUNT" + | "REGISTER_CREDENTIAL" + | "REMOVE_CREDENTIAL" + | "LIST_CREDENTIALS"; export type OnHandleEventContext = | { diff --git a/package-lock.json b/package-lock.json index c2564b15a..bedfbbbd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -106,7 +106,7 @@ "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0", - "supertokens-web-js": "^0.15" + "supertokens-web-js": "github:supertokens/supertokens-web-js#0.16" } }, "eslint": { @@ -24452,8 +24452,8 @@ "license": "Apache-2.0" }, "node_modules/supertokens-web-js": { - "version": "0.15.0", - "resolved": "git+ssh://git@github.com/supertokens/supertokens-web-js.git#64366a553d3e5611a2ec5cd9ffa66856dd0fd063", + "version": "0.16.0", + "resolved": "git+ssh://git@github.com/supertokens/supertokens-web-js.git#0df91a3837c76406d13d86d8e5dbb9a4a65de2a2", "license": "Apache-2.0", "peer": true, "dependencies": { diff --git a/package.json b/package.json index 14232bcd3..9a3d8a7da 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0", - "supertokens-web-js": "^0.15" + "supertokens-web-js": "github:supertokens/supertokens-web-js#0.16" }, "scripts": { "init": "bash ./init.sh",