/** * FIDO2SDK - Main SDK Class * * The primary entry point for FIDO2-JS SDK. This class wraps all core modules * and provides a clean, high-level API for FIDO2 biometric authentication. * * @module sdk */ import { SDKConfig, PartialSDKConfig } from './types/config'; import { type Credential, type AuthenticationResult } from './core/webauthn'; import { type VerifiedSummary } from './core/verified-summary'; import { type AAGUIDVerificationResult } from './core/verifier'; import type { CustomOptions } from './core/encoder'; /** * Verification mode for authentication results * Matches iOS SDK VerificationMode enum */ export type VerificationMode = 'none' | 'default' | { custom: { endpoint: string; apiKey: string; }; }; /** * Options for registering a new credential */ export interface RegisterOptions { /** * Username for the credential */ username: string; /** * Relying party domain (e.g., 'example.com') */ domain: string; /** * Optional display name for the user */ displayName?: string; /** * Optional authenticator name to verify (e.g., 'ultrapass') * If provided, will verify the AAGUID matches the configured value */ authenticator?: string; /** * Optional custom challenge for registration (base64-encoded string) * If not provided, a random challenge will be generated */ challenge?: string; /** * Optional user ID (base64-encoded string) * Will be decoded to binary data internally * If not provided, a random user ID will be generated * Note: Should be base64-encoded binary data (like iOS SDK), typically received from backend */ userId?: string; /** * Optional relying party name */ rpName?: string; /** * Optional timeout in milliseconds */ timeout?: number; /** * Enable mobile deeplink flow (two-phase authentication) * When true, redirects to native app for biometric capture (Phase 1) * then completes WebAuthn ceremony on callback (Phase 2) * * Only available on iOS and Android platforms * @default false */ useMobileFlow?: boolean; /** * Return URL for mobile deeplink callback * Required if useMobileFlow is true * The mobile app will redirect back to this URL after biometric capture * * @example 'https://example.com/auth/callback' */ returnURL?: string; /** * Public key that unlocks `encryptedEmbedding` on the result, for local silent * re-verification. Passed on the iOS enroll deeplink and stored by the app for the ceremony. * * This SDK has no key manager, so supply the key yourself; without it enrollment still * succeeds but no encrypted embedding is ever produced. * * @platform iOS */ verifyPublicKey?: string; /** * Caller-supplied age, used by the app to adapt enrollment — for instance skipping the * document scan for under-16 users. Typically computed from a birthday collected at sign-up. * * @platform iOS */ userAge?: number; /** * Optional authenticator attachment constraint. * - 'cross-platform': Roaming/external authenticator (USB HID) — use for physical UltraPass device on desktop * - 'platform': Built-in authenticator (Face ID, Touch ID, Windows Hello, Android biometrics) * - undefined: No constraint — browser offers all available authenticators (default) */ authenticatorAttachment?: AuthenticatorAttachment; } /** * Result from successful registration */ export interface RegisterResult extends Credential { /** * AAGUID verification result (if authenticator was specified) */ verification?: AAGUIDVerificationResult; } /** * Options for authenticating with an existing credential */ export interface AuthenticateOptions { /** * Relying party domain (e.g., 'example.com') */ domain: string; /** * Custom authentication options (liveness, age, geo-location checks) */ customOptions?: CustomOptions; /** * Verification mode for JWE token * * - 'none': No verification - just return JWE token (default) * - 'default': Use built-in default endpoint and API key * - { custom: { endpoint, apiKey } }: Use custom endpoint * * @default 'none' * * @example * ```typescript * // No verification * verification: 'none' * * // Use default endpoint (recommended) * verification: 'default' * * // Custom endpoint * verification: { * custom: { * endpoint: 'https://api.example.com/verify', * apiKey: 'your-api-key' * } * } * ``` */ verification?: VerificationMode; /** * Optional challenge for authentication (base64-encoded string) * If not provided, a random challenge will be generated */ challenge?: string; /** * Optional array of allowed credential IDs (base64-encoded strings) */ allowCredentials?: string[]; /** * Optional timeout in milliseconds */ timeout?: number; /** * Enable mobile deeplink flow (two-phase authentication) * When true, redirects to native app for biometric capture (Phase 1) * then completes WebAuthn ceremony on callback (Phase 2) * * Only available on iOS and Android platforms * @default false */ useMobileFlow?: boolean; /** * Return URL for mobile deeplink callback * Required if useMobileFlow is true * The mobile app will redirect back to this URL after biometric capture * * @example 'https://example.com/auth/callback' */ returnURL?: string; /** * Specific credential ID to verify against (base64-encoded) * Used in mobile flows to specify which credential to use */ credentialId?: string; /** * User ID associated with the credential (base64-encoded) * Used in mobile flows for user identification */ userId?: string; /** * Username to match a local credential against, sent on the iOS `start` deeplink. * * Also echoed on the return URL so the callback can recover which user was authenticating * when sessionStorage does not survive the redirect. */ username?: string; /** * Pre-select the biometric modality on iOS, skipping UltraPass's auth-method picker. * * Recognised by both the `start` host and the `mode=verify` branch of the app; anything * other than 'face' or 'voice' is ignored there. * * @platform iOS */ verifyMethod?: 'face' | 'voice'; /** * Allow UltraPass to fall back to enrollment when no local credential matches, instead of * returning `credential_invalid`. * * Defaults to false — an explicit `authenticate()` should fail rather than silently turn * into a registration, which is the class of bug that stale-operation handling exists to * catch. Set true only if you want `ultrapass://start` to behave as a combined * sign-in-or-register entry point. * * @default false * @platform iOS (`start` path only) */ allowEnrollFallback?: boolean; } /** * Result from successful authentication */ export interface AuthenticateResult extends AuthenticationResult { /** * Raw verify response from the backend, when verification ran. * * Prefer {@link verifiedSummary} for typed access; use this for fields the summary * does not surface. */ verifiedData?: unknown; /** * Typed projection over the verify response, or null when verification was skipped. * * Gate access on `verifiedSummary.allChecksPassed` rather than on the absence of a * thrown error: a verify call can succeed at the HTTP level while reporting that face * match or liveness failed. * * @example * ```typescript * const result = await sdk.authenticate({ domain, verification: 'default' }); * if (result.verifiedSummary?.allChecksPassed) { * grantAccess(); * } else { * denyAccess(result.verifiedSummary?.errorDescription ?? 'checks did not pass'); * } * ``` */ verifiedSummary?: VerifiedSummary | null; } /** * Main FIDO2SDK class * * This class provides the primary API for FIDO2 biometric authentication. * It integrates all core modules (API client, WebAuthn, verifier, encoder) * and provides a clean, type-safe interface. * * @example * ```typescript * // Initialize SDK * const sdk = new FIDO2SDK({ * apiKey: 'your-api-key' * }); * * await sdk.init(); * * // Register a new credential * const credential = await sdk.register({ * username: 'john.doe', * domain: 'example.com', * authenticator: 'ultrapass' * }); * * // Authenticate * const result = await sdk.authenticate({ * domain: 'example.com', * customOptions: { * checkLiveness: true, * checkAge: true, * ageThreshold: 18 * } * }); * * if (result.verifiedData) { * console.log('Authentication successful:', result.verifiedData); * } * ``` */ export declare class FIDO2SDK { /** * SDK configuration */ private config; /** * API client for backend communication */ private api; /** * Creates a new FIDO2SDK instance * * @param userConfig - Partial SDK configuration (apiKey is required) * * @throws {Error} If apiKey is not provided * * @example * ```typescript * const sdk = new FIDO2SDK({ * apiKey: 'your-api-key', * timeout: 30000, * debug: true * }); * ``` */ constructor(userConfig: PartialSDKConfig); /** * Resolves the `'auto'` ceremony options against the current platform. * * Four of our WebAuthn options diverge from the PingFederate adapter's templates * (`ultrapass-pf-adapter`, branch `feat/usernameless-auth`) — the reference implementation * known to work on Windows. It requests no PRF, no credProtect keys, and no `transports` hint, * and it enforces the timeout with an AbortController because Windows ignores * `publicKey.timeout`. * * `'auto'` therefore means: match the PF adapter on desktop, and keep today's behaviour on the * deeplink platforms, where PRF is how custom biometric options reach UltraPass and the timing * is tuned (see the iOS Phase 2 notes in `docs/`). Each knob is independently overridable so a * Windows failure can be bisected rather than guessed at. * * @returns Concrete values to hand to `createCredential` / `getCredential` */ private resolveCeremonyOptions; /** * Registers a new FIDO2 credential * * This method creates a new WebAuthn credential and optionally verifies * the authenticator AAGUID against the configured value. * * Works in standalone mode - challenges and userIds are auto-generated. * * @param options - Registration options * @returns Promise that resolves to the created credential * * @throws {Error} If WebAuthn is not supported * @throws {WebAuthnError} If credential creation fails * @throws {Error} If AAGUID verification fails (when authenticator specified) * * @example * ```typescript * // Standalone mode - no init needed * const credential = await sdk.register({ * username: 'john.doe', * domain: 'example.com' * }); * * // With AAGUID verification * const credential = await sdk.register({ * username: 'john.doe', * domain: 'example.com', * authenticator: 'ultrapass' * }); * ``` */ register(options: RegisterOptions): Promise; /** * Authenticates with an existing FIDO2 credential * * This method retrieves a WebAuthn credential and optionally verifies * the JWE token with the backend. Custom options (liveness, age, geo-location) * can be encoded and passed to the authenticator via the PRF extension. * * Works in standalone mode - challenges are auto-generated. * * @param options - Authentication options * @returns Promise that resolves to the authentication result * * @throws {Error} If WebAuthn is not supported * @throws {WebAuthnError} If credential retrieval fails * @throws {APIError} If backend verification fails (when verification is enabled) * * @example * ```typescript * // Option 1: No verification (default) * const result = await sdk.authenticate({ * domain: 'example.com' * }); * // result.jweToken available, no verification * * // Option 2: Use default verification (recommended) * const result = await sdk.authenticate({ * domain: 'example.com', * verification: 'default' * }); * // result.verifiedData contains decoded user data * * // Option 3: Custom verification endpoint * const result = await sdk.authenticate({ * domain: 'example.com', * verification: { * custom: { * endpoint: 'https://api.example.com/verify', * apiKey: 'your-api-key' * } * } * }); * * // With custom biometric options * const result = await sdk.authenticate({ * domain: 'example.com', * customOptions: { * checkLiveness: true, * checkAge: true, * ageThreshold: 18 * }, * verification: 'default' * }); * * if (result.verifiedData) { * console.log('User data:', result.verifiedData); * } * ``` */ authenticate(options: AuthenticateOptions): Promise; /** * Handles deeplink callback from mobile app * * This method should be called after the mobile app redirects back to your website * following the biometric capture (Phase 1). It will: * 1. Check the return URL status (success/error/cancelled) * 2. Retrieve the pending operation from sessionStorage * 3. Complete the WebAuthn ceremony (Phase 2) if successful * 4. Clean up session state * * **Usage Pattern:** * ```typescript * // On your callback page * const sdk = new FIDO2SDK({ apiKey: 'your-api-key' }); * * if (sdk.hasPendingOperation()) { * const result = await sdk.handleDeeplinkCallback(); * if (result) { * console.log('Authentication successful:', result); * } * } * ``` * * @param url - Optional URL to parse (defaults to current window.location) * @returns Promise that resolves to the result (RegisterResult or AuthenticateResult), or null if no pending operation * * @throws {Error} If status is error or cancelled * @throws {Error} If WebAuthn ceremony fails * * @example * ```typescript * // Automatic handling on page load * window.addEventListener('load', async () => { * const sdk = new FIDO2SDK({ apiKey: 'your-api-key' }); * * try { * const result = await sdk.handleDeeplinkCallback(); * if (result) { * document.getElementById('status').textContent = 'Success!'; * } * } catch (error) { * document.getElementById('status').textContent = `Error: ${error.message}`; * } * }); * ``` */ handleDeeplinkCallback(url?: string): Promise; /** * Runs Android's `resolve_profile_and_credentials` preflight. * * This is Android's analogue of the iOS `start` flow: a **scan-free** check that asks * UltraPass whether a credential for this RP already exists on the device, so the browser * learns whether to register or authenticate instead of guessing. The app's own docs call it * the thing to use "instead of `VERIFY_CREDENTIALS` before register/authenticate". * * Like the iOS deeplink flows this redirects the page and does not resolve — UltraPass sends * the outcome back to `returnURL`, where {@link handleDeeplinkCallback} picks it up and runs * the ceremony the app selected. No biometric is captured during the preflight; the face scan * happens later inside the WebAuthn ceremony. * * This is opt-in and does not change the default Android flow, which reaches UltraPass through * a non-redirecting init deeplink followed by a direct WebAuthn ceremony. * * @param options - Resolve options * @returns A promise that never resolves (the page redirects) * @throws {Error} If called on a platform other than Android, or without a returnURL * * @example * ```typescript * // Before showing sign-in vs register buttons: * await sdk.resolveAndroidCredentials({ * domain: window.location.host, * returnURL: window.location.href, * credentialIds: knownCredentialIds * }); * // ...page redirects; on return, handleDeeplinkCallback() runs the right ceremony. * ``` */ resolveAndroidCredentials(options: { domain: string; returnURL: string; credentialIds?: string[]; username?: string; }): Promise; /** * Checks if there is a pending mobile deeplink operation * * Use this to detect if the current page load is a callback from a mobile app. * * @returns true if there is a pending operation * * @example * ```typescript * if (sdk.hasPendingOperation()) { * await sdk.handleDeeplinkCallback(); * } * ``` */ hasPendingOperation(): boolean; /** * Updates the SDK configuration * * This method allows updating the configuration after SDK creation. * The API client will be recreated with the new configuration. * * @param newConfig - Partial configuration to merge with current config * * @example * ```typescript * sdk.updateConfig({ * timeout: 30000, * debug: true * }); * ``` */ updateConfig(newConfig: Partial): void; /** * Gets a read-only copy of the current configuration * * @returns Frozen copy of the SDK configuration * * @example * ```typescript * const config = sdk.getConfig(); * console.log('Timeout:', config.timeout); * ``` */ getConfig(): Readonly; /** * Verifies an AAGUID without registration * * This is a utility method to check if an attestation object contains * a specific authenticator AAGUID. * * @param attestationObject - Base64-encoded attestation object * @param authenticatorName - Name of authenticator to verify against * @returns AAGUID verification result * * @throws {VerificationError} If verification fails * * @example * ```typescript * const verification = sdk.verifyAAGUID( * credential.attestationObject, * 'ultrapass' * ); * * if (verification.matches) { * console.log('Authenticator verified!'); * } * ``` */ verifyAAGUID(attestationObject: string, authenticatorName: string): AAGUIDVerificationResult; /** * Verifies an AAGUID against any configured authenticator * * This is a utility method to check if an attestation object contains * any of the configured authenticator AAGUIDs. * * @param attestationObject - Base64-encoded attestation object * @returns AAGUID verification result * * @throws {VerificationError} If verification fails * * @example * ```typescript * const verification = sdk.verifyAAGUIDAny(credential.attestationObject); * * if (verification.matches) { * console.log('Matched authenticator:', verification.authenticatorName); * } * ``` */ verifyAAGUIDAny(attestationObject: string): AAGUIDVerificationResult; /** * Ensures WebAuthn is supported before operations * * @throws {Error} If WebAuthn is not supported * @private */ private ensureWebAuthnSupported; } //# sourceMappingURL=sdk.d.ts.map