diff --git a/.changeset/gorgeous-schools-drive.md b/.changeset/gorgeous-schools-drive.md new file mode 100644 index 00000000000..f74e71ad6ea --- /dev/null +++ b/.changeset/gorgeous-schools-drive.md @@ -0,0 +1,40 @@ +--- +'@clerk/localizations': minor +'@clerk/clerk-js': minor +'@clerk/types': minor +--- + +Move passkey related apis to stable: + +- Register passkey for a user + Usage: `await clerk.user.createPasskey()` +- Authenticate with passkey + Usage: `await clerk.client.signIn.authenticateWithPasskey()` + ```ts + try { + await clerk.client.signIn.authenticateWithPasskey(...args); + }catch (e) { + if (isClerkRuntimeError(e)) { + if (err.code === 'passkey_operation_aborted') { + ... + } + } + } + ``` +- ClerkRuntimeError codes introduced: + - `passkey_not_supported` + - `passkeys_pa_not_supported` + - `passkey_invalid_rpID_or_domain` + - `passkey_already_exists` + - `passkey_operation_aborted` + - `passkey_retrieval_cancelled` + - `passkey_retrieval_failed` + - `passkey_registration_cancelled` + - `passkey_registration_failed` + +- Get the user's passkeys + `clerk.user.passkeys` +- Update the name of a passkey + `clerk.user.passkeys?.[0].update({name:'Company issued passkey'})` +- Delete a passkey + `clerk.user.passkeys?.[0].delete()` diff --git a/packages/clerk-js/src/core/resources/Passkey.ts b/packages/clerk-js/src/core/resources/Passkey.ts index 96557ccebc4..a9c83aadb2c 100644 --- a/packages/clerk-js/src/core/resources/Passkey.ts +++ b/packages/clerk-js/src/core/resources/Passkey.ts @@ -1,10 +1,10 @@ import type { - __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse, DeletedObjectJSON, DeletedObjectResource, PasskeyJSON, PasskeyResource, PasskeyVerificationResource, + PublicKeyCredentialWithAuthenticatorAttestationResponse, UpdatePasskeyParams, } from '@clerk/types'; @@ -42,7 +42,7 @@ export class Passkey extends BaseResource implements PasskeyResource { private static async attemptVerification( passkeyId: string, - credential: __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse, + credential: PublicKeyCredentialWithAuthenticatorAttestationResponse, ) { const jsonPublicKeyCredential = serializePublicKeyCredential(credential); return BaseResource._fetch({ diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index 742c8afbdd9..3d41952e4f3 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -1,13 +1,15 @@ import { deepSnakeToCamel, Poller } from '@clerk/shared'; import type { - __experimental_PasskeyFactor, AttemptFirstFactorParams, AttemptSecondFactorParams, + AuthenticateWithPasskeyParams, AuthenticateWithRedirectParams, AuthenticateWithWeb3Params, CreateEmailLinkFlowReturn, EmailCodeConfig, EmailLinkConfig, + PassKeyConfig, + PasskeyFactor, PhoneCodeConfig, PrepareFirstFactorParams, PrepareSecondFactorParams, @@ -85,9 +87,7 @@ export class SignIn extends BaseResource implements SignInResource { prepareFirstFactor = (factor: PrepareFirstFactorParams): Promise => { let config; switch (factor.strategy) { - // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level case 'passkey': - // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level config = {} as PassKeyConfig; break; case 'email_link': @@ -132,10 +132,8 @@ export class SignIn extends BaseResource implements SignInResource { attemptFirstFactor = (attemptFactor: AttemptFirstFactorParams): Promise => { let config; switch (attemptFactor.strategy) { - // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level case 'passkey': config = { - // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level publicKeyCredential: JSON.stringify(serializePublicKeyCredentialAssertion(attemptFactor.publicKeyCredential)), }; break; @@ -263,9 +261,7 @@ export class SignIn extends BaseResource implements SignInResource { }); }; - public __experimental_authenticateWithPasskey = async (params?: { - flow?: 'autofill' | 'discoverable'; - }): Promise => { + public authenticateWithPasskey = async (params?: AuthenticateWithPasskeyParams): Promise => { const { flow } = params || {}; /** @@ -286,7 +282,7 @@ export class SignIn extends BaseResource implements SignInResource { const passKeyFactor = this.supportedFirstFactors.find( // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level f => f.strategy === 'passkey', - ) as __experimental_PasskeyFactor; + ) as PasskeyFactor; if (!passKeyFactor) { clerkVerifyPasskeyCalledBeforeCreate(); @@ -324,11 +320,17 @@ export class SignIn extends BaseResource implements SignInResource { return this.attemptFirstFactor({ publicKeyCredential, - // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level strategy: 'passkey', }); }; + // TODO-PASSKEYS: Remove in the next minor + public __experimental_authenticateWithPasskey = async (params?: { + flow?: 'autofill' | 'discoverable'; + }): Promise => { + return this.authenticateWithPasskey(params); + }; + validatePassword: ReturnType = (password, cb) => { if (SignIn.clerk.__unstable__environment?.userSettings.passwordSettings) { return createValidatePassword({ diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index d9c8b8db19d..3f3ba657e80 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -61,7 +61,9 @@ export class User extends BaseResource implements UserResource { phoneNumbers: PhoneNumberResource[] = []; web3Wallets: Web3WalletResource[] = []; externalAccounts: ExternalAccountResource[] = []; + // TODO-PASSKEY: Remove in the next minor __experimental_passkeys: PasskeyResource[] = []; + passkeys: PasskeyResource[] = []; samlAccounts: SamlAccountResource[] = []; @@ -135,6 +137,10 @@ export class User extends BaseResource implements UserResource { return Passkey.registerPasskey(); }; + createPasskey = (): Promise => { + return Passkey.registerPasskey(); + }; + createPhoneNumber = (params: CreatePhoneNumberParams): Promise => { const { phoneNumber } = params || {}; return new PhoneNumber( @@ -339,6 +345,7 @@ export class User extends BaseResource implements UserResource { ); this.__experimental_passkeys = (data.passkeys || []).map(passkey => new Passkey(passkey)); + this.passkeys = (data.passkeys || []).map(passkey => new Passkey(passkey)); this.organizationMemberships = (data.organization_memberships || []).map(om => new OrganizationMembership(om)); diff --git a/packages/clerk-js/src/core/resources/Verification.ts b/packages/clerk-js/src/core/resources/Verification.ts index 48c68ede2de..e301098065e 100644 --- a/packages/clerk-js/src/core/resources/Verification.ts +++ b/packages/clerk-js/src/core/resources/Verification.ts @@ -1,9 +1,9 @@ import { parseError } from '@clerk/shared/error'; import type { - __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions, ClerkAPIError, PasskeyVerificationResource, PublicKeyCredentialCreationOptionsJSON, + PublicKeyCredentialCreationOptionsWithoutExtensions, SignUpVerificationJSON, SignUpVerificationResource, SignUpVerificationsJSON, @@ -58,7 +58,7 @@ export class Verification extends BaseResource implements VerificationResource { } export class PasskeyVerification extends Verification implements PasskeyVerificationResource { - publicKey: __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions | null = null; + publicKey: PublicKeyCredentialCreationOptionsWithoutExtensions | null = null; constructor(data: VerificationJSON | null) { super(data); diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOne.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOne.test.tsx index 5e3152672f7..5602f5f9299 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOne.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOne.test.tsx @@ -765,7 +765,7 @@ describe('SignInFactorOne', () => { f.withPreferredSignInStrategy({ strategy: 'otp' }); f.startSignInWithEmailAddress({ supportPasskey: true, supportEmailCode: true }); }); - fixtures.signIn.__experimental_authenticateWithPasskey.mockResolvedValue({ + fixtures.signIn.authenticateWithPasskey.mockResolvedValue({ status: 'complete', } as SignInResource); const { userEvent } = render(, { wrapper }); @@ -773,7 +773,7 @@ describe('SignInFactorOne', () => { await userEvent.click(screen.getByText('Continue')); await waitFor(() => { - expect(fixtures.signIn.__experimental_authenticateWithPasskey).toHaveBeenCalled(); + expect(fixtures.signIn.authenticateWithPasskey).toHaveBeenCalled(); }); }); }); diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx index 7364a45d50d..5888becc4ee 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx @@ -80,14 +80,14 @@ describe('SignInStart', () => { }); }); - fixtures.signIn.__experimental_authenticateWithPasskey.mockResolvedValue({ + fixtures.signIn.authenticateWithPasskey.mockResolvedValue({ status: 'complete', } as SignInResource); render(, { wrapper }); expect(screen.queryByText('Use passkey instead')).not.toBeInTheDocument(); await waitFor(() => { - expect(fixtures.signIn.__experimental_authenticateWithPasskey).toHaveBeenCalledWith({ + expect(fixtures.signIn.authenticateWithPasskey).toHaveBeenCalledWith({ flow: 'autofill', }); }); @@ -145,12 +145,12 @@ describe('SignInStart', () => { show_sign_in_button: true, }); }); - fixtures.signIn.__experimental_authenticateWithPasskey.mockResolvedValue({ + fixtures.signIn.authenticateWithPasskey.mockResolvedValue({ status: 'complete', } as SignInResource); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use passkey instead')); - expect(fixtures.signIn.__experimental_authenticateWithPasskey).toHaveBeenCalledWith({ + expect(fixtures.signIn.authenticateWithPasskey).toHaveBeenCalledWith({ flow: 'discoverable', }); }); diff --git a/packages/clerk-js/src/ui/components/SignIn/shared.ts b/packages/clerk-js/src/ui/components/SignIn/shared.ts index a7ad44f8a5b..84810aa0eca 100644 --- a/packages/clerk-js/src/ui/components/SignIn/shared.ts +++ b/packages/clerk-js/src/ui/components/SignIn/shared.ts @@ -14,7 +14,7 @@ function useHandleAuthenticateWithPasskey(onSecondFactor: () => Promise const { setActive } = useClerk(); const supportEmail = useSupportEmail(); const { navigateAfterSignIn } = useSignInContext(); - const { __experimental_authenticateWithPasskey } = useCoreSignIn(); + const { authenticateWithPasskey } = useCoreSignIn(); useEffect(() => { return () => { @@ -22,9 +22,9 @@ function useHandleAuthenticateWithPasskey(onSecondFactor: () => Promise }; }, []); - return useCallback(async (...args: Parameters) => { + return useCallback(async (...args: Parameters) => { try { - const res = await __experimental_authenticateWithPasskey(...args); + const res = await authenticateWithPasskey(...args); switch (res.status) { case 'complete': return setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignIn }); diff --git a/packages/clerk-js/src/ui/components/UserProfile/PasskeySection.tsx b/packages/clerk-js/src/ui/components/UserProfile/PasskeySection.tsx index 1d8bc9fb9bb..a577ee8b5f2 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/PasskeySection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/PasskeySection.tsx @@ -67,8 +67,8 @@ export const UpdatePasskeyForm = withCardStateProvider((props: UpdatePasskeyForm return ( @@ -96,12 +96,12 @@ export const PasskeySection = () => { return ( - {user.__experimental_passkeys.map(passkey => ( + {user.passkeys.map(passkey => ( { const actions = [ { - label: localizationKeys('userProfile.start.__experimental_passkeysSection.menuAction__rename'), + label: localizationKeys('userProfile.start.passkeysSection.menuAction__rename'), onClick: () => open('rename'), }, { - label: localizationKeys('userProfile.start.__experimental_passkeysSection.menuAction__destructive'), + label: localizationKeys('userProfile.start.passkeysSection.menuAction__destructive'), isDestructive: true, onClick: () => open('remove'), }, @@ -194,7 +194,7 @@ const AddPasskeyButton = () => { const handleCreatePasskey = async () => { try { - await user?.__experimental_createPasskey(); + await user?.createPasskey(); } catch (e) { handleError(e, [], card.setError); } diff --git a/packages/clerk-js/src/ui/components/UserProfile/RemoveResourceForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/RemoveResourceForm.tsx index a6abd780d41..b746da4b6a2 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/RemoveResourceForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/RemoveResourceForm.tsx @@ -209,8 +209,8 @@ export const RemovePasskeyForm = (props: RemovePasskeyFormProps) => { return ( { it('create a new passkey', async () => { const { wrapper, fixtures } = await createFixtures(withPasskeys); - fixtures.clerk.user?.__experimental_createPasskey.mockReturnValueOnce(Promise.resolve({} as any)); + fixtures.clerk.user?.createPasskey.mockReturnValueOnce(Promise.resolve({} as any)); const { getByRole, userEvent } = render( @@ -72,7 +72,7 @@ describe('PasskeySection', () => { ); await userEvent.click(getByRole('button', { name: 'Add a passkey' })); - expect(fixtures.clerk.user?.__experimental_createPasskey).toHaveBeenCalled(); + expect(fixtures.clerk.user?.createPasskey).toHaveBeenCalled(); }); }); @@ -101,7 +101,7 @@ describe('PasskeySection', () => { it('update the name of a new passkey', async () => { const { wrapper, fixtures } = await createFixtures(withPasskeys); - fixtures.clerk.user?.__experimental_passkeys[0].update.mockResolvedValue({} as PasskeyResource); + fixtures.clerk.user?.passkeys[0].update.mockResolvedValue({} as PasskeyResource); const { getByRole, userEvent, getByText, getByLabelText } = render( @@ -121,7 +121,7 @@ describe('PasskeySection', () => { await userEvent.type(getByLabelText(/Name of Passkey/i), 'os'); expect(getByRole('button', { name: /save$/i })).not.toHaveAttribute('disabled'); await userEvent.click(getByRole('button', { name: /save$/i })); - expect(fixtures.clerk.user?.__experimental_passkeys[0].update).toHaveBeenCalledWith({ name: 'Chrome on Macos' }); + expect(fixtures.clerk.user?.passkeys[0].update).toHaveBeenCalledWith({ name: 'Chrome on Macos' }); }); }); @@ -157,7 +157,7 @@ describe('PasskeySection', () => { { wrapper }, ); - fixtures.clerk.user?.__experimental_passkeys[0].delete.mockResolvedValue({ + fixtures.clerk.user?.passkeys[0].delete.mockResolvedValue({ object: 'passkey', deleted: true, }); @@ -173,7 +173,7 @@ describe('PasskeySection', () => { await waitFor(() => getByRole('heading', { name: /remove passkey/i })); await userEvent.click(getByRole('button', { name: /remove/i })); - expect(fixtures.clerk.user?.__experimental_passkeys[0].delete).toHaveBeenCalled(); + expect(fixtures.clerk.user?.passkeys[0].delete).toHaveBeenCalled(); await waitFor(() => expect(queryByRole('heading', { name: /remove passkey/i })).not.toBeInTheDocument()); }); diff --git a/packages/clerk-js/src/ui/utils/test/mockHelpers.ts b/packages/clerk-js/src/ui/utils/test/mockHelpers.ts index 52ffd6624ff..644f01104aa 100644 --- a/packages/clerk-js/src/ui/utils/test/mockHelpers.ts +++ b/packages/clerk-js/src/ui/utils/test/mockHelpers.ts @@ -45,7 +45,7 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepJestMocked mockMethodsOf(m)); + session.user?.passkeys.forEach(m => mockMethodsOf(m)); }); mockProp(clerk, 'navigate'); mockProp(clerk, 'setActive'); diff --git a/packages/clerk-js/src/utils/__tests__/passkeys.test.ts b/packages/clerk-js/src/utils/__tests__/passkeys.test.ts index 3ee30a5e539..c78c0f40b49 100644 --- a/packages/clerk-js/src/utils/__tests__/passkeys.test.ts +++ b/packages/clerk-js/src/utils/__tests__/passkeys.test.ts @@ -1,8 +1,8 @@ import type { - type __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse, - type __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, + PublicKeyCredentialWithAuthenticatorAssertionResponse, + PublicKeyCredentialWithAuthenticatorAttestationResponse, } from '@clerk/types'; import { @@ -89,7 +89,7 @@ describe('Passkey utils', () => { }); it('serializePublicKeyCredential()', () => { - const publicKeyCredential: __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse = { + const publicKeyCredential: PublicKeyCredentialWithAuthenticatorAttestationResponse = { type: 'public-key', id: 'credentialId_123', rawId: new Uint8Array([99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 73, 100, 95, 49, 50, 51]), @@ -113,7 +113,7 @@ describe('Passkey utils', () => { }); it('serializePublicKeyCredentialAssertion()', () => { - const publicKeyCredential: __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse = { + const publicKeyCredential: PublicKeyCredentialWithAuthenticatorAssertionResponse = { type: 'public-key', id: 'credentialId_123', rawId: new Uint8Array([99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 73, 100, 95, 49, 50, 51]), diff --git a/packages/clerk-js/src/utils/passkeys.ts b/packages/clerk-js/src/utils/passkeys.ts index 932b250e64e..189e3d9422c 100644 --- a/packages/clerk-js/src/utils/passkeys.ts +++ b/packages/clerk-js/src/utils/passkeys.ts @@ -1,12 +1,12 @@ import { isValidBrowser } from '@clerk/shared/browser'; import { ClerkRuntimeError } from '@clerk/shared/error'; import type { - __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions, - __experimental_PublicKeyCredentialRequestOptionsWithoutExtensions, - __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse, - __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse, PublicKeyCredentialCreationOptionsJSON, + PublicKeyCredentialCreationOptionsWithoutExtensions, PublicKeyCredentialRequestOptionsJSON, + PublicKeyCredentialRequestOptionsWithoutExtensions, + PublicKeyCredentialWithAuthenticatorAssertionResponse, + PublicKeyCredentialWithAuthenticatorAttestationResponse, } from '@clerk/types'; type CredentialReturn = @@ -19,10 +19,8 @@ type CredentialReturn = error: ClerkWebAuthnError | Error; }; -type WebAuthnCreateCredentialReturn = - CredentialReturn<__experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse>; -type WebAuthnGetCredentialReturn = - CredentialReturn<__experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse>; +type WebAuthnCreateCredentialReturn = CredentialReturn; +type WebAuthnGetCredentialReturn = CredentialReturn; type ClerkWebAuthnErrorCode = // Generic @@ -87,13 +85,13 @@ class Base64Converter { } async function webAuthnCreateCredential( - publicKeyOptions: __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions, + publicKeyOptions: PublicKeyCredentialCreationOptionsWithoutExtensions, ): Promise { try { // Typescript types are not aligned with the spec. These type assertions are required to comply with the spec. const credential = (await navigator.credentials.create({ publicKey: publicKeyOptions, - })) as __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse | null; + })) as PublicKeyCredentialWithAuthenticatorAttestationResponse | null; if (!credential) { return { @@ -141,7 +139,7 @@ async function webAuthnGetCredential({ publicKeyOptions, conditionalUI, }: { - publicKeyOptions: __experimental_PublicKeyCredentialRequestOptionsWithoutExtensions; + publicKeyOptions: PublicKeyCredentialRequestOptionsWithoutExtensions; conditionalUI: boolean; }): Promise { try { @@ -150,7 +148,7 @@ async function webAuthnGetCredential({ publicKey: publicKeyOptions, mediation: conditionalUI ? 'conditional' : 'optional', signal: __internal_WebAuthnAbortService.createAbortSignal(), - })) as __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse | null; + })) as PublicKeyCredentialWithAuthenticatorAssertionResponse | null; if (!credential) { return { @@ -218,7 +216,7 @@ function convertJSONToPublicKeyCreateOptions(jsonPublicKey: PublicKeyCredentialC ...jsonPublicKey.user, id: userIdBuffer, }, - } as __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions; + } as PublicKeyCredentialCreationOptionsWithoutExtensions; } function convertJSONToPublicKeyRequestOptions(jsonPublicKey: PublicKeyCredentialRequestOptionsJSON) { @@ -233,7 +231,7 @@ function convertJSONToPublicKeyRequestOptions(jsonPublicKey: PublicKeyCredential ...jsonPublicKey, allowCredentials: allowCredentialsWithBuffer, challenge: challengeBuffer, - } as __experimental_PublicKeyCredentialRequestOptionsWithoutExtensions; + } as PublicKeyCredentialRequestOptionsWithoutExtensions; } function __serializePublicKeyCredential>(pkc: T) { @@ -245,7 +243,7 @@ function __serializePublicKeyCredential & { }; export type PhoneCodeConfig = Omit; export type Web3SignatureConfig = Web3SignatureFactor; -/** - * @experimental - */ -export type __experimental_PassKeyConfig = __experimental_PasskeyFactor; + +export type PassKeyConfig = PasskeyFactor; export type OAuthConfig = OauthFactor & { redirectUrl: string; actionCompleteRedirectUrl: string; @@ -128,12 +123,9 @@ export type PasswordAttempt = { password: string; }; -/** - * @experimental - */ -export type __experimental_PasskeyAttempt = { - strategy: __experimental_PasskeyStrategy; - publicKeyCredential: __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse; +export type PasskeyAttempt = { + strategy: PasskeyStrategy; + publicKeyCredential: PublicKeyCredentialWithAuthenticatorAssertionResponse; }; export type Web3Attempt = { diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 40fba3bbd6e..97254c71721 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -48,10 +48,12 @@ type _LocalizationResource = { formFieldLabel__organizationDomainDeletePending: LocalizationValue; formFieldLabel__confirmDeletion: LocalizationValue; formFieldLabel__role: LocalizationValue; + // TODO-PASSKEYS: Remove in the next minor /** * @experimental */ __experimental_formFieldLabel__passkeyName: LocalizationValue; + formFieldLabel__passkeyName: LocalizationValue; formFieldInputPlaceholder__emailAddress: LocalizationValue; formFieldInputPlaceholder__emailAddresses: LocalizationValue; formFieldInputPlaceholder__phoneNumber: LocalizationValue; @@ -333,6 +335,7 @@ type _LocalizationResource = { primaryButton__updatePassword: LocalizationValue; primaryButton__setPassword: LocalizationValue; }; + // TODO-PASSKEYS: Remove in the next minor /** * @experimental */ @@ -341,6 +344,11 @@ type _LocalizationResource = { menuAction__rename: LocalizationValue; menuAction__destructive: LocalizationValue; }; + passkeysSection: { + title: LocalizationValue; + menuAction__rename: LocalizationValue; + menuAction__destructive: LocalizationValue; + }; mfaSection: { title: LocalizationValue; primaryButton: LocalizationValue; @@ -411,6 +419,8 @@ type _LocalizationResource = { successMessage: LocalizationValue; }; }; + + // TODO-PASSKEYS: Remove in the next minor /** * @experimental */ @@ -422,6 +432,14 @@ type _LocalizationResource = { messageLine1: LocalizationValue; }; }; + passkeyScreen: { + title__rename: LocalizationValue; + subtitle__rename: LocalizationValue; + removeResource: { + title: LocalizationValue; + messageLine1: LocalizationValue; + }; + }; phoneNumberPage: { title: LocalizationValue; verifyTitle: LocalizationValue; diff --git a/packages/types/src/passkey.ts b/packages/types/src/passkey.ts index 34940c4e9b2..f048333ec62 100644 --- a/packages/types/src/passkey.ts +++ b/packages/types/src/passkey.ts @@ -20,33 +20,24 @@ export interface PasskeyResource extends ClerkResource { delete: () => Promise; } -/** - * @experimental - */ -export type __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions = Omit< +export type PublicKeyCredentialCreationOptionsWithoutExtensions = Omit< Required, 'extensions' >; -/** - * @experimental - */ -export type __experimental_PublicKeyCredentialRequestOptionsWithoutExtensions = Omit< + +export type PublicKeyCredentialRequestOptionsWithoutExtensions = Omit< Required, 'extensions' >; -/** - * @experimental - */ -export type __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse = Omit< + +export type PublicKeyCredentialWithAuthenticatorAttestationResponse = Omit< PublicKeyCredential, 'response' | 'getClientExtensionResults' > & { response: Omit; }; -/** - * @experimental - */ -export type __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse = Omit< + +export type PublicKeyCredentialWithAuthenticatorAssertionResponse = Omit< PublicKeyCredential, 'response' | 'getClientExtensionResults' > & { diff --git a/packages/types/src/signIn.ts b/packages/types/src/signIn.ts index 60d4403210a..e58d080aa08 100644 --- a/packages/types/src/signIn.ts +++ b/packages/types/src/signIn.ts @@ -8,6 +8,9 @@ import type { EmailLinkFactor, OAuthConfig, OauthFactor, + PasskeyAttempt, + PassKeyConfig, + PasskeyFactor, PasswordAttempt, PasswordFactor, PhoneCodeAttempt, @@ -49,6 +52,7 @@ import type { EmailCodeStrategy, EmailLinkStrategy, OAuthStrategy, + PasskeyStrategy, PasswordStrategy, PhoneCodeStrategy, ResetPasswordEmailCodeStrategy, @@ -93,8 +97,11 @@ export interface SignInResource extends ClerkResource { authenticateWithMetamask: () => Promise; + // TODO-PASSKEY: Remove in the next minor __experimental_authenticateWithPasskey: (params?: { flow?: 'autofill' | 'discoverable' }) => Promise; + authenticateWithPasskey: (params?: AuthenticateWithPasskeyParams) => Promise; + createEmailLinkFlow: () => CreateEmailLinkFlowReturn; validatePassword: (password: string, callbacks?: ValidatePasswordCallbacks) => void; @@ -118,8 +125,7 @@ export type SignInFirstFactor = | EmailLinkFactor | PhoneCodeFactor | PasswordFactor - // TODO-PASSKEYS: Include this when the feature is not longer considered experimental - // | __experimental_PasskeyFactor + | PasskeyFactor | ResetPasswordPhoneCodeFactor | ResetPasswordEmailCodeFactor | Web3SignatureFactor @@ -142,16 +148,14 @@ export type PrepareFirstFactorParams = | EmailLinkConfig | PhoneCodeConfig | Web3SignatureConfig - // TODO-PASSKEYS: Include this when the feature is not longer considered experimental - // | __experimental_PassKeyConfig + | PassKeyConfig | ResetPasswordPhoneCodeFactorConfig | ResetPasswordEmailCodeFactorConfig | OAuthConfig | SamlConfig; export type AttemptFirstFactorParams = - // TODO-PASSKEYS: Include this when the feature is not longer considered experimental - // | __experimental_PasskeyAttempt + | PasskeyAttempt | EmailCodeAttempt | PhoneCodeAttempt | PasswordAttempt @@ -179,8 +183,7 @@ export type SignInCreateParams = ( password: string; identifier: string; } - // TODO-PASSKEYS: Include this when the feature is not longer considered experimental - // | { strategy: __experimental_PasskeyStrategy } + | { strategy: PasskeyStrategy } | { strategy: | PhoneCodeStrategy @@ -206,13 +209,16 @@ export type ResetPasswordParams = { signOutOfOtherSessions?: boolean; }; +export type AuthenticateWithPasskeyParams = { + flow?: 'autofill' | 'discoverable'; +}; + export interface SignInStartEmailLinkFlowParams extends StartEmailLinkFlowParams { emailAddressId: string; } export type SignInStrategy = - // TODO-PASSKEYS: Include this when the feature is not longer considered experimental - // | __experimental_PasskeyStrategy + | PasskeyStrategy | PasswordStrategy | ResetPasswordPhoneCodeStrategy | ResetPasswordEmailCodeStrategy diff --git a/packages/types/src/strategies.ts b/packages/types/src/strategies.ts index e9d25d1f5b3..3d72e79dcb6 100644 --- a/packages/types/src/strategies.ts +++ b/packages/types/src/strategies.ts @@ -1,10 +1,7 @@ import type { OAuthProvider } from './oauth'; import type { Web3Provider } from './web3'; -/** - * @experimental - */ -export type __experimental_PasskeyStrategy = 'passkey'; +export type PasskeyStrategy = 'passkey'; export type PasswordStrategy = 'password'; export type PhoneCodeStrategy = 'phone_code'; export type EmailCodeStrategy = 'email_code'; diff --git a/packages/types/src/user.ts b/packages/types/src/user.ts index 8a9ac3c9499..ebffe084a2e 100644 --- a/packages/types/src/user.ts +++ b/packages/types/src/user.ts @@ -68,11 +68,13 @@ export interface UserResource extends ClerkResource { phoneNumbers: PhoneNumberResource[]; web3Wallets: Web3WalletResource[]; externalAccounts: ExternalAccountResource[]; + // TODO-PASSKEY: Remove in the next minor /** * @experimental * This property is experimental, avoid using this in production applications */ __experimental_passkeys: PasskeyResource[]; + passkeys: PasskeyResource[]; samlAccounts: SamlAccountResource[]; organizationMemberships: OrganizationMembershipResource[]; @@ -94,11 +96,13 @@ export interface UserResource extends ClerkResource { removePassword: (params: RemoveUserPasswordParams) => Promise; createEmailAddress: (params: CreateEmailAddressParams) => Promise; + // TODO-PASSKEY: Remove in the next minor /** * @experimental * This method is experimental, avoid using this in production applications */ __experimental_createPasskey: () => Promise; + createPasskey: () => Promise; createPhoneNumber: (params: CreatePhoneNumberParams) => Promise; createWeb3Wallet: (params: CreateWeb3WalletParams) => Promise; isPrimaryIdentification: (ident: EmailAddressResource | PhoneNumberResource | Web3WalletResource) => boolean; diff --git a/packages/types/src/verification.ts b/packages/types/src/verification.ts index 0e1492493ad..9fa41444e98 100644 --- a/packages/types/src/verification.ts +++ b/packages/types/src/verification.ts @@ -1,5 +1,5 @@ import type { ClerkAPIError } from './api'; -import type { __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions } from './passkey'; +import type { PublicKeyCredentialCreationOptionsWithoutExtensions } from './passkey'; import type { ClerkResource } from './resource'; export interface VerificationResource extends ClerkResource { @@ -15,7 +15,7 @@ export interface VerificationResource extends ClerkResource { } export interface PasskeyVerificationResource extends VerificationResource { - publicKey: __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions | null; + publicKey: PublicKeyCredentialCreationOptionsWithoutExtensions | null; } export type VerificationStatus = 'unverified' | 'verified' | 'transferable' | 'failed' | 'expired';