diff --git a/.changeset/light-ligers-beam.md b/.changeset/light-ligers-beam.md new file mode 100644 index 0000000000..3e8b33905d --- /dev/null +++ b/.changeset/light-ligers-beam.md @@ -0,0 +1,7 @@ +--- +'@clerk/clerk-js': minor +'@clerk/types': minor +--- + +Experimental support for authenticating with a passkey. +Example usage: `await signIn.authenticateWithPasskey()`. diff --git a/packages/clerk-js/src/core/errors.ts b/packages/clerk-js/src/core/errors.ts index 92597227fa..cd350ce7a6 100644 --- a/packages/clerk-js/src/core/errors.ts +++ b/packages/clerk-js/src/core/errors.ts @@ -58,6 +58,12 @@ export function clerkVerifyWeb3WalletCalledBeforeCreate(type: 'SignIn' | 'SignUp ); } +export function clerkVerifyPasskeyCalledBeforeCreate(): never { + throw new Error( + `${errorPrefix} You need to start a SignIn flow by calling SignIn.create({ strategy: 'passkey' }) first`, + ); +} + export function clerkMissingOptionError(name = ''): never { throw new Error(`${errorPrefix} Missing '${name}' option`); } diff --git a/packages/clerk-js/src/core/resources/Passkey.ts b/packages/clerk-js/src/core/resources/Passkey.ts index d96c0d6116..d29dd5ab68 100644 --- a/packages/clerk-js/src/core/resources/Passkey.ts +++ b/packages/clerk-js/src/core/resources/Passkey.ts @@ -1,4 +1,5 @@ import type { + __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse, DeletedObjectJSON, DeletedObjectResource, PasskeyJSON, @@ -8,7 +9,6 @@ import type { } from '@clerk/types'; import { unixEpochToDate } from '../../utils/date'; -import type { PublicKeyCredentialWithAuthenticatorAttestationResponse } from '../../utils/passkeys'; import { isWebAuthnPlatformAuthenticatorSupported, isWebAuthnSupported, @@ -41,7 +41,7 @@ export class Passkey extends BaseResource implements PasskeyResource { private static async attemptVerification( passkeyId: string, - credential: PublicKeyCredentialWithAuthenticatorAttestationResponse, + credential: __experimental_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 2d02c9df35..9fef4a38c5 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -1,5 +1,6 @@ -import { deepSnakeToCamel, Poller } from '@clerk/shared'; +import { ClerkRuntimeError, deepSnakeToCamel, Poller } from '@clerk/shared'; import type { + __experimental_PasskeyFactor, AttemptFirstFactorParams, AttemptSecondFactorParams, AuthenticateWithRedirectParams, @@ -28,12 +29,20 @@ import type { } from '@clerk/types'; import { generateSignatureWithMetamask, getMetamaskIdentifier, windowNavigate } from '../../utils'; +import { + convertJSONToPublicKeyRequestOptions, + isWebAuthnAutofillSupported, + isWebAuthnSupported, + serializePublicKeyCredentialAssertion, + webAuthnGetCredential, +} from '../../utils/passkeys'; import { createValidatePassword } from '../../utils/passwords/password'; import { clerkInvalidFAPIResponse, clerkInvalidStrategy, clerkMissingOptionError, clerkVerifyEmailAddressCalledBeforeCreate, + clerkVerifyPasskeyCalledBeforeCreate, clerkVerifyWeb3WalletCalledBeforeCreate, } from '../errors'; import { BaseResource, UserData, Verification } from './internal'; @@ -74,6 +83,11 @@ 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': config = { emailAddressId: factor.emailAddressId, @@ -113,9 +127,22 @@ export class SignIn extends BaseResource implements SignInResource { }); }; - attemptFirstFactor = (params: AttemptFirstFactorParams): Promise => { + 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; + default: + config = { ...attemptFactor }; + } + return this._basePost({ - body: params, + body: { ...config, strategy: attemptFactor.strategy }, action: 'attempt_first_factor', }); }; @@ -234,6 +261,60 @@ export class SignIn extends BaseResource implements SignInResource { }); }; + public __experimental_authenticateWithPasskey = async (): Promise => { + /** + * The UI should always prevent from this method being called if WebAuthn is not supported. + * As a precaution we need to check if WebAuthn is supported. + */ + if (!isWebAuthnSupported()) { + throw new ClerkRuntimeError('Passkeys are not supported', { + code: 'passkeys_unsupported', + }); + } + + if (!this.firstFactorVerification.nonce) { + // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level + await this.create({ strategy: 'passkey' }); + } + + // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level + 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; + + if (!passKeyFactor) { + clerkVerifyPasskeyCalledBeforeCreate(); + } + + // @ts-ignore As this is experimental we want to support it at runtime, but not at the type level + await this.prepareFirstFactor(passKeyFactor); + + const { nonce } = this.firstFactorVerification; + const publicKey = nonce ? convertJSONToPublicKeyRequestOptions(JSON.parse(nonce)) : null; + + if (!publicKey) { + // TODO-PASSKEYS: Implement this later + throw 'Missing key'; + } + + // Invoke the WebAuthn get() method. + const { publicKeyCredential, error } = await webAuthnGetCredential({ + publicKeyOptions: publicKey, + conditionalUI: await isWebAuthnAutofillSupported(), + }); + + if (!publicKeyCredential) { + throw error; + } + + 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', + }); + }; + validatePassword: ReturnType = (password, cb) => { if (SignIn.clerk.__unstable__environment?.userSettings.passwordSettings) { return createValidatePassword({ diff --git a/packages/clerk-js/src/core/resources/Verification.ts b/packages/clerk-js/src/core/resources/Verification.ts index 4f8d983cd6..e3a268ba6b 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: PublicKeyCredentialCreationOptionsWithoutExtensions | null = null; + publicKey: __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions | null = null; constructor(data: VerificationJSON | null) { super(data); diff --git a/packages/clerk-js/src/utils/__tests__/passkeys.test.ts b/packages/clerk-js/src/utils/__tests__/passkeys.test.ts index d66f8d3f1c..3ee30a5e53 100644 --- a/packages/clerk-js/src/utils/__tests__/passkeys.test.ts +++ b/packages/clerk-js/src/utils/__tests__/passkeys.test.ts @@ -1,7 +1,17 @@ -import type { PublicKeyCredentialCreationOptionsJSON } from '@clerk/types'; +import type { + type __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse, + type __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse, + PublicKeyCredentialCreationOptionsJSON, + PublicKeyCredentialRequestOptionsJSON, +} from '@clerk/types'; -import type { PublicKeyCredentialWithAuthenticatorAttestationResponse } from '../passkeys'; -import { bufferToBase64Url, convertJSONToPublicKeyCreateOptions, serializePublicKeyCredential } from '../passkeys'; +import { + bufferToBase64Url, + convertJSONToPublicKeyCreateOptions, + convertJSONToPublicKeyRequestOptions, + serializePublicKeyCredential, + serializePublicKeyCredentialAssertion, +} from '../passkeys'; describe('Passkey utils', () => { describe('serialization', () => { @@ -57,8 +67,29 @@ describe('Passkey utils', () => { expect(bufferToBase64Url(result.excludeCredentials[0].id)).toEqual(pkCreateOptions.excludeCredentials[0].id); }); + it('convertJSONToPublicKeyCreateOptions()', () => { + const pkCreateOptions: PublicKeyCredentialRequestOptionsJSON = { + rpId: 'clerk.com', + allowCredentials: [ + { + type: 'public-key', + id: 'cmFuZG9tX2lk', + }, + ], + userVerification: 'required', + timeout: 10000, + challenge: 'Y2hhbGxlbmdlXzEyMw', // challenge_123 encoded as base64url + }; + + const result = convertJSONToPublicKeyRequestOptions(pkCreateOptions); + + expect(result.rpId).toEqual('clerk.com'); + expect(result.userVerification).toEqual('required'); + expect(bufferToBase64Url(result.allowCredentials[0].id)).toEqual(pkCreateOptions.allowCredentials[0].id); + }); + it('serializePublicKeyCredential()', () => { - const publicKeyCredential: PublicKeyCredentialWithAuthenticatorAttestationResponse = { + const publicKeyCredential: __experimental_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]), @@ -80,5 +111,30 @@ describe('Passkey utils', () => { expect(result.response.attestationObject).toEqual('bElkXzE'); expect(result.response.transports).toEqual(['usb']); }); + + it('serializePublicKeyCredentialAssertion()', () => { + const publicKeyCredential: __experimental_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]), + authenticatorAttachment: 'cross-platform', + response: { + clientDataJSON: new Uint8Array([110, 116, 105, 97]), + signature: new Uint8Array([108, 73, 100, 95, 49]), + authenticatorData: new Uint8Array([108, 73, 100, 95, 49]), + userHandle: null, + }, + }; + + const result = serializePublicKeyCredentialAssertion(publicKeyCredential); + + expect(result.type).toEqual('public-key'); + expect(result.id).toEqual('credentialId_123'); + expect(result.rawId).toEqual('Y3JlZGVudGlhbElkXzEyMw'); + + expect(result.response.clientDataJSON).toEqual('bnRpYQ'); + expect(result.response.signature).toEqual('bElkXzE'); + expect(result.response.userHandle).toEqual(null); + }); }); }); diff --git a/packages/clerk-js/src/utils/passkeys.ts b/packages/clerk-js/src/utils/passkeys.ts index 1448eaff18..cb27fb2059 100644 --- a/packages/clerk-js/src/utils/passkeys.ts +++ b/packages/clerk-js/src/utils/passkeys.ts @@ -1,20 +1,17 @@ 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, } from '@clerk/types'; -type PublicKeyCredentialWithAuthenticatorAttestationResponse = Omit< - PublicKeyCredential, - 'response' | 'getClientExtensionResults' -> & { - response: Omit; -}; - -type WebAuthnCreateCredentialReturn = +type CredentialReturn = | { - publicKeyCredential: PublicKeyCredentialWithAuthenticatorAttestationResponse; + publicKeyCredential: T; error: null; } | { @@ -22,7 +19,16 @@ type WebAuthnCreateCredentialReturn = error: ClerkWebAuthnError | Error; }; -type ClerkWebAuthnErrorCode = 'passkey_exists' | 'passkey_registration_cancelled' | 'passkey_credential_failed'; +type WebAuthnCreateCredentialReturn = + CredentialReturn<__experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse>; +type WebAuthnGetCredentialReturn = + CredentialReturn<__experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse>; + +type ClerkWebAuthnErrorCode = + | 'passkey_exists' + | 'passkey_registration_cancelled' + | 'passkey_credential_create_failed' + | 'passkey_credential_get_failed'; function isWebAuthnSupported() { return ( @@ -73,17 +79,19 @@ class Base64Converter { } async function webAuthnCreateCredential( - publicKeyOptions: PublicKeyCredentialCreationOptionsWithoutExtensions, + publicKeyOptions: __experimental_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 PublicKeyCredentialWithAuthenticatorAttestationResponse | null; + })) as __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse | null; if (!credential) { return { - error: new ClerkWebAuthnError('Browser failed to create credential', { code: 'passkey_credential_failed' }), + error: new ClerkWebAuthnError('Browser failed to create credential', { + code: 'passkey_credential_create_failed', + }), publicKeyCredential: null, }; } @@ -94,6 +102,33 @@ async function webAuthnCreateCredential( } } +async function webAuthnGetCredential({ + publicKeyOptions, + conditionalUI, +}: { + publicKeyOptions: __experimental_PublicKeyCredentialRequestOptionsWithoutExtensions; + conditionalUI: boolean; +}): 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.get({ + publicKey: publicKeyOptions, + mediation: conditionalUI ? 'conditional' : 'optional', + })) as __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse | null; + + if (!credential) { + return { + error: new ClerkWebAuthnError('Browser failed to get credential', { code: 'passkey_credential_get_failed' }), + publicKeyCredential: null, + }; + } + + return { publicKeyCredential: credential, error: null }; + } catch (e) { + return { error: handlePublicKeyGetError(e), publicKeyCredential: null }; + } +} + /** * Map webauthn errors from `navigator.credentials.create()` to Clerk-js errors * @param error @@ -107,6 +142,17 @@ function handlePublicKeyCreateError(error: Error): ClerkWebAuthnError | ClerkRun return error; } +/** + * Map webauthn errors from `navigator.credentials.get()` to Clerk-js errors + * @param error + */ +function handlePublicKeyGetError(error: Error): ClerkWebAuthnError | ClerkRuntimeError | Error { + if (error.name === 'NotAllowedError') { + return new ClerkWebAuthnError(error.message, { code: 'passkey_registration_cancelled' }); + } + return error; +} + function convertJSONToPublicKeyCreateOptions(jsonPublicKey: PublicKeyCredentialCreationOptionsJSON) { const userIdBuffer = base64UrlToBuffer(jsonPublicKey.user.id); const challengeBuffer = base64UrlToBuffer(jsonPublicKey.challenge); @@ -124,16 +170,37 @@ function convertJSONToPublicKeyCreateOptions(jsonPublicKey: PublicKeyCredentialC ...jsonPublicKey.user, id: userIdBuffer, }, - } as PublicKeyCredentialCreationOptionsWithoutExtensions; + } as __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions; +} + +function convertJSONToPublicKeyRequestOptions(jsonPublicKey: PublicKeyCredentialRequestOptionsJSON) { + const challengeBuffer = base64UrlToBuffer(jsonPublicKey.challenge); + + const allowCredentialsWithBuffer = (jsonPublicKey.allowCredentials || []).map(cred => ({ + ...cred, + id: base64UrlToBuffer(cred.id), + })); + + return { + ...jsonPublicKey, + allowCredentials: allowCredentialsWithBuffer, + challenge: challengeBuffer, + } as __experimental_PublicKeyCredentialRequestOptionsWithoutExtensions; } -function serializePublicKeyCredential(publicKeyCredential: PublicKeyCredentialWithAuthenticatorAttestationResponse) { - const response = publicKeyCredential.response; +function __serializePublicKeyCredential>(pkc: T) { return { - type: publicKeyCredential.type, - id: publicKeyCredential.id, - rawId: bufferToBase64Url(publicKeyCredential.rawId), - authenticatorAttachment: publicKeyCredential.authenticatorAttachment, + type: pkc.type, + id: pkc.id, + rawId: bufferToBase64Url(pkc.rawId), + authenticatorAttachment: pkc.authenticatorAttachment, + }; +} + +function serializePublicKeyCredential(pkc: __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse) { + const response = pkc.response; + return { + ...__serializePublicKeyCredential(pkc), response: { clientDataJSON: bufferToBase64Url(response.clientDataJSON), attestationObject: bufferToBase64Url(response.attestationObject), @@ -142,6 +209,21 @@ function serializePublicKeyCredential(publicKeyCredential: PublicKeyCredentialWi }; } +function serializePublicKeyCredentialAssertion( + pkc: __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse, +) { + const response = pkc.response; + return { + ...__serializePublicKeyCredential(pkc), + response: { + clientDataJSON: bufferToBase64Url(response.clientDataJSON), + authenticatorData: bufferToBase64Url(response.authenticatorData), + signature: bufferToBase64Url(response.signature), + userHandle: response.userHandle ? bufferToBase64Url(response.userHandle) : null, + }, + }; +} + const bufferToBase64Url = Base64Converter.encode.bind(Base64Converter); const base64UrlToBuffer = Base64Converter.decode.bind(Base64Converter); @@ -165,8 +247,9 @@ export { bufferToBase64Url, handlePublicKeyCreateError, webAuthnCreateCredential, + webAuthnGetCredential, convertJSONToPublicKeyCreateOptions, + convertJSONToPublicKeyRequestOptions, serializePublicKeyCredential, + serializePublicKeyCredentialAssertion, }; - -export type { PublicKeyCredentialWithAuthenticatorAttestationResponse }; diff --git a/packages/types/src/factors.ts b/packages/types/src/factors.ts index 268f25f848..8808382792 100644 --- a/packages/types/src/factors.ts +++ b/packages/types/src/factors.ts @@ -1,4 +1,6 @@ +import type { __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse } from './passkey'; import type { + __experimental_PasskeyStrategy, BackupCodeStrategy, EmailCodeStrategy, EmailLinkStrategy, @@ -44,6 +46,13 @@ export type PasswordFactor = { strategy: PasswordStrategy; }; +/** + * @experimental + */ +export type __experimental_PasskeyFactor = { + strategy: __experimental_PasskeyStrategy; +}; + export type OauthFactor = { strategy: OAuthStrategy; }; @@ -85,6 +94,10 @@ export type EmailLinkConfig = Omit & { }; export type PhoneCodeConfig = Omit; export type Web3SignatureConfig = Web3SignatureFactor; +/** + * @experimental + */ +export type __experimental_PassKeyConfig = __experimental_PasskeyFactor; export type OAuthConfig = OauthFactor & { redirectUrl: string; actionCompleteRedirectUrl: string; @@ -115,6 +128,14 @@ export type PasswordAttempt = { password: string; }; +/** + * @experimental + */ +export type __experimental_PasskeyAttempt = { + strategy: __experimental_PasskeyStrategy; + publicKeyCredential: __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse; +}; + export type Web3Attempt = { strategy: Web3Strategy; signature: string; diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts index 65cd815f4a..4aad3e2b85 100644 --- a/packages/types/src/json.ts +++ b/packages/types/src/json.ts @@ -461,7 +461,7 @@ interface PublicKeyCredentialUserEntityJSON { id: Base64UrlString; } -export interface ExcludedCredentialJSON { +interface PublicKeyCredentialDescriptorJSON { type: 'public-key'; id: Base64UrlString; transports?: ('ble' | 'hybrid' | 'internal' | 'nfc' | 'usb')[]; @@ -479,11 +479,19 @@ export interface PublicKeyCredentialCreationOptionsJSON { challenge: Base64UrlString; pubKeyCredParams: PublicKeyCredentialParameters[]; timeout: number; - excludeCredentials: ExcludedCredentialJSON[]; + excludeCredentials: PublicKeyCredentialDescriptorJSON[]; authenticatorSelection: AuthenticatorSelectionCriteriaJSON; attestation: 'direct' | 'enterprise' | 'indirect' | 'none'; } +export interface PublicKeyCredentialRequestOptionsJSON { + allowCredentials: PublicKeyCredentialDescriptorJSON[]; + challenge: Base64UrlString; + rpId: string; + timeout: number; + userVerification: 'discouraged' | 'preferred' | 'required'; +} + // TODO-PASSKEYS: Decide if we are keeping this // export interface PassKeyVerificationJSON extends VerificationJSON { // publicKey: PublicKeyCredentialCreationOptionsJSON | null; diff --git a/packages/types/src/passkey.ts b/packages/types/src/passkey.ts index 2e2aea8ed3..01a694773d 100644 --- a/packages/types/src/passkey.ts +++ b/packages/types/src/passkey.ts @@ -20,3 +20,36 @@ export interface PasskeyResource extends ClerkResource { update: (params: UpdatePasskeyParams) => Promise; delete: () => Promise; } + +/** + * @experimental + */ +export type __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions = Omit< + Required, + 'extensions' +>; +/** + * @experimental + */ +export type __experimental_PublicKeyCredentialRequestOptionsWithoutExtensions = Omit< + Required, + 'extensions' +>; +/** + * @experimental + */ +export type __experimental_PublicKeyCredentialWithAuthenticatorAttestationResponse = Omit< + PublicKeyCredential, + 'response' | 'getClientExtensionResults' +> & { + response: Omit; +}; +/** + * @experimental + */ +export type __experimental_PublicKeyCredentialWithAuthenticatorAssertionResponse = Omit< + PublicKeyCredential, + 'response' | 'getClientExtensionResults' +> & { + response: AuthenticatorAssertionResponse; +}; diff --git a/packages/types/src/signIn.ts b/packages/types/src/signIn.ts index 7d240118af..b0fd06dc27 100644 --- a/packages/types/src/signIn.ts +++ b/packages/types/src/signIn.ts @@ -90,6 +90,8 @@ export interface SignInResource extends ClerkResource { authenticateWithMetamask: () => Promise; + __experimental_authenticateWithPasskey: () => Promise; + createEmailLinkFlow: () => CreateEmailLinkFlowReturn; validatePassword: (password: string, callbacks?: ValidatePasswordCallbacks) => void; @@ -113,6 +115,8 @@ export type SignInFirstFactor = | EmailLinkFactor | PhoneCodeFactor | PasswordFactor + // TODO-PASSKEYS: Include this when the feature is not longer considered experimental + // | __experimental_PasskeyFactor | ResetPasswordPhoneCodeFactor | ResetPasswordEmailCodeFactor | Web3SignatureFactor @@ -135,12 +139,16 @@ export type PrepareFirstFactorParams = | EmailLinkConfig | PhoneCodeConfig | Web3SignatureConfig + // TODO-PASSKEYS: Include this when the feature is not longer considered experimental + // | __experimental_PassKeyConfig | ResetPasswordPhoneCodeFactorConfig | ResetPasswordEmailCodeFactorConfig | OAuthConfig | SamlConfig; export type AttemptFirstFactorParams = + // TODO-PASSKEYS: Include this when the feature is not longer considered experimental + // | __experimental_PasskeyAttempt | EmailCodeAttempt | PhoneCodeAttempt | PasswordAttempt @@ -168,6 +176,8 @@ export type SignInCreateParams = ( password: string; identifier: string; } + // TODO-PASSKEYS: Include this when the feature is not longer considered experimental + // | { strategy: __experimental_PasskeyStrategy } | { strategy: | PhoneCodeStrategy @@ -198,6 +208,8 @@ export interface SignInStartEmailLinkFlowParams extends StartEmailLinkFlowParams } export type SignInStrategy = + // TODO-PASSKEYS: Include this when the feature is not longer considered experimental + // | __experimental_PasskeyStrategy | PasswordStrategy | ResetPasswordPhoneCodeStrategy | ResetPasswordEmailCodeStrategy diff --git a/packages/types/src/strategies.ts b/packages/types/src/strategies.ts index f6005d60fc..e9d25d1f5b 100644 --- a/packages/types/src/strategies.ts +++ b/packages/types/src/strategies.ts @@ -1,6 +1,10 @@ import type { OAuthProvider } from './oauth'; import type { Web3Provider } from './web3'; +/** + * @experimental + */ +export type __experimental_PasskeyStrategy = 'passkey'; export type PasswordStrategy = 'password'; export type PhoneCodeStrategy = 'phone_code'; export type EmailCodeStrategy = 'email_code'; diff --git a/packages/types/src/verification.ts b/packages/types/src/verification.ts index 86b6c1d1ce..0e1492493a 100644 --- a/packages/types/src/verification.ts +++ b/packages/types/src/verification.ts @@ -1,4 +1,5 @@ import type { ClerkAPIError } from './api'; +import type { __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions } from './passkey'; import type { ClerkResource } from './resource'; export interface VerificationResource extends ClerkResource { @@ -13,12 +14,8 @@ export interface VerificationResource extends ClerkResource { verifiedFromTheSameClient: () => boolean; } -export type PublicKeyCredentialCreationOptionsWithoutExtensions = Omit< - Required, - 'extensions' ->; export interface PasskeyVerificationResource extends VerificationResource { - publicKey: PublicKeyCredentialCreationOptionsWithoutExtensions | null; + publicKey: __experimental_PublicKeyCredentialCreationOptionsWithoutExtensions | null; } export type VerificationStatus = 'unverified' | 'verified' | 'transferable' | 'failed' | 'expired';