@thru/passkey 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/dist/index.cjs +892 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +187 -0
- package/dist/index.d.ts +187 -0
- package/dist/index.js +850 -0
- package/dist/index.js.map +1 -0
- package/package.json +27 -0
- package/src/capabilities.ts +254 -0
- package/src/index.ts +86 -0
- package/src/popup-service.ts +168 -0
- package/src/popup.ts +192 -0
- package/src/register.ts +228 -0
- package/src/sign.ts +280 -0
- package/src/types.ts +149 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/register.ts","../src/capabilities.ts","../src/popup.ts","../src/sign.ts","../src/index.ts","../src/popup-service.ts"],"sourcesContent":["import type { PasskeyRegistrationResult, PasskeyPopupRegistrationResult } from './types';\nimport { arrayBufferToBase64Url, bytesToHex } from '@thru/passkey-manager';\nimport {\n isWebAuthnSupported,\n getPasskeyPromptMode,\n maybePreopenPopup,\n shouldFallbackToPopup,\n type PasskeyPromptAction,\n} from './capabilities';\nimport { requestPasskeyPopup, openPasskeyPopupWindow, closePopup } from './popup';\n\n/**\n * Register a new passkey for a profile.\n */\nexport async function registerPasskey(\n alias: string,\n userId: string,\n rpId: string\n): Promise<PasskeyRegistrationResult> {\n if (!isWebAuthnSupported()) {\n throw new Error('WebAuthn is not supported in this browser');\n }\n\n return runWithPromptMode(\n 'create',\n () => registerPasskeyInline(alias, userId, rpId),\n (preopenedPopup) => registerPasskeyViaPopup(alias, userId, rpId, preopenedPopup)\n );\n}\n\nasync function runWithPromptMode<T>(\n action: PasskeyPromptAction,\n inlineFn: () => Promise<T>,\n popupFn: (preopenedPopup?: Window | null) => Promise<T>\n): Promise<T> {\n const preopenedPopup = maybePreopenPopup(action, openPasskeyPopupWindow);\n const promptMode = await getPasskeyPromptMode(action);\n if (promptMode === 'popup') {\n return popupFn(preopenedPopup);\n }\n\n closePopup(preopenedPopup);\n\n try {\n return await inlineFn();\n } catch (error) {\n if (shouldFallbackToPopup(error)) {\n return popupFn();\n }\n throw error;\n }\n}\n\nasync function registerPasskeyInline(\n alias: string,\n userId: string,\n rpId: string\n): Promise<PasskeyRegistrationResult> {\n const rpName = 'Thru Wallet';\n\n const userIdBytes = new TextEncoder().encode(userId);\n const userIdBuffer = userIdBytes.slice(0, 64);\n\n const challenge = crypto.getRandomValues(new Uint8Array(32));\n\n const createOptions: PublicKeyCredentialCreationOptions = {\n challenge,\n rp: {\n id: rpId,\n name: rpName,\n },\n user: {\n id: userIdBuffer,\n name: alias,\n displayName: alias,\n },\n pubKeyCredParams: [\n { type: 'public-key', alg: -7 },\n ],\n authenticatorSelection: {\n authenticatorAttachment: 'platform',\n userVerification: 'required',\n residentKey: 'required',\n requireResidentKey: true,\n },\n attestation: 'none',\n timeout: 60000,\n };\n\n const credential = (await navigator.credentials.create({\n publicKey: createOptions,\n })) as PublicKeyCredential | null;\n\n if (!credential) {\n throw new Error('Passkey registration was cancelled');\n }\n\n const response = credential.response as AuthenticatorAttestationResponse;\n const { x, y } = extractP256PublicKey(response);\n\n return {\n credentialId: arrayBufferToBase64Url(credential.rawId),\n publicKeyX: bytesToHex(x),\n publicKeyY: bytesToHex(y),\n rpId,\n };\n}\n\nasync function registerPasskeyViaPopup(\n alias: string,\n userId: string,\n rpId: string,\n preopenedPopup?: Window | null\n): Promise<PasskeyRegistrationResult> {\n const result = await requestPasskeyPopup<PasskeyPopupRegistrationResult>(\n 'create',\n { alias, userId, rpId },\n preopenedPopup\n );\n return result;\n}\n\n// Key extraction helpers\n\nfunction extractP256PublicKey(\n response: AuthenticatorAttestationResponse\n): { x: Uint8Array; y: Uint8Array } {\n if (typeof response.getPublicKey === 'function') {\n const spkiKey = response.getPublicKey();\n if (spkiKey) {\n return extractFromSpki(new Uint8Array(spkiKey));\n }\n }\n\n if (typeof response.getAuthenticatorData === 'function') {\n const authData = new Uint8Array(response.getAuthenticatorData());\n return extractFromAuthenticatorData(authData);\n }\n\n throw new Error('Unable to extract public key: browser does not support required WebAuthn methods');\n}\n\nfunction extractFromSpki(spki: Uint8Array): { x: Uint8Array; y: Uint8Array } {\n const pointStart = spki.length - 65;\n\n if (spki[pointStart] !== 0x04) {\n throw new Error('Invalid SPKI format: expected uncompressed point');\n }\n\n const x = spki.slice(pointStart + 1, pointStart + 33);\n const y = spki.slice(pointStart + 33, pointStart + 65);\n\n if (x.length !== 32 || y.length !== 32) {\n throw new Error('Invalid SPKI format: incorrect coordinate length');\n }\n\n return { x, y };\n}\n\nfunction extractFromAuthenticatorData(authData: Uint8Array): { x: Uint8Array; y: Uint8Array } {\n const rpIdHashLength = 32;\n const flagsLength = 1;\n const counterLength = 4;\n const offset = rpIdHashLength + flagsLength + counterLength;\n const aaguidLength = 16;\n const credIdLenOffset = offset + aaguidLength;\n const credIdLength = (authData[credIdLenOffset] << 8) | authData[credIdLenOffset + 1];\n const coseKeyOffset = credIdLenOffset + 2 + credIdLength;\n const coseKey = authData.slice(coseKeyOffset);\n\n return extractFromCoseKey(coseKey);\n}\n\nfunction extractFromCoseKey(coseKey: Uint8Array): { x: Uint8Array; y: Uint8Array } {\n const mapStart = coseKey[0];\n if (mapStart !== 0xa5 && mapStart !== 0xa4) {\n throw new Error('Invalid COSE key format');\n }\n\n let offset = 1;\n let x: Uint8Array | null = null;\n let y: Uint8Array | null = null;\n\n while (offset < coseKey.length) {\n const key = coseKey[offset++];\n const valueType = coseKey[offset++];\n\n if (key === 0x21) {\n const length = valueType & 0x1f;\n x = coseKey.slice(offset, offset + length);\n offset += length;\n continue;\n }\n\n if (key === 0x22) {\n const length = valueType & 0x1f;\n y = coseKey.slice(offset, offset + length);\n offset += length;\n continue;\n }\n\n if (valueType >= 0x40 && valueType <= 0x5f) {\n const length = valueType & 0x1f;\n offset += length;\n continue;\n }\n\n if (valueType === 0x01 || valueType === 0x02 || valueType === 0x03) {\n continue;\n }\n\n if (valueType >= 0x18 && valueType <= 0x1b) {\n const size = 1 << (valueType - 0x18);\n offset += size;\n continue;\n }\n }\n\n if (!x || !y) {\n throw new Error('Failed to extract P-256 public key from COSE data');\n }\n\n if (x.length !== 32 || y.length !== 32) {\n throw new Error('Invalid COSE key: incorrect coordinate length');\n }\n\n return { x, y };\n}\n","import type { PasskeyClientCapabilities } from './types';\n\nconst DEBUG = typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_PASSKEY_DEBUG === '1';\n\nlet cachedClientCapabilities: PasskeyClientCapabilities | null | undefined;\nlet clientCapabilitiesPromise: Promise<PasskeyClientCapabilities | null> | null = null;\n\nexport function isWebAuthnSupported(): boolean {\n const supported =\n typeof window !== 'undefined' &&\n typeof window.PublicKeyCredential !== 'undefined' &&\n typeof navigator.credentials !== 'undefined';\n\n if (DEBUG) {\n console.log('[Passkey] WebAuthn support check:', {\n window: typeof window !== 'undefined',\n PublicKeyCredential:\n typeof window !== 'undefined' && typeof window.PublicKeyCredential !== 'undefined',\n credentials:\n typeof window !== 'undefined' &&\n typeof navigator !== 'undefined' &&\n typeof navigator.credentials !== 'undefined',\n supported,\n });\n }\n\n return supported;\n}\n\nasync function fetchPasskeyClientCapabilities(): Promise<PasskeyClientCapabilities | null> {\n if (typeof window === 'undefined' || typeof window.PublicKeyCredential === 'undefined') {\n return null;\n }\n\n const getClientCapabilities = (window.PublicKeyCredential as {\n getClientCapabilities?: () => Promise<PasskeyClientCapabilities>;\n }).getClientCapabilities;\n\n if (typeof getClientCapabilities !== 'function') {\n return null;\n }\n\n try {\n const capabilities = await getClientCapabilities.call(window.PublicKeyCredential);\n if (DEBUG) {\n console.log('[Passkey] WebAuthn client capabilities:', capabilities);\n }\n return capabilities ?? null;\n } catch (error) {\n if (DEBUG) {\n console.warn('[Passkey] Failed to read client capabilities:', error);\n }\n return null;\n }\n}\n\nexport function preloadPasskeyClientCapabilities(): void {\n if (cachedClientCapabilities !== undefined || clientCapabilitiesPromise) {\n return;\n }\n\n clientCapabilitiesPromise = fetchPasskeyClientCapabilities().then((capabilities) => {\n cachedClientCapabilities = capabilities;\n return capabilities;\n });\n}\n\nexport async function getPasskeyClientCapabilities(): Promise<PasskeyClientCapabilities | null> {\n if (cachedClientCapabilities !== undefined) {\n return cachedClientCapabilities;\n }\n\n if (!clientCapabilitiesPromise) {\n preloadPasskeyClientCapabilities();\n }\n\n if (!clientCapabilitiesPromise) {\n cachedClientCapabilities = null;\n return null;\n }\n\n const capabilities = await clientCapabilitiesPromise;\n cachedClientCapabilities = capabilities;\n return capabilities;\n}\n\nexport function getCachedPasskeyClientCapabilities(): PasskeyClientCapabilities | null | undefined {\n return cachedClientCapabilities;\n}\n\nexport function isInIframe(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n try {\n return window.self !== window.top;\n } catch {\n return true;\n }\n}\n\nexport type PasskeyPromptAction = 'get' | 'create';\n\nexport async function shouldUsePasskeyPopup(action: PasskeyPromptAction): Promise<boolean> {\n if (!isInIframe()) {\n return false;\n }\n const mode = await getPasskeyPromptMode(action);\n return mode === 'popup';\n}\n\ntype PasskeyPromptMode = 'inline' | 'popup';\n\nfunction getPermissionsPolicyAllowsFeature(feature: string): boolean | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const policy = (document as { permissionsPolicy?: { allowsFeature?: (name: string) => boolean } })\n .permissionsPolicy;\n const featurePolicy = (document as { featurePolicy?: { allowsFeature?: (name: string) => boolean } })\n .featurePolicy;\n const allowsFeature = policy?.allowsFeature || featurePolicy?.allowsFeature;\n\n if (typeof allowsFeature !== 'function') {\n return null;\n }\n\n try {\n return allowsFeature(feature);\n } catch {\n return null;\n }\n}\n\nfunction getCachedPromptMode(action: PasskeyPromptAction): PasskeyPromptMode | 'unknown' {\n if (!isInIframe()) {\n return 'inline';\n }\n\n if (cachedClientCapabilities === undefined && !clientCapabilitiesPromise) {\n preloadPasskeyClientCapabilities();\n }\n\n const feature =\n action === 'create' ? 'publickey-credentials-create' : 'publickey-credentials-get';\n const policyAllows = getPermissionsPolicyAllowsFeature(feature);\n const capabilities = getCachedPasskeyClientCapabilities();\n const supportsInline =\n capabilities?.passkeyPlatformAuthenticator === true ||\n capabilities?.userVerifyingPlatformAuthenticator === true;\n\n if (policyAllows === false) {\n return 'popup';\n }\n\n if (capabilities === undefined) {\n return 'unknown';\n }\n\n if (!supportsInline) {\n return 'popup';\n }\n\n return 'inline';\n}\n\nexport async function getPasskeyPromptMode(action: PasskeyPromptAction): Promise<PasskeyPromptMode> {\n if (!isInIframe()) {\n return 'inline';\n }\n\n const feature =\n action === 'create' ? 'publickey-credentials-create' : 'publickey-credentials-get';\n const policyAllows = getPermissionsPolicyAllowsFeature(feature);\n const capabilities = await getPasskeyClientCapabilities();\n const supportsInline =\n capabilities?.passkeyPlatformAuthenticator === true ||\n capabilities?.userVerifyingPlatformAuthenticator === true;\n\n if (DEBUG) {\n console.log('[Passkey] Prompt mode check:', {\n action,\n policyAllows,\n supportsInline,\n capabilities,\n });\n }\n\n if (!supportsInline) {\n return 'popup';\n }\n\n if (policyAllows === false) {\n return 'popup';\n }\n\n return 'inline';\n}\n\nexport function maybePreopenPopup(action: PasskeyPromptAction, openPopupFn: () => Window): Window | null {\n const cachedMode = getCachedPromptMode(action);\n if (cachedMode !== 'popup') {\n return null;\n }\n\n try {\n return openPopupFn();\n } catch {\n return null;\n }\n}\n\nexport function shouldFallbackToPopup(error: unknown): boolean {\n if (!isInIframe()) {\n return false;\n }\n\n const name =\n error && typeof error === 'object' && 'name' in error ? String((error as { name?: unknown }).name) : '';\n const message =\n error && typeof error === 'object' && 'message' in error\n ? String((error as { message?: unknown }).message)\n : '';\n const normalized = `${name} ${message}`.toLowerCase();\n\n if (\n normalized.includes('cancel') ||\n normalized.includes('canceled') ||\n normalized.includes('cancelled') ||\n normalized.includes('user canceled') ||\n normalized.includes('user cancelled') ||\n normalized.includes('aborted')\n ) {\n return false;\n }\n\n if (normalized.includes('securityerror')) {\n return true;\n }\n\n if (normalized.includes('notallowederror')) {\n if (\n normalized.includes('permission') ||\n normalized.includes('policy') ||\n normalized.includes('iframe') ||\n normalized.includes('frame')\n ) {\n return true;\n }\n }\n\n return false;\n}\n","import type {\n PasskeyPopupAction,\n PasskeyPopupRequestPayload,\n PasskeyPopupRequest,\n PasskeyPopupResponse,\n} from './types';\n\nexport const PASSKEY_POPUP_PATH = '/passkey/popup';\nexport const PASSKEY_POPUP_READY_EVENT = 'thru:passkey-popup-ready';\nexport const PASSKEY_POPUP_REQUEST_EVENT = 'thru:passkey-popup-request';\nexport const PASSKEY_POPUP_RESPONSE_EVENT = 'thru:passkey-popup-response';\nexport const PASSKEY_POPUP_CHANNEL = 'thru:passkey-popup-channel';\n\nconst PASSKEY_POPUP_TIMEOUT_MS = 60000;\n\nexport function closePopup(popup: Window | null | undefined): void {\n if (popup && !popup.closed) {\n popup.close();\n }\n}\n\nexport function openPasskeyPopupWindow(): Window {\n const popupUrl = new URL(PASSKEY_POPUP_PATH, window.location.origin).toString();\n const popup = window.open(\n popupUrl,\n 'thru_passkey_popup',\n 'popup=yes,width=440,height=640'\n );\n\n if (!popup) {\n throw new Error('Passkey popup was blocked');\n }\n\n return popup;\n}\n\nfunction createPopupRequestId(): string {\n const rand = Math.random().toString(36).slice(2, 10);\n return `passkey_${Date.now()}_${rand}`;\n}\n\nexport async function requestPasskeyPopup<T>(\n action: PasskeyPopupAction,\n payload: PasskeyPopupRequestPayload,\n preopenedPopup?: Window | null\n): Promise<T> {\n if (typeof window === 'undefined') {\n throw new Error('Passkey popup is only available in the browser');\n }\n\n const requestId = createPopupRequestId();\n const targetOrigin = window.location.origin;\n let popup: Window | null = preopenedPopup ?? null;\n const channel =\n typeof BroadcastChannel !== 'undefined' ? new BroadcastChannel(PASSKEY_POPUP_CHANNEL) : null;\n\n return new Promise<T>((resolve, reject) => {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let closePoll: ReturnType<typeof setInterval> | null = null;\n let requestSent = false;\n\n const cleanup = () => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n if (closePoll) {\n clearInterval(closePoll);\n closePoll = null;\n }\n window.removeEventListener('message', handleMessage);\n if (channel) {\n channel.removeEventListener('message', handleChannelMessage);\n channel.close();\n }\n };\n\n const sendRequest = (viaChannel: boolean) => {\n if (requestSent) {\n return;\n }\n requestSent = true;\n\n const request: PasskeyPopupRequest = {\n type: PASSKEY_POPUP_REQUEST_EVENT,\n requestId,\n action,\n payload,\n };\n\n if (viaChannel) {\n channel?.postMessage(request);\n return;\n }\n\n popup?.postMessage(request, targetOrigin);\n };\n\n const handleResponse = (data: PasskeyPopupResponse) => {\n if (data.requestId !== requestId) {\n return;\n }\n\n cleanup();\n if (popup && !popup.closed) {\n popup.close();\n }\n\n if (data.success) {\n resolve((data as Extract<PasskeyPopupResponse, { success: true }>).result as T);\n } else {\n const err = new Error(data.error?.message || 'Passkey popup failed');\n if (data.error?.name) {\n (err as { name?: string }).name = data.error.name;\n }\n reject(err);\n }\n };\n\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== targetOrigin) {\n return;\n }\n\n const data = event.data as PasskeyPopupResponse | { type?: string };\n if (!data || typeof data !== 'object') {\n return;\n }\n\n if (data.type === PASSKEY_POPUP_READY_EVENT) {\n if (popup && event.source !== popup) {\n return;\n }\n sendRequest(false);\n return;\n }\n\n if (data.type === PASSKEY_POPUP_RESPONSE_EVENT && 'requestId' in data) {\n handleResponse(data as PasskeyPopupResponse);\n }\n };\n\n window.addEventListener('message', handleMessage);\n\n const handleChannelMessage = (event: MessageEvent) => {\n const data = event.data as PasskeyPopupResponse | { type?: string };\n if (!data || typeof data !== 'object') {\n return;\n }\n\n if (data.type === PASSKEY_POPUP_READY_EVENT) {\n sendRequest(true);\n return;\n }\n\n if (data.type === PASSKEY_POPUP_RESPONSE_EVENT && 'requestId' in data) {\n handleResponse(data as PasskeyPopupResponse);\n }\n };\n\n if (channel) {\n channel.addEventListener('message', handleChannelMessage);\n }\n\n if (!popup) {\n try {\n popup = openPasskeyPopupWindow();\n } catch (error) {\n cleanup();\n reject(error);\n return;\n }\n }\n\n timeout = setTimeout(() => {\n cleanup();\n try {\n popup?.close();\n } catch {\n /* ignore */\n }\n reject(new Error('Passkey popup timed out'));\n }, PASSKEY_POPUP_TIMEOUT_MS);\n\n closePoll = setInterval(() => {\n if (popup && popup.closed) {\n cleanup();\n reject(new Error('Passkey popup was closed'));\n }\n }, 250);\n });\n}\n","import type {\n PasskeySigningResult,\n PasskeyStoredSigningResult,\n PasskeyDiscoverableSigningResult,\n PasskeyMetadata,\n PasskeyPopupContext,\n PasskeyPopupSigningResult,\n PasskeyPopupStoredSigningResult,\n} from './types';\nimport {\n arrayBufferToBase64Url,\n base64UrlToArrayBuffer,\n bytesToBase64Url,\n base64UrlToBytes,\n parseDerSignature,\n normalizeLowS,\n} from '@thru/passkey-manager';\nimport {\n isWebAuthnSupported,\n getPasskeyPromptMode,\n isInIframe,\n maybePreopenPopup,\n shouldFallbackToPopup,\n type PasskeyPromptAction,\n} from './capabilities';\nimport { requestPasskeyPopup, openPasskeyPopupWindow, closePopup } from './popup';\n\n/**\n * Sign a challenge with an existing passkey (by credential ID).\n */\nexport async function signWithPasskey(\n credentialId: string,\n challenge: Uint8Array,\n rpId: string\n): Promise<PasskeySigningResult> {\n if (!isWebAuthnSupported()) {\n throw new Error('WebAuthn is not supported in this browser');\n }\n\n return runWithPromptMode(\n 'get',\n () => signWithPasskeyInline(credentialId, challenge, rpId),\n (preopenedPopup) => signWithPasskeyViaPopup(credentialId, challenge, rpId, preopenedPopup)\n );\n}\n\n/**\n * Sign with stored passkey (for embedded/popup contexts).\n */\nexport async function signWithStoredPasskey(\n challenge: Uint8Array,\n rpId: string,\n preferredPasskey: PasskeyMetadata | null,\n allPasskeys: PasskeyMetadata[],\n context?: PasskeyPopupContext\n): Promise<PasskeyStoredSigningResult> {\n if (!isWebAuthnSupported()) {\n throw new Error('WebAuthn is not supported in this browser');\n }\n\n const preopenedPopup = maybePreopenPopup('get', openPasskeyPopupWindow);\n const promptMode = await getPasskeyPromptMode('get');\n const storedPasskey = preferredPasskey;\n const canUsePopup = isInIframe();\n\n if (promptMode === 'popup' || (canUsePopup && !storedPasskey)) {\n return requestStoredPasskeyPopup(challenge, preopenedPopup, context);\n }\n\n closePopup(preopenedPopup);\n\n try {\n if (storedPasskey) {\n const result = await signWithPasskeyInline(\n storedPasskey.credentialId,\n challenge,\n storedPasskey.rpId\n );\n return {\n ...result,\n passkey: storedPasskey,\n };\n }\n\n const discoverable = await signWithDiscoverablePasskey(challenge, rpId);\n const matchingPasskey = allPasskeys.find(p => p.credentialId === discoverable.credentialId) ?? null;\n const now = new Date().toISOString();\n const passkey = matchingPasskey ?? {\n credentialId: discoverable.credentialId,\n publicKeyX: '',\n publicKeyY: '',\n rpId: discoverable.rpId,\n createdAt: now,\n lastUsedAt: now,\n };\n\n return {\n signature: discoverable.signature,\n authenticatorData: discoverable.authenticatorData,\n clientDataJSON: discoverable.clientDataJSON,\n signatureR: discoverable.signatureR,\n signatureS: discoverable.signatureS,\n passkey,\n };\n } catch (error) {\n if (canUsePopup && shouldFallbackToPopup(error)) {\n return requestStoredPasskeyPopup(challenge, undefined, context);\n }\n\n throw error;\n }\n}\n\n/**\n * Sign with a discoverable passkey (no credential ID - browser prompts user to select).\n */\nexport async function signWithDiscoverablePasskey(\n challenge: Uint8Array,\n rpId: string\n): Promise<PasskeyDiscoverableSigningResult> {\n if (!isWebAuthnSupported()) {\n throw new Error('WebAuthn is not supported in this browser');\n }\n\n const resolvedRpId = rpId;\n const result = await signWithPasskeyAssertion(challenge, resolvedRpId);\n\n return {\n signature: result.signature,\n authenticatorData: result.authenticatorData,\n clientDataJSON: result.clientDataJSON,\n signatureR: result.signatureR,\n signatureS: result.signatureS,\n credentialId: result.credentialId,\n rpId: resolvedRpId,\n };\n}\n\n// Internal helpers\n\nasync function runWithPromptMode<T>(\n action: PasskeyPromptAction,\n inlineFn: () => Promise<T>,\n popupFn: (preopenedPopup?: Window | null) => Promise<T>\n): Promise<T> {\n const preopenedPopup = maybePreopenPopup(action, openPasskeyPopupWindow);\n const promptMode = await getPasskeyPromptMode(action);\n if (promptMode === 'popup') {\n return popupFn(preopenedPopup);\n }\n\n closePopup(preopenedPopup);\n\n try {\n return await inlineFn();\n } catch (error) {\n if (shouldFallbackToPopup(error)) {\n return popupFn();\n }\n throw error;\n }\n}\n\nasync function signWithPasskeyInline(\n credentialId: string,\n challenge: Uint8Array,\n rpId: string\n): Promise<PasskeySigningResult> {\n const result = await signWithPasskeyAssertion(challenge, rpId, credentialId);\n return {\n signature: result.signature,\n authenticatorData: result.authenticatorData,\n clientDataJSON: result.clientDataJSON,\n signatureR: result.signatureR,\n signatureS: result.signatureS,\n };\n}\n\nasync function signWithPasskeyAssertion(\n challenge: Uint8Array,\n rpId: string,\n credentialId?: string\n): Promise<PasskeySigningResult & { credentialId: string }> {\n const challengeBytes = new Uint8Array(challenge);\n const getOptions: PublicKeyCredentialRequestOptions = {\n challenge: challengeBytes,\n rpId,\n userVerification: 'required',\n timeout: 60000,\n };\n\n if (credentialId) {\n const credentialIdBuffer = base64UrlToArrayBuffer(credentialId);\n getOptions.allowCredentials = [\n {\n type: 'public-key',\n id: credentialIdBuffer,\n transports: ['internal', 'hybrid', 'usb', 'ble', 'nfc'],\n },\n ];\n }\n\n const assertion = (await navigator.credentials.get({\n publicKey: getOptions,\n })) as PublicKeyCredential | null;\n\n if (!assertion) {\n throw new Error('Passkey authentication was cancelled');\n }\n\n const response = assertion.response as AuthenticatorAssertionResponse;\n\n const signature = new Uint8Array(response.signature);\n let { r, s } = parseDerSignature(signature);\n s = normalizeLowS(s);\n\n return {\n signature: new Uint8Array([...r, ...s]),\n authenticatorData: new Uint8Array(response.authenticatorData),\n clientDataJSON: new Uint8Array(response.clientDataJSON),\n signatureR: r,\n signatureS: s,\n credentialId: arrayBufferToBase64Url(assertion.rawId),\n };\n}\n\nasync function signWithPasskeyViaPopup(\n credentialId: string,\n challenge: Uint8Array,\n rpId: string,\n preopenedPopup?: Window | null\n): Promise<PasskeySigningResult> {\n const result = await requestPasskeyPopup<PasskeyPopupSigningResult>(\n 'get',\n {\n credentialId,\n challengeBase64Url: bytesToBase64Url(challenge),\n rpId,\n },\n preopenedPopup\n );\n\n return decodePopupSigningResult(result);\n}\n\nasync function requestStoredPasskeyPopup(\n challenge: Uint8Array,\n preopenedPopup?: Window | null,\n context?: PasskeyPopupContext\n): Promise<PasskeyStoredSigningResult> {\n const result = await requestPasskeyPopup<PasskeyPopupStoredSigningResult>(\n 'getStored',\n {\n challengeBase64Url: bytesToBase64Url(challenge),\n context,\n },\n preopenedPopup\n );\n return decodePopupStoredSigningResult(result);\n}\n\nfunction decodePopupSigningResult(result: PasskeyPopupSigningResult): PasskeySigningResult {\n return {\n signature: base64UrlToBytes(result.signatureBase64Url),\n authenticatorData: base64UrlToBytes(result.authenticatorDataBase64Url),\n clientDataJSON: base64UrlToBytes(result.clientDataJSONBase64Url),\n signatureR: base64UrlToBytes(result.signatureRBase64Url),\n signatureS: base64UrlToBytes(result.signatureSBase64Url),\n };\n}\n\nfunction decodePopupStoredSigningResult(\n result: PasskeyPopupStoredSigningResult\n): PasskeyStoredSigningResult {\n return {\n ...decodePopupSigningResult(result),\n passkey: result.passkey,\n accounts: result.accounts,\n };\n}\n","// Types\nexport type {\n PasskeyRegistrationResult,\n PasskeySigningResult,\n PasskeyDiscoverableSigningResult,\n PasskeyStoredSigningResult,\n PasskeyMetadata,\n PasskeyClientCapabilities,\n PasskeyPopupContext,\n PasskeyPopupAccount,\n PasskeyPopupAction,\n PasskeyPopupGetRequestPayload,\n PasskeyPopupCreateRequestPayload,\n PasskeyPopupGetStoredRequestPayload,\n PasskeyPopupRequestPayload,\n PasskeyPopupRequest,\n PasskeyPopupSigningResult,\n PasskeyPopupStoredPasskey,\n PasskeyPopupStoredSigningResult,\n PasskeyPopupRegistrationResult,\n PasskeyPopupResponse,\n} from './types';\n\n// Registration\nexport { registerPasskey } from './register';\n\n// Signing\nexport { signWithPasskey, signWithStoredPasskey, signWithDiscoverablePasskey } from './sign';\n\n// Crypto (re-exported from @thru/passkey-manager)\nexport {\n parseDerSignature,\n normalizeLowS,\n normalizeSignatureComponent,\n P256_N,\n P256_HALF_N,\n bytesToBigIntBE,\n bigIntToBytesBE,\n} from '@thru/passkey-manager';\n\n// Capabilities\nexport {\n isWebAuthnSupported,\n preloadPasskeyClientCapabilities,\n getPasskeyClientCapabilities,\n getCachedPasskeyClientCapabilities,\n shouldUsePasskeyPopup,\n isInIframe,\n type PasskeyPromptAction,\n} from './capabilities';\n\n// Encoding (re-exported from @thru/passkey-manager)\nexport {\n arrayBufferToBase64Url,\n base64UrlToArrayBuffer,\n bytesToBase64Url,\n base64UrlToBytes,\n bytesToHex,\n hexToBytes,\n bytesEqual,\n compareBytes,\n uniqueAccounts,\n} from '@thru/passkey-manager';\n\n// Popup (parent side)\nexport {\n PASSKEY_POPUP_PATH,\n PASSKEY_POPUP_READY_EVENT,\n PASSKEY_POPUP_REQUEST_EVENT,\n PASSKEY_POPUP_RESPONSE_EVENT,\n PASSKEY_POPUP_CHANNEL,\n openPasskeyPopupWindow,\n closePopup,\n requestPasskeyPopup,\n} from './popup';\n\n// Popup service (popup window side)\nexport {\n toPopupSigningResult,\n buildSuccessResponse,\n decodeChallenge,\n getPopupDisplayInfo,\n getResponseError,\n signWithPreferredPasskey,\n buildStoredPasskeyResult,\n} from './popup-service';\n","import type {\n PasskeyPopupAction,\n PasskeyPopupAccount,\n PasskeyPopupContext,\n PasskeyPopupResponse,\n PasskeyPopupSigningResult,\n PasskeyPopupStoredPasskey,\n PasskeyPopupStoredSigningResult,\n PasskeyMetadata,\n PasskeySigningResult,\n} from './types';\nimport {\n PASSKEY_POPUP_RESPONSE_EVENT,\n} from './popup';\nimport { bytesToBase64Url, base64UrlToBytes } from '@thru/passkey-manager';\nimport { signWithPasskey, signWithDiscoverablePasskey } from './sign';\n\ntype PasskeySignResult =\n | Awaited<ReturnType<typeof signWithDiscoverablePasskey>>\n | Awaited<ReturnType<typeof signWithPasskey>>;\n\nexport function toPopupSigningResult(result: PasskeySigningResult): PasskeyPopupSigningResult {\n return {\n signatureBase64Url: bytesToBase64Url(result.signature),\n authenticatorDataBase64Url: bytesToBase64Url(result.authenticatorData),\n clientDataJSONBase64Url: bytesToBase64Url(result.clientDataJSON),\n signatureRBase64Url: bytesToBase64Url(result.signatureR),\n signatureSBase64Url: bytesToBase64Url(result.signatureS),\n };\n}\n\nexport function buildSuccessResponse<T>(\n requestId: string,\n action: PasskeyPopupAction,\n result: T\n): PasskeyPopupResponse {\n return {\n type: PASSKEY_POPUP_RESPONSE_EVENT,\n requestId,\n action,\n success: true,\n result,\n } as PasskeyPopupResponse;\n}\n\nexport function decodeChallenge(base64Url: string): Uint8Array {\n return base64UrlToBytes(base64Url);\n}\n\nexport function getPopupDisplayInfo(context?: PasskeyPopupContext): {\n name: string;\n url?: string;\n imageUrl?: string;\n logoText: string;\n} {\n const name = context?.appName || context?.origin || 'A dApp';\n const url = context?.appUrl || context?.origin;\n const logoText = name.charAt(0).toUpperCase() || 'A';\n return {\n name,\n url,\n imageUrl: context?.imageUrl,\n logoText,\n };\n}\n\nexport function getResponseError(action: PasskeyPopupAction, error: unknown): { name?: string; message: string } {\n const { name, message } = normalizeError(error);\n const actionLabel = `Popup ${action}`;\n const messageText = message || 'Passkey popup failed';\n const detailedMessage = messageText.includes('Popup')\n ? messageText\n : `${actionLabel}: ${messageText}`;\n return {\n name,\n message: detailedMessage,\n };\n}\n\nexport async function signWithPreferredPasskey(\n preferredPasskey: PasskeyMetadata | null,\n challenge: Uint8Array,\n log?: (message: string) => void\n): Promise<{ result: PasskeySignResult; credentialId: string; rpId: string }> {\n const resolvedRpId = preferredPasskey?.rpId ?? window.location.hostname;\n\n if (preferredPasskey?.credentialId && preferredPasskey.rpId) {\n try {\n const storedResult = await signWithPasskey(\n preferredPasskey.credentialId,\n challenge,\n preferredPasskey.rpId\n );\n return {\n result: storedResult,\n credentialId: preferredPasskey.credentialId,\n rpId: preferredPasskey.rpId,\n };\n } catch (error) {\n if (!shouldFallbackToDiscoverable(error)) {\n throw error;\n }\n if (log) {\n log('stored passkey failed; falling back to discoverable prompt');\n }\n }\n }\n\n const discovered = await signWithDiscoverablePasskey(challenge, resolvedRpId);\n return {\n result: discovered,\n credentialId: discovered.credentialId,\n rpId: resolvedRpId,\n };\n}\n\nexport function buildStoredPasskeyResult(\n signed: { result: PasskeySignResult; credentialId: string; rpId: string },\n preferredPasskey: PasskeyMetadata | null,\n profiles: Array<{ passkey: PasskeyMetadata | null }>,\n accounts: PasskeyPopupAccount[]\n): PasskeyPopupStoredSigningResult {\n const now = new Date().toISOString();\n const matchingPasskey =\n profiles.find((profile) => profile.passkey?.credentialId === signed.credentialId)?.passkey ??\n null;\n\n const passkey: PasskeyPopupStoredPasskey = (matchingPasskey ?? {\n credentialId: signed.credentialId,\n publicKeyX: '',\n publicKeyY: '',\n rpId: signed.rpId,\n label: preferredPasskey?.label,\n createdAt: now,\n lastUsedAt: now,\n }) as PasskeyPopupStoredPasskey;\n\n return {\n ...toPopupSigningResult(signed.result),\n passkey: matchingPasskey ? { ...passkey, lastUsedAt: now } : passkey,\n accounts,\n };\n}\n\nfunction normalizeError(error: unknown): { name?: string; message?: string; normalized: string } {\n const name =\n error && typeof error === 'object' && 'name' in error\n ? String((error as { name?: unknown }).name)\n : '';\n const message =\n error && typeof error === 'object' && 'message' in error\n ? String((error as { message?: unknown }).message)\n : '';\n return {\n name,\n message,\n normalized: `${name} ${message}`.toLowerCase(),\n };\n}\n\nfunction shouldFallbackToDiscoverable(error: unknown): boolean {\n const normalized = normalizeError(error).normalized;\n return (\n normalized.includes('notfounderror') ||\n normalized.includes('notallowederror') ||\n normalized.includes('securityerror')\n );\n}\n"],"mappings":"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","names":["arrayBufferToBase64Url","runWithPromptMode","arrayBufferToBase64Url","parseDerSignature","normalizeLowS","arrayBufferToBase64Url","base64UrlToArrayBuffer","bytesToBase64Url","base64UrlToBytes","bytesToHex","bytesToBase64Url","base64UrlToBytes","bytesToBase64Url","base64UrlToBytes"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thru/passkey",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@thru/passkey-manager": "0.2.1"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"tsup": "^8.5.0",
|
|
20
|
+
"typescript": "^5.9.3"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"clean": "rm -rf dist"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import type { PasskeyClientCapabilities } from './types';
|
|
2
|
+
|
|
3
|
+
const DEBUG = typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_PASSKEY_DEBUG === '1';
|
|
4
|
+
|
|
5
|
+
let cachedClientCapabilities: PasskeyClientCapabilities | null | undefined;
|
|
6
|
+
let clientCapabilitiesPromise: Promise<PasskeyClientCapabilities | null> | null = null;
|
|
7
|
+
|
|
8
|
+
export function isWebAuthnSupported(): boolean {
|
|
9
|
+
const supported =
|
|
10
|
+
typeof window !== 'undefined' &&
|
|
11
|
+
typeof window.PublicKeyCredential !== 'undefined' &&
|
|
12
|
+
typeof navigator.credentials !== 'undefined';
|
|
13
|
+
|
|
14
|
+
if (DEBUG) {
|
|
15
|
+
console.log('[Passkey] WebAuthn support check:', {
|
|
16
|
+
window: typeof window !== 'undefined',
|
|
17
|
+
PublicKeyCredential:
|
|
18
|
+
typeof window !== 'undefined' && typeof window.PublicKeyCredential !== 'undefined',
|
|
19
|
+
credentials:
|
|
20
|
+
typeof window !== 'undefined' &&
|
|
21
|
+
typeof navigator !== 'undefined' &&
|
|
22
|
+
typeof navigator.credentials !== 'undefined',
|
|
23
|
+
supported,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return supported;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function fetchPasskeyClientCapabilities(): Promise<PasskeyClientCapabilities | null> {
|
|
31
|
+
if (typeof window === 'undefined' || typeof window.PublicKeyCredential === 'undefined') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const getClientCapabilities = (window.PublicKeyCredential as {
|
|
36
|
+
getClientCapabilities?: () => Promise<PasskeyClientCapabilities>;
|
|
37
|
+
}).getClientCapabilities;
|
|
38
|
+
|
|
39
|
+
if (typeof getClientCapabilities !== 'function') {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const capabilities = await getClientCapabilities.call(window.PublicKeyCredential);
|
|
45
|
+
if (DEBUG) {
|
|
46
|
+
console.log('[Passkey] WebAuthn client capabilities:', capabilities);
|
|
47
|
+
}
|
|
48
|
+
return capabilities ?? null;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (DEBUG) {
|
|
51
|
+
console.warn('[Passkey] Failed to read client capabilities:', error);
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function preloadPasskeyClientCapabilities(): void {
|
|
58
|
+
if (cachedClientCapabilities !== undefined || clientCapabilitiesPromise) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
clientCapabilitiesPromise = fetchPasskeyClientCapabilities().then((capabilities) => {
|
|
63
|
+
cachedClientCapabilities = capabilities;
|
|
64
|
+
return capabilities;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function getPasskeyClientCapabilities(): Promise<PasskeyClientCapabilities | null> {
|
|
69
|
+
if (cachedClientCapabilities !== undefined) {
|
|
70
|
+
return cachedClientCapabilities;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!clientCapabilitiesPromise) {
|
|
74
|
+
preloadPasskeyClientCapabilities();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!clientCapabilitiesPromise) {
|
|
78
|
+
cachedClientCapabilities = null;
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const capabilities = await clientCapabilitiesPromise;
|
|
83
|
+
cachedClientCapabilities = capabilities;
|
|
84
|
+
return capabilities;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getCachedPasskeyClientCapabilities(): PasskeyClientCapabilities | null | undefined {
|
|
88
|
+
return cachedClientCapabilities;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function isInIframe(): boolean {
|
|
92
|
+
if (typeof window === 'undefined') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return window.self !== window.top;
|
|
97
|
+
} catch {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type PasskeyPromptAction = 'get' | 'create';
|
|
103
|
+
|
|
104
|
+
export async function shouldUsePasskeyPopup(action: PasskeyPromptAction): Promise<boolean> {
|
|
105
|
+
if (!isInIframe()) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
const mode = await getPasskeyPromptMode(action);
|
|
109
|
+
return mode === 'popup';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type PasskeyPromptMode = 'inline' | 'popup';
|
|
113
|
+
|
|
114
|
+
function getPermissionsPolicyAllowsFeature(feature: string): boolean | null {
|
|
115
|
+
if (typeof document === 'undefined') {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const policy = (document as { permissionsPolicy?: { allowsFeature?: (name: string) => boolean } })
|
|
120
|
+
.permissionsPolicy;
|
|
121
|
+
const featurePolicy = (document as { featurePolicy?: { allowsFeature?: (name: string) => boolean } })
|
|
122
|
+
.featurePolicy;
|
|
123
|
+
const allowsFeature = policy?.allowsFeature || featurePolicy?.allowsFeature;
|
|
124
|
+
|
|
125
|
+
if (typeof allowsFeature !== 'function') {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
return allowsFeature(feature);
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getCachedPromptMode(action: PasskeyPromptAction): PasskeyPromptMode | 'unknown' {
|
|
137
|
+
if (!isInIframe()) {
|
|
138
|
+
return 'inline';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (cachedClientCapabilities === undefined && !clientCapabilitiesPromise) {
|
|
142
|
+
preloadPasskeyClientCapabilities();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const feature =
|
|
146
|
+
action === 'create' ? 'publickey-credentials-create' : 'publickey-credentials-get';
|
|
147
|
+
const policyAllows = getPermissionsPolicyAllowsFeature(feature);
|
|
148
|
+
const capabilities = getCachedPasskeyClientCapabilities();
|
|
149
|
+
const supportsInline =
|
|
150
|
+
capabilities?.passkeyPlatformAuthenticator === true ||
|
|
151
|
+
capabilities?.userVerifyingPlatformAuthenticator === true;
|
|
152
|
+
|
|
153
|
+
if (policyAllows === false) {
|
|
154
|
+
return 'popup';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (capabilities === undefined) {
|
|
158
|
+
return 'unknown';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!supportsInline) {
|
|
162
|
+
return 'popup';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return 'inline';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function getPasskeyPromptMode(action: PasskeyPromptAction): Promise<PasskeyPromptMode> {
|
|
169
|
+
if (!isInIframe()) {
|
|
170
|
+
return 'inline';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const feature =
|
|
174
|
+
action === 'create' ? 'publickey-credentials-create' : 'publickey-credentials-get';
|
|
175
|
+
const policyAllows = getPermissionsPolicyAllowsFeature(feature);
|
|
176
|
+
const capabilities = await getPasskeyClientCapabilities();
|
|
177
|
+
const supportsInline =
|
|
178
|
+
capabilities?.passkeyPlatformAuthenticator === true ||
|
|
179
|
+
capabilities?.userVerifyingPlatformAuthenticator === true;
|
|
180
|
+
|
|
181
|
+
if (DEBUG) {
|
|
182
|
+
console.log('[Passkey] Prompt mode check:', {
|
|
183
|
+
action,
|
|
184
|
+
policyAllows,
|
|
185
|
+
supportsInline,
|
|
186
|
+
capabilities,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!supportsInline) {
|
|
191
|
+
return 'popup';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (policyAllows === false) {
|
|
195
|
+
return 'popup';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return 'inline';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function maybePreopenPopup(action: PasskeyPromptAction, openPopupFn: () => Window): Window | null {
|
|
202
|
+
const cachedMode = getCachedPromptMode(action);
|
|
203
|
+
if (cachedMode !== 'popup') {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
return openPopupFn();
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function shouldFallbackToPopup(error: unknown): boolean {
|
|
215
|
+
if (!isInIframe()) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const name =
|
|
220
|
+
error && typeof error === 'object' && 'name' in error ? String((error as { name?: unknown }).name) : '';
|
|
221
|
+
const message =
|
|
222
|
+
error && typeof error === 'object' && 'message' in error
|
|
223
|
+
? String((error as { message?: unknown }).message)
|
|
224
|
+
: '';
|
|
225
|
+
const normalized = `${name} ${message}`.toLowerCase();
|
|
226
|
+
|
|
227
|
+
if (
|
|
228
|
+
normalized.includes('cancel') ||
|
|
229
|
+
normalized.includes('canceled') ||
|
|
230
|
+
normalized.includes('cancelled') ||
|
|
231
|
+
normalized.includes('user canceled') ||
|
|
232
|
+
normalized.includes('user cancelled') ||
|
|
233
|
+
normalized.includes('aborted')
|
|
234
|
+
) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (normalized.includes('securityerror')) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (normalized.includes('notallowederror')) {
|
|
243
|
+
if (
|
|
244
|
+
normalized.includes('permission') ||
|
|
245
|
+
normalized.includes('policy') ||
|
|
246
|
+
normalized.includes('iframe') ||
|
|
247
|
+
normalized.includes('frame')
|
|
248
|
+
) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return false;
|
|
254
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export type {
|
|
3
|
+
PasskeyRegistrationResult,
|
|
4
|
+
PasskeySigningResult,
|
|
5
|
+
PasskeyDiscoverableSigningResult,
|
|
6
|
+
PasskeyStoredSigningResult,
|
|
7
|
+
PasskeyMetadata,
|
|
8
|
+
PasskeyClientCapabilities,
|
|
9
|
+
PasskeyPopupContext,
|
|
10
|
+
PasskeyPopupAccount,
|
|
11
|
+
PasskeyPopupAction,
|
|
12
|
+
PasskeyPopupGetRequestPayload,
|
|
13
|
+
PasskeyPopupCreateRequestPayload,
|
|
14
|
+
PasskeyPopupGetStoredRequestPayload,
|
|
15
|
+
PasskeyPopupRequestPayload,
|
|
16
|
+
PasskeyPopupRequest,
|
|
17
|
+
PasskeyPopupSigningResult,
|
|
18
|
+
PasskeyPopupStoredPasskey,
|
|
19
|
+
PasskeyPopupStoredSigningResult,
|
|
20
|
+
PasskeyPopupRegistrationResult,
|
|
21
|
+
PasskeyPopupResponse,
|
|
22
|
+
} from './types';
|
|
23
|
+
|
|
24
|
+
// Registration
|
|
25
|
+
export { registerPasskey } from './register';
|
|
26
|
+
|
|
27
|
+
// Signing
|
|
28
|
+
export { signWithPasskey, signWithStoredPasskey, signWithDiscoverablePasskey } from './sign';
|
|
29
|
+
|
|
30
|
+
// Crypto (re-exported from @thru/passkey-manager)
|
|
31
|
+
export {
|
|
32
|
+
parseDerSignature,
|
|
33
|
+
normalizeLowS,
|
|
34
|
+
normalizeSignatureComponent,
|
|
35
|
+
P256_N,
|
|
36
|
+
P256_HALF_N,
|
|
37
|
+
bytesToBigIntBE,
|
|
38
|
+
bigIntToBytesBE,
|
|
39
|
+
} from '@thru/passkey-manager';
|
|
40
|
+
|
|
41
|
+
// Capabilities
|
|
42
|
+
export {
|
|
43
|
+
isWebAuthnSupported,
|
|
44
|
+
preloadPasskeyClientCapabilities,
|
|
45
|
+
getPasskeyClientCapabilities,
|
|
46
|
+
getCachedPasskeyClientCapabilities,
|
|
47
|
+
shouldUsePasskeyPopup,
|
|
48
|
+
isInIframe,
|
|
49
|
+
type PasskeyPromptAction,
|
|
50
|
+
} from './capabilities';
|
|
51
|
+
|
|
52
|
+
// Encoding (re-exported from @thru/passkey-manager)
|
|
53
|
+
export {
|
|
54
|
+
arrayBufferToBase64Url,
|
|
55
|
+
base64UrlToArrayBuffer,
|
|
56
|
+
bytesToBase64Url,
|
|
57
|
+
base64UrlToBytes,
|
|
58
|
+
bytesToHex,
|
|
59
|
+
hexToBytes,
|
|
60
|
+
bytesEqual,
|
|
61
|
+
compareBytes,
|
|
62
|
+
uniqueAccounts,
|
|
63
|
+
} from '@thru/passkey-manager';
|
|
64
|
+
|
|
65
|
+
// Popup (parent side)
|
|
66
|
+
export {
|
|
67
|
+
PASSKEY_POPUP_PATH,
|
|
68
|
+
PASSKEY_POPUP_READY_EVENT,
|
|
69
|
+
PASSKEY_POPUP_REQUEST_EVENT,
|
|
70
|
+
PASSKEY_POPUP_RESPONSE_EVENT,
|
|
71
|
+
PASSKEY_POPUP_CHANNEL,
|
|
72
|
+
openPasskeyPopupWindow,
|
|
73
|
+
closePopup,
|
|
74
|
+
requestPasskeyPopup,
|
|
75
|
+
} from './popup';
|
|
76
|
+
|
|
77
|
+
// Popup service (popup window side)
|
|
78
|
+
export {
|
|
79
|
+
toPopupSigningResult,
|
|
80
|
+
buildSuccessResponse,
|
|
81
|
+
decodeChallenge,
|
|
82
|
+
getPopupDisplayInfo,
|
|
83
|
+
getResponseError,
|
|
84
|
+
signWithPreferredPasskey,
|
|
85
|
+
buildStoredPasskeyResult,
|
|
86
|
+
} from './popup-service';
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PasskeyPopupAction,
|
|
3
|
+
PasskeyPopupAccount,
|
|
4
|
+
PasskeyPopupContext,
|
|
5
|
+
PasskeyPopupResponse,
|
|
6
|
+
PasskeyPopupSigningResult,
|
|
7
|
+
PasskeyPopupStoredPasskey,
|
|
8
|
+
PasskeyPopupStoredSigningResult,
|
|
9
|
+
PasskeyMetadata,
|
|
10
|
+
PasskeySigningResult,
|
|
11
|
+
} from './types';
|
|
12
|
+
import {
|
|
13
|
+
PASSKEY_POPUP_RESPONSE_EVENT,
|
|
14
|
+
} from './popup';
|
|
15
|
+
import { bytesToBase64Url, base64UrlToBytes } from '@thru/passkey-manager';
|
|
16
|
+
import { signWithPasskey, signWithDiscoverablePasskey } from './sign';
|
|
17
|
+
|
|
18
|
+
type PasskeySignResult =
|
|
19
|
+
| Awaited<ReturnType<typeof signWithDiscoverablePasskey>>
|
|
20
|
+
| Awaited<ReturnType<typeof signWithPasskey>>;
|
|
21
|
+
|
|
22
|
+
export function toPopupSigningResult(result: PasskeySigningResult): PasskeyPopupSigningResult {
|
|
23
|
+
return {
|
|
24
|
+
signatureBase64Url: bytesToBase64Url(result.signature),
|
|
25
|
+
authenticatorDataBase64Url: bytesToBase64Url(result.authenticatorData),
|
|
26
|
+
clientDataJSONBase64Url: bytesToBase64Url(result.clientDataJSON),
|
|
27
|
+
signatureRBase64Url: bytesToBase64Url(result.signatureR),
|
|
28
|
+
signatureSBase64Url: bytesToBase64Url(result.signatureS),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function buildSuccessResponse<T>(
|
|
33
|
+
requestId: string,
|
|
34
|
+
action: PasskeyPopupAction,
|
|
35
|
+
result: T
|
|
36
|
+
): PasskeyPopupResponse {
|
|
37
|
+
return {
|
|
38
|
+
type: PASSKEY_POPUP_RESPONSE_EVENT,
|
|
39
|
+
requestId,
|
|
40
|
+
action,
|
|
41
|
+
success: true,
|
|
42
|
+
result,
|
|
43
|
+
} as PasskeyPopupResponse;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function decodeChallenge(base64Url: string): Uint8Array {
|
|
47
|
+
return base64UrlToBytes(base64Url);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getPopupDisplayInfo(context?: PasskeyPopupContext): {
|
|
51
|
+
name: string;
|
|
52
|
+
url?: string;
|
|
53
|
+
imageUrl?: string;
|
|
54
|
+
logoText: string;
|
|
55
|
+
} {
|
|
56
|
+
const name = context?.appName || context?.origin || 'A dApp';
|
|
57
|
+
const url = context?.appUrl || context?.origin;
|
|
58
|
+
const logoText = name.charAt(0).toUpperCase() || 'A';
|
|
59
|
+
return {
|
|
60
|
+
name,
|
|
61
|
+
url,
|
|
62
|
+
imageUrl: context?.imageUrl,
|
|
63
|
+
logoText,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getResponseError(action: PasskeyPopupAction, error: unknown): { name?: string; message: string } {
|
|
68
|
+
const { name, message } = normalizeError(error);
|
|
69
|
+
const actionLabel = `Popup ${action}`;
|
|
70
|
+
const messageText = message || 'Passkey popup failed';
|
|
71
|
+
const detailedMessage = messageText.includes('Popup')
|
|
72
|
+
? messageText
|
|
73
|
+
: `${actionLabel}: ${messageText}`;
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
message: detailedMessage,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function signWithPreferredPasskey(
|
|
81
|
+
preferredPasskey: PasskeyMetadata | null,
|
|
82
|
+
challenge: Uint8Array,
|
|
83
|
+
log?: (message: string) => void
|
|
84
|
+
): Promise<{ result: PasskeySignResult; credentialId: string; rpId: string }> {
|
|
85
|
+
const resolvedRpId = preferredPasskey?.rpId ?? window.location.hostname;
|
|
86
|
+
|
|
87
|
+
if (preferredPasskey?.credentialId && preferredPasskey.rpId) {
|
|
88
|
+
try {
|
|
89
|
+
const storedResult = await signWithPasskey(
|
|
90
|
+
preferredPasskey.credentialId,
|
|
91
|
+
challenge,
|
|
92
|
+
preferredPasskey.rpId
|
|
93
|
+
);
|
|
94
|
+
return {
|
|
95
|
+
result: storedResult,
|
|
96
|
+
credentialId: preferredPasskey.credentialId,
|
|
97
|
+
rpId: preferredPasskey.rpId,
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (!shouldFallbackToDiscoverable(error)) {
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
if (log) {
|
|
104
|
+
log('stored passkey failed; falling back to discoverable prompt');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const discovered = await signWithDiscoverablePasskey(challenge, resolvedRpId);
|
|
110
|
+
return {
|
|
111
|
+
result: discovered,
|
|
112
|
+
credentialId: discovered.credentialId,
|
|
113
|
+
rpId: resolvedRpId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildStoredPasskeyResult(
|
|
118
|
+
signed: { result: PasskeySignResult; credentialId: string; rpId: string },
|
|
119
|
+
preferredPasskey: PasskeyMetadata | null,
|
|
120
|
+
profiles: Array<{ passkey: PasskeyMetadata | null }>,
|
|
121
|
+
accounts: PasskeyPopupAccount[]
|
|
122
|
+
): PasskeyPopupStoredSigningResult {
|
|
123
|
+
const now = new Date().toISOString();
|
|
124
|
+
const matchingPasskey =
|
|
125
|
+
profiles.find((profile) => profile.passkey?.credentialId === signed.credentialId)?.passkey ??
|
|
126
|
+
null;
|
|
127
|
+
|
|
128
|
+
const passkey: PasskeyPopupStoredPasskey = (matchingPasskey ?? {
|
|
129
|
+
credentialId: signed.credentialId,
|
|
130
|
+
publicKeyX: '',
|
|
131
|
+
publicKeyY: '',
|
|
132
|
+
rpId: signed.rpId,
|
|
133
|
+
label: preferredPasskey?.label,
|
|
134
|
+
createdAt: now,
|
|
135
|
+
lastUsedAt: now,
|
|
136
|
+
}) as PasskeyPopupStoredPasskey;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
...toPopupSigningResult(signed.result),
|
|
140
|
+
passkey: matchingPasskey ? { ...passkey, lastUsedAt: now } : passkey,
|
|
141
|
+
accounts,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizeError(error: unknown): { name?: string; message?: string; normalized: string } {
|
|
146
|
+
const name =
|
|
147
|
+
error && typeof error === 'object' && 'name' in error
|
|
148
|
+
? String((error as { name?: unknown }).name)
|
|
149
|
+
: '';
|
|
150
|
+
const message =
|
|
151
|
+
error && typeof error === 'object' && 'message' in error
|
|
152
|
+
? String((error as { message?: unknown }).message)
|
|
153
|
+
: '';
|
|
154
|
+
return {
|
|
155
|
+
name,
|
|
156
|
+
message,
|
|
157
|
+
normalized: `${name} ${message}`.toLowerCase(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function shouldFallbackToDiscoverable(error: unknown): boolean {
|
|
162
|
+
const normalized = normalizeError(error).normalized;
|
|
163
|
+
return (
|
|
164
|
+
normalized.includes('notfounderror') ||
|
|
165
|
+
normalized.includes('notallowederror') ||
|
|
166
|
+
normalized.includes('securityerror')
|
|
167
|
+
);
|
|
168
|
+
}
|