@korajs/auth 0.3.3 → 0.4.0
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/dist/{chunk-FSU4SK32.js → chunk-IO2MCCG2.js} +35 -40
- package/dist/chunk-IO2MCCG2.js.map +1 -0
- package/dist/{chunk-HOZXDR6Y.js → chunk-L7GXPS74.js} +3 -9
- package/dist/chunk-L7GXPS74.js.map +1 -0
- package/dist/index.cjs +102 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +69 -84
- package/dist/index.js.map +1 -1
- package/dist/{password-hash-HDH6VQCQ.js → password-hash-QRBG6BNJ.js} +2 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +1486 -1515
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1331 -1351
- package/dist/server.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-FSU4SK32.js.map +0 -1
- package/dist/chunk-HOZXDR6Y.js.map +0 -1
- /package/dist/{password-hash-HDH6VQCQ.js.map → password-hash-QRBG6BNJ.js.map} +0 -0
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client/auth-client.ts","../src/client/org-client.ts","../src/device/device-identity.ts","../src/device/device-store.ts","../src/tokens/token-store.ts","../src/tokens/encrypted-token-store.ts","../src/encryption/database-encryption.ts","../src/passkey/passkey-client.ts","../src/encryption/key-derivation.ts","../src/encryption/auto-lock.ts","../src/encryption/operation-encryptor.ts"],"sourcesContent":["// @korajs/auth — public API\n// Every export here is a public API commitment. Be explicit.\n\n// === Client ===\nexport { AuthClient, AuthError } from './client/auth-client'\nexport type { AuthClientConfig, AuthUser, AuthState } from './client/auth-client'\n\n// === Organization Client ===\nexport { OrgClient, OrgClientError } from './client/org-client'\nexport type {\n\tOrgClientConfig,\n\tClientOrganization,\n\tClientMembership,\n\tClientInvitation,\n} from './client/org-client'\n\n// === Token Types ===\nexport type {\n\tAuthTokens,\n\tTokenPayload,\n\tTokenType,\n\tAuthConfig,\n\tAuthStatus,\n\tAuthEvent,\n\tAuthEventType,\n} from './types'\n\n// === Device Identity ===\nexport {\n\tgenerateDeviceKeyPair,\n\texportPublicKeyJwk,\n\tsignChallenge,\n\tverifyChallenge,\n\tcomputePublicKeyThumbprint,\n\ttoBase64Url,\n\tfromBase64Url,\n\tCryptoUnavailableError,\n\tDeviceIdentityError,\n} from './device/device-identity'\n\n// === Device Key Store (persistent storage for device key pairs) ===\nexport {\n\tcreateDeviceKeyStore,\n\tIndexedDBDeviceKeyStore,\n\tInMemoryDeviceKeyStore,\n\tDeviceKeyStoreError,\n} from './device/device-store'\nexport type { DeviceKeyStore } from './device/device-store'\n\n// === Token Store (client-side persistence) ===\nexport { TokenStore } from './tokens/token-store'\n\n// === Encrypted Token Store (AES-256-GCM encrypted localStorage) ===\nexport { EncryptedTokenStore, EncryptedTokenStoreError } from './tokens/encrypted-token-store'\nexport type { EncryptedTokenStoreConfig } from './tokens/encrypted-token-store'\n\n// === Passkey / WebAuthn (client-side credential creation and assertion) ===\nexport {\n\tisPasskeySupported,\n\tisPlatformAuthenticatorAvailable,\n\tcreatePasskeyCredential,\n\tauthenticateWithPasskey,\n\tPasskeyError,\n\tPasskeyUnsupportedError,\n} from './passkey/passkey-client'\nexport type {\n\tPasskeyRegistrationResponse,\n\tPasskeyAuthenticationResponse,\n} from './passkey/passkey-client'\n\n// === Encryption (Phase 2: local data protection) ===\nexport {\n\tgenerateEncryptionKey,\n\tencryptData,\n\tdecryptData,\n\texportKey,\n\timportKey,\n\tEncryptionError,\n} from './encryption/database-encryption'\n\nexport {\n\tderiveEncryptionKey,\n\tgenerateSalt,\n\tKeyDerivationError,\n} from './encryption/key-derivation'\n\nexport { AutoLockManager } from './encryption/auto-lock'\nexport type { AutoLockConfig } from './encryption/auto-lock'\n\n// === E2E Operation Encryption (encrypt data fields for sync) ===\nexport {\n\tOperationEncryptor,\n\tOperationEncryptionError,\n\tisEncryptedField,\n} from './encryption/operation-encryptor'\nexport type { OperationEncryptorConfig } from './encryption/operation-encryptor'\n","import { KoraError } from '@korajs/core'\n\n// ---------------------------------------------------------------------------\n// Auth-specific error\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when an authentication operation fails.\n * Includes a machine-readable code and optional context for debugging.\n */\nexport class AuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuthError'\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Possible authentication states for the client.\n * - 'loading': Initial state while restoring tokens from storage\n * - 'authenticated': User is signed in with a valid session\n * - 'unauthenticated': No valid session exists\n */\nexport type AuthState = 'loading' | 'authenticated' | 'unauthenticated'\n\n/**\n * Authenticated user information.\n */\nexport interface AuthUser {\n\t/** Unique user identifier */\n\tid: string\n\n\t/** User email address */\n\temail: string\n\n\t/** Display name (may be absent if user did not provide one) */\n\tname: string | null\n}\n\n/**\n * Configuration for the AuthClient.\n */\nexport interface AuthClientConfig {\n\t/** Base URL of the auth server (e.g. 'http://localhost:3001') */\n\tserverUrl: string\n\n\t/** Storage key prefix for tokens. Defaults to 'kora_auth' */\n\tstorageKey?: string\n}\n\n/**\n * Token pair returned by the auth server on sign-up, sign-in, and refresh.\n */\ninterface AuthTokensResponse {\n\taccessToken: string\n\trefreshToken: string\n}\n\n/**\n * Sign-up and sign-in responses include user data alongside tokens.\n */\ninterface AuthSignInResponse {\n\tuser: { id: string; email: string; name: string }\n\ttokens: AuthTokensResponse\n}\n\n/**\n * User profile returned by the /auth/me endpoint.\n */\ninterface UserProfileResponse {\n\tid: string\n\temail: string\n\tname: string | null\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Number of seconds before actual expiry at which we consider a token expired. */\nconst EXPIRY_BUFFER_SECONDS = 30\n\n/**\n * Decode the payload portion of a JWT without verifying the signature.\n * Client-side only -- verification is the server's responsibility.\n *\n * Returns null if the token is malformed.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\t// Base64url -> standard base64\n\t\tconst base64 = (parts[1] as string).replace(/-/g, '+').replace(/_/g, '/')\n\t\tconst json = atob(base64)\n\t\treturn JSON.parse(json) as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Returns true if the JWT's `exp` claim is in the past (with a small buffer).\n * If the token cannot be decoded, returns true (treat as expired).\n */\nfunction isTokenExpired(token: string): boolean {\n\tconst payload = decodeJwtPayload(token)\n\tif (!payload || typeof payload['exp'] !== 'number') {\n\t\treturn true\n\t}\n\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\treturn (payload['exp'] as number) <= nowSeconds + EXPIRY_BUFFER_SECONDS\n}\n\n/**\n * Extracts the `sub` (user ID) from a JWT payload.\n * Returns null if the token is malformed or missing the sub claim.\n */\nfunction getUserIdFromToken(token: string): string | null {\n\tconst payload = decodeJwtPayload(token)\n\tif (!payload || typeof payload['sub'] !== 'string') {\n\t\treturn null\n\t}\n\treturn payload['sub'] as string\n}\n\n// ---------------------------------------------------------------------------\n// Simple token storage backed by localStorage (browser) or in-memory fallback\n// ---------------------------------------------------------------------------\n\ninterface TokenStorage {\n\tgetAccessToken(): string | null\n\tgetRefreshToken(): string | null\n\tsetTokens(access: string, refresh: string): void\n\tclear(): void\n}\n\nfunction createTokenStorage(prefix: string): TokenStorage {\n\t// Try localStorage; fall back to in-memory if unavailable (SSR, Web Worker, etc.)\n\tlet useLocalStorage = false\n\ttry {\n\t\tif (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {\n\t\t\t// Smoke test: ensure we can actually write\n\t\t\tconst testKey = `${prefix}_test`\n\t\t\twindow.localStorage.setItem(testKey, '1')\n\t\t\twindow.localStorage.removeItem(testKey)\n\t\t\tuseLocalStorage = true\n\t\t}\n\t} catch {\n\t\t// localStorage not available (e.g., Safari private browsing throws in some contexts)\n\t}\n\n\tif (useLocalStorage) {\n\t\tconst accessKey = `${prefix}_access_token`\n\t\tconst refreshKey = `${prefix}_refresh_token`\n\t\treturn {\n\t\t\tgetAccessToken(): string | null {\n\t\t\t\treturn window.localStorage.getItem(accessKey)\n\t\t\t},\n\t\t\tgetRefreshToken(): string | null {\n\t\t\t\treturn window.localStorage.getItem(refreshKey)\n\t\t\t},\n\t\t\tsetTokens(access: string, refresh: string): void {\n\t\t\t\twindow.localStorage.setItem(accessKey, access)\n\t\t\t\twindow.localStorage.setItem(refreshKey, refresh)\n\t\t\t},\n\t\t\tclear(): void {\n\t\t\t\twindow.localStorage.removeItem(accessKey)\n\t\t\t\twindow.localStorage.removeItem(refreshKey)\n\t\t\t},\n\t\t}\n\t}\n\n\t// In-memory fallback\n\tlet accessToken: string | null = null\n\tlet refreshToken: string | null = null\n\treturn {\n\t\tgetAccessToken(): string | null {\n\t\t\treturn accessToken\n\t\t},\n\t\tgetRefreshToken(): string | null {\n\t\t\treturn refreshToken\n\t\t},\n\t\tsetTokens(access: string, refresh: string): void {\n\t\t\taccessToken = access\n\t\t\trefreshToken = refresh\n\t\t},\n\t\tclear(): void {\n\t\t\taccessToken = null\n\t\t\trefreshToken = null\n\t\t},\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// AuthClient\n// ---------------------------------------------------------------------------\n\n/**\n * Client-side authentication manager for Kora.js.\n *\n * Manages token storage, session restoration, sign-up, sign-in, sign-out,\n * token refresh, and auth state change notifications. Framework-agnostic --\n * works in any JavaScript environment with `fetch` and optionally `localStorage`.\n *\n * @example\n * ```typescript\n * const auth = new AuthClient({ serverUrl: 'http://localhost:3001' })\n * await auth.initialize()\n *\n * if (!auth.isAuthenticated) {\n * await auth.signIn({ email: 'user@example.com', password: 'secret' })\n * }\n *\n * const unsub = auth.onAuthChange((state) => {\n * console.log('Auth state:', state)\n * })\n * ```\n */\nexport class AuthClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly storage: TokenStorage\n\tprivate readonly listeners: Set<(state: AuthState) => void> = new Set()\n\n\tprivate _state: AuthState = 'loading'\n\tprivate _user: AuthUser | null = null\n\tprivate _refreshPromise: Promise<string | null> | null = null\n\tprivate _initialized = false\n\n\t/**\n\t * Creates a new AuthClient.\n\t *\n\t * @param config - Auth client configuration\n\t */\n\tconstructor(config: AuthClientConfig) {\n\t\t// Strip trailing slash to normalize URLs\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tconst prefix = config.storageKey ?? 'kora_auth'\n\t\tthis.storage = createTokenStorage(prefix)\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Public getters\n\t// -----------------------------------------------------------------------\n\n\t/** Current authentication state. */\n\tget state(): AuthState {\n\t\treturn this._state\n\t}\n\n\t/** Current authenticated user, or null if not signed in. */\n\tget currentUser(): AuthUser | null {\n\t\treturn this._user\n\t}\n\n\t/** Whether the user is currently authenticated. */\n\tget isAuthenticated(): boolean {\n\t\treturn this._state === 'authenticated'\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Initialization\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Initialize the auth client by restoring a session from stored tokens.\n\t *\n\t * Loads tokens from storage, validates the access token, and attempts a\n\t * refresh if the access token is expired but a refresh token is available.\n\t * Safe to call multiple times -- subsequent calls are no-ops once initialized.\n\t */\n\tasync initialize(): Promise<void> {\n\t\t// Guard against double initialization (e.g., React StrictMode double-mount)\n\t\tif (this._initialized) {\n\t\t\treturn\n\t\t}\n\t\tthis._initialized = true\n\n\t\tconst accessToken = this.storage.getAccessToken()\n\t\tconst refreshToken = this.storage.getRefreshToken()\n\n\t\t// No stored tokens -- stay unauthenticated\n\t\tif (!accessToken || !refreshToken) {\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn\n\t\t}\n\n\t\t// Access token still valid -- restore session from it\n\t\tif (!isTokenExpired(accessToken)) {\n\t\t\tawait this.restoreSession(accessToken)\n\t\t\treturn\n\t\t}\n\n\t\t// Access token expired -- try refreshing\n\t\ttry {\n\t\t\tconst newAccessToken = await this.refreshAccessToken(refreshToken)\n\t\t\tif (newAccessToken) {\n\t\t\t\tawait this.restoreSession(newAccessToken)\n\t\t\t\treturn\n\t\t\t}\n\t\t} catch {\n\t\t\t// Refresh failed (network error, token revoked, etc.)\n\t\t}\n\n\t\t// Could not restore session\n\t\tthis.storage.clear()\n\t\tthis.setState('unauthenticated', null)\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Sign up / Sign in / Sign out\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Register a new user account.\n\t *\n\t * @param params - Sign-up credentials\n\t * @returns The newly created AuthUser\n\t * @throws {AuthError} If the request fails or the server returns an error\n\t */\n\tasync signUp(params: { email: string; password: string; name?: string }): Promise<AuthUser> {\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signup', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tthis.storage.setTokens(tokens.accessToken, tokens.refreshToken)\n\n\t\tconst user = await this.fetchUserProfile(tokens.accessToken)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Sign in with email and password.\n\t *\n\t * @param params - Sign-in credentials\n\t * @returns The authenticated AuthUser\n\t * @throws {AuthError} If the credentials are invalid or the request fails\n\t */\n\tasync signIn(params: { email: string; password: string }): Promise<AuthUser> {\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signin', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tthis.storage.setTokens(tokens.accessToken, tokens.refreshToken)\n\n\t\tconst user = await this.fetchUserProfile(tokens.accessToken)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Sign out the current user.\n\t *\n\t * Clears local tokens and attempts to revoke the refresh token on the server\n\t * (best-effort — succeeds even if the server is unreachable). This ensures that\n\t * stolen refresh tokens cannot be used after the user explicitly signs out.\n\t */\n\tasync signOut(): Promise<void> {\n\t\tconst accessToken = this.storage.getAccessToken()\n\t\tconst refreshToken = this.storage.getRefreshToken()\n\n\t\t// Clear local state immediately (don't wait for server)\n\t\tthis.storage.clear()\n\t\tthis._refreshPromise = null\n\t\tthis.setState('unauthenticated', null)\n\n\t\t// Best-effort server-side revocation\n\t\tif (accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.request('/auth/signout', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: { refreshToken: refreshToken ?? undefined },\n\t\t\t\t\ttoken: accessToken,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\t// Server may be unreachable (offline) — local sign-out still succeeds\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Token access\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Get a valid access token, automatically refreshing if expired.\n\t *\n\t * @returns A valid access token string, or null if the user is not\n\t * authenticated and refresh is not possible\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst accessToken = this.storage.getAccessToken()\n\n\t\tif (accessToken && !isTokenExpired(accessToken)) {\n\t\t\treturn accessToken\n\t\t}\n\n\t\t// Attempt refresh\n\t\tconst refreshToken = this.storage.getRefreshToken()\n\t\tif (!refreshToken) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst newAccessToken = await this.refreshAccessToken(refreshToken)\n\t\t\treturn newAccessToken\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Get a valid token for the sync engine handshake.\n\t * Alias for {@link getAccessToken}.\n\t *\n\t * @returns A valid access token string, or null if unavailable\n\t */\n\tasync getSyncToken(): Promise<string | null> {\n\t\treturn this.getAccessToken()\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// State change subscriptions\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Subscribe to authentication state changes.\n\t *\n\t * The callback is invoked whenever the auth state transitions (e.g., from\n\t * 'unauthenticated' to 'authenticated' on sign-in).\n\t *\n\t * @param callback - Function called with the new AuthState on each change\n\t * @returns An unsubscribe function that removes the listener\n\t *\n\t * @example\n\t * ```typescript\n\t * const unsub = auth.onAuthChange((state) => {\n\t * console.log('Auth state changed to:', state)\n\t * })\n\t * // Later: unsub()\n\t * ```\n\t */\n\tonAuthChange(callback: (state: AuthState) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Update internal state and notify all listeners.\n\t */\n\tprivate setState(state: AuthState, user: AuthUser | null): void {\n\t\tconst changed = this._state !== state || this._user !== user\n\t\tthis._state = state\n\t\tthis._user = user\n\n\t\tif (changed) {\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\ttry {\n\t\t\t\t\tlistener(state)\n\t\t\t\t} catch {\n\t\t\t\t\t// Listeners should not throw, but if they do, do not let it\n\t\t\t\t\t// break the notification loop for other listeners.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Restore a session from a valid access token by fetching the user profile.\n\t * Falls back to extracting the user ID from the token payload if the\n\t * /auth/me request fails (offline scenario).\n\t */\n\tprivate async restoreSession(accessToken: string): Promise<void> {\n\t\ttry {\n\t\t\tconst user = await this.fetchUserProfile(accessToken)\n\t\t\tthis.setState('authenticated', user)\n\t\t} catch {\n\t\t\t// Network may be unavailable -- extract minimal user info from the token\n\t\t\tconst userId = getUserIdFromToken(accessToken)\n\t\t\tif (userId) {\n\t\t\t\tthis.setState('authenticated', {\n\t\t\t\t\tid: userId,\n\t\t\t\t\temail: '',\n\t\t\t\t\tname: null,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tthis.storage.clear()\n\t\t\t\tthis.setState('unauthenticated', null)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Fetch the current user profile from the server.\n\t */\n\tprivate async fetchUserProfile(accessToken: string): Promise<AuthUser> {\n\t\tconst profile = await this.request<UserProfileResponse>('/auth/me', {\n\t\t\tmethod: 'GET',\n\t\t\ttoken: accessToken,\n\t\t})\n\t\treturn {\n\t\t\tid: profile.id,\n\t\t\temail: profile.email,\n\t\t\tname: profile.name ?? null,\n\t\t}\n\t}\n\n\t/**\n\t * Refresh the access token using a refresh token.\n\t * De-duplicates concurrent refresh calls so only one network request is made.\n\t */\n\tprivate async refreshAccessToken(refreshToken: string): Promise<string | null> {\n\t\t// De-duplicate: if a refresh is already in progress, return the same promise\n\t\tif (this._refreshPromise) {\n\t\t\treturn this._refreshPromise\n\t\t}\n\n\t\tthis._refreshPromise = this.performRefresh(refreshToken)\n\n\t\ttry {\n\t\t\tconst result = await this._refreshPromise\n\t\t\treturn result\n\t\t} finally {\n\t\t\tthis._refreshPromise = null\n\t\t}\n\t}\n\n\t/**\n\t * Execute the token refresh network request.\n\t */\n\tprivate async performRefresh(refreshToken: string): Promise<string | null> {\n\t\ttry {\n\t\t\tconst response = await this.request<AuthTokensResponse>('/auth/refresh', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { refreshToken },\n\t\t\t})\n\n\t\t\tthis.storage.setTokens(response.accessToken, response.refreshToken)\n\t\t\treturn response.accessToken\n\t\t} catch {\n\t\t\t// Refresh failed -- clear tokens to avoid infinite retry loops\n\t\t\tthis.storage.clear()\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Make an HTTP request to the auth server.\n\t *\n\t * @param path - URL path relative to serverUrl (e.g. '/auth/signin')\n\t * @param options - Request options\n\t * @returns Parsed JSON response body\n\t * @throws {AuthError} On network failure or non-2xx response\n\t */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST'\n\t\t\tbody?: Record<string, unknown>\n\t\t\ttoken?: string\n\t\t},\n\t): Promise<T> {\n\t\tconst url = `${this.serverUrl}${path}`\n\n\t\tconst headers: Record<string, string> = {}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\t\tif (options.token) {\n\t\t\theaders['Authorization'] = `Bearer ${options.token}`\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new AuthError(\n\t\t\t\t`Network request to ${path} failed. The auth server at ${this.serverUrl} may be unreachable. ` +\n\t\t\t\t\t'Check your network connection and serverUrl configuration.',\n\t\t\t\t'AUTH_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Auth server returned HTTP ${response.status}`\n\t\t\tlet serverError: string | undefined\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body['error'] === 'string') {\n\t\t\t\t\terrorMessage = body['error'] as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t} else if (typeof body['message'] === 'string') {\n\t\t\t\t\terrorMessage = body['message'] as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Response body is not JSON -- use the status text\n\t\t\t}\n\n\t\t\tthrow new AuthError(\n\t\t\t\terrorMessage,\n\t\t\t\t'AUTH_SERVER_ERROR',\n\t\t\t\t{ path, status: response.status, serverError },\n\t\t\t)\n\t\t}\n\n\t\tconst json = (await response.json()) as Record<string, unknown>\n\n\t\t// The BuiltInAuthRoutes server wraps success responses in { data: T }.\n\t\t// Unwrap the envelope so callers get the inner payload directly.\n\t\tconst data = (json['data'] !== undefined ? json['data'] : json) as T\n\t\treturn data\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Organization info returned by the server.\n */\nexport interface ClientOrganization {\n\tid: string\n\tname: string\n\tslug: string\n\townerId: string\n\tcreatedAt: number\n\tupdatedAt: number\n\tmetadata: Record<string, unknown>\n}\n\n/**\n * Membership info returned by the server.\n */\nexport interface ClientMembership {\n\tid: string\n\torgId: string\n\tuserId: string\n\trole: string\n\tinvitedBy: string | null\n\tjoinedAt: number\n}\n\n/**\n * Invitation info returned by the server.\n */\nexport interface ClientInvitation {\n\tid: string\n\torgId: string\n\temail: string\n\trole: string\n\tinvitedBy: string\n\ttoken: string\n\tcreatedAt: number\n\texpiresAt: number\n\tstatus: string\n}\n\n/**\n * Configuration for the OrgClient.\n */\nexport interface OrgClientConfig {\n\t/** Base URL of the auth/org server */\n\tserverUrl: string\n\t/** Function that returns a valid access token for authenticated requests */\n\tgetAccessToken: () => Promise<string | null>\n}\n\n/**\n * Thrown when an org operation fails.\n */\nexport class OrgClientError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OrgClientError'\n\t}\n}\n\n// ============================================================================\n// OrgClient\n// ============================================================================\n\n/**\n * Client-side organization manager.\n *\n * Handles org CRUD, member management, invitations, and active org switching.\n * Framework-agnostic — works in any JavaScript environment with `fetch`.\n *\n * @example\n * ```typescript\n * const orgClient = new OrgClient({\n * serverUrl: 'http://localhost:3001',\n * getAccessToken: () => authClient.getAccessToken(),\n * })\n *\n * const org = await orgClient.createOrg({ name: 'Acme Inc', slug: 'acme' })\n * orgClient.switchOrg(org.id)\n * ```\n */\nexport class OrgClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly getAccessToken: () => Promise<string | null>\n\tprivate readonly listeners = new Set<(orgId: string | null) => void>()\n\n\tprivate _activeOrgId: string | null = null\n\tprivate _activeOrg: ClientOrganization | null = null\n\tprivate _activeRole: string | null = null\n\n\tconstructor(config: OrgClientConfig) {\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tthis.getAccessToken = config.getAccessToken\n\t}\n\n\t// --- Getters ---\n\n\t/** Currently active organization ID */\n\tget activeOrgId(): string | null {\n\t\treturn this._activeOrgId\n\t}\n\n\t/** Currently active organization */\n\tget activeOrg(): ClientOrganization | null {\n\t\treturn this._activeOrg\n\t}\n\n\t/** Current user's role in the active organization */\n\tget activeRole(): string | null {\n\t\treturn this._activeRole\n\t}\n\n\t// --- Organization Operations ---\n\n\t/**\n\t * Create a new organization.\n\t */\n\tasync createOrg(params: {\n\t\tname: string\n\t\tslug?: string\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>('/orgs', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * List all organizations the current user belongs to.\n\t */\n\tasync listOrgs(): Promise<ClientOrganization[]> {\n\t\treturn this.request<ClientOrganization[]>('/orgs', { method: 'GET' })\n\t}\n\n\t/**\n\t * Get an organization by ID.\n\t */\n\tasync getOrg(orgId: string): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Update an organization.\n\t */\n\tasync updateOrg(\n\t\torgId: string,\n\t\tparams: { name?: string; slug?: string; metadata?: Record<string, unknown> },\n\t): Promise<ClientOrganization> {\n\t\tconst result = await this.request<ClientOrganization>(`/orgs/${orgId}`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: params,\n\t\t})\n\t\t// Update cached active org if this is the active one\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrg = result\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Delete an organization.\n\t */\n\tasync deleteOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}`, { method: 'DELETE' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Org Switching ---\n\n\t/**\n\t * Switch the active organization context.\n\t * Fetches the org details and the user's membership/role.\n\t */\n\tasync switchOrg(orgId: string): Promise<void> {\n\t\tconst org = await this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t\tconst membership = await this.request<ClientMembership>(`/orgs/${orgId}/membership`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\n\t\tthis._activeOrgId = orgId\n\t\tthis._activeOrg = org\n\t\tthis._activeRole = membership.role\n\t\tthis.notifyListeners()\n\t}\n\n\t/**\n\t * Clear the active organization (no org selected).\n\t */\n\tclearActiveOrg(): void {\n\t\tthis._activeOrgId = null\n\t\tthis._activeOrg = null\n\t\tthis._activeRole = null\n\t\tthis.notifyListeners()\n\t}\n\n\t// --- Member Management ---\n\n\t/**\n\t * List members of an organization.\n\t */\n\tasync listMembers(orgId: string): Promise<ClientMembership[]> {\n\t\treturn this.request<ClientMembership[]>(`/orgs/${orgId}/members`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Remove a member from an organization.\n\t */\n\tasync removeMember(orgId: string, userId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/members/${userId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * Update a member's role.\n\t */\n\tasync updateMemberRole(\n\t\torgId: string,\n\t\tuserId: string,\n\t\trole: string,\n\t): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>(`/orgs/${orgId}/members/${userId}/role`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: { role },\n\t\t})\n\t}\n\n\t/**\n\t * Transfer ownership to another member.\n\t */\n\tasync transferOwnership(orgId: string, newOwnerId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/transfer`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { newOwnerId },\n\t\t})\n\t}\n\n\t/**\n\t * Leave an organization (remove yourself).\n\t */\n\tasync leaveOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/leave`, { method: 'POST' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Invitations ---\n\n\t/**\n\t * Invite a user to an organization by email.\n\t */\n\tasync inviteMember(\n\t\torgId: string,\n\t\tparams: { email: string; role: string },\n\t): Promise<ClientInvitation> {\n\t\treturn this.request<ClientInvitation>(`/orgs/${orgId}/invitations`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * Accept an invitation by token.\n\t */\n\tasync acceptInvitation(token: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>('/invitations/accept', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { token },\n\t\t})\n\t}\n\n\t/**\n\t * List pending invitations for an organization.\n\t */\n\tasync listInvitations(orgId: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(`/orgs/${orgId}/invitations`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Revoke a pending invitation.\n\t */\n\tasync revokeInvitation(orgId: string, invitationId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * List pending invitations for the current user's email.\n\t */\n\tasync listMyInvitations(email: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(\n\t\t\t`/invitations?email=${encodeURIComponent(email)}`,\n\t\t\t{ method: 'GET' },\n\t\t)\n\t}\n\n\t// --- Subscriptions ---\n\n\t/**\n\t * Subscribe to active org changes.\n\t * @returns Unsubscribe function\n\t */\n\tonOrgChange(callback: (orgId: string | null) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// --- Internal ---\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(this._activeOrgId)\n\t\t\t} catch {\n\t\t\t\t// Don't let listener errors break the notification loop\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async request<T = void>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t},\n\t): Promise<T> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t'Not authenticated. Sign in before performing organization operations.',\n\t\t\t\t'ORG_NOT_AUTHENTICATED',\n\t\t\t)\n\t\t}\n\n\t\tconst url = `${this.serverUrl}${path}`\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t`Network request to ${path} failed.`,\n\t\t\t\t'ORG_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Server returned HTTP ${response.status}`\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body['error'] === 'string') {\n\t\t\t\t\terrorMessage = body['error'] as string\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// not JSON\n\t\t\t}\n\t\t\tthrow new OrgClientError(errorMessage, 'ORG_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t})\n\t\t}\n\n\t\t// Handle empty responses (DELETE, etc.)\n\t\tconst text = await response.text()\n\t\tif (text.length === 0) return undefined as T\n\n\t\ttry {\n\t\t\tconst body = JSON.parse(text)\n\t\t\t// Unwrap { data: ... } envelope if present\n\t\t\tif (body && typeof body === 'object' && 'data' in body) {\n\t\t\t\treturn body.data as T\n\t\t\t}\n\t\t\treturn body as T\n\t\t} catch {\n\t\t\treturn undefined as T\n\t\t}\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Auth-specific errors ---\n\n/**\n * Thrown when the Web Crypto API is not available in the current environment.\n * This can happen in older Node.js versions or SSR environments without crypto support.\n */\nexport class CryptoUnavailableError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Device identity requires crypto.subtle, which is available in modern browsers and Node.js 20+. ' +\n\t\t\t\t'If running in SSR, ensure your runtime provides the Web Crypto API.',\n\t\t\t'CRYPTO_UNAVAILABLE',\n\t\t)\n\t\tthis.name = 'CryptoUnavailableError'\n\t}\n}\n\n/**\n * Thrown when a device identity operation fails (key generation, signing, verification).\n */\nexport class DeviceIdentityError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_IDENTITY_ERROR', context)\n\t\tthis.name = 'DeviceIdentityError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes an ArrayBuffer as a base64url string (no padding).\n *\n * @param buffer - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nexport function toBase64Url(buffer: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(buffer)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\t// Standard base64, then convert to base64url (no padding)\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nexport function fromBase64Url(str: string): Uint8Array {\n\t// Convert base64url back to standard base64\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\t// Add padding if necessary\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Internal helpers ---\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (\n\t\ttypeof globalThis.crypto === 'undefined' ||\n\t\ttypeof globalThis.crypto.subtle === 'undefined'\n\t) {\n\t\tthrow new CryptoUnavailableError()\n\t}\n}\n\n/** ECDSA algorithm parameters used throughout the module. */\nconst ECDSA_ALGORITHM: EcKeyGenParams = {\n\tname: 'ECDSA',\n\tnamedCurve: 'P-256',\n}\n\n/** Signing algorithm parameters: ECDSA with SHA-256. */\nconst ECDSA_SIGN_ALGORITHM: EcdsaParams = {\n\tname: 'ECDSA',\n\thash: { name: 'SHA-256' },\n}\n\n// --- Public API ---\n\n/**\n * Generates an ECDSA P-256 key pair for device identity.\n *\n * The private key is marked as non-extractable, ensuring it cannot be\n * exported from the browser's crypto subsystem. This provides\n * proof-of-possession: only code running on this device can sign with the key.\n *\n * @returns A CryptoKeyPair containing the public and private ECDSA P-256 keys\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If key generation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * // keyPair.publicKey can be exported; keyPair.privateKey stays on device\n * ```\n */\nexport async function generateDeviceKeyPair(): Promise<CryptoKeyPair> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst keyPair = await globalThis.crypto.subtle.generateKey(\n\t\t\tECDSA_ALGORITHM,\n\t\t\t// extractable: false makes the private key non-extractable.\n\t\t\t// The public key is always extractable regardless of this flag.\n\t\t\tfalse,\n\t\t\t['sign', 'verify'],\n\t\t)\n\t\treturn keyPair\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to generate ECDSA P-256 device key pair. ' +\n\t\t\t\t'Ensure the runtime supports the ECDSA algorithm with the P-256 curve.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Exports the public key from a key pair as a JSON Web Key (JWK).\n *\n * The JWK can be safely transmitted to a server or other devices to identify\n * this device. It contains only the public component of the key pair.\n *\n * @param keyPair - The CryptoKeyPair whose public key should be exported\n * @returns The public key in JWK format\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the export operation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const jwk = await exportPublicKeyJwk(keyPair)\n * // jwk contains { kty: 'EC', crv: 'P-256', x: '...', y: '...' }\n * ```\n */\nexport async function exportPublicKeyJwk(keyPair: CryptoKeyPair): Promise<JsonWebKey> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst jwk = await globalThis.crypto.subtle.exportKey('jwk', keyPair.publicKey)\n\t\treturn jwk\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to export public key as JWK. ' +\n\t\t\t\t'The key pair may be invalid or the public key may not support JWK export.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Signs a challenge string with the device's private key.\n *\n * Used for proof-of-possession during authentication: the server sends a\n * random challenge, and the device proves it holds the private key by signing it.\n *\n * @param privateKey - The device's private CryptoKey (ECDSA P-256)\n * @param challenge - The challenge string to sign (typically a random nonce from the server)\n * @returns A base64url-encoded ECDSA signature (no padding)\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the signing operation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const signature = await signChallenge(keyPair.privateKey, 'server-nonce-abc123')\n * // signature is a base64url string like 'MEUCIQDx...'\n * ```\n */\nexport async function signChallenge(privateKey: CryptoKey, challenge: string): Promise<string> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst encoded = new TextEncoder().encode(challenge)\n\t\tconst signatureBuffer = await globalThis.crypto.subtle.sign(\n\t\t\tECDSA_SIGN_ALGORITHM,\n\t\t\tprivateKey,\n\t\t\tencoded,\n\t\t)\n\t\treturn toBase64Url(signatureBuffer)\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to sign challenge. ' +\n\t\t\t\t'Ensure the key is a valid ECDSA P-256 private key with \"sign\" usage.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Verifies a challenge signature against a public key.\n *\n * Used server-side (or on any verifying party) to confirm that a device\n * holds the private key corresponding to the given public key.\n *\n * @param publicKeyJwk - The device's public key in JWK format\n * @param challenge - The original challenge string that was signed\n * @param signature - The base64url-encoded signature to verify\n * @returns `true` if the signature is valid, `false` otherwise\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the verification operation fails due to an invalid key or format\n *\n * @example\n * ```typescript\n * const isValid = await verifyChallenge(publicKeyJwk, 'server-nonce-abc123', signature)\n * if (isValid) {\n * // Device proved possession of the private key\n * }\n * ```\n */\nexport async function verifyChallenge(\n\tpublicKeyJwk: JsonWebKey,\n\tchallenge: string,\n\tsignature: string,\n): Promise<boolean> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst publicKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'jwk',\n\t\t\tpublicKeyJwk,\n\t\t\tECDSA_ALGORITHM,\n\t\t\ttrue,\n\t\t\t['verify'],\n\t\t)\n\n\t\tconst encoded = new TextEncoder().encode(challenge)\n\t\tconst signatureBytes = fromBase64Url(signature)\n\n\t\tconst isValid = await globalThis.crypto.subtle.verify(\n\t\t\tECDSA_SIGN_ALGORITHM,\n\t\t\tpublicKey,\n\t\t\tsignatureBytes as unknown as ArrayBuffer,\n\t\t\tencoded,\n\t\t)\n\t\treturn isValid\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to verify challenge signature. ' +\n\t\t\t\t'The public key JWK or signature format may be invalid.',\n\t\t\t{\n\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\tpublicKeyKty: publicKeyJwk.kty,\n\t\t\t\tpublicKeyCrv: publicKeyJwk.crv,\n\t\t\t},\n\t\t)\n\t}\n}\n\n/**\n * Computes a SHA-256 thumbprint of a JWK public key.\n *\n * The thumbprint is computed per RFC 7638: the JWK members required for the key\n * type are serialized in lexicographic order, then hashed with SHA-256. For EC keys\n * (kty: \"EC\"), the required members are `crv`, `kty`, `x`, and `y`.\n *\n * This thumbprint serves as a compact, stable identifier for the device's public key\n * (used as the `dpk` claim in device credentials).\n *\n * @param publicKeyJwk - The public key in JWK format (must be an EC P-256 key)\n * @returns A base64url-encoded SHA-256 thumbprint (no padding)\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the thumbprint computation fails or the JWK is missing required fields\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const jwk = await exportPublicKeyJwk(keyPair)\n * const thumbprint = await computePublicKeyThumbprint(jwk)\n * // thumbprint is a base64url string, e.g., 'NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs'\n * ```\n */\nexport async function computePublicKeyThumbprint(publicKeyJwk: JsonWebKey): Promise<string> {\n\tassertCryptoAvailable()\n\n\t// RFC 7638 requires specific members in lexicographic order for each key type.\n\t// For EC (kty: \"EC\"), the required members are: crv, kty, x, y.\n\tif (publicKeyJwk.kty !== 'EC') {\n\t\tthrow new DeviceIdentityError(\n\t\t\t`Expected JWK key type \"EC\" but received \"${publicKeyJwk.kty ?? 'undefined'}\". ` +\n\t\t\t\t'Only ECDSA public keys are supported for device identity.',\n\t\t\t{ kty: publicKeyJwk.kty },\n\t\t)\n\t}\n\n\tif (!publicKeyJwk.crv || !publicKeyJwk.x || !publicKeyJwk.y) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'JWK is missing required EC fields. ' +\n\t\t\t\t'An EC public key JWK must include \"crv\", \"x\", and \"y\" members.',\n\t\t\t{\n\t\t\t\thasCrv: Boolean(publicKeyJwk.crv),\n\t\t\t\thasX: Boolean(publicKeyJwk.x),\n\t\t\t\thasY: Boolean(publicKeyJwk.y),\n\t\t\t},\n\t\t)\n\t}\n\n\t// Build the canonical JSON with only the required members in lexicographic order.\n\t// Per RFC 7638, no whitespace, keys in sorted order.\n\tconst canonicalJson = JSON.stringify({\n\t\tcrv: publicKeyJwk.crv,\n\t\tkty: publicKeyJwk.kty,\n\t\tx: publicKeyJwk.x,\n\t\ty: publicKeyJwk.y,\n\t})\n\n\ttry {\n\t\tconst encoded = new TextEncoder().encode(canonicalJson)\n\t\tconst hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', encoded)\n\t\treturn toBase64Url(hashBuffer)\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to compute SHA-256 thumbprint of the public key JWK.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Device key store errors ---\n\n/**\n * Thrown when a device key store operation fails.\n * Provides context about the operation and the underlying cause.\n */\nexport class DeviceKeyStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_KEY_STORE_ERROR', context)\n\t\tthis.name = 'DeviceKeyStoreError'\n\t}\n}\n\n// --- Interface ---\n\n/**\n * Persistent storage interface for ECDSA P-256 device key pairs.\n *\n * Device key pairs are used for proof-of-possession authentication:\n * the private key never leaves the device, and the public key is\n * registered with the server. This store persists key pairs across\n * page reloads and app restarts.\n *\n * In the browser, CryptoKey objects are structured-cloneable, so they\n * can be stored directly in IndexedDB without serialization. In Node.js\n * or test environments, an in-memory implementation is used instead.\n */\nexport interface DeviceKeyStore {\n\t/**\n\t * Persist a key pair for the given device.\n\t *\n\t * Overwrites any previously stored key pair for the same device ID.\n\t *\n\t * @param deviceId - The unique device identifier (typically a UUID v7)\n\t * @param keyPair - The ECDSA P-256 CryptoKeyPair to store\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tsaveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>\n\n\t/**\n\t * Load a previously stored key pair for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns The stored CryptoKeyPair, or null if no key pair exists for the device\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tloadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>\n\n\t/**\n\t * Delete a stored key pair for the given device.\n\t *\n\t * No-op if no key pair exists for the device ID.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tdeleteKeyPair(deviceId: string): Promise<void>\n\n\t/**\n\t * Check whether a key pair exists for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns True if a key pair is stored for the device, false otherwise\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\thasKeyPair(deviceId: string): Promise<boolean>\n}\n\n// --- IndexedDB constants ---\n\n/** IndexedDB database name for device key storage. */\nconst IDB_DATABASE_NAME = 'kora_device_keys'\n\n/** IndexedDB object store name within the database. */\nconst IDB_STORE_NAME = 'keypairs'\n\n/** Current database schema version. */\nconst IDB_VERSION = 1\n\n// --- IndexedDB implementation ---\n\n/**\n * Browser-based device key store backed by IndexedDB.\n *\n * CryptoKey objects are structured-cloneable, so they can be stored\n * directly in IndexedDB without needing to export/import them as JWK.\n * This preserves the non-extractable flag on private keys, ensuring\n * they cannot be read even from storage.\n *\n * The database uses a single object store (`keypairs`) with the device ID\n * as the key and the full CryptoKeyPair as the value.\n *\n * @example\n * ```typescript\n * const store = new IndexedDBDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * // loaded.privateKey is still non-extractable\n * ```\n */\nexport class IndexedDBDeviceKeyStore implements DeviceKeyStore {\n\tprivate dbPromise: Promise<IDBDatabase> | null = null\n\n\t/**\n\t * Opens (or creates) the IndexedDB database.\n\t *\n\t * The database connection is lazily initialized on first use and\n\t * reused for subsequent operations. If the database does not exist,\n\t * it is created with the `keypairs` object store.\n\t */\n\tprivate openDatabase(): Promise<IDBDatabase> {\n\t\t// Reuse the database connection if already opened.\n\t\t// This avoids repeatedly opening the database on every operation.\n\t\tif (this.dbPromise !== null) {\n\t\t\treturn this.dbPromise\n\t\t}\n\n\t\tthis.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n\t\t\tlet request: IDBOpenDBRequest\n\n\t\t\ttry {\n\t\t\t\trequest = globalThis.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\t\t} catch (cause) {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage. ' +\n\t\t\t\t\t\t\t'IndexedDB may be unavailable or access may be denied.',\n\t\t\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequest.onupgradeneeded = () => {\n\t\t\t\tconst db = request.result\n\t\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve(request.result)\n\t\t\t}\n\n\t\t\trequest.onerror = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage.',\n\t\t\t\t\t\t{ error: request.error?.message },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\trequest.onblocked = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'IndexedDB database open was blocked. ' +\n\t\t\t\t\t\t\t'Another tab may have an older version of the database open. ' +\n\t\t\t\t\t\t\t'Close other tabs and try again.',\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\treturn this.dbPromise\n\t}\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Store the CryptoKeyPair as a structured clone with the deviceId as the key.\n\t\t\t\t// IndexedDB handles structured cloning of CryptoKey objects natively.\n\t\t\t\tstore.put(keyPair, deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to save key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: tx.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to save key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<CryptoKeyPair | null>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tconst request = store.get(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst result = request.result as CryptoKeyPair | undefined\n\t\t\t\t\tresolve(result ?? null)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to load key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to load key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tstore.delete(deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to delete key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: tx.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to delete key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<boolean>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Use count() with the key to check existence without loading the value.\n\t\t\t\t// This is more efficient than get() for large CryptoKeyPair objects.\n\t\t\t\tconst request = store.count(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tresolve(request.result > 0)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// --- In-memory implementation ---\n\n/**\n * In-memory device key store for Node.js and testing environments.\n *\n * Key pairs are stored in a plain Map and do not survive process restarts.\n * This is suitable for server-side rendering, Node.js scripts, and unit tests\n * where IndexedDB is not available.\n *\n * @example\n * ```typescript\n * const store = new InMemoryDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * ```\n */\nexport class InMemoryDeviceKeyStore implements DeviceKeyStore {\n\tprivate readonly store = new Map<string, CryptoKeyPair>()\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tthis.store.set(deviceId, keyPair)\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\treturn this.store.get(deviceId) ?? null\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tthis.store.delete(deviceId)\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\treturn this.store.has(deviceId)\n\t}\n}\n\n// --- Factory ---\n\n/**\n * Creates a DeviceKeyStore appropriate for the current environment.\n *\n * In browsers where IndexedDB is available, returns an {@link IndexedDBDeviceKeyStore}\n * that persists CryptoKeyPair objects as structured clones. In Node.js, SSR,\n * or environments without IndexedDB, returns an {@link InMemoryDeviceKeyStore}.\n *\n * @returns A DeviceKeyStore instance for the current environment\n *\n * @example\n * ```typescript\n * const store = createDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair(deviceId, keyPair)\n * ```\n */\nexport function createDeviceKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn new IndexedDBDeviceKeyStore()\n\t}\n\treturn new InMemoryDeviceKeyStore()\n}\n","import type { AuthTokens } from '../types'\n\n/** Default key used for localStorage persistence. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_tokens'\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that TokenStore needs. This allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\t// Probe that read/write actually works (some browsers throw on access)\n\t\t\tconst testKey = '__kora_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied (e.g., private mode in some browsers)\n\t}\n\treturn null\n}\n\n/**\n * Client-side token storage for Kora auth tokens.\n *\n * Persists tokens to localStorage when available, falling back to in-memory\n * storage in environments where localStorage is unavailable (Node.js, SSR,\n * restricted browser contexts). Encrypted storage is planned for Phase 2.\n *\n * All operations are synchronous since both localStorage and in-memory\n * storage are synchronous.\n *\n * @example\n * ```typescript\n * const store = new TokenStore()\n *\n * // After login\n * store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls\n * const token = store.getAccessToken()\n *\n * // On logout\n * store.clearTokens()\n * ```\n */\nexport class TokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new TokenStore instance.\n\t *\n\t * @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tconstructor(storageKey?: string) {\n\t\tthis.storageKey = storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Save tokens to persistent storage.\n\t *\n\t * Overwrites any previously stored tokens. The tokens are serialized\n\t * as JSON. Only the `accessToken`, `refreshToken`, and optional\n\t * `deviceCredential` fields are persisted.\n\t *\n\t * @param tokens - The token set to store\n\t */\n\tsaveTokens(tokens: AuthTokens): void {\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\t\tthis.storage.setItem(this.storageKey, JSON.stringify(serialized))\n\t}\n\n\t/**\n\t * Load tokens from storage.\n\t *\n\t * @returns The stored {@link AuthTokens}, or null if no tokens have been saved\n\t */\n\tloadTokens(): AuthTokens | null {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst record = parsed as Record<string, unknown>\n\n\t\t\t// Validate required fields are present and are strings\n\t\t\tif (\n\t\t\t\ttypeof record['accessToken'] !== 'string'\n\t\t\t\t|| typeof record['refreshToken'] !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: record['accessToken'],\n\t\t\t\trefreshToken: record['refreshToken'],\n\t\t\t}\n\n\t\t\tif (typeof record['deviceCredential'] === 'string') {\n\t\t\t\ttokens.deviceCredential = record['deviceCredential']\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Corrupted data in storage; treat as empty\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored tokens.\n\t *\n\t * Call this on logout to remove credentials from persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The access token string, or null if no tokens are stored\n\t */\n\tgetAccessToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token.\n\t *\n\t * @returns The refresh token string, or null if no tokens are stored\n\t */\n\tgetRefreshToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { encryptData, decryptData } from '../encryption/database-encryption'\nimport type { AuthTokens } from '../types'\n\n// --- Errors ---\n\n/**\n * Thrown when encrypted token storage operations fail.\n * Includes context about what went wrong to aid debugging without\n * exposing sensitive key or token material.\n */\nexport class EncryptedTokenStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ENCRYPTED_TOKEN_STORE_ERROR', context)\n\t\tthis.name = 'EncryptedTokenStoreError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes a Uint8Array as a base64url string (no padding).\n * Implemented locally to avoid coupling to the device-identity module.\n *\n * @param bytes - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nfunction toBase64Url(bytes: Uint8Array): string {\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nfunction fromBase64Url(str: string): Uint8Array {\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Storage helpers ---\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that EncryptedTokenStore needs. Allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\tconst testKey = '__kora_encrypted_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied\n\t}\n\treturn null\n}\n\n// --- Types ---\n\n/** Default storage key prefix for encrypted tokens. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_encrypted'\n\n/**\n * The serialized format stored in localStorage.\n * Both `iv` and `data` are base64url-encoded strings.\n */\ninterface EncryptedPayload {\n\t/** Base64url-encoded initialization vector (12 bytes for AES-GCM) */\n\tiv: string\n\t/** Base64url-encoded AES-256-GCM ciphertext of the JSON-serialized AuthTokens */\n\tdata: string\n}\n\n/**\n * Configuration for the encrypted token store.\n */\nexport interface EncryptedTokenStoreConfig {\n\t/**\n\t * Storage key prefix. Defaults to 'kora_auth_encrypted'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tstorageKey?: string\n\t/**\n\t * The AES-256-GCM CryptoKey used to encrypt and decrypt tokens.\n\t *\n\t * This can be:\n\t * - A key derived from a user passphrase via {@link deriveEncryptionKey}\n\t * - A device-derived key (e.g., from secure hardware or biometric unlock)\n\t * - A randomly generated key from {@link generateEncryptionKey}\n\t *\n\t * The caller is responsible for obtaining the key before constructing the store.\n\t */\n\tkey: CryptoKey\n}\n\n// --- Implementation ---\n\n/**\n * Encrypted token store that protects auth tokens at rest.\n *\n * Addresses the security vulnerability of storing tokens in plaintext localStorage,\n * which is accessible to any JavaScript running on the page (XSS attack surface).\n * Tokens are encrypted with AES-256-GCM before being written to storage.\n *\n * The stored format is a JSON string with two base64url-encoded fields:\n * - `iv`: the 12-byte initialization vector (unique per encryption)\n * - `data`: the AES-256-GCM ciphertext of the JSON-serialized tokens\n *\n * AES-GCM provides both confidentiality (tokens are unreadable without the key)\n * and integrity (tampered ciphertext is detected and rejected).\n *\n * @example\n * ```typescript\n * import { deriveEncryptionKey } from '@korajs/auth'\n *\n * // Derive a key from the user's passphrase\n * const { key } = await deriveEncryptionKey('user-passphrase', storedSalt)\n *\n * const store = new EncryptedTokenStore({ key })\n *\n * // After login: encrypt and persist tokens\n * await store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls: decrypt and retrieve\n * const accessToken = await store.getAccessToken()\n *\n * // On logout: remove encrypted data\n * store.clearTokens()\n * ```\n */\nexport class EncryptedTokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly key: CryptoKey\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new EncryptedTokenStore instance.\n\t *\n\t * @param config - Configuration including the encryption key and optional storage key\n\t */\n\tconstructor(config: EncryptedTokenStoreConfig) {\n\t\tthis.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.key = config.key\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Encrypt and save tokens to persistent storage.\n\t *\n\t * Serializes the tokens as JSON, encrypts with AES-256-GCM using a fresh\n\t * random IV, then stores the result as a JSON object containing the\n\t * base64url-encoded IV and ciphertext.\n\t *\n\t * Overwrites any previously stored tokens.\n\t *\n\t * @param tokens - The token set to encrypt and store\n\t * @throws {EncryptedTokenStoreError} If encryption fails\n\t */\n\tasync saveTokens(tokens: AuthTokens): Promise<void> {\n\t\t// Build a clean token object with only the expected fields\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\n\t\tconst plaintext = new TextEncoder().encode(JSON.stringify(serialized))\n\n\t\ttry {\n\t\t\tconst { ciphertext, iv } = await encryptData(this.key, plaintext)\n\n\t\t\tconst payload: EncryptedPayload = {\n\t\t\t\tiv: toBase64Url(iv),\n\t\t\t\tdata: toBase64Url(ciphertext),\n\t\t\t}\n\n\t\t\tthis.storage.setItem(this.storageKey, JSON.stringify(payload))\n\t\t} catch (cause) {\n\t\t\t// Re-throw EncryptedTokenStoreError as-is\n\t\t\tif (cause instanceof EncryptedTokenStoreError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\n\t\t\tthrow new EncryptedTokenStoreError(\n\t\t\t\t'Failed to encrypt and save auth tokens. ' +\n\t\t\t\t\t'Ensure the encryption key is a valid AES-256-GCM CryptoKey.',\n\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Load and decrypt tokens from storage.\n\t *\n\t * Reads the encrypted payload from localStorage, decodes the base64url IV\n\t * and ciphertext, decrypts with AES-256-GCM, and parses the resulting JSON.\n\t *\n\t * Returns null (without throwing) if:\n\t * - No tokens have been saved\n\t * - The stored data is corrupted or not valid JSON\n\t * - Decryption fails (wrong key, tampered ciphertext, or wrong IV)\n\t * - The decrypted data does not contain valid token fields\n\t *\n\t * This fail-silent design prevents decryption errors from crashing the\n\t * application. The caller should treat null as \"no valid tokens available\"\n\t * and initiate a re-authentication flow.\n\t *\n\t * @returns The decrypted {@link AuthTokens}, or null if unavailable or decryption fails\n\t */\n\tasync loadTokens(): Promise<AuthTokens | null> {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\t// Parse the stored encrypted payload\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst record = parsed as Record<string, unknown>\n\t\t\tif (typeof record['iv'] !== 'string' || typeof record['data'] !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Decode the base64url-encoded IV and ciphertext\n\t\t\tconst iv = fromBase64Url(record['iv'])\n\t\t\tconst ciphertext = fromBase64Url(record['data'])\n\n\t\t\t// Decrypt with AES-256-GCM\n\t\t\tconst plaintextBytes = await decryptData(this.key, ciphertext, iv)\n\t\t\tconst json = new TextDecoder().decode(plaintextBytes)\n\n\t\t\t// Parse the decrypted JSON and validate token structure\n\t\t\tconst tokenData: unknown = JSON.parse(json)\n\t\t\tif (typeof tokenData !== 'object' || tokenData === null || Array.isArray(tokenData)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokenRecord = tokenData as Record<string, unknown>\n\t\t\tif (\n\t\t\t\ttypeof tokenRecord['accessToken'] !== 'string'\n\t\t\t\t|| typeof tokenRecord['refreshToken'] !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: tokenRecord['accessToken'],\n\t\t\t\trefreshToken: tokenRecord['refreshToken'],\n\t\t\t}\n\n\t\t\tif (typeof tokenRecord['deviceCredential'] === 'string') {\n\t\t\t\ttokens.deviceCredential = tokenRecord['deviceCredential']\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Decryption failure, corrupted data, or JSON parse error.\n\t\t\t// Return null instead of throwing to allow graceful fallback\n\t\t\t// to re-authentication.\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored encrypted tokens.\n\t *\n\t * Removes the encrypted payload from storage. This is a synchronous\n\t * operation since it only removes the localStorage entry.\n\t *\n\t * Call this on logout to ensure no encrypted credential material\n\t * remains in persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token by decrypting stored tokens.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The decrypted access token string, or null if no valid tokens are stored\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token by decrypting stored tokens.\n\t *\n\t * @returns The decrypted refresh token string, or null if no valid tokens are stored\n\t */\n\tasync getRefreshToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Encryption-specific errors ---\n\n/**\n * Thrown when an encryption or decryption operation fails.\n * Includes context about what went wrong to aid debugging.\n */\nexport class EncryptionError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ENCRYPTION_ERROR', context)\n\t\tthis.name = 'EncryptionError'\n\t}\n}\n\n/**\n * Thrown when the Web Crypto API is not available in the current environment.\n * The encryption module requires `crypto.subtle` for AES-256-GCM operations.\n */\nexport class CryptoUnavailableError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Database encryption requires crypto.subtle, which is available in modern browsers and Node.js 20+. ' +\n\t\t\t\t'If running in SSR, ensure your runtime provides the Web Crypto API.',\n\t\t\t'CRYPTO_UNAVAILABLE',\n\t\t)\n\t\tthis.name = 'CryptoUnavailableError'\n\t}\n}\n\n// --- Internal helpers ---\n\n/** AES-GCM algorithm name, used throughout the module. */\nconst AES_GCM = 'AES-GCM' as const\n\n/** AES-256 key length in bits. */\nconst AES_KEY_LENGTH = 256\n\n/** GCM initialization vector length in bytes (96 bits / 12 bytes is the recommended size). */\nconst IV_LENGTH = 12\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (\n\t\ttypeof globalThis.crypto === 'undefined' ||\n\t\ttypeof globalThis.crypto.subtle === 'undefined'\n\t) {\n\t\tthrow new CryptoUnavailableError()\n\t}\n}\n\n// --- Public API ---\n\n/**\n * Generates a random 256-bit AES-GCM encryption key.\n *\n * The key is extractable so it can be exported for persistence (e.g., encrypted\n * with a passphrase-derived key and stored locally). Use {@link exportKey} to\n * get the raw bytes.\n *\n * @returns A CryptoKey for AES-256-GCM encryption and decryption\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If key generation fails\n *\n * @example\n * ```typescript\n * const key = await generateEncryptionKey()\n * // key can be used with encryptData() and decryptData()\n * ```\n */\nexport async function generateEncryptionKey(): Promise<CryptoKey> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst key = await globalThis.crypto.subtle.generateKey(\n\t\t\t{ name: AES_GCM, length: AES_KEY_LENGTH },\n\t\t\t// extractable: true so the key can be exported and persisted\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\t\treturn key\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to generate AES-256-GCM encryption key. ' +\n\t\t\t\t'Ensure the runtime supports the AES-GCM algorithm.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Encrypts data using AES-256-GCM with a randomly generated IV.\n *\n * Each call generates a fresh 12-byte IV, ensuring that encrypting the same\n * plaintext twice produces different ciphertext. The IV must be stored alongside\n * the ciphertext for decryption.\n *\n * AES-GCM provides both confidentiality and integrity: the ciphertext includes\n * an authentication tag that detects tampering.\n *\n * @param key - An AES-256-GCM CryptoKey (from {@link generateEncryptionKey} or {@link importKey})\n * @param plaintext - The data to encrypt\n * @returns An object containing the ciphertext and the IV used for encryption\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If encryption fails\n *\n * @example\n * ```typescript\n * const key = await generateEncryptionKey()\n * const data = new TextEncoder().encode('sensitive data')\n * const { ciphertext, iv } = await encryptData(key, data)\n * // Store ciphertext and iv together; both are needed for decryption\n * ```\n */\nexport async function encryptData(\n\tkey: CryptoKey,\n\tplaintext: Uint8Array,\n): Promise<{ ciphertext: Uint8Array; iv: Uint8Array }> {\n\tassertCryptoAvailable()\n\n\t// Generate a fresh random IV for each encryption operation.\n\t// AES-GCM with a 96-bit IV is the recommended configuration per NIST SP 800-38D.\n\tconst iv = globalThis.crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n\n\ttry {\n\t\tconst ciphertextBuffer = await globalThis.crypto.subtle.encrypt(\n\t\t\t{ name: AES_GCM, iv: iv as unknown as ArrayBuffer },\n\t\t\tkey,\n\t\t\tplaintext as unknown as ArrayBuffer,\n\t\t)\n\t\treturn {\n\t\t\tciphertext: new Uint8Array(ciphertextBuffer),\n\t\t\tiv,\n\t\t}\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to encrypt data with AES-256-GCM. ' +\n\t\t\t\t'Ensure the key is a valid AES-GCM CryptoKey with \"encrypt\" usage.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Decrypts AES-256-GCM encrypted data.\n *\n * The IV must be the same one that was used during encryption. AES-GCM\n * authenticates the ciphertext, so any tampering will cause decryption to fail.\n *\n * @param key - The AES-256-GCM CryptoKey used for encryption\n * @param ciphertext - The encrypted data (from {@link encryptData})\n * @param iv - The initialization vector used during encryption (from {@link encryptData})\n * @returns The decrypted plaintext\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If decryption fails (wrong key, tampered ciphertext, or wrong IV)\n *\n * @example\n * ```typescript\n * const decrypted = await decryptData(key, ciphertext, iv)\n * const text = new TextDecoder().decode(decrypted)\n * ```\n */\nexport async function decryptData(\n\tkey: CryptoKey,\n\tciphertext: Uint8Array,\n\tiv: Uint8Array,\n): Promise<Uint8Array> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst plaintextBuffer = await globalThis.crypto.subtle.decrypt(\n\t\t\t{ name: AES_GCM, iv: iv as unknown as ArrayBuffer },\n\t\t\tkey,\n\t\t\tciphertext as unknown as ArrayBuffer,\n\t\t)\n\t\treturn new Uint8Array(plaintextBuffer)\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to decrypt data with AES-256-GCM. ' +\n\t\t\t\t'This may indicate a wrong key, tampered ciphertext, or incorrect IV.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Exports an AES-256-GCM CryptoKey to its raw byte representation.\n *\n * The raw key is 32 bytes (256 bits). This is useful for persisting the key\n * (e.g., encrypting it with a passphrase-derived key before storing to disk).\n *\n * **Security warning:** Raw key bytes are sensitive material. Never log them,\n * store them in plaintext, or transmit them over the network without encryption.\n *\n * @param key - An extractable AES-256-GCM CryptoKey\n * @returns The raw key bytes (32 bytes for AES-256)\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If the key export fails (e.g., key is not extractable)\n *\n * @example\n * ```typescript\n * const key = await generateEncryptionKey()\n * const rawBytes = await exportKey(key)\n * // rawBytes.length === 32\n * ```\n */\nexport async function exportKey(key: CryptoKey): Promise<Uint8Array> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst rawBuffer = await globalThis.crypto.subtle.exportKey('raw', key)\n\t\treturn new Uint8Array(rawBuffer)\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to export AES-256-GCM key. ' +\n\t\t\t\t'The key may not be extractable. Only keys generated with extractable=true can be exported.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Imports raw key bytes into an AES-256-GCM CryptoKey.\n *\n * The input must be exactly 32 bytes (256 bits). The imported key is extractable\n * and supports both encrypt and decrypt operations.\n *\n * @param rawKey - Raw key bytes (must be exactly 32 bytes for AES-256)\n * @returns A CryptoKey for AES-256-GCM operations\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If the raw key is invalid or import fails\n *\n * @example\n * ```typescript\n * const rawBytes = new Uint8Array(32) // previously exported key bytes\n * const key = await importKey(rawBytes)\n * // key can now be used with encryptData() and decryptData()\n * ```\n */\nexport async function importKey(rawKey: Uint8Array): Promise<CryptoKey> {\n\tassertCryptoAvailable()\n\n\tif (rawKey.length !== 32) {\n\t\tthrow new EncryptionError(\n\t\t\t`Invalid key length: expected 32 bytes (256 bits) for AES-256, but received ${rawKey.length} bytes.`,\n\t\t\t{ actualLength: rawKey.length, expectedLength: 32 },\n\t\t)\n\t}\n\n\ttry {\n\t\tconst key = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\trawKey as unknown as ArrayBuffer,\n\t\t\t{ name: AES_GCM, length: AES_KEY_LENGTH },\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\t\treturn key\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to import raw key bytes as AES-256-GCM key. ' +\n\t\t\t\t'Ensure the key material is valid.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { fromBase64Url, toBase64Url } from '../device/device-identity'\n\n// ============================================================================\n// Passkey-specific errors\n// ============================================================================\n\n/**\n * Thrown when a passkey operation fails (registration, authentication,\n * or browser API interaction).\n */\nexport class PasskeyError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'PASSKEY_ERROR', context)\n\t\tthis.name = 'PasskeyError'\n\t}\n}\n\n/**\n * Thrown when the browser does not support WebAuthn.\n * Passkey authentication requires a modern browser with the\n * Web Authentication API (navigator.credentials).\n */\nexport class PasskeyUnsupportedError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'WebAuthn is not supported in this browser. Passkey authentication requires a modern browser with WebAuthn support.',\n\t\t\t'PASSKEY_UNSUPPORTED',\n\t\t)\n\t\tthis.name = 'PasskeyUnsupportedError'\n\t}\n}\n\n// ============================================================================\n// Passkey registration response\n// ============================================================================\n\n/** Response from a passkey registration (credential creation). */\nexport interface PasskeyRegistrationResponse {\n\t/** Base64url-encoded credential ID */\n\tcredentialId: string\n\t/** Base64url-encoded public key (COSE format) */\n\tpublicKey: string\n\t/** Base64url-encoded clientDataJSON */\n\tclientDataJSON: string\n\t/** Base64url-encoded attestation object */\n\tattestationObject: string\n}\n\n// ============================================================================\n// Passkey authentication response\n// ============================================================================\n\n/** Response from a passkey authentication (assertion). */\nexport interface PasskeyAuthenticationResponse {\n\t/** Base64url-encoded credential ID */\n\tcredentialId: string\n\t/** Base64url-encoded authenticator data */\n\tauthenticatorData: string\n\t/** Base64url-encoded clientDataJSON */\n\tclientDataJSON: string\n\t/** Base64url-encoded ECDSA signature */\n\tsignature: string\n\t/** Base64url-encoded user handle (may be null for non-resident keys) */\n\tuserHandle: string | null\n}\n\n// ============================================================================\n// Feature detection\n// ============================================================================\n\n/**\n * Check if WebAuthn/passkeys are supported in the current environment.\n *\n * Returns true if the `navigator.credentials` API is available and supports\n * the `create` and `get` methods required for WebAuthn.\n *\n * @returns `true` if WebAuthn is available, `false` otherwise\n *\n * @example\n * ```typescript\n * if (isPasskeySupported()) {\n * // Show passkey login option\n * }\n * ```\n */\nexport function isPasskeySupported(): boolean {\n\treturn (\n\t\ttypeof globalThis.navigator !== 'undefined' &&\n\t\ttypeof globalThis.navigator.credentials !== 'undefined' &&\n\t\ttypeof globalThis.navigator.credentials.create === 'function' &&\n\t\ttypeof globalThis.navigator.credentials.get === 'function'\n\t)\n}\n\n/**\n * Check if a platform authenticator (biometric) is available.\n *\n * A platform authenticator is built into the device (Touch ID, Face ID,\n * Windows Hello). Returns false if only roaming authenticators (security\n * keys) are available, or if WebAuthn is not supported.\n *\n * @returns `true` if a platform authenticator is available\n *\n * @example\n * ```typescript\n * if (await isPlatformAuthenticatorAvailable()) {\n * // Show \"Sign in with Touch ID\" button\n * }\n * ```\n */\nexport async function isPlatformAuthenticatorAvailable(): Promise<boolean> {\n\tif (!isPasskeySupported()) {\n\t\treturn false\n\t}\n\n\t// PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable is\n\t// the standard way to check for biometric/platform authenticator support.\n\tif (\n\t\ttypeof PublicKeyCredential !== 'undefined' &&\n\t\ttypeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === 'function'\n\t) {\n\t\ttry {\n\t\t\treturn await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ============================================================================\n// Registration (credential creation)\n// ============================================================================\n\n/**\n * Create a passkey credential (registration flow).\n *\n * Called during user registration. The server provides a challenge and user info,\n * the browser prompts for biometric verification, and this function returns the\n * credential data to send back to the server for verification.\n *\n * @param options - Registration options from the server\n * @param options.challenge - Base64url-encoded challenge from server\n * @param options.rpId - Relying party ID (your domain, e.g. \"example.com\")\n * @param options.rpName - Relying party display name (e.g. \"My App\")\n * @param options.userId - User ID as base64url-encoded opaque bytes\n * @param options.userName - User's email or username for display\n * @param options.userDisplayName - Human-readable display name\n * @param options.excludeCredentialIds - Credential IDs to exclude (prevents re-registration)\n * @param options.authenticatorSelection - Authenticator selection criteria\n * @returns The credential response to send to the server for verification\n * @throws {PasskeyUnsupportedError} If WebAuthn is not available\n * @throws {PasskeyError} If credential creation fails or is cancelled by the user\n *\n * @example\n * ```typescript\n * const credential = await createPasskeyCredential({\n * challenge: serverOptions.challenge,\n * rpId: 'example.com',\n * rpName: 'My App',\n * userId: serverOptions.userId,\n * userName: 'alice@example.com',\n * userDisplayName: 'Alice',\n * })\n * // Send `credential` to server for verification\n * ```\n */\nexport async function createPasskeyCredential(options: {\n\tchallenge: string\n\trpId: string\n\trpName: string\n\tuserId: string\n\tuserName: string\n\tuserDisplayName: string\n\texcludeCredentialIds?: string[]\n\tauthenticatorSelection?: {\n\t\tauthenticatorAttachment?: 'platform' | 'cross-platform'\n\t\tresidentKey?: 'required' | 'preferred' | 'discouraged'\n\t\tuserVerification?: 'required' | 'preferred' | 'discouraged'\n\t}\n}): Promise<PasskeyRegistrationResponse> {\n\tif (!isPasskeySupported()) {\n\t\tthrow new PasskeyUnsupportedError()\n\t}\n\n\t// Build the excludeCredentials list from base64url credential IDs\n\tconst excludeCredentials: PublicKeyCredentialDescriptor[] = (\n\t\toptions.excludeCredentialIds ?? []\n\t).map((id) => ({\n\t\ttype: 'public-key' as const,\n\t\tid: fromBase64Url(id).buffer as unknown as ArrayBuffer,\n\t}))\n\n\t// Build the authenticator selection criteria with sensible defaults\n\tconst authenticatorSelection: AuthenticatorSelectionCriteria = {\n\t\tauthenticatorAttachment:\n\t\t\toptions.authenticatorSelection?.authenticatorAttachment ?? 'platform',\n\t\tresidentKey: options.authenticatorSelection?.residentKey ?? 'preferred',\n\t\tuserVerification:\n\t\t\toptions.authenticatorSelection?.userVerification ?? 'required',\n\t}\n\n\t// If residentKey is 'required', requireResidentKey must also be true\n\t// for backwards compatibility with older browsers.\n\tif (authenticatorSelection.residentKey === 'required') {\n\t\tauthenticatorSelection.requireResidentKey = true\n\t}\n\n\tconst publicKeyOptions: PublicKeyCredentialCreationOptions = {\n\t\tchallenge: fromBase64Url(options.challenge).buffer as unknown as ArrayBuffer,\n\t\trp: {\n\t\t\tid: options.rpId,\n\t\t\tname: options.rpName,\n\t\t},\n\t\tuser: {\n\t\t\tid: fromBase64Url(options.userId).buffer as unknown as ArrayBuffer,\n\t\t\tname: options.userName,\n\t\t\tdisplayName: options.userDisplayName,\n\t\t},\n\t\tpubKeyCredParams: [\n\t\t\t// ES256 (ECDSA w/ SHA-256) — the most widely supported algorithm\n\t\t\t{ type: 'public-key', alg: -7 },\n\t\t\t// RS256 (RSASSA-PKCS1-v1_5 w/ SHA-256) — fallback for older authenticators\n\t\t\t{ type: 'public-key', alg: -257 },\n\t\t],\n\t\texcludeCredentials,\n\t\tauthenticatorSelection,\n\t\ttimeout: 60000,\n\t\tattestation: 'none',\n\t}\n\n\tlet credential: PublicKeyCredential\n\ttry {\n\t\tconst result = await navigator.credentials.create({\n\t\t\tpublicKey: publicKeyOptions,\n\t\t})\n\n\t\tif (result === null) {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'Credential creation returned null. The user may have cancelled the operation.',\n\t\t\t\t{ rpId: options.rpId },\n\t\t\t)\n\t\t}\n\n\t\tcredential = result as PublicKeyCredential\n\t} catch (error) {\n\t\tif (error instanceof PasskeyError) {\n\t\t\tthrow error\n\t\t}\n\n\t\t// Map common WebAuthn DOMException names to user-friendly messages\n\t\tconst domError = error as DOMException\n\t\tif (domError.name === 'NotAllowedError') {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'Passkey creation was cancelled or not allowed. The user may have dismissed the prompt or the operation timed out.',\n\t\t\t\t{ rpId: options.rpId, errorName: domError.name },\n\t\t\t)\n\t\t}\n\t\tif (domError.name === 'InvalidStateError') {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'A passkey already exists for this user on this authenticator. Use the existing passkey to sign in.',\n\t\t\t\t{ rpId: options.rpId, errorName: domError.name },\n\t\t\t)\n\t\t}\n\n\t\tthrow new PasskeyError(\n\t\t\t`Passkey creation failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t{\n\t\t\t\trpId: options.rpId,\n\t\t\t\terrorName: error instanceof Error ? error.name : undefined,\n\t\t\t},\n\t\t)\n\t}\n\n\tconst response = credential.response as AuthenticatorAttestationResponse\n\n\t// Extract the public key from the attestation response.\n\t// getPublicKey() returns the SubjectPublicKeyInfo (SPKI) encoded public key.\n\t// We also need the raw COSE public key from the attestation for server-side storage.\n\t// The attestation object contains the credential public key in COSE format.\n\tconst attestationObject = new Uint8Array(response.attestationObject)\n\tconst clientDataJSON = new Uint8Array(response.clientDataJSON)\n\n\t// Extract the COSE public key from the authenticator data within the attestation object.\n\t// The attestation object is CBOR-encoded and contains authData which includes the\n\t// credential public key in COSE_Key format.\n\tconst publicKeyBytes = extractPublicKeyFromAttestationObject(attestationObject)\n\n\treturn {\n\t\tcredentialId: toBase64Url(credential.rawId),\n\t\tpublicKey: toBase64Url(publicKeyBytes.buffer as unknown as ArrayBuffer),\n\t\tclientDataJSON: toBase64Url(clientDataJSON.buffer as unknown as ArrayBuffer),\n\t\tattestationObject: toBase64Url(attestationObject.buffer as unknown as ArrayBuffer),\n\t}\n}\n\n// ============================================================================\n// Authentication (assertion)\n// ============================================================================\n\n/**\n * Authenticate with a passkey (assertion flow).\n *\n * Called during login. The server provides a challenge, the browser prompts\n * for biometric verification, and this function returns the signed assertion\n * to send back to the server for verification.\n *\n * @param options - Authentication options from the server\n * @param options.challenge - Base64url-encoded challenge from server\n * @param options.rpId - Relying party ID (your domain)\n * @param options.allowCredentialIds - Limit authentication to specific credentials\n * @param options.userVerification - User verification requirement (default: 'preferred')\n * @param options.timeout - Timeout in milliseconds (default: 60000)\n * @returns The assertion response to send to the server for verification\n * @throws {PasskeyUnsupportedError} If WebAuthn is not available\n * @throws {PasskeyError} If authentication fails or is cancelled by the user\n *\n * @example\n * ```typescript\n * const assertion = await authenticateWithPasskey({\n * challenge: serverOptions.challenge,\n * rpId: 'example.com',\n * })\n * // Send `assertion` to server for verification\n * ```\n */\nexport async function authenticateWithPasskey(options: {\n\tchallenge: string\n\trpId: string\n\tallowCredentialIds?: string[]\n\tuserVerification?: 'required' | 'preferred' | 'discouraged'\n\ttimeout?: number\n}): Promise<PasskeyAuthenticationResponse> {\n\tif (!isPasskeySupported()) {\n\t\tthrow new PasskeyUnsupportedError()\n\t}\n\n\t// Build allowCredentials list from base64url credential IDs\n\tconst allowCredentials: PublicKeyCredentialDescriptor[] | undefined =\n\t\toptions.allowCredentialIds?.map((id) => ({\n\t\t\ttype: 'public-key' as const,\n\t\t\tid: fromBase64Url(id).buffer as unknown as ArrayBuffer,\n\t\t}))\n\n\tconst publicKeyOptions: PublicKeyCredentialRequestOptions = {\n\t\tchallenge: fromBase64Url(options.challenge).buffer as unknown as ArrayBuffer,\n\t\trpId: options.rpId,\n\t\tallowCredentials,\n\t\tuserVerification: options.userVerification ?? 'preferred',\n\t\ttimeout: options.timeout ?? 60000,\n\t}\n\n\tlet credential: PublicKeyCredential\n\ttry {\n\t\tconst result = await navigator.credentials.get({\n\t\t\tpublicKey: publicKeyOptions,\n\t\t})\n\n\t\tif (result === null) {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'Authentication returned null. The user may have cancelled the operation.',\n\t\t\t\t{ rpId: options.rpId },\n\t\t\t)\n\t\t}\n\n\t\tcredential = result as PublicKeyCredential\n\t} catch (error) {\n\t\tif (error instanceof PasskeyError) {\n\t\t\tthrow error\n\t\t}\n\n\t\tconst domError = error as DOMException\n\t\tif (domError.name === 'NotAllowedError') {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'Passkey authentication was cancelled or not allowed. The user may have dismissed the prompt or the operation timed out.',\n\t\t\t\t{ rpId: options.rpId, errorName: domError.name },\n\t\t\t)\n\t\t}\n\n\t\tthrow new PasskeyError(\n\t\t\t`Passkey authentication failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t{\n\t\t\t\trpId: options.rpId,\n\t\t\t\terrorName: error instanceof Error ? error.name : undefined,\n\t\t\t},\n\t\t)\n\t}\n\n\tconst response = credential.response as AuthenticatorAssertionResponse\n\n\tconst authenticatorData = new Uint8Array(response.authenticatorData)\n\tconst clientDataJSON = new Uint8Array(response.clientDataJSON)\n\tconst signature = new Uint8Array(response.signature)\n\tconst userHandle =\n\t\tresponse.userHandle !== null && response.userHandle.byteLength > 0\n\t\t\t? toBase64Url(response.userHandle)\n\t\t\t: null\n\n\treturn {\n\t\tcredentialId: toBase64Url(credential.rawId),\n\t\tauthenticatorData: toBase64Url(authenticatorData.buffer as unknown as ArrayBuffer),\n\t\tclientDataJSON: toBase64Url(clientDataJSON.buffer as unknown as ArrayBuffer),\n\t\tsignature: toBase64Url(signature.buffer as unknown as ArrayBuffer),\n\t\tuserHandle,\n\t}\n}\n\n// ============================================================================\n// Internal: Extract public key from attestation object\n// ============================================================================\n\n/**\n * Extracts the COSE-encoded public key from a CBOR-encoded attestation object.\n *\n * The attestation object structure (CBOR map):\n * - \"fmt\": attestation format (e.g. \"none\")\n * - \"attStmt\": attestation statement (empty map for \"none\")\n * - \"authData\": authenticator data (byte string)\n *\n * The authenticator data structure:\n * - rpIdHash (32 bytes)\n * - flags (1 byte)\n * - signCount (4 bytes, big-endian)\n * - [if flags.AT set] attestedCredentialData:\n * - aaguid (16 bytes)\n * - credentialIdLength (2 bytes, big-endian)\n * - credentialId (credentialIdLength bytes)\n * - credentialPublicKey (CBOR-encoded COSE_Key, remaining bytes)\n */\nfunction extractPublicKeyFromAttestationObject(\n\tattestationObject: Uint8Array,\n): Uint8Array {\n\t// Decode the top-level CBOR map to get authData\n\tconst decoded = decodeCbor(attestationObject, 0)\n\tconst topMap = decoded.value as Map<string, unknown>\n\tconst authData = topMap.get('authData')\n\n\tif (!(authData instanceof Uint8Array)) {\n\t\tthrow new PasskeyError(\n\t\t\t'Invalid attestation object: authData is missing or not a byte string.',\n\t\t)\n\t}\n\n\t// Parse authenticator data to find the credential public key\n\tlet offset = 0\n\n\t// rpIdHash: 32 bytes\n\toffset += 32\n\n\t// flags: 1 byte\n\tconst flags = authData[offset] as number\n\toffset += 1\n\n\t// signCount: 4 bytes\n\toffset += 4\n\n\t// Check if attestedCredentialData is present (bit 6 of flags)\n\tconst hasAttestedCredentialData = (flags & 0x40) !== 0\n\tif (!hasAttestedCredentialData) {\n\t\tthrow new PasskeyError(\n\t\t\t'Attestation object does not contain attested credential data. ' +\n\t\t\t\t'The authenticator did not include a public key.',\n\t\t)\n\t}\n\n\t// aaguid: 16 bytes\n\toffset += 16\n\n\t// credentialIdLength: 2 bytes, big-endian\n\tconst credentialIdLength =\n\t\t((authData[offset] as number) << 8) | (authData[offset + 1] as number)\n\toffset += 2\n\n\t// credentialId: credentialIdLength bytes\n\toffset += credentialIdLength\n\n\t// The remaining bytes in authData from this offset are the CBOR-encoded\n\t// COSE public key. We need to extract exactly those bytes.\n\t// To find the exact length, we decode the CBOR value and use the consumed byte count.\n\tconst coseKeyResult = decodeCbor(authData, offset)\n\tconst coseKeyLength = coseKeyResult.offset - offset\n\n\treturn authData.slice(offset, offset + coseKeyLength)\n}\n\n// ============================================================================\n// Minimal CBOR decoder\n// ============================================================================\n\n/**\n * Minimal CBOR decoder supporting only the types needed for WebAuthn:\n * - Major type 0: Unsigned integer\n * - Major type 1: Negative integer\n * - Major type 2: Byte string\n * - Major type 3: Text string\n * - Major type 4: Array\n * - Major type 5: Map\n *\n * This decoder handles the subset of CBOR used in WebAuthn attestation\n * objects and COSE keys. It does not support tags, floats, or indefinite-length\n * encodings, which are not used in the WebAuthn specification.\n */\n\ninterface CborDecodeResult {\n\tvalue: unknown\n\t/** Byte offset after the decoded value (used for sequential decoding) */\n\toffset: number\n}\n\nfunction decodeCbor(data: Uint8Array, offset: number): CborDecodeResult {\n\tif (offset >= data.length) {\n\t\tthrow new PasskeyError('CBOR decode error: unexpected end of data.')\n\t}\n\n\tconst initialByte = data[offset] as number\n\tconst majorType = initialByte >> 5\n\tconst additionalInfo = initialByte & 0x1f\n\toffset += 1\n\n\t// Decode the argument (length or value) based on additionalInfo\n\tlet argument: number\n\tif (additionalInfo < 24) {\n\t\targument = additionalInfo\n\t} else if (additionalInfo === 24) {\n\t\targument = data[offset] as number\n\t\toffset += 1\n\t} else if (additionalInfo === 25) {\n\t\targument = ((data[offset] as number) << 8) | (data[offset + 1] as number)\n\t\toffset += 2\n\t} else if (additionalInfo === 26) {\n\t\targument =\n\t\t\t((data[offset] as number) << 24) |\n\t\t\t((data[offset + 1] as number) << 16) |\n\t\t\t((data[offset + 2] as number) << 8) |\n\t\t\t(data[offset + 3] as number)\n\t\t// Handle unsigned 32-bit properly (bitwise ops produce signed 32-bit in JS)\n\t\targument = argument >>> 0\n\t\toffset += 4\n\t} else {\n\t\tthrow new PasskeyError(\n\t\t\t`CBOR decode error: unsupported additional info ${additionalInfo} at byte ${offset - 1}. ` +\n\t\t\t\t'This CBOR decoder only supports definite-length encodings.',\n\t\t)\n\t}\n\n\tswitch (majorType) {\n\t\t// Major type 0: Unsigned integer\n\t\tcase 0:\n\t\t\treturn { value: argument, offset }\n\n\t\t// Major type 1: Negative integer (-1 - argument)\n\t\tcase 1:\n\t\t\treturn { value: -1 - argument, offset }\n\n\t\t// Major type 2: Byte string\n\t\tcase 2: {\n\t\t\tconst bytes = data.slice(offset, offset + argument)\n\t\t\treturn { value: bytes, offset: offset + argument }\n\t\t}\n\n\t\t// Major type 3: Text string (UTF-8)\n\t\tcase 3: {\n\t\t\tconst textBytes = data.slice(offset, offset + argument)\n\t\t\tconst text = new TextDecoder().decode(textBytes)\n\t\t\treturn { value: text, offset: offset + argument }\n\t\t}\n\n\t\t// Major type 4: Array\n\t\tcase 4: {\n\t\t\tconst arr: unknown[] = []\n\t\t\tlet currentOffset = offset\n\t\t\tfor (let i = 0; i < argument; i++) {\n\t\t\t\tconst item = decodeCbor(data, currentOffset)\n\t\t\t\tarr.push(item.value)\n\t\t\t\tcurrentOffset = item.offset\n\t\t\t}\n\t\t\treturn { value: arr, offset: currentOffset }\n\t\t}\n\n\t\t// Major type 5: Map\n\t\tcase 5: {\n\t\t\tconst map = new Map<string | number, unknown>()\n\t\t\tlet currentOffset = offset\n\t\t\tfor (let i = 0; i < argument; i++) {\n\t\t\t\tconst keyResult = decodeCbor(data, currentOffset)\n\t\t\t\tconst valResult = decodeCbor(data, keyResult.offset)\n\t\t\t\tmap.set(keyResult.value as string | number, valResult.value)\n\t\t\t\tcurrentOffset = valResult.offset\n\t\t\t}\n\t\t\treturn { value: map, offset: currentOffset }\n\t\t}\n\n\t\tdefault:\n\t\t\tthrow new PasskeyError(\n\t\t\t\t`CBOR decode error: unsupported major type ${majorType} at byte ${offset - 1}. ` +\n\t\t\t\t\t'This CBOR decoder only supports types 0-5 (integers, byte/text strings, arrays, maps).',\n\t\t\t)\n\t}\n}\n\n// Export the CBOR decoder for testing purposes (used by passkey-server too)\nexport { decodeCbor, type CborDecodeResult }\n","import { KoraError } from '@korajs/core'\n\n// --- Key derivation errors ---\n\n/**\n * Thrown when a key derivation operation fails.\n */\nexport class KeyDerivationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'KEY_DERIVATION_ERROR', context)\n\t\tthis.name = 'KeyDerivationError'\n\t}\n}\n\n// --- Internal helpers ---\n\n/** Salt length in bytes. 32 bytes (256 bits) provides sufficient randomness. */\nconst SALT_LENGTH = 32\n\n/**\n * PBKDF2 iteration count. 600,000 iterations is the OWASP-recommended minimum\n * for SHA-256 as of 2024, providing strong resistance against brute-force attacks.\n */\nconst PBKDF2_ITERATIONS = 600_000\n\n/** Derived key length in bits (AES-256). */\nconst DERIVED_KEY_LENGTH = 256\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (\n\t\ttypeof globalThis.crypto === 'undefined' ||\n\t\ttypeof globalThis.crypto.subtle === 'undefined'\n\t) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Key derivation requires crypto.subtle, which is available in modern browsers and Node.js 20+.',\n\t\t)\n\t}\n}\n\n// --- Public API ---\n\n/**\n * Generates a cryptographically random 32-byte salt for key derivation.\n *\n * Each call returns a unique salt. The salt should be stored alongside the\n * encrypted data so that the same passphrase can reproduce the same key later.\n *\n * @returns A random 32-byte Uint8Array\n *\n * @example\n * ```typescript\n * const salt = generateSalt()\n * // salt.length === 32\n * ```\n */\nexport function generateSalt(): Uint8Array {\n\tif (typeof globalThis.crypto === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto) is not available. Cannot generate random salt.',\n\t\t)\n\t}\n\n\treturn globalThis.crypto.getRandomValues(new Uint8Array(SALT_LENGTH))\n}\n\n/**\n * Derives an AES-256-GCM encryption key from a passphrase using PBKDF2.\n *\n * Uses PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended minimum)\n * to derive a 256-bit key from the passphrase. The derived key can be used with\n * the database encryption functions ({@link encryptData}, {@link decryptData}).\n *\n * If no salt is provided, a random 32-byte salt is generated. The salt must be\n * persisted alongside the encrypted data so the key can be re-derived later.\n *\n * **Deterministic:** The same passphrase and salt always produce the same key.\n * This is essential for decryption: the user enters their passphrase, the stored\n * salt is used, and the identical key is re-derived to decrypt the data.\n *\n * @param passphrase - The user's passphrase (any non-empty string)\n * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.\n * @returns An object containing the derived CryptoKey and the salt used\n * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails\n *\n * @example\n * ```typescript\n * // First time: derive key with a new salt\n * const { key, salt } = await deriveEncryptionKey('my-secure-passphrase')\n * // Store the salt alongside the encrypted data\n *\n * // Later: re-derive the same key using the stored salt\n * const { key: sameKey } = await deriveEncryptionKey('my-secure-passphrase', salt)\n * ```\n */\nexport async function deriveEncryptionKey(\n\tpassphrase: string,\n\tsalt?: Uint8Array,\n): Promise<{ key: CryptoKey; salt: Uint8Array }> {\n\tassertCryptoAvailable()\n\n\tif (passphrase.length === 0) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Passphrase must not be empty. Provide a non-empty string for key derivation.',\n\t\t)\n\t}\n\n\tconst usedSalt = salt ?? generateSalt()\n\n\ttry {\n\t\t// Step 1: Import the passphrase as raw key material for PBKDF2\n\t\tconst passphraseBytes = new TextEncoder().encode(passphrase)\n\t\tconst baseKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\tpassphraseBytes,\n\t\t\t'PBKDF2',\n\t\t\tfalse,\n\t\t\t['deriveBits', 'deriveKey'],\n\t\t)\n\n\t\t// Step 2: Derive an AES-256-GCM key using PBKDF2 with SHA-256\n\t\tconst derivedKey = await globalThis.crypto.subtle.deriveKey(\n\t\t\t{\n\t\t\t\tname: 'PBKDF2',\n\t\t\t\tsalt: usedSalt as unknown as ArrayBuffer,\n\t\t\t\titerations: PBKDF2_ITERATIONS,\n\t\t\t\thash: 'SHA-256',\n\t\t\t},\n\t\t\tbaseKey,\n\t\t\t{ name: 'AES-GCM', length: DERIVED_KEY_LENGTH },\n\t\t\t// extractable: true so the derived key can be exported if needed\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\n\t\treturn { key: derivedKey, salt: usedSalt }\n\t} catch (cause) {\n\t\t// Re-throw KeyDerivationError as-is (e.g., from assertCryptoAvailable)\n\t\tif (cause instanceof KeyDerivationError) {\n\t\t\tthrow cause\n\t\t}\n\n\t\tthrow new KeyDerivationError(\n\t\t\t'Failed to derive encryption key from passphrase using PBKDF2. ' +\n\t\t\t\t'Ensure the runtime supports PBKDF2 with SHA-256.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","/**\n * Configuration for the AutoLockManager.\n */\nexport interface AutoLockConfig {\n\t/** Inactivity timeout in milliseconds before auto-locking. */\n\ttimeout: number\n\t/** Callback invoked when the lock engages (either by timeout or manual lock). */\n\tonLock: () => void\n}\n\n/**\n * Manages inactivity-based auto-locking for the encrypted local store.\n *\n * The AutoLockManager starts an inactivity timer when {@link start} is called.\n * If no user activity is reported via {@link reportActivity} within the configured\n * timeout, the manager transitions to the locked state and invokes the `onLock`\n * callback.\n *\n * This class has no DOM dependencies. It uses `setTimeout` and `clearTimeout` for\n * timing and accepts an `onLock` callback for side effects. The consuming code is\n * responsible for wiring DOM events (e.g., visibility changes, user interactions)\n * to {@link reportActivity}.\n *\n * @example\n * ```typescript\n * const manager = new AutoLockManager({\n * timeout: 15 * 60 * 1000, // 15 minutes\n * onLock: () => {\n * // Clear decrypted data from memory\n * // Show lock screen\n * }\n * })\n *\n * manager.start()\n *\n * // Call on user interactions to reset the timer\n * document.addEventListener('click', () => manager.reportActivity())\n * document.addEventListener('keydown', () => manager.reportActivity())\n *\n * // Check lock state\n * if (manager.isLocked) {\n * // Prompt for passphrase\n * }\n * ```\n */\nexport class AutoLockManager {\n\tprivate readonly _timeout: number\n\tprivate readonly _onLock: () => void\n\tprivate _timerId: ReturnType<typeof setTimeout> | null = null\n\tprivate _isLocked: boolean = false\n\tprivate _isRunning: boolean = false\n\n\tconstructor(config: AutoLockConfig) {\n\t\tif (config.timeout <= 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`AutoLockManager timeout must be a positive number, but received ${config.timeout}.`,\n\t\t\t)\n\t\t}\n\n\t\tthis._timeout = config.timeout\n\t\tthis._onLock = config.onLock\n\t}\n\n\t/**\n\t * Whether the manager is currently in the locked state.\n\t *\n\t * Becomes `true` when the inactivity timeout fires or {@link lock} is called manually.\n\t * Returns to `false` only when {@link unlock} is called.\n\t */\n\tget isLocked(): boolean {\n\t\treturn this._isLocked\n\t}\n\n\t/**\n\t * Starts monitoring for inactivity.\n\t *\n\t * Begins the inactivity countdown. If the manager is already running, this is\n\t * a no-op to prevent creating multiple timers. Calling `start()` also resets\n\t * the locked state if the manager was previously locked.\n\t */\n\tstart(): void {\n\t\tif (this._isRunning) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._isRunning = true\n\t\tthis._isLocked = false\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Stops monitoring for inactivity.\n\t *\n\t * Clears the pending inactivity timer. Does not change the lock state: if\n\t * the manager was locked, it remains locked. If unlocked, it remains unlocked.\n\t * To resume monitoring, call {@link start} again.\n\t */\n\tstop(): void {\n\t\tthis._isRunning = false\n\t\tthis._clearTimer()\n\t}\n\n\t/**\n\t * Reports user activity, resetting the inactivity timer.\n\t *\n\t * Call this whenever the user interacts with the application (clicks, key presses,\n\t * touches, etc.). If the manager is not running or is already locked, this is a no-op.\n\t */\n\treportActivity(): void {\n\t\tif (!this._isRunning || this._isLocked) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._clearTimer()\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Manually locks the manager immediately.\n\t *\n\t * Clears the inactivity timer and transitions to the locked state. The `onLock`\n\t * callback is invoked. The manager remains running but locked: call {@link unlock}\n\t * to return to the unlocked state, which will restart the inactivity timer.\n\t */\n\tlock(): void {\n\t\tthis._clearTimer()\n\n\t\tif (!this._isLocked) {\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}\n\t}\n\n\t/**\n\t * Unlocks the manager, returning to the unlocked state.\n\t *\n\t * If the manager is running, the inactivity timer is restarted. If the manager\n\t * is not running (was stopped), it simply clears the locked state without\n\t * starting a timer.\n\t */\n\tunlock(): void {\n\t\tthis._isLocked = false\n\n\t\tif (this._isRunning) {\n\t\t\tthis._clearTimer()\n\t\t\tthis._startTimer()\n\t\t}\n\t}\n\n\t/**\n\t * Starts the inactivity timer. When it fires, the manager locks.\n\t */\n\tprivate _startTimer(): void {\n\t\tthis._timerId = setTimeout(() => {\n\t\t\tthis._timerId = null\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}, this._timeout)\n\t}\n\n\t/**\n\t * Clears the pending inactivity timer, if any.\n\t */\n\tprivate _clearTimer(): void {\n\t\tif (this._timerId !== null) {\n\t\t\tclearTimeout(this._timerId)\n\t\t\tthis._timerId = null\n\t\t}\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport type { Operation } from '@korajs/core'\nimport { encryptData, decryptData } from './database-encryption'\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Marker field indicating that an operation's data has been encrypted. */\nconst ENCRYPTED_MARKER = '__kora_encrypted' as const\n\n/** Current encryption envelope version for forward compatibility. */\nconst ENCRYPTION_VERSION = 1\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * The envelope structure stored in an operation's `data` or `previousData`\n * field when encrypted. The original field contents are replaced with this\n * envelope, which the server relays opaquely.\n */\ninterface EncryptedFieldEnvelope {\n\t/** Marker flag — always true. Used to detect encrypted fields. */\n\t[ENCRYPTED_MARKER]: true\n\t/** Base64url-encoded AES-256-GCM ciphertext */\n\tciphertext: string\n\t/** Base64url-encoded 12-byte initialization vector */\n\tiv: string\n\t/** Algorithm identifier for forward compatibility */\n\talgorithm: 'AES-256-GCM'\n\t/** Envelope version for schema evolution */\n\tversion: number\n}\n\n/**\n * Configuration for the operation encryptor.\n */\nexport interface OperationEncryptorConfig {\n\t/**\n\t * The AES-256-GCM CryptoKey used to encrypt and decrypt operation data.\n\t *\n\t * This can be:\n\t * - A key derived from a user passphrase via `deriveEncryptionKey`\n\t * - A randomly generated key from `generateEncryptionKey`\n\t * - A key imported from raw bytes via `importKey`\n\t *\n\t * All devices that need to read each other's operations must share\n\t * the same encryption key (or keys, if key rotation is implemented).\n\t */\n\tkey: CryptoKey\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Thrown when operation encryption or decryption fails.\n */\nexport class OperationEncryptionError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'OPERATION_ENCRYPTION_ERROR', context)\n\t\tthis.name = 'OperationEncryptionError'\n\t}\n}\n\n// ============================================================================\n// Base64url encoding helpers\n// ============================================================================\n\nfunction toBase64Url(bytes: Uint8Array): string {\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction fromBase64Url(str: string): Uint8Array {\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Encrypts and decrypts the `data` and `previousData` fields of Kora operations.\n *\n * This provides end-to-end encryption for sync: the server relays operations\n * without being able to read the user's data. Only metadata needed for sync\n * orchestration (id, nodeId, collection, timestamp, causalDeps, etc.) remains\n * in cleartext so the server can route, deduplicate, and order operations.\n *\n * **How it works:**\n * 1. `encryptOperation` replaces `data` and `previousData` with encrypted envelopes\n * containing base64url-encoded AES-256-GCM ciphertext\n * 2. The encrypted operation is sent through the normal sync pipeline\n * 3. The server stores and relays the operation without modification\n * 4. `decryptOperation` on receiving clients restores the original field values\n *\n * **What stays in cleartext (needed for sync):**\n * - `id` (content-addressed hash — used for deduplication)\n * - `nodeId`, `sequenceNumber` (version vectors)\n * - `timestamp` (causal ordering)\n * - `type`, `collection`, `recordId` (routing and storage)\n * - `causalDeps` (dependency tracking)\n * - `schemaVersion` (migration)\n *\n * **What gets encrypted (user data):**\n * - `data` (record field values for inserts/updates)\n * - `previousData` (previous field values for 3-way merge)\n *\n * @example\n * ```typescript\n * import { OperationEncryptor, generateEncryptionKey } from '@korajs/auth'\n *\n * const key = await generateEncryptionKey()\n * const encryptor = new OperationEncryptor({ key })\n *\n * // Before sending via sync\n * const encrypted = await encryptor.encryptOperation(operation)\n * syncEngine.send(encrypted)\n *\n * // After receiving from sync\n * const decrypted = await encryptor.decryptOperation(receivedOp)\n * store.apply(decrypted)\n * ```\n */\nexport class OperationEncryptor {\n\tprivate readonly key: CryptoKey\n\n\tconstructor(config: OperationEncryptorConfig) {\n\t\tthis.key = config.key\n\t}\n\n\t/**\n\t * Encrypt an operation's data fields.\n\t *\n\t * Returns a new Operation with `data` and `previousData` replaced by\n\t * encrypted envelopes. The original operation is not mutated.\n\t *\n\t * If `data` or `previousData` is null (e.g., delete operations),\n\t * that field remains null — there is nothing to encrypt.\n\t *\n\t * @param operation - The operation to encrypt\n\t * @returns A new operation with encrypted data fields\n\t * @throws {OperationEncryptionError} If encryption fails\n\t */\n\tasync encryptOperation(operation: Operation): Promise<Operation> {\n\t\tconst [encryptedData, encryptedPreviousData] = await Promise.all([\n\t\t\tthis.encryptField(operation.data, operation.id, 'data'),\n\t\t\tthis.encryptField(operation.previousData, operation.id, 'previousData'),\n\t\t])\n\n\t\treturn {\n\t\t\t...operation,\n\t\t\tdata: encryptedData,\n\t\t\tpreviousData: encryptedPreviousData,\n\t\t}\n\t}\n\n\t/**\n\t * Decrypt an operation's data fields.\n\t *\n\t * Returns a new Operation with the original `data` and `previousData`\n\t * restored from their encrypted envelopes. The original operation is\n\t * not mutated.\n\t *\n\t * If a field is null or not encrypted (no marker), it passes through unchanged.\n\t * This enables mixed plaintext/encrypted operations during migration.\n\t *\n\t * @param operation - The operation to decrypt\n\t * @returns A new operation with decrypted data fields\n\t * @throws {OperationEncryptionError} If decryption fails (wrong key, tampered data)\n\t */\n\tasync decryptOperation(operation: Operation): Promise<Operation> {\n\t\tconst [decryptedData, decryptedPreviousData] = await Promise.all([\n\t\t\tthis.decryptField(operation.data, operation.id, 'data'),\n\t\t\tthis.decryptField(operation.previousData, operation.id, 'previousData'),\n\t\t])\n\n\t\treturn {\n\t\t\t...operation,\n\t\t\tdata: decryptedData,\n\t\t\tpreviousData: decryptedPreviousData,\n\t\t}\n\t}\n\n\t/**\n\t * Check if an operation's data fields are encrypted.\n\t *\n\t * Returns true if either `data` or `previousData` contains an encrypted\n\t * envelope marker. Useful for determining whether decryption is needed\n\t * before applying an operation.\n\t *\n\t * @param operation - The operation to check\n\t * @returns true if any data field is encrypted\n\t */\n\tisEncrypted(operation: Operation): boolean {\n\t\treturn isEncryptedEnvelope(operation.data) || isEncryptedEnvelope(operation.previousData)\n\t}\n\n\t/**\n\t * Encrypt a batch of operations.\n\t *\n\t * Convenience method for encrypting multiple operations at once.\n\t * Operations are encrypted in parallel for performance.\n\t *\n\t * @param operations - The operations to encrypt\n\t * @returns New operations with encrypted data fields\n\t */\n\tasync encryptBatch(operations: Operation[]): Promise<Operation[]> {\n\t\treturn Promise.all(operations.map((op) => this.encryptOperation(op)))\n\t}\n\n\t/**\n\t * Decrypt a batch of operations.\n\t *\n\t * Convenience method for decrypting multiple operations at once.\n\t * Operations are decrypted in parallel for performance.\n\t *\n\t * @param operations - The operations to decrypt\n\t * @returns New operations with decrypted data fields\n\t */\n\tasync decryptBatch(operations: Operation[]): Promise<Operation[]> {\n\t\treturn Promise.all(operations.map((op) => this.decryptOperation(op)))\n\t}\n\n\t// --- Private helpers ---\n\n\tprivate async encryptField(\n\t\tfield: Record<string, unknown> | null,\n\t\toperationId: string,\n\t\tfieldName: string,\n\t): Promise<Record<string, unknown> | null> {\n\t\tif (field === null) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst plaintext = new TextEncoder().encode(JSON.stringify(field))\n\n\t\ttry {\n\t\t\tconst { ciphertext, iv } = await encryptData(this.key, plaintext)\n\n\t\t\tconst envelope: EncryptedFieldEnvelope = {\n\t\t\t\t[ENCRYPTED_MARKER]: true,\n\t\t\t\tciphertext: toBase64Url(ciphertext),\n\t\t\t\tiv: toBase64Url(iv),\n\t\t\t\talgorithm: 'AES-256-GCM',\n\t\t\t\tversion: ENCRYPTION_VERSION,\n\t\t\t}\n\n\t\t\treturn envelope as unknown as Record<string, unknown>\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof OperationEncryptionError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\t\t\tthrow new OperationEncryptionError(\n\t\t\t\t`Failed to encrypt operation ${fieldName} field.`,\n\t\t\t\t{\n\t\t\t\t\toperationId,\n\t\t\t\t\tfieldName,\n\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate async decryptField(\n\t\tfield: Record<string, unknown> | null,\n\t\toperationId: string,\n\t\tfieldName: string,\n\t): Promise<Record<string, unknown> | null> {\n\t\tif (field === null) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Pass through unencrypted fields (backward compatibility)\n\t\tif (!isEncryptedEnvelope(field)) {\n\t\t\treturn field\n\t\t}\n\n\t\tconst envelope = field as unknown as EncryptedFieldEnvelope\n\n\t\tif (envelope.version > ENCRYPTION_VERSION) {\n\t\t\tthrow new OperationEncryptionError(\n\t\t\t\t`Encrypted field uses version ${envelope.version}, but this client only supports version ${ENCRYPTION_VERSION}. ` +\n\t\t\t\t\t'Update your @korajs/auth package to decrypt this operation.',\n\t\t\t\t{ operationId, fieldName, version: envelope.version },\n\t\t\t)\n\t\t}\n\n\t\ttry {\n\t\t\tconst ciphertext = fromBase64Url(envelope.ciphertext)\n\t\t\tconst iv = fromBase64Url(envelope.iv)\n\n\t\t\tconst plaintextBytes = await decryptData(this.key, ciphertext, iv)\n\t\t\tconst json = new TextDecoder().decode(plaintextBytes)\n\t\t\tconst parsed: unknown = JSON.parse(json)\n\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\tthrow new OperationEncryptionError(\n\t\t\t\t\t`Decrypted ${fieldName} is not a valid record object.`,\n\t\t\t\t\t{ operationId, fieldName },\n\t\t\t\t)\n\t\t\t}\n\n\t\t\treturn parsed as Record<string, unknown>\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof OperationEncryptionError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\t\t\tthrow new OperationEncryptionError(\n\t\t\t\t`Failed to decrypt operation ${fieldName} field. ` +\n\t\t\t\t\t'This may indicate a wrong encryption key or tampered data.',\n\t\t\t\t{\n\t\t\t\t\toperationId,\n\t\t\t\t\tfieldName,\n\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Utility functions\n// ============================================================================\n\n/**\n * Check if a field value is an encrypted envelope.\n *\n * This is a standalone utility function that can be used without constructing\n * an OperationEncryptor instance. Useful for routing logic that needs to\n * detect encrypted operations.\n *\n * @param field - An operation's `data` or `previousData` field\n * @returns true if the field is an encrypted envelope\n */\nexport function isEncryptedField(field: Record<string, unknown> | null): boolean {\n\treturn isEncryptedEnvelope(field)\n}\n\nfunction isEncryptedEnvelope(field: Record<string, unknown> | null): boolean {\n\tif (field === null || typeof field !== 'object') {\n\t\treturn false\n\t}\n\treturn (\n\t\tfield[ENCRYPTED_MARKER] === true &&\n\t\ttypeof field['ciphertext'] === 'string' &&\n\t\ttypeof field['iv'] === 'string' &&\n\t\tfield['algorithm'] === 'AES-256-GCM'\n\t)\n}\n"],"mappings":"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","names":["import_core","import_core","import_core","import_core","import_core","CryptoUnavailableError","assertCryptoAvailable","toBase64Url","fromBase64Url","MemoryStorage","tryGetLocalStorage","DEFAULT_STORAGE_KEY","import_core","import_core","assertCryptoAvailable","import_core","toBase64Url","fromBase64Url"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client/auth-client.ts","../src/client/org-client.ts","../src/device/device-identity.ts","../src/device/device-store.ts","../src/tokens/token-store.ts","../src/tokens/encrypted-token-store.ts","../src/encryption/database-encryption.ts","../src/passkey/passkey-client.ts","../src/encryption/key-derivation.ts","../src/encryption/auto-lock.ts","../src/encryption/operation-encryptor.ts"],"sourcesContent":["// @korajs/auth — public API\n// Every export here is a public API commitment. Be explicit.\n\n// === Client ===\nexport { AuthClient, AuthError } from './client/auth-client'\nexport type { AuthClientConfig, AuthUser, AuthState } from './client/auth-client'\n\n// === Organization Client ===\nexport { OrgClient, OrgClientError } from './client/org-client'\nexport type {\n\tOrgClientConfig,\n\tClientOrganization,\n\tClientMembership,\n\tClientInvitation,\n} from './client/org-client'\n\n// === Token Types ===\nexport type {\n\tAuthTokens,\n\tTokenPayload,\n\tTokenType,\n\tAuthConfig,\n\tAuthStatus,\n\tAuthEvent,\n\tAuthEventType,\n} from './types'\n\n// === Device Identity ===\nexport {\n\tgenerateDeviceKeyPair,\n\texportPublicKeyJwk,\n\tsignChallenge,\n\tverifyChallenge,\n\tcomputePublicKeyThumbprint,\n\ttoBase64Url,\n\tfromBase64Url,\n\tCryptoUnavailableError,\n\tDeviceIdentityError,\n} from './device/device-identity'\n\n// === Device Key Store (persistent storage for device key pairs) ===\nexport {\n\tcreateDeviceKeyStore,\n\tIndexedDBDeviceKeyStore,\n\tInMemoryDeviceKeyStore,\n\tDeviceKeyStoreError,\n} from './device/device-store'\nexport type { DeviceKeyStore } from './device/device-store'\n\n// === Token Store (client-side persistence) ===\nexport { TokenStore } from './tokens/token-store'\n\n// === Encrypted Token Store (AES-256-GCM encrypted localStorage) ===\nexport { EncryptedTokenStore, EncryptedTokenStoreError } from './tokens/encrypted-token-store'\nexport type { EncryptedTokenStoreConfig } from './tokens/encrypted-token-store'\n\n// === Passkey / WebAuthn (client-side credential creation and assertion) ===\nexport {\n\tisPasskeySupported,\n\tisPlatformAuthenticatorAvailable,\n\tcreatePasskeyCredential,\n\tauthenticateWithPasskey,\n\tPasskeyError,\n\tPasskeyUnsupportedError,\n} from './passkey/passkey-client'\nexport type {\n\tPasskeyRegistrationResponse,\n\tPasskeyAuthenticationResponse,\n} from './passkey/passkey-client'\n\n// === Encryption (Phase 2: local data protection) ===\nexport {\n\tgenerateEncryptionKey,\n\tencryptData,\n\tdecryptData,\n\texportKey,\n\timportKey,\n\tEncryptionError,\n} from './encryption/database-encryption'\n\nexport {\n\tderiveEncryptionKey,\n\tgenerateSalt,\n\tKeyDerivationError,\n} from './encryption/key-derivation'\n\nexport { AutoLockManager } from './encryption/auto-lock'\nexport type { AutoLockConfig } from './encryption/auto-lock'\n\n// === E2E Operation Encryption (encrypt data fields for sync) ===\nexport {\n\tOperationEncryptor,\n\tOperationEncryptionError,\n\tisEncryptedField,\n} from './encryption/operation-encryptor'\nexport type { OperationEncryptorConfig } from './encryption/operation-encryptor'\n","import { KoraError } from '@korajs/core'\n\n// ---------------------------------------------------------------------------\n// Auth-specific error\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when an authentication operation fails.\n * Includes a machine-readable code and optional context for debugging.\n */\nexport class AuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuthError'\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Possible authentication states for the client.\n * - 'loading': Initial state while restoring tokens from storage\n * - 'authenticated': User is signed in with a valid session\n * - 'unauthenticated': No valid session exists\n */\nexport type AuthState = 'loading' | 'authenticated' | 'unauthenticated'\n\n/**\n * Authenticated user information.\n */\nexport interface AuthUser {\n\t/** Unique user identifier */\n\tid: string\n\n\t/** User email address */\n\temail: string\n\n\t/** Display name (may be absent if user did not provide one) */\n\tname: string | null\n}\n\n/**\n * Configuration for the AuthClient.\n */\nexport interface AuthClientConfig {\n\t/** Base URL of the auth server (e.g. 'http://localhost:3001') */\n\tserverUrl: string\n\n\t/** Storage key prefix for tokens. Defaults to 'kora_auth' */\n\tstorageKey?: string\n}\n\n/**\n * Token pair returned by the auth server on sign-up, sign-in, and refresh.\n */\ninterface AuthTokensResponse {\n\taccessToken: string\n\trefreshToken: string\n}\n\n/**\n * Sign-up and sign-in responses include user data alongside tokens.\n */\ninterface AuthSignInResponse {\n\tuser: { id: string; email: string; name: string }\n\ttokens: AuthTokensResponse\n}\n\n/**\n * User profile returned by the /auth/me endpoint.\n */\ninterface UserProfileResponse {\n\tid: string\n\temail: string\n\tname: string | null\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Number of seconds before actual expiry at which we consider a token expired. */\nconst EXPIRY_BUFFER_SECONDS = 30\n\n/**\n * Decode the payload portion of a JWT without verifying the signature.\n * Client-side only -- verification is the server's responsibility.\n *\n * Returns null if the token is malformed.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\t// Base64url -> standard base64\n\t\tconst base64 = (parts[1] as string).replace(/-/g, '+').replace(/_/g, '/')\n\t\tconst json = atob(base64)\n\t\treturn JSON.parse(json) as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Returns true if the JWT's `exp` claim is in the past (with a small buffer).\n * If the token cannot be decoded, returns true (treat as expired).\n */\nfunction isTokenExpired(token: string): boolean {\n\tconst payload = decodeJwtPayload(token)\n\tif (!payload || typeof payload.exp !== 'number') {\n\t\treturn true\n\t}\n\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\treturn (payload.exp as number) <= nowSeconds + EXPIRY_BUFFER_SECONDS\n}\n\n/**\n * Extracts the `sub` (user ID) from a JWT payload.\n * Returns null if the token is malformed or missing the sub claim.\n */\nfunction getUserIdFromToken(token: string): string | null {\n\tconst payload = decodeJwtPayload(token)\n\tif (!payload || typeof payload.sub !== 'string') {\n\t\treturn null\n\t}\n\treturn payload.sub as string\n}\n\n// ---------------------------------------------------------------------------\n// Simple token storage backed by localStorage (browser) or in-memory fallback\n// ---------------------------------------------------------------------------\n\ninterface TokenStorage {\n\tgetAccessToken(): string | null\n\tgetRefreshToken(): string | null\n\tsetTokens(access: string, refresh: string): void\n\tclear(): void\n}\n\nfunction createTokenStorage(prefix: string): TokenStorage {\n\t// Try localStorage; fall back to in-memory if unavailable (SSR, Web Worker, etc.)\n\tlet useLocalStorage = false\n\ttry {\n\t\tif (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {\n\t\t\t// Smoke test: ensure we can actually write\n\t\t\tconst testKey = `${prefix}_test`\n\t\t\twindow.localStorage.setItem(testKey, '1')\n\t\t\twindow.localStorage.removeItem(testKey)\n\t\t\tuseLocalStorage = true\n\t\t}\n\t} catch {\n\t\t// localStorage not available (e.g., Safari private browsing throws in some contexts)\n\t}\n\n\tif (useLocalStorage) {\n\t\tconst accessKey = `${prefix}_access_token`\n\t\tconst refreshKey = `${prefix}_refresh_token`\n\t\treturn {\n\t\t\tgetAccessToken(): string | null {\n\t\t\t\treturn window.localStorage.getItem(accessKey)\n\t\t\t},\n\t\t\tgetRefreshToken(): string | null {\n\t\t\t\treturn window.localStorage.getItem(refreshKey)\n\t\t\t},\n\t\t\tsetTokens(access: string, refresh: string): void {\n\t\t\t\twindow.localStorage.setItem(accessKey, access)\n\t\t\t\twindow.localStorage.setItem(refreshKey, refresh)\n\t\t\t},\n\t\t\tclear(): void {\n\t\t\t\twindow.localStorage.removeItem(accessKey)\n\t\t\t\twindow.localStorage.removeItem(refreshKey)\n\t\t\t},\n\t\t}\n\t}\n\n\t// In-memory fallback\n\tlet accessToken: string | null = null\n\tlet refreshToken: string | null = null\n\treturn {\n\t\tgetAccessToken(): string | null {\n\t\t\treturn accessToken\n\t\t},\n\t\tgetRefreshToken(): string | null {\n\t\t\treturn refreshToken\n\t\t},\n\t\tsetTokens(access: string, refresh: string): void {\n\t\t\taccessToken = access\n\t\t\trefreshToken = refresh\n\t\t},\n\t\tclear(): void {\n\t\t\taccessToken = null\n\t\t\trefreshToken = null\n\t\t},\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// AuthClient\n// ---------------------------------------------------------------------------\n\n/**\n * Client-side authentication manager for Kora.js.\n *\n * Manages token storage, session restoration, sign-up, sign-in, sign-out,\n * token refresh, and auth state change notifications. Framework-agnostic --\n * works in any JavaScript environment with `fetch` and optionally `localStorage`.\n *\n * @example\n * ```typescript\n * const auth = new AuthClient({ serverUrl: 'http://localhost:3001' })\n * await auth.initialize()\n *\n * if (!auth.isAuthenticated) {\n * await auth.signIn({ email: 'user@example.com', password: 'secret' })\n * }\n *\n * const unsub = auth.onAuthChange((state) => {\n * console.log('Auth state:', state)\n * })\n * ```\n */\nexport class AuthClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly storage: TokenStorage\n\tprivate readonly listeners: Set<(state: AuthState) => void> = new Set()\n\n\tprivate _state: AuthState = 'loading'\n\tprivate _user: AuthUser | null = null\n\tprivate _refreshPromise: Promise<string | null> | null = null\n\tprivate _initialized = false\n\n\t/**\n\t * Creates a new AuthClient.\n\t *\n\t * @param config - Auth client configuration\n\t */\n\tconstructor(config: AuthClientConfig) {\n\t\t// Strip trailing slash to normalize URLs\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tconst prefix = config.storageKey ?? 'kora_auth'\n\t\tthis.storage = createTokenStorage(prefix)\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Public getters\n\t// -----------------------------------------------------------------------\n\n\t/** Current authentication state. */\n\tget state(): AuthState {\n\t\treturn this._state\n\t}\n\n\t/** Current authenticated user, or null if not signed in. */\n\tget currentUser(): AuthUser | null {\n\t\treturn this._user\n\t}\n\n\t/** Whether the user is currently authenticated. */\n\tget isAuthenticated(): boolean {\n\t\treturn this._state === 'authenticated'\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Initialization\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Initialize the auth client by restoring a session from stored tokens.\n\t *\n\t * Loads tokens from storage, validates the access token, and attempts a\n\t * refresh if the access token is expired but a refresh token is available.\n\t * Safe to call multiple times -- subsequent calls are no-ops once initialized.\n\t */\n\tasync initialize(): Promise<void> {\n\t\t// Guard against double initialization (e.g., React StrictMode double-mount)\n\t\tif (this._initialized) {\n\t\t\treturn\n\t\t}\n\t\tthis._initialized = true\n\n\t\tconst accessToken = this.storage.getAccessToken()\n\t\tconst refreshToken = this.storage.getRefreshToken()\n\n\t\t// No stored tokens -- stay unauthenticated\n\t\tif (!accessToken || !refreshToken) {\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn\n\t\t}\n\n\t\t// Access token still valid -- restore session from it\n\t\tif (!isTokenExpired(accessToken)) {\n\t\t\tawait this.restoreSession(accessToken)\n\t\t\treturn\n\t\t}\n\n\t\t// Access token expired -- try refreshing\n\t\ttry {\n\t\t\tconst newAccessToken = await this.refreshAccessToken(refreshToken)\n\t\t\tif (newAccessToken) {\n\t\t\t\tawait this.restoreSession(newAccessToken)\n\t\t\t\treturn\n\t\t\t}\n\t\t} catch {\n\t\t\t// Refresh failed (network error, token revoked, etc.)\n\t\t}\n\n\t\t// Could not restore session\n\t\tthis.storage.clear()\n\t\tthis.setState('unauthenticated', null)\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Sign up / Sign in / Sign out\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Register a new user account.\n\t *\n\t * @param params - Sign-up credentials\n\t * @returns The newly created AuthUser\n\t * @throws {AuthError} If the request fails or the server returns an error\n\t */\n\tasync signUp(params: { email: string; password: string; name?: string }): Promise<AuthUser> {\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signup', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tthis.storage.setTokens(tokens.accessToken, tokens.refreshToken)\n\n\t\tconst user = await this.fetchUserProfile(tokens.accessToken)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Sign in with email and password.\n\t *\n\t * @param params - Sign-in credentials\n\t * @returns The authenticated AuthUser\n\t * @throws {AuthError} If the credentials are invalid or the request fails\n\t */\n\tasync signIn(params: { email: string; password: string }): Promise<AuthUser> {\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signin', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tthis.storage.setTokens(tokens.accessToken, tokens.refreshToken)\n\n\t\tconst user = await this.fetchUserProfile(tokens.accessToken)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Sign out the current user.\n\t *\n\t * Clears local tokens and attempts to revoke the refresh token on the server\n\t * (best-effort — succeeds even if the server is unreachable). This ensures that\n\t * stolen refresh tokens cannot be used after the user explicitly signs out.\n\t */\n\tasync signOut(): Promise<void> {\n\t\tconst accessToken = this.storage.getAccessToken()\n\t\tconst refreshToken = this.storage.getRefreshToken()\n\n\t\t// Clear local state immediately (don't wait for server)\n\t\tthis.storage.clear()\n\t\tthis._refreshPromise = null\n\t\tthis.setState('unauthenticated', null)\n\n\t\t// Best-effort server-side revocation\n\t\tif (accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.request('/auth/signout', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: { refreshToken: refreshToken ?? undefined },\n\t\t\t\t\ttoken: accessToken,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\t// Server may be unreachable (offline) — local sign-out still succeeds\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Token access\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Get a valid access token, automatically refreshing if expired.\n\t *\n\t * @returns A valid access token string, or null if the user is not\n\t * authenticated and refresh is not possible\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst accessToken = this.storage.getAccessToken()\n\n\t\tif (accessToken && !isTokenExpired(accessToken)) {\n\t\t\treturn accessToken\n\t\t}\n\n\t\t// Attempt refresh\n\t\tconst refreshToken = this.storage.getRefreshToken()\n\t\tif (!refreshToken) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst newAccessToken = await this.refreshAccessToken(refreshToken)\n\t\t\treturn newAccessToken\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Get a valid token for the sync engine handshake.\n\t * Alias for {@link getAccessToken}.\n\t *\n\t * @returns A valid access token string, or null if unavailable\n\t */\n\tasync getSyncToken(): Promise<string | null> {\n\t\treturn this.getAccessToken()\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// State change subscriptions\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Subscribe to authentication state changes.\n\t *\n\t * The callback is invoked whenever the auth state transitions (e.g., from\n\t * 'unauthenticated' to 'authenticated' on sign-in).\n\t *\n\t * @param callback - Function called with the new AuthState on each change\n\t * @returns An unsubscribe function that removes the listener\n\t *\n\t * @example\n\t * ```typescript\n\t * const unsub = auth.onAuthChange((state) => {\n\t * console.log('Auth state changed to:', state)\n\t * })\n\t * // Later: unsub()\n\t * ```\n\t */\n\tonAuthChange(callback: (state: AuthState) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Update internal state and notify all listeners.\n\t */\n\tprivate setState(state: AuthState, user: AuthUser | null): void {\n\t\tconst changed = this._state !== state || this._user !== user\n\t\tthis._state = state\n\t\tthis._user = user\n\n\t\tif (changed) {\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\ttry {\n\t\t\t\t\tlistener(state)\n\t\t\t\t} catch {\n\t\t\t\t\t// Listeners should not throw, but if they do, do not let it\n\t\t\t\t\t// break the notification loop for other listeners.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Restore a session from a valid access token by fetching the user profile.\n\t * Falls back to extracting the user ID from the token payload if the\n\t * /auth/me request fails (offline scenario).\n\t */\n\tprivate async restoreSession(accessToken: string): Promise<void> {\n\t\ttry {\n\t\t\tconst user = await this.fetchUserProfile(accessToken)\n\t\t\tthis.setState('authenticated', user)\n\t\t} catch {\n\t\t\t// Network may be unavailable -- extract minimal user info from the token\n\t\t\tconst userId = getUserIdFromToken(accessToken)\n\t\t\tif (userId) {\n\t\t\t\tthis.setState('authenticated', {\n\t\t\t\t\tid: userId,\n\t\t\t\t\temail: '',\n\t\t\t\t\tname: null,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tthis.storage.clear()\n\t\t\t\tthis.setState('unauthenticated', null)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Fetch the current user profile from the server.\n\t */\n\tprivate async fetchUserProfile(accessToken: string): Promise<AuthUser> {\n\t\tconst profile = await this.request<UserProfileResponse>('/auth/me', {\n\t\t\tmethod: 'GET',\n\t\t\ttoken: accessToken,\n\t\t})\n\t\treturn {\n\t\t\tid: profile.id,\n\t\t\temail: profile.email,\n\t\t\tname: profile.name ?? null,\n\t\t}\n\t}\n\n\t/**\n\t * Refresh the access token using a refresh token.\n\t * De-duplicates concurrent refresh calls so only one network request is made.\n\t */\n\tprivate async refreshAccessToken(refreshToken: string): Promise<string | null> {\n\t\t// De-duplicate: if a refresh is already in progress, return the same promise\n\t\tif (this._refreshPromise) {\n\t\t\treturn this._refreshPromise\n\t\t}\n\n\t\tthis._refreshPromise = this.performRefresh(refreshToken)\n\n\t\ttry {\n\t\t\tconst result = await this._refreshPromise\n\t\t\treturn result\n\t\t} finally {\n\t\t\tthis._refreshPromise = null\n\t\t}\n\t}\n\n\t/**\n\t * Execute the token refresh network request.\n\t */\n\tprivate async performRefresh(refreshToken: string): Promise<string | null> {\n\t\ttry {\n\t\t\tconst response = await this.request<AuthTokensResponse>('/auth/refresh', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { refreshToken },\n\t\t\t})\n\n\t\t\tthis.storage.setTokens(response.accessToken, response.refreshToken)\n\t\t\treturn response.accessToken\n\t\t} catch {\n\t\t\t// Refresh failed -- clear tokens to avoid infinite retry loops\n\t\t\tthis.storage.clear()\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Make an HTTP request to the auth server.\n\t *\n\t * @param path - URL path relative to serverUrl (e.g. '/auth/signin')\n\t * @param options - Request options\n\t * @returns Parsed JSON response body\n\t * @throws {AuthError} On network failure or non-2xx response\n\t */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST'\n\t\t\tbody?: Record<string, unknown>\n\t\t\ttoken?: string\n\t\t},\n\t): Promise<T> {\n\t\tconst url = `${this.serverUrl}${path}`\n\n\t\tconst headers: Record<string, string> = {}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\t\tif (options.token) {\n\t\t\theaders.Authorization = `Bearer ${options.token}`\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new AuthError(\n\t\t\t\t`Network request to ${path} failed. The auth server at ${this.serverUrl} may be unreachable. Check your network connection and serverUrl configuration.`,\n\t\t\t\t'AUTH_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Auth server returned HTTP ${response.status}`\n\t\t\tlet serverError: string | undefined\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body.error === 'string') {\n\t\t\t\t\terrorMessage = body.error as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t} else if (typeof body.message === 'string') {\n\t\t\t\t\terrorMessage = body.message as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Response body is not JSON -- use the status text\n\t\t\t}\n\n\t\t\tthrow new AuthError(errorMessage, 'AUTH_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t\tserverError,\n\t\t\t})\n\t\t}\n\n\t\tconst json = (await response.json()) as Record<string, unknown>\n\n\t\t// The BuiltInAuthRoutes server wraps success responses in { data: T }.\n\t\t// Unwrap the envelope so callers get the inner payload directly.\n\t\tconst data = (json.data !== undefined ? json.data : json) as T\n\t\treturn data\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Organization info returned by the server.\n */\nexport interface ClientOrganization {\n\tid: string\n\tname: string\n\tslug: string\n\townerId: string\n\tcreatedAt: number\n\tupdatedAt: number\n\tmetadata: Record<string, unknown>\n}\n\n/**\n * Membership info returned by the server.\n */\nexport interface ClientMembership {\n\tid: string\n\torgId: string\n\tuserId: string\n\trole: string\n\tinvitedBy: string | null\n\tjoinedAt: number\n}\n\n/**\n * Invitation info returned by the server.\n */\nexport interface ClientInvitation {\n\tid: string\n\torgId: string\n\temail: string\n\trole: string\n\tinvitedBy: string\n\ttoken: string\n\tcreatedAt: number\n\texpiresAt: number\n\tstatus: string\n}\n\n/**\n * Configuration for the OrgClient.\n */\nexport interface OrgClientConfig {\n\t/** Base URL of the auth/org server */\n\tserverUrl: string\n\t/** Function that returns a valid access token for authenticated requests */\n\tgetAccessToken: () => Promise<string | null>\n}\n\n/**\n * Thrown when an org operation fails.\n */\nexport class OrgClientError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OrgClientError'\n\t}\n}\n\n// ============================================================================\n// OrgClient\n// ============================================================================\n\n/**\n * Client-side organization manager.\n *\n * Handles org CRUD, member management, invitations, and active org switching.\n * Framework-agnostic — works in any JavaScript environment with `fetch`.\n *\n * @example\n * ```typescript\n * const orgClient = new OrgClient({\n * serverUrl: 'http://localhost:3001',\n * getAccessToken: () => authClient.getAccessToken(),\n * })\n *\n * const org = await orgClient.createOrg({ name: 'Acme Inc', slug: 'acme' })\n * orgClient.switchOrg(org.id)\n * ```\n */\nexport class OrgClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly getAccessToken: () => Promise<string | null>\n\tprivate readonly listeners = new Set<(orgId: string | null) => void>()\n\n\tprivate _activeOrgId: string | null = null\n\tprivate _activeOrg: ClientOrganization | null = null\n\tprivate _activeRole: string | null = null\n\n\tconstructor(config: OrgClientConfig) {\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tthis.getAccessToken = config.getAccessToken\n\t}\n\n\t// --- Getters ---\n\n\t/** Currently active organization ID */\n\tget activeOrgId(): string | null {\n\t\treturn this._activeOrgId\n\t}\n\n\t/** Currently active organization */\n\tget activeOrg(): ClientOrganization | null {\n\t\treturn this._activeOrg\n\t}\n\n\t/** Current user's role in the active organization */\n\tget activeRole(): string | null {\n\t\treturn this._activeRole\n\t}\n\n\t// --- Organization Operations ---\n\n\t/**\n\t * Create a new organization.\n\t */\n\tasync createOrg(params: {\n\t\tname: string\n\t\tslug?: string\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>('/orgs', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * List all organizations the current user belongs to.\n\t */\n\tasync listOrgs(): Promise<ClientOrganization[]> {\n\t\treturn this.request<ClientOrganization[]>('/orgs', { method: 'GET' })\n\t}\n\n\t/**\n\t * Get an organization by ID.\n\t */\n\tasync getOrg(orgId: string): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Update an organization.\n\t */\n\tasync updateOrg(\n\t\torgId: string,\n\t\tparams: { name?: string; slug?: string; metadata?: Record<string, unknown> },\n\t): Promise<ClientOrganization> {\n\t\tconst result = await this.request<ClientOrganization>(`/orgs/${orgId}`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: params,\n\t\t})\n\t\t// Update cached active org if this is the active one\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrg = result\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Delete an organization.\n\t */\n\tasync deleteOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}`, { method: 'DELETE' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Org Switching ---\n\n\t/**\n\t * Switch the active organization context.\n\t * Fetches the org details and the user's membership/role.\n\t */\n\tasync switchOrg(orgId: string): Promise<void> {\n\t\tconst org = await this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t\tconst membership = await this.request<ClientMembership>(`/orgs/${orgId}/membership`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\n\t\tthis._activeOrgId = orgId\n\t\tthis._activeOrg = org\n\t\tthis._activeRole = membership.role\n\t\tthis.notifyListeners()\n\t}\n\n\t/**\n\t * Clear the active organization (no org selected).\n\t */\n\tclearActiveOrg(): void {\n\t\tthis._activeOrgId = null\n\t\tthis._activeOrg = null\n\t\tthis._activeRole = null\n\t\tthis.notifyListeners()\n\t}\n\n\t// --- Member Management ---\n\n\t/**\n\t * List members of an organization.\n\t */\n\tasync listMembers(orgId: string): Promise<ClientMembership[]> {\n\t\treturn this.request<ClientMembership[]>(`/orgs/${orgId}/members`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Remove a member from an organization.\n\t */\n\tasync removeMember(orgId: string, userId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/members/${userId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * Update a member's role.\n\t */\n\tasync updateMemberRole(orgId: string, userId: string, role: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>(`/orgs/${orgId}/members/${userId}/role`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: { role },\n\t\t})\n\t}\n\n\t/**\n\t * Transfer ownership to another member.\n\t */\n\tasync transferOwnership(orgId: string, newOwnerId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/transfer`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { newOwnerId },\n\t\t})\n\t}\n\n\t/**\n\t * Leave an organization (remove yourself).\n\t */\n\tasync leaveOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/leave`, { method: 'POST' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Invitations ---\n\n\t/**\n\t * Invite a user to an organization by email.\n\t */\n\tasync inviteMember(\n\t\torgId: string,\n\t\tparams: { email: string; role: string },\n\t): Promise<ClientInvitation> {\n\t\treturn this.request<ClientInvitation>(`/orgs/${orgId}/invitations`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * Accept an invitation by token.\n\t */\n\tasync acceptInvitation(token: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>('/invitations/accept', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { token },\n\t\t})\n\t}\n\n\t/**\n\t * List pending invitations for an organization.\n\t */\n\tasync listInvitations(orgId: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(`/orgs/${orgId}/invitations`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Revoke a pending invitation.\n\t */\n\tasync revokeInvitation(orgId: string, invitationId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * List pending invitations for the current user's email.\n\t */\n\tasync listMyInvitations(email: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(`/invitations?email=${encodeURIComponent(email)}`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\t}\n\n\t// --- Subscriptions ---\n\n\t/**\n\t * Subscribe to active org changes.\n\t * @returns Unsubscribe function\n\t */\n\tonOrgChange(callback: (orgId: string | null) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// --- Internal ---\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(this._activeOrgId)\n\t\t\t} catch {\n\t\t\t\t// Don't let listener errors break the notification loop\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async request<T = void>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t},\n\t): Promise<T> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t'Not authenticated. Sign in before performing organization operations.',\n\t\t\t\t'ORG_NOT_AUTHENTICATED',\n\t\t\t)\n\t\t}\n\n\t\tconst url = `${this.serverUrl}${path}`\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new OrgClientError(`Network request to ${path} failed.`, 'ORG_NETWORK_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Server returned HTTP ${response.status}`\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body.error === 'string') {\n\t\t\t\t\terrorMessage = body.error as string\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// not JSON\n\t\t\t}\n\t\t\tthrow new OrgClientError(errorMessage, 'ORG_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t})\n\t\t}\n\n\t\t// Handle empty responses (DELETE, etc.)\n\t\tconst text = await response.text()\n\t\tif (text.length === 0) return undefined as T\n\n\t\ttry {\n\t\t\tconst body = JSON.parse(text)\n\t\t\t// Unwrap { data: ... } envelope if present\n\t\t\tif (body && typeof body === 'object' && 'data' in body) {\n\t\t\t\treturn body.data as T\n\t\t\t}\n\t\t\treturn body as T\n\t\t} catch {\n\t\t\treturn undefined as T\n\t\t}\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Auth-specific errors ---\n\n/**\n * Thrown when the Web Crypto API is not available in the current environment.\n * This can happen in older Node.js versions or SSR environments without crypto support.\n */\nexport class CryptoUnavailableError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Device identity requires crypto.subtle, which is available in modern browsers and Node.js 20+. ' +\n\t\t\t\t'If running in SSR, ensure your runtime provides the Web Crypto API.',\n\t\t\t'CRYPTO_UNAVAILABLE',\n\t\t)\n\t\tthis.name = 'CryptoUnavailableError'\n\t}\n}\n\n/**\n * Thrown when a device identity operation fails (key generation, signing, verification).\n */\nexport class DeviceIdentityError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_IDENTITY_ERROR', context)\n\t\tthis.name = 'DeviceIdentityError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes an ArrayBuffer as a base64url string (no padding).\n *\n * @param buffer - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nexport function toBase64Url(buffer: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(buffer)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\t// Standard base64, then convert to base64url (no padding)\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nexport function fromBase64Url(str: string): Uint8Array {\n\t// Convert base64url back to standard base64\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\t// Add padding if necessary\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Internal helpers ---\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {\n\t\tthrow new CryptoUnavailableError()\n\t}\n}\n\n/** ECDSA algorithm parameters used throughout the module. */\nconst ECDSA_ALGORITHM: EcKeyGenParams = {\n\tname: 'ECDSA',\n\tnamedCurve: 'P-256',\n}\n\n/** Signing algorithm parameters: ECDSA with SHA-256. */\nconst ECDSA_SIGN_ALGORITHM: EcdsaParams = {\n\tname: 'ECDSA',\n\thash: { name: 'SHA-256' },\n}\n\n// --- Public API ---\n\n/**\n * Generates an ECDSA P-256 key pair for device identity.\n *\n * The private key is marked as non-extractable, ensuring it cannot be\n * exported from the browser's crypto subsystem. This provides\n * proof-of-possession: only code running on this device can sign with the key.\n *\n * @returns A CryptoKeyPair containing the public and private ECDSA P-256 keys\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If key generation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * // keyPair.publicKey can be exported; keyPair.privateKey stays on device\n * ```\n */\nexport async function generateDeviceKeyPair(): Promise<CryptoKeyPair> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst keyPair = await globalThis.crypto.subtle.generateKey(\n\t\t\tECDSA_ALGORITHM,\n\t\t\t// extractable: false makes the private key non-extractable.\n\t\t\t// The public key is always extractable regardless of this flag.\n\t\t\tfalse,\n\t\t\t['sign', 'verify'],\n\t\t)\n\t\treturn keyPair\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to generate ECDSA P-256 device key pair. ' +\n\t\t\t\t'Ensure the runtime supports the ECDSA algorithm with the P-256 curve.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Exports the public key from a key pair as a JSON Web Key (JWK).\n *\n * The JWK can be safely transmitted to a server or other devices to identify\n * this device. It contains only the public component of the key pair.\n *\n * @param keyPair - The CryptoKeyPair whose public key should be exported\n * @returns The public key in JWK format\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the export operation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const jwk = await exportPublicKeyJwk(keyPair)\n * // jwk contains { kty: 'EC', crv: 'P-256', x: '...', y: '...' }\n * ```\n */\nexport async function exportPublicKeyJwk(keyPair: CryptoKeyPair): Promise<JsonWebKey> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst jwk = await globalThis.crypto.subtle.exportKey('jwk', keyPair.publicKey)\n\t\treturn jwk\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to export public key as JWK. ' +\n\t\t\t\t'The key pair may be invalid or the public key may not support JWK export.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Signs a challenge string with the device's private key.\n *\n * Used for proof-of-possession during authentication: the server sends a\n * random challenge, and the device proves it holds the private key by signing it.\n *\n * @param privateKey - The device's private CryptoKey (ECDSA P-256)\n * @param challenge - The challenge string to sign (typically a random nonce from the server)\n * @returns A base64url-encoded ECDSA signature (no padding)\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the signing operation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const signature = await signChallenge(keyPair.privateKey, 'server-nonce-abc123')\n * // signature is a base64url string like 'MEUCIQDx...'\n * ```\n */\nexport async function signChallenge(privateKey: CryptoKey, challenge: string): Promise<string> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst encoded = new TextEncoder().encode(challenge)\n\t\tconst signatureBuffer = await globalThis.crypto.subtle.sign(\n\t\t\tECDSA_SIGN_ALGORITHM,\n\t\t\tprivateKey,\n\t\t\tencoded,\n\t\t)\n\t\treturn toBase64Url(signatureBuffer)\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to sign challenge. ' +\n\t\t\t\t'Ensure the key is a valid ECDSA P-256 private key with \"sign\" usage.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Verifies a challenge signature against a public key.\n *\n * Used server-side (or on any verifying party) to confirm that a device\n * holds the private key corresponding to the given public key.\n *\n * @param publicKeyJwk - The device's public key in JWK format\n * @param challenge - The original challenge string that was signed\n * @param signature - The base64url-encoded signature to verify\n * @returns `true` if the signature is valid, `false` otherwise\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the verification operation fails due to an invalid key or format\n *\n * @example\n * ```typescript\n * const isValid = await verifyChallenge(publicKeyJwk, 'server-nonce-abc123', signature)\n * if (isValid) {\n * // Device proved possession of the private key\n * }\n * ```\n */\nexport async function verifyChallenge(\n\tpublicKeyJwk: JsonWebKey,\n\tchallenge: string,\n\tsignature: string,\n): Promise<boolean> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst publicKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'jwk',\n\t\t\tpublicKeyJwk,\n\t\t\tECDSA_ALGORITHM,\n\t\t\ttrue,\n\t\t\t['verify'],\n\t\t)\n\n\t\tconst encoded = new TextEncoder().encode(challenge)\n\t\tconst signatureBytes = fromBase64Url(signature)\n\n\t\tconst isValid = await globalThis.crypto.subtle.verify(\n\t\t\tECDSA_SIGN_ALGORITHM,\n\t\t\tpublicKey,\n\t\t\tsignatureBytes as unknown as ArrayBuffer,\n\t\t\tencoded,\n\t\t)\n\t\treturn isValid\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to verify challenge signature. ' +\n\t\t\t\t'The public key JWK or signature format may be invalid.',\n\t\t\t{\n\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\tpublicKeyKty: publicKeyJwk.kty,\n\t\t\t\tpublicKeyCrv: publicKeyJwk.crv,\n\t\t\t},\n\t\t)\n\t}\n}\n\n/**\n * Computes a SHA-256 thumbprint of a JWK public key.\n *\n * The thumbprint is computed per RFC 7638: the JWK members required for the key\n * type are serialized in lexicographic order, then hashed with SHA-256. For EC keys\n * (kty: \"EC\"), the required members are `crv`, `kty`, `x`, and `y`.\n *\n * This thumbprint serves as a compact, stable identifier for the device's public key\n * (used as the `dpk` claim in device credentials).\n *\n * @param publicKeyJwk - The public key in JWK format (must be an EC P-256 key)\n * @returns A base64url-encoded SHA-256 thumbprint (no padding)\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the thumbprint computation fails or the JWK is missing required fields\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const jwk = await exportPublicKeyJwk(keyPair)\n * const thumbprint = await computePublicKeyThumbprint(jwk)\n * // thumbprint is a base64url string, e.g., 'NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs'\n * ```\n */\nexport async function computePublicKeyThumbprint(publicKeyJwk: JsonWebKey): Promise<string> {\n\tassertCryptoAvailable()\n\n\t// RFC 7638 requires specific members in lexicographic order for each key type.\n\t// For EC (kty: \"EC\"), the required members are: crv, kty, x, y.\n\tif (publicKeyJwk.kty !== 'EC') {\n\t\tthrow new DeviceIdentityError(\n\t\t\t`Expected JWK key type \"EC\" but received \"${publicKeyJwk.kty ?? 'undefined'}\". Only ECDSA public keys are supported for device identity.`,\n\t\t\t{ kty: publicKeyJwk.kty },\n\t\t)\n\t}\n\n\tif (!publicKeyJwk.crv || !publicKeyJwk.x || !publicKeyJwk.y) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'JWK is missing required EC fields. ' +\n\t\t\t\t'An EC public key JWK must include \"crv\", \"x\", and \"y\" members.',\n\t\t\t{\n\t\t\t\thasCrv: Boolean(publicKeyJwk.crv),\n\t\t\t\thasX: Boolean(publicKeyJwk.x),\n\t\t\t\thasY: Boolean(publicKeyJwk.y),\n\t\t\t},\n\t\t)\n\t}\n\n\t// Build the canonical JSON with only the required members in lexicographic order.\n\t// Per RFC 7638, no whitespace, keys in sorted order.\n\tconst canonicalJson = JSON.stringify({\n\t\tcrv: publicKeyJwk.crv,\n\t\tkty: publicKeyJwk.kty,\n\t\tx: publicKeyJwk.x,\n\t\ty: publicKeyJwk.y,\n\t})\n\n\ttry {\n\t\tconst encoded = new TextEncoder().encode(canonicalJson)\n\t\tconst hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', encoded)\n\t\treturn toBase64Url(hashBuffer)\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError('Failed to compute SHA-256 thumbprint of the public key JWK.', {\n\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t})\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Device key store errors ---\n\n/**\n * Thrown when a device key store operation fails.\n * Provides context about the operation and the underlying cause.\n */\nexport class DeviceKeyStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_KEY_STORE_ERROR', context)\n\t\tthis.name = 'DeviceKeyStoreError'\n\t}\n}\n\n// --- Interface ---\n\n/**\n * Persistent storage interface for ECDSA P-256 device key pairs.\n *\n * Device key pairs are used for proof-of-possession authentication:\n * the private key never leaves the device, and the public key is\n * registered with the server. This store persists key pairs across\n * page reloads and app restarts.\n *\n * In the browser, CryptoKey objects are structured-cloneable, so they\n * can be stored directly in IndexedDB without serialization. In Node.js\n * or test environments, an in-memory implementation is used instead.\n */\nexport interface DeviceKeyStore {\n\t/**\n\t * Persist a key pair for the given device.\n\t *\n\t * Overwrites any previously stored key pair for the same device ID.\n\t *\n\t * @param deviceId - The unique device identifier (typically a UUID v7)\n\t * @param keyPair - The ECDSA P-256 CryptoKeyPair to store\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tsaveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>\n\n\t/**\n\t * Load a previously stored key pair for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns The stored CryptoKeyPair, or null if no key pair exists for the device\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tloadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>\n\n\t/**\n\t * Delete a stored key pair for the given device.\n\t *\n\t * No-op if no key pair exists for the device ID.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tdeleteKeyPair(deviceId: string): Promise<void>\n\n\t/**\n\t * Check whether a key pair exists for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns True if a key pair is stored for the device, false otherwise\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\thasKeyPair(deviceId: string): Promise<boolean>\n}\n\n// --- IndexedDB constants ---\n\n/** IndexedDB database name for device key storage. */\nconst IDB_DATABASE_NAME = 'kora_device_keys'\n\n/** IndexedDB object store name within the database. */\nconst IDB_STORE_NAME = 'keypairs'\n\n/** Current database schema version. */\nconst IDB_VERSION = 1\n\n// --- IndexedDB implementation ---\n\n/**\n * Browser-based device key store backed by IndexedDB.\n *\n * CryptoKey objects are structured-cloneable, so they can be stored\n * directly in IndexedDB without needing to export/import them as JWK.\n * This preserves the non-extractable flag on private keys, ensuring\n * they cannot be read even from storage.\n *\n * The database uses a single object store (`keypairs`) with the device ID\n * as the key and the full CryptoKeyPair as the value.\n *\n * @example\n * ```typescript\n * const store = new IndexedDBDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * // loaded.privateKey is still non-extractable\n * ```\n */\nexport class IndexedDBDeviceKeyStore implements DeviceKeyStore {\n\tprivate dbPromise: Promise<IDBDatabase> | null = null\n\n\t/**\n\t * Opens (or creates) the IndexedDB database.\n\t *\n\t * The database connection is lazily initialized on first use and\n\t * reused for subsequent operations. If the database does not exist,\n\t * it is created with the `keypairs` object store.\n\t */\n\tprivate openDatabase(): Promise<IDBDatabase> {\n\t\t// Reuse the database connection if already opened.\n\t\t// This avoids repeatedly opening the database on every operation.\n\t\tif (this.dbPromise !== null) {\n\t\t\treturn this.dbPromise\n\t\t}\n\n\t\tthis.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n\t\t\tlet request: IDBOpenDBRequest\n\n\t\t\ttry {\n\t\t\t\trequest = globalThis.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\t\t} catch (cause) {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage. ' +\n\t\t\t\t\t\t\t'IndexedDB may be unavailable or access may be denied.',\n\t\t\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequest.onupgradeneeded = () => {\n\t\t\t\tconst db = request.result\n\t\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve(request.result)\n\t\t\t}\n\n\t\t\trequest.onerror = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError('Failed to open IndexedDB database for device key storage.', {\n\t\t\t\t\t\terror: request.error?.message,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\trequest.onblocked = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'IndexedDB database open was blocked. ' +\n\t\t\t\t\t\t\t'Another tab may have an older version of the database open. ' +\n\t\t\t\t\t\t\t'Close other tabs and try again.',\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\treturn this.dbPromise\n\t}\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Store the CryptoKeyPair as a structured clone with the deviceId as the key.\n\t\t\t\t// IndexedDB handles structured cloning of CryptoKey objects natively.\n\t\t\t\tstore.put(keyPair, deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to save key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: tx.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to save key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<CryptoKeyPair | null>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tconst request = store.get(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst result = request.result as CryptoKeyPair | undefined\n\t\t\t\t\tresolve(result ?? null)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to load key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: request.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to load key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tstore.delete(deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to delete key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: tx.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to delete key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<boolean>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Use count() with the key to check existence without loading the value.\n\t\t\t\t// This is more efficient than get() for large CryptoKeyPair objects.\n\t\t\t\tconst request = store.count(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tresolve(request.result > 0)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to check if key pair exists for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// --- In-memory implementation ---\n\n/**\n * In-memory device key store for Node.js and testing environments.\n *\n * Key pairs are stored in a plain Map and do not survive process restarts.\n * This is suitable for server-side rendering, Node.js scripts, and unit tests\n * where IndexedDB is not available.\n *\n * @example\n * ```typescript\n * const store = new InMemoryDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * ```\n */\nexport class InMemoryDeviceKeyStore implements DeviceKeyStore {\n\tprivate readonly store = new Map<string, CryptoKeyPair>()\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tthis.store.set(deviceId, keyPair)\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\treturn this.store.get(deviceId) ?? null\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tthis.store.delete(deviceId)\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\treturn this.store.has(deviceId)\n\t}\n}\n\n// --- Factory ---\n\n/**\n * Creates a DeviceKeyStore appropriate for the current environment.\n *\n * In browsers where IndexedDB is available, returns an {@link IndexedDBDeviceKeyStore}\n * that persists CryptoKeyPair objects as structured clones. In Node.js, SSR,\n * or environments without IndexedDB, returns an {@link InMemoryDeviceKeyStore}.\n *\n * @returns A DeviceKeyStore instance for the current environment\n *\n * @example\n * ```typescript\n * const store = createDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair(deviceId, keyPair)\n * ```\n */\nexport function createDeviceKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn new IndexedDBDeviceKeyStore()\n\t}\n\treturn new InMemoryDeviceKeyStore()\n}\n","import type { AuthTokens } from '../types'\n\n/** Default key used for localStorage persistence. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_tokens'\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that TokenStore needs. This allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\t// Probe that read/write actually works (some browsers throw on access)\n\t\t\tconst testKey = '__kora_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied (e.g., private mode in some browsers)\n\t}\n\treturn null\n}\n\n/**\n * Client-side token storage for Kora auth tokens.\n *\n * Persists tokens to localStorage when available, falling back to in-memory\n * storage in environments where localStorage is unavailable (Node.js, SSR,\n * restricted browser contexts). Encrypted storage is planned for Phase 2.\n *\n * All operations are synchronous since both localStorage and in-memory\n * storage are synchronous.\n *\n * @example\n * ```typescript\n * const store = new TokenStore()\n *\n * // After login\n * store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls\n * const token = store.getAccessToken()\n *\n * // On logout\n * store.clearTokens()\n * ```\n */\nexport class TokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new TokenStore instance.\n\t *\n\t * @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tconstructor(storageKey?: string) {\n\t\tthis.storageKey = storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Save tokens to persistent storage.\n\t *\n\t * Overwrites any previously stored tokens. The tokens are serialized\n\t * as JSON. Only the `accessToken`, `refreshToken`, and optional\n\t * `deviceCredential` fields are persisted.\n\t *\n\t * @param tokens - The token set to store\n\t */\n\tsaveTokens(tokens: AuthTokens): void {\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\t\tthis.storage.setItem(this.storageKey, JSON.stringify(serialized))\n\t}\n\n\t/**\n\t * Load tokens from storage.\n\t *\n\t * @returns The stored {@link AuthTokens}, or null if no tokens have been saved\n\t */\n\tloadTokens(): AuthTokens | null {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst record = parsed as Record<string, unknown>\n\n\t\t\t// Validate required fields are present and are strings\n\t\t\tif (typeof record.accessToken !== 'string' || typeof record.refreshToken !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: record.accessToken,\n\t\t\t\trefreshToken: record.refreshToken,\n\t\t\t}\n\n\t\t\tif (typeof record.deviceCredential === 'string') {\n\t\t\t\ttokens.deviceCredential = record.deviceCredential\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Corrupted data in storage; treat as empty\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored tokens.\n\t *\n\t * Call this on logout to remove credentials from persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The access token string, or null if no tokens are stored\n\t */\n\tgetAccessToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token.\n\t *\n\t * @returns The refresh token string, or null if no tokens are stored\n\t */\n\tgetRefreshToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { decryptData, encryptData } from '../encryption/database-encryption'\nimport type { AuthTokens } from '../types'\n\n// --- Errors ---\n\n/**\n * Thrown when encrypted token storage operations fail.\n * Includes context about what went wrong to aid debugging without\n * exposing sensitive key or token material.\n */\nexport class EncryptedTokenStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ENCRYPTED_TOKEN_STORE_ERROR', context)\n\t\tthis.name = 'EncryptedTokenStoreError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes a Uint8Array as a base64url string (no padding).\n * Implemented locally to avoid coupling to the device-identity module.\n *\n * @param bytes - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nfunction toBase64Url(bytes: Uint8Array): string {\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nfunction fromBase64Url(str: string): Uint8Array {\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Storage helpers ---\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that EncryptedTokenStore needs. Allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\tconst testKey = '__kora_encrypted_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied\n\t}\n\treturn null\n}\n\n// --- Types ---\n\n/** Default storage key prefix for encrypted tokens. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_encrypted'\n\n/**\n * The serialized format stored in localStorage.\n * Both `iv` and `data` are base64url-encoded strings.\n */\ninterface EncryptedPayload {\n\t/** Base64url-encoded initialization vector (12 bytes for AES-GCM) */\n\tiv: string\n\t/** Base64url-encoded AES-256-GCM ciphertext of the JSON-serialized AuthTokens */\n\tdata: string\n}\n\n/**\n * Configuration for the encrypted token store.\n */\nexport interface EncryptedTokenStoreConfig {\n\t/**\n\t * Storage key prefix. Defaults to 'kora_auth_encrypted'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tstorageKey?: string\n\t/**\n\t * The AES-256-GCM CryptoKey used to encrypt and decrypt tokens.\n\t *\n\t * This can be:\n\t * - A key derived from a user passphrase via {@link deriveEncryptionKey}\n\t * - A device-derived key (e.g., from secure hardware or biometric unlock)\n\t * - A randomly generated key from {@link generateEncryptionKey}\n\t *\n\t * The caller is responsible for obtaining the key before constructing the store.\n\t */\n\tkey: CryptoKey\n}\n\n// --- Implementation ---\n\n/**\n * Encrypted token store that protects auth tokens at rest.\n *\n * Addresses the security vulnerability of storing tokens in plaintext localStorage,\n * which is accessible to any JavaScript running on the page (XSS attack surface).\n * Tokens are encrypted with AES-256-GCM before being written to storage.\n *\n * The stored format is a JSON string with two base64url-encoded fields:\n * - `iv`: the 12-byte initialization vector (unique per encryption)\n * - `data`: the AES-256-GCM ciphertext of the JSON-serialized tokens\n *\n * AES-GCM provides both confidentiality (tokens are unreadable without the key)\n * and integrity (tampered ciphertext is detected and rejected).\n *\n * @example\n * ```typescript\n * import { deriveEncryptionKey } from '@korajs/auth'\n *\n * // Derive a key from the user's passphrase\n * const { key } = await deriveEncryptionKey('user-passphrase', storedSalt)\n *\n * const store = new EncryptedTokenStore({ key })\n *\n * // After login: encrypt and persist tokens\n * await store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls: decrypt and retrieve\n * const accessToken = await store.getAccessToken()\n *\n * // On logout: remove encrypted data\n * store.clearTokens()\n * ```\n */\nexport class EncryptedTokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly key: CryptoKey\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new EncryptedTokenStore instance.\n\t *\n\t * @param config - Configuration including the encryption key and optional storage key\n\t */\n\tconstructor(config: EncryptedTokenStoreConfig) {\n\t\tthis.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.key = config.key\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Encrypt and save tokens to persistent storage.\n\t *\n\t * Serializes the tokens as JSON, encrypts with AES-256-GCM using a fresh\n\t * random IV, then stores the result as a JSON object containing the\n\t * base64url-encoded IV and ciphertext.\n\t *\n\t * Overwrites any previously stored tokens.\n\t *\n\t * @param tokens - The token set to encrypt and store\n\t * @throws {EncryptedTokenStoreError} If encryption fails\n\t */\n\tasync saveTokens(tokens: AuthTokens): Promise<void> {\n\t\t// Build a clean token object with only the expected fields\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\n\t\tconst plaintext = new TextEncoder().encode(JSON.stringify(serialized))\n\n\t\ttry {\n\t\t\tconst { ciphertext, iv } = await encryptData(this.key, plaintext)\n\n\t\t\tconst payload: EncryptedPayload = {\n\t\t\t\tiv: toBase64Url(iv),\n\t\t\t\tdata: toBase64Url(ciphertext),\n\t\t\t}\n\n\t\t\tthis.storage.setItem(this.storageKey, JSON.stringify(payload))\n\t\t} catch (cause) {\n\t\t\t// Re-throw EncryptedTokenStoreError as-is\n\t\t\tif (cause instanceof EncryptedTokenStoreError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\n\t\t\tthrow new EncryptedTokenStoreError(\n\t\t\t\t'Failed to encrypt and save auth tokens. ' +\n\t\t\t\t\t'Ensure the encryption key is a valid AES-256-GCM CryptoKey.',\n\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Load and decrypt tokens from storage.\n\t *\n\t * Reads the encrypted payload from localStorage, decodes the base64url IV\n\t * and ciphertext, decrypts with AES-256-GCM, and parses the resulting JSON.\n\t *\n\t * Returns null (without throwing) if:\n\t * - No tokens have been saved\n\t * - The stored data is corrupted or not valid JSON\n\t * - Decryption fails (wrong key, tampered ciphertext, or wrong IV)\n\t * - The decrypted data does not contain valid token fields\n\t *\n\t * This fail-silent design prevents decryption errors from crashing the\n\t * application. The caller should treat null as \"no valid tokens available\"\n\t * and initiate a re-authentication flow.\n\t *\n\t * @returns The decrypted {@link AuthTokens}, or null if unavailable or decryption fails\n\t */\n\tasync loadTokens(): Promise<AuthTokens | null> {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\t// Parse the stored encrypted payload\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst record = parsed as Record<string, unknown>\n\t\t\tif (typeof record.iv !== 'string' || typeof record.data !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Decode the base64url-encoded IV and ciphertext\n\t\t\tconst iv = fromBase64Url(record.iv)\n\t\t\tconst ciphertext = fromBase64Url(record.data)\n\n\t\t\t// Decrypt with AES-256-GCM\n\t\t\tconst plaintextBytes = await decryptData(this.key, ciphertext, iv)\n\t\t\tconst json = new TextDecoder().decode(plaintextBytes)\n\n\t\t\t// Parse the decrypted JSON and validate token structure\n\t\t\tconst tokenData: unknown = JSON.parse(json)\n\t\t\tif (typeof tokenData !== 'object' || tokenData === null || Array.isArray(tokenData)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokenRecord = tokenData as Record<string, unknown>\n\t\t\tif (\n\t\t\t\ttypeof tokenRecord.accessToken !== 'string' ||\n\t\t\t\ttypeof tokenRecord.refreshToken !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: tokenRecord.accessToken,\n\t\t\t\trefreshToken: tokenRecord.refreshToken,\n\t\t\t}\n\n\t\t\tif (typeof tokenRecord.deviceCredential === 'string') {\n\t\t\t\ttokens.deviceCredential = tokenRecord.deviceCredential\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Decryption failure, corrupted data, or JSON parse error.\n\t\t\t// Return null instead of throwing to allow graceful fallback\n\t\t\t// to re-authentication.\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored encrypted tokens.\n\t *\n\t * Removes the encrypted payload from storage. This is a synchronous\n\t * operation since it only removes the localStorage entry.\n\t *\n\t * Call this on logout to ensure no encrypted credential material\n\t * remains in persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token by decrypting stored tokens.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The decrypted access token string, or null if no valid tokens are stored\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token by decrypting stored tokens.\n\t *\n\t * @returns The decrypted refresh token string, or null if no valid tokens are stored\n\t */\n\tasync getRefreshToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Encryption-specific errors ---\n\n/**\n * Thrown when an encryption or decryption operation fails.\n * Includes context about what went wrong to aid debugging.\n */\nexport class EncryptionError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ENCRYPTION_ERROR', context)\n\t\tthis.name = 'EncryptionError'\n\t}\n}\n\n/**\n * Thrown when the Web Crypto API is not available in the current environment.\n * The encryption module requires `crypto.subtle` for AES-256-GCM operations.\n */\nexport class CryptoUnavailableError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Database encryption requires crypto.subtle, which is available in modern browsers and Node.js 20+. ' +\n\t\t\t\t'If running in SSR, ensure your runtime provides the Web Crypto API.',\n\t\t\t'CRYPTO_UNAVAILABLE',\n\t\t)\n\t\tthis.name = 'CryptoUnavailableError'\n\t}\n}\n\n// --- Internal helpers ---\n\n/** AES-GCM algorithm name, used throughout the module. */\nconst AES_GCM = 'AES-GCM' as const\n\n/** AES-256 key length in bits. */\nconst AES_KEY_LENGTH = 256\n\n/** GCM initialization vector length in bytes (96 bits / 12 bytes is the recommended size). */\nconst IV_LENGTH = 12\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {\n\t\tthrow new CryptoUnavailableError()\n\t}\n}\n\n// --- Public API ---\n\n/**\n * Generates a random 256-bit AES-GCM encryption key.\n *\n * The key is extractable so it can be exported for persistence (e.g., encrypted\n * with a passphrase-derived key and stored locally). Use {@link exportKey} to\n * get the raw bytes.\n *\n * @returns A CryptoKey for AES-256-GCM encryption and decryption\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If key generation fails\n *\n * @example\n * ```typescript\n * const key = await generateEncryptionKey()\n * // key can be used with encryptData() and decryptData()\n * ```\n */\nexport async function generateEncryptionKey(): Promise<CryptoKey> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst key = await globalThis.crypto.subtle.generateKey(\n\t\t\t{ name: AES_GCM, length: AES_KEY_LENGTH },\n\t\t\t// extractable: true so the key can be exported and persisted\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\t\treturn key\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to generate AES-256-GCM encryption key. ' +\n\t\t\t\t'Ensure the runtime supports the AES-GCM algorithm.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Encrypts data using AES-256-GCM with a randomly generated IV.\n *\n * Each call generates a fresh 12-byte IV, ensuring that encrypting the same\n * plaintext twice produces different ciphertext. The IV must be stored alongside\n * the ciphertext for decryption.\n *\n * AES-GCM provides both confidentiality and integrity: the ciphertext includes\n * an authentication tag that detects tampering.\n *\n * @param key - An AES-256-GCM CryptoKey (from {@link generateEncryptionKey} or {@link importKey})\n * @param plaintext - The data to encrypt\n * @returns An object containing the ciphertext and the IV used for encryption\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If encryption fails\n *\n * @example\n * ```typescript\n * const key = await generateEncryptionKey()\n * const data = new TextEncoder().encode('sensitive data')\n * const { ciphertext, iv } = await encryptData(key, data)\n * // Store ciphertext and iv together; both are needed for decryption\n * ```\n */\nexport async function encryptData(\n\tkey: CryptoKey,\n\tplaintext: Uint8Array,\n): Promise<{ ciphertext: Uint8Array; iv: Uint8Array }> {\n\tassertCryptoAvailable()\n\n\t// Generate a fresh random IV for each encryption operation.\n\t// AES-GCM with a 96-bit IV is the recommended configuration per NIST SP 800-38D.\n\tconst iv = globalThis.crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n\n\ttry {\n\t\tconst ciphertextBuffer = await globalThis.crypto.subtle.encrypt(\n\t\t\t{ name: AES_GCM, iv: iv as unknown as ArrayBuffer },\n\t\t\tkey,\n\t\t\tplaintext as unknown as ArrayBuffer,\n\t\t)\n\t\treturn {\n\t\t\tciphertext: new Uint8Array(ciphertextBuffer),\n\t\t\tiv,\n\t\t}\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to encrypt data with AES-256-GCM. ' +\n\t\t\t\t'Ensure the key is a valid AES-GCM CryptoKey with \"encrypt\" usage.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Decrypts AES-256-GCM encrypted data.\n *\n * The IV must be the same one that was used during encryption. AES-GCM\n * authenticates the ciphertext, so any tampering will cause decryption to fail.\n *\n * @param key - The AES-256-GCM CryptoKey used for encryption\n * @param ciphertext - The encrypted data (from {@link encryptData})\n * @param iv - The initialization vector used during encryption (from {@link encryptData})\n * @returns The decrypted plaintext\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If decryption fails (wrong key, tampered ciphertext, or wrong IV)\n *\n * @example\n * ```typescript\n * const decrypted = await decryptData(key, ciphertext, iv)\n * const text = new TextDecoder().decode(decrypted)\n * ```\n */\nexport async function decryptData(\n\tkey: CryptoKey,\n\tciphertext: Uint8Array,\n\tiv: Uint8Array,\n): Promise<Uint8Array> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst plaintextBuffer = await globalThis.crypto.subtle.decrypt(\n\t\t\t{ name: AES_GCM, iv: iv as unknown as ArrayBuffer },\n\t\t\tkey,\n\t\t\tciphertext as unknown as ArrayBuffer,\n\t\t)\n\t\treturn new Uint8Array(plaintextBuffer)\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to decrypt data with AES-256-GCM. ' +\n\t\t\t\t'This may indicate a wrong key, tampered ciphertext, or incorrect IV.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Exports an AES-256-GCM CryptoKey to its raw byte representation.\n *\n * The raw key is 32 bytes (256 bits). This is useful for persisting the key\n * (e.g., encrypting it with a passphrase-derived key before storing to disk).\n *\n * **Security warning:** Raw key bytes are sensitive material. Never log them,\n * store them in plaintext, or transmit them over the network without encryption.\n *\n * @param key - An extractable AES-256-GCM CryptoKey\n * @returns The raw key bytes (32 bytes for AES-256)\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If the key export fails (e.g., key is not extractable)\n *\n * @example\n * ```typescript\n * const key = await generateEncryptionKey()\n * const rawBytes = await exportKey(key)\n * // rawBytes.length === 32\n * ```\n */\nexport async function exportKey(key: CryptoKey): Promise<Uint8Array> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst rawBuffer = await globalThis.crypto.subtle.exportKey('raw', key)\n\t\treturn new Uint8Array(rawBuffer)\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to export AES-256-GCM key. ' +\n\t\t\t\t'The key may not be extractable. Only keys generated with extractable=true can be exported.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Imports raw key bytes into an AES-256-GCM CryptoKey.\n *\n * The input must be exactly 32 bytes (256 bits). The imported key is extractable\n * and supports both encrypt and decrypt operations.\n *\n * @param rawKey - Raw key bytes (must be exactly 32 bytes for AES-256)\n * @returns A CryptoKey for AES-256-GCM operations\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {EncryptionError} If the raw key is invalid or import fails\n *\n * @example\n * ```typescript\n * const rawBytes = new Uint8Array(32) // previously exported key bytes\n * const key = await importKey(rawBytes)\n * // key can now be used with encryptData() and decryptData()\n * ```\n */\nexport async function importKey(rawKey: Uint8Array): Promise<CryptoKey> {\n\tassertCryptoAvailable()\n\n\tif (rawKey.length !== 32) {\n\t\tthrow new EncryptionError(\n\t\t\t`Invalid key length: expected 32 bytes (256 bits) for AES-256, but received ${rawKey.length} bytes.`,\n\t\t\t{ actualLength: rawKey.length, expectedLength: 32 },\n\t\t)\n\t}\n\n\ttry {\n\t\tconst key = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\trawKey as unknown as ArrayBuffer,\n\t\t\t{ name: AES_GCM, length: AES_KEY_LENGTH },\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\t\treturn key\n\t} catch (cause) {\n\t\tthrow new EncryptionError(\n\t\t\t'Failed to import raw key bytes as AES-256-GCM key. ' + 'Ensure the key material is valid.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { fromBase64Url, toBase64Url } from '../device/device-identity'\n\n// ============================================================================\n// Passkey-specific errors\n// ============================================================================\n\n/**\n * Thrown when a passkey operation fails (registration, authentication,\n * or browser API interaction).\n */\nexport class PasskeyError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'PASSKEY_ERROR', context)\n\t\tthis.name = 'PasskeyError'\n\t}\n}\n\n/**\n * Thrown when the browser does not support WebAuthn.\n * Passkey authentication requires a modern browser with the\n * Web Authentication API (navigator.credentials).\n */\nexport class PasskeyUnsupportedError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'WebAuthn is not supported in this browser. Passkey authentication requires a modern browser with WebAuthn support.',\n\t\t\t'PASSKEY_UNSUPPORTED',\n\t\t)\n\t\tthis.name = 'PasskeyUnsupportedError'\n\t}\n}\n\n// ============================================================================\n// Passkey registration response\n// ============================================================================\n\n/** Response from a passkey registration (credential creation). */\nexport interface PasskeyRegistrationResponse {\n\t/** Base64url-encoded credential ID */\n\tcredentialId: string\n\t/** Base64url-encoded public key (COSE format) */\n\tpublicKey: string\n\t/** Base64url-encoded clientDataJSON */\n\tclientDataJSON: string\n\t/** Base64url-encoded attestation object */\n\tattestationObject: string\n}\n\n// ============================================================================\n// Passkey authentication response\n// ============================================================================\n\n/** Response from a passkey authentication (assertion). */\nexport interface PasskeyAuthenticationResponse {\n\t/** Base64url-encoded credential ID */\n\tcredentialId: string\n\t/** Base64url-encoded authenticator data */\n\tauthenticatorData: string\n\t/** Base64url-encoded clientDataJSON */\n\tclientDataJSON: string\n\t/** Base64url-encoded ECDSA signature */\n\tsignature: string\n\t/** Base64url-encoded user handle (may be null for non-resident keys) */\n\tuserHandle: string | null\n}\n\n// ============================================================================\n// Feature detection\n// ============================================================================\n\n/**\n * Check if WebAuthn/passkeys are supported in the current environment.\n *\n * Returns true if the `navigator.credentials` API is available and supports\n * the `create` and `get` methods required for WebAuthn.\n *\n * @returns `true` if WebAuthn is available, `false` otherwise\n *\n * @example\n * ```typescript\n * if (isPasskeySupported()) {\n * // Show passkey login option\n * }\n * ```\n */\nexport function isPasskeySupported(): boolean {\n\treturn (\n\t\ttypeof globalThis.navigator !== 'undefined' &&\n\t\ttypeof globalThis.navigator.credentials !== 'undefined' &&\n\t\ttypeof globalThis.navigator.credentials.create === 'function' &&\n\t\ttypeof globalThis.navigator.credentials.get === 'function'\n\t)\n}\n\n/**\n * Check if a platform authenticator (biometric) is available.\n *\n * A platform authenticator is built into the device (Touch ID, Face ID,\n * Windows Hello). Returns false if only roaming authenticators (security\n * keys) are available, or if WebAuthn is not supported.\n *\n * @returns `true` if a platform authenticator is available\n *\n * @example\n * ```typescript\n * if (await isPlatformAuthenticatorAvailable()) {\n * // Show \"Sign in with Touch ID\" button\n * }\n * ```\n */\nexport async function isPlatformAuthenticatorAvailable(): Promise<boolean> {\n\tif (!isPasskeySupported()) {\n\t\treturn false\n\t}\n\n\t// PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable is\n\t// the standard way to check for biometric/platform authenticator support.\n\tif (\n\t\ttypeof PublicKeyCredential !== 'undefined' &&\n\t\ttypeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === 'function'\n\t) {\n\t\ttry {\n\t\t\treturn await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ============================================================================\n// Registration (credential creation)\n// ============================================================================\n\n/**\n * Create a passkey credential (registration flow).\n *\n * Called during user registration. The server provides a challenge and user info,\n * the browser prompts for biometric verification, and this function returns the\n * credential data to send back to the server for verification.\n *\n * @param options - Registration options from the server\n * @param options.challenge - Base64url-encoded challenge from server\n * @param options.rpId - Relying party ID (your domain, e.g. \"example.com\")\n * @param options.rpName - Relying party display name (e.g. \"My App\")\n * @param options.userId - User ID as base64url-encoded opaque bytes\n * @param options.userName - User's email or username for display\n * @param options.userDisplayName - Human-readable display name\n * @param options.excludeCredentialIds - Credential IDs to exclude (prevents re-registration)\n * @param options.authenticatorSelection - Authenticator selection criteria\n * @returns The credential response to send to the server for verification\n * @throws {PasskeyUnsupportedError} If WebAuthn is not available\n * @throws {PasskeyError} If credential creation fails or is cancelled by the user\n *\n * @example\n * ```typescript\n * const credential = await createPasskeyCredential({\n * challenge: serverOptions.challenge,\n * rpId: 'example.com',\n * rpName: 'My App',\n * userId: serverOptions.userId,\n * userName: 'alice@example.com',\n * userDisplayName: 'Alice',\n * })\n * // Send `credential` to server for verification\n * ```\n */\nexport async function createPasskeyCredential(options: {\n\tchallenge: string\n\trpId: string\n\trpName: string\n\tuserId: string\n\tuserName: string\n\tuserDisplayName: string\n\texcludeCredentialIds?: string[]\n\tauthenticatorSelection?: {\n\t\tauthenticatorAttachment?: 'platform' | 'cross-platform'\n\t\tresidentKey?: 'required' | 'preferred' | 'discouraged'\n\t\tuserVerification?: 'required' | 'preferred' | 'discouraged'\n\t}\n}): Promise<PasskeyRegistrationResponse> {\n\tif (!isPasskeySupported()) {\n\t\tthrow new PasskeyUnsupportedError()\n\t}\n\n\t// Build the excludeCredentials list from base64url credential IDs\n\tconst excludeCredentials: PublicKeyCredentialDescriptor[] = (\n\t\toptions.excludeCredentialIds ?? []\n\t).map((id) => ({\n\t\ttype: 'public-key' as const,\n\t\tid: fromBase64Url(id).buffer as unknown as ArrayBuffer,\n\t}))\n\n\t// Build the authenticator selection criteria with sensible defaults\n\tconst authenticatorSelection: AuthenticatorSelectionCriteria = {\n\t\tauthenticatorAttachment: options.authenticatorSelection?.authenticatorAttachment ?? 'platform',\n\t\tresidentKey: options.authenticatorSelection?.residentKey ?? 'preferred',\n\t\tuserVerification: options.authenticatorSelection?.userVerification ?? 'required',\n\t}\n\n\t// If residentKey is 'required', requireResidentKey must also be true\n\t// for backwards compatibility with older browsers.\n\tif (authenticatorSelection.residentKey === 'required') {\n\t\tauthenticatorSelection.requireResidentKey = true\n\t}\n\n\tconst publicKeyOptions: PublicKeyCredentialCreationOptions = {\n\t\tchallenge: fromBase64Url(options.challenge).buffer as unknown as ArrayBuffer,\n\t\trp: {\n\t\t\tid: options.rpId,\n\t\t\tname: options.rpName,\n\t\t},\n\t\tuser: {\n\t\t\tid: fromBase64Url(options.userId).buffer as unknown as ArrayBuffer,\n\t\t\tname: options.userName,\n\t\t\tdisplayName: options.userDisplayName,\n\t\t},\n\t\tpubKeyCredParams: [\n\t\t\t// ES256 (ECDSA w/ SHA-256) — the most widely supported algorithm\n\t\t\t{ type: 'public-key', alg: -7 },\n\t\t\t// RS256 (RSASSA-PKCS1-v1_5 w/ SHA-256) — fallback for older authenticators\n\t\t\t{ type: 'public-key', alg: -257 },\n\t\t],\n\t\texcludeCredentials,\n\t\tauthenticatorSelection,\n\t\ttimeout: 60000,\n\t\tattestation: 'none',\n\t}\n\n\tlet credential: PublicKeyCredential\n\ttry {\n\t\tconst result = await navigator.credentials.create({\n\t\t\tpublicKey: publicKeyOptions,\n\t\t})\n\n\t\tif (result === null) {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'Credential creation returned null. The user may have cancelled the operation.',\n\t\t\t\t{ rpId: options.rpId },\n\t\t\t)\n\t\t}\n\n\t\tcredential = result as PublicKeyCredential\n\t} catch (error) {\n\t\tif (error instanceof PasskeyError) {\n\t\t\tthrow error\n\t\t}\n\n\t\t// Map common WebAuthn DOMException names to user-friendly messages\n\t\tconst domError = error as DOMException\n\t\tif (domError.name === 'NotAllowedError') {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'Passkey creation was cancelled or not allowed. The user may have dismissed the prompt or the operation timed out.',\n\t\t\t\t{ rpId: options.rpId, errorName: domError.name },\n\t\t\t)\n\t\t}\n\t\tif (domError.name === 'InvalidStateError') {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'A passkey already exists for this user on this authenticator. Use the existing passkey to sign in.',\n\t\t\t\t{ rpId: options.rpId, errorName: domError.name },\n\t\t\t)\n\t\t}\n\n\t\tthrow new PasskeyError(\n\t\t\t`Passkey creation failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t{\n\t\t\t\trpId: options.rpId,\n\t\t\t\terrorName: error instanceof Error ? error.name : undefined,\n\t\t\t},\n\t\t)\n\t}\n\n\tconst response = credential.response as AuthenticatorAttestationResponse\n\n\t// Extract the public key from the attestation response.\n\t// getPublicKey() returns the SubjectPublicKeyInfo (SPKI) encoded public key.\n\t// We also need the raw COSE public key from the attestation for server-side storage.\n\t// The attestation object contains the credential public key in COSE format.\n\tconst attestationObject = new Uint8Array(response.attestationObject)\n\tconst clientDataJSON = new Uint8Array(response.clientDataJSON)\n\n\t// Extract the COSE public key from the authenticator data within the attestation object.\n\t// The attestation object is CBOR-encoded and contains authData which includes the\n\t// credential public key in COSE_Key format.\n\tconst publicKeyBytes = extractPublicKeyFromAttestationObject(attestationObject)\n\n\treturn {\n\t\tcredentialId: toBase64Url(credential.rawId),\n\t\tpublicKey: toBase64Url(publicKeyBytes.buffer as unknown as ArrayBuffer),\n\t\tclientDataJSON: toBase64Url(clientDataJSON.buffer as unknown as ArrayBuffer),\n\t\tattestationObject: toBase64Url(attestationObject.buffer as unknown as ArrayBuffer),\n\t}\n}\n\n// ============================================================================\n// Authentication (assertion)\n// ============================================================================\n\n/**\n * Authenticate with a passkey (assertion flow).\n *\n * Called during login. The server provides a challenge, the browser prompts\n * for biometric verification, and this function returns the signed assertion\n * to send back to the server for verification.\n *\n * @param options - Authentication options from the server\n * @param options.challenge - Base64url-encoded challenge from server\n * @param options.rpId - Relying party ID (your domain)\n * @param options.allowCredentialIds - Limit authentication to specific credentials\n * @param options.userVerification - User verification requirement (default: 'preferred')\n * @param options.timeout - Timeout in milliseconds (default: 60000)\n * @returns The assertion response to send to the server for verification\n * @throws {PasskeyUnsupportedError} If WebAuthn is not available\n * @throws {PasskeyError} If authentication fails or is cancelled by the user\n *\n * @example\n * ```typescript\n * const assertion = await authenticateWithPasskey({\n * challenge: serverOptions.challenge,\n * rpId: 'example.com',\n * })\n * // Send `assertion` to server for verification\n * ```\n */\nexport async function authenticateWithPasskey(options: {\n\tchallenge: string\n\trpId: string\n\tallowCredentialIds?: string[]\n\tuserVerification?: 'required' | 'preferred' | 'discouraged'\n\ttimeout?: number\n}): Promise<PasskeyAuthenticationResponse> {\n\tif (!isPasskeySupported()) {\n\t\tthrow new PasskeyUnsupportedError()\n\t}\n\n\t// Build allowCredentials list from base64url credential IDs\n\tconst allowCredentials: PublicKeyCredentialDescriptor[] | undefined =\n\t\toptions.allowCredentialIds?.map((id) => ({\n\t\t\ttype: 'public-key' as const,\n\t\t\tid: fromBase64Url(id).buffer as unknown as ArrayBuffer,\n\t\t}))\n\n\tconst publicKeyOptions: PublicKeyCredentialRequestOptions = {\n\t\tchallenge: fromBase64Url(options.challenge).buffer as unknown as ArrayBuffer,\n\t\trpId: options.rpId,\n\t\tallowCredentials,\n\t\tuserVerification: options.userVerification ?? 'preferred',\n\t\ttimeout: options.timeout ?? 60000,\n\t}\n\n\tlet credential: PublicKeyCredential\n\ttry {\n\t\tconst result = await navigator.credentials.get({\n\t\t\tpublicKey: publicKeyOptions,\n\t\t})\n\n\t\tif (result === null) {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'Authentication returned null. The user may have cancelled the operation.',\n\t\t\t\t{ rpId: options.rpId },\n\t\t\t)\n\t\t}\n\n\t\tcredential = result as PublicKeyCredential\n\t} catch (error) {\n\t\tif (error instanceof PasskeyError) {\n\t\t\tthrow error\n\t\t}\n\n\t\tconst domError = error as DOMException\n\t\tif (domError.name === 'NotAllowedError') {\n\t\t\tthrow new PasskeyError(\n\t\t\t\t'Passkey authentication was cancelled or not allowed. The user may have dismissed the prompt or the operation timed out.',\n\t\t\t\t{ rpId: options.rpId, errorName: domError.name },\n\t\t\t)\n\t\t}\n\n\t\tthrow new PasskeyError(\n\t\t\t`Passkey authentication failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t{\n\t\t\t\trpId: options.rpId,\n\t\t\t\terrorName: error instanceof Error ? error.name : undefined,\n\t\t\t},\n\t\t)\n\t}\n\n\tconst response = credential.response as AuthenticatorAssertionResponse\n\n\tconst authenticatorData = new Uint8Array(response.authenticatorData)\n\tconst clientDataJSON = new Uint8Array(response.clientDataJSON)\n\tconst signature = new Uint8Array(response.signature)\n\tconst userHandle =\n\t\tresponse.userHandle !== null && response.userHandle.byteLength > 0\n\t\t\t? toBase64Url(response.userHandle)\n\t\t\t: null\n\n\treturn {\n\t\tcredentialId: toBase64Url(credential.rawId),\n\t\tauthenticatorData: toBase64Url(authenticatorData.buffer as unknown as ArrayBuffer),\n\t\tclientDataJSON: toBase64Url(clientDataJSON.buffer as unknown as ArrayBuffer),\n\t\tsignature: toBase64Url(signature.buffer as unknown as ArrayBuffer),\n\t\tuserHandle,\n\t}\n}\n\n// ============================================================================\n// Internal: Extract public key from attestation object\n// ============================================================================\n\n/**\n * Extracts the COSE-encoded public key from a CBOR-encoded attestation object.\n *\n * The attestation object structure (CBOR map):\n * - \"fmt\": attestation format (e.g. \"none\")\n * - \"attStmt\": attestation statement (empty map for \"none\")\n * - \"authData\": authenticator data (byte string)\n *\n * The authenticator data structure:\n * - rpIdHash (32 bytes)\n * - flags (1 byte)\n * - signCount (4 bytes, big-endian)\n * - [if flags.AT set] attestedCredentialData:\n * - aaguid (16 bytes)\n * - credentialIdLength (2 bytes, big-endian)\n * - credentialId (credentialIdLength bytes)\n * - credentialPublicKey (CBOR-encoded COSE_Key, remaining bytes)\n */\nfunction extractPublicKeyFromAttestationObject(attestationObject: Uint8Array): Uint8Array {\n\t// Decode the top-level CBOR map to get authData\n\tconst decoded = decodeCbor(attestationObject, 0)\n\tconst topMap = decoded.value as Map<string, unknown>\n\tconst authData = topMap.get('authData')\n\n\tif (!(authData instanceof Uint8Array)) {\n\t\tthrow new PasskeyError('Invalid attestation object: authData is missing or not a byte string.')\n\t}\n\n\t// Parse authenticator data to find the credential public key\n\tlet offset = 0\n\n\t// rpIdHash: 32 bytes\n\toffset += 32\n\n\t// flags: 1 byte\n\tconst flags = authData[offset] as number\n\toffset += 1\n\n\t// signCount: 4 bytes\n\toffset += 4\n\n\t// Check if attestedCredentialData is present (bit 6 of flags)\n\tconst hasAttestedCredentialData = (flags & 0x40) !== 0\n\tif (!hasAttestedCredentialData) {\n\t\tthrow new PasskeyError(\n\t\t\t'Attestation object does not contain attested credential data. ' +\n\t\t\t\t'The authenticator did not include a public key.',\n\t\t)\n\t}\n\n\t// aaguid: 16 bytes\n\toffset += 16\n\n\t// credentialIdLength: 2 bytes, big-endian\n\tconst credentialIdLength = ((authData[offset] as number) << 8) | (authData[offset + 1] as number)\n\toffset += 2\n\n\t// credentialId: credentialIdLength bytes\n\toffset += credentialIdLength\n\n\t// The remaining bytes in authData from this offset are the CBOR-encoded\n\t// COSE public key. We need to extract exactly those bytes.\n\t// To find the exact length, we decode the CBOR value and use the consumed byte count.\n\tconst coseKeyResult = decodeCbor(authData, offset)\n\tconst coseKeyLength = coseKeyResult.offset - offset\n\n\treturn authData.slice(offset, offset + coseKeyLength)\n}\n\n// ============================================================================\n// Minimal CBOR decoder\n// ============================================================================\n\n/**\n * Minimal CBOR decoder supporting only the types needed for WebAuthn:\n * - Major type 0: Unsigned integer\n * - Major type 1: Negative integer\n * - Major type 2: Byte string\n * - Major type 3: Text string\n * - Major type 4: Array\n * - Major type 5: Map\n *\n * This decoder handles the subset of CBOR used in WebAuthn attestation\n * objects and COSE keys. It does not support tags, floats, or indefinite-length\n * encodings, which are not used in the WebAuthn specification.\n */\n\ninterface CborDecodeResult {\n\tvalue: unknown\n\t/** Byte offset after the decoded value (used for sequential decoding) */\n\toffset: number\n}\n\nfunction decodeCbor(data: Uint8Array, offset: number): CborDecodeResult {\n\tlet pos = offset\n\n\tif (pos >= data.length) {\n\t\tthrow new PasskeyError('CBOR decode error: unexpected end of data.')\n\t}\n\n\tconst initialByte = data[pos] as number\n\tconst majorType = initialByte >> 5\n\tconst additionalInfo = initialByte & 0x1f\n\tpos += 1\n\n\t// Decode the argument (length or value) based on additionalInfo\n\tlet argument: number\n\tif (additionalInfo < 24) {\n\t\targument = additionalInfo\n\t} else if (additionalInfo === 24) {\n\t\targument = data[pos] as number\n\t\tpos += 1\n\t} else if (additionalInfo === 25) {\n\t\targument = ((data[pos] as number) << 8) | (data[pos + 1] as number)\n\t\tpos += 2\n\t} else if (additionalInfo === 26) {\n\t\targument =\n\t\t\t((data[pos] as number) << 24) |\n\t\t\t((data[pos + 1] as number) << 16) |\n\t\t\t((data[pos + 2] as number) << 8) |\n\t\t\t(data[pos + 3] as number)\n\t\t// Handle unsigned 32-bit properly (bitwise ops produce signed 32-bit in JS)\n\t\targument = argument >>> 0\n\t\tpos += 4\n\t} else {\n\t\tthrow new PasskeyError(\n\t\t\t`CBOR decode error: unsupported additional info ${additionalInfo} at byte ${pos - 1}. This CBOR decoder only supports definite-length encodings.`,\n\t\t)\n\t}\n\n\tswitch (majorType) {\n\t\t// Major type 0: Unsigned integer\n\t\tcase 0:\n\t\t\treturn { value: argument, offset: pos }\n\n\t\t// Major type 1: Negative integer (-1 - argument)\n\t\tcase 1:\n\t\t\treturn { value: -1 - argument, offset: pos }\n\n\t\t// Major type 2: Byte string\n\t\tcase 2: {\n\t\t\tconst bytes = data.slice(pos, pos + argument)\n\t\t\treturn { value: bytes, offset: pos + argument }\n\t\t}\n\n\t\t// Major type 3: Text string (UTF-8)\n\t\tcase 3: {\n\t\t\tconst textBytes = data.slice(pos, pos + argument)\n\t\t\tconst text = new TextDecoder().decode(textBytes)\n\t\t\treturn { value: text, offset: pos + argument }\n\t\t}\n\n\t\t// Major type 4: Array\n\t\tcase 4: {\n\t\t\tconst arr: unknown[] = []\n\t\t\tlet currentOffset = pos\n\t\t\tfor (let i = 0; i < argument; i++) {\n\t\t\t\tconst item = decodeCbor(data, currentOffset)\n\t\t\t\tarr.push(item.value)\n\t\t\t\tcurrentOffset = item.offset\n\t\t\t}\n\t\t\treturn { value: arr, offset: currentOffset }\n\t\t}\n\n\t\t// Major type 5: Map\n\t\tcase 5: {\n\t\t\tconst map = new Map<string | number, unknown>()\n\t\t\tlet currentOffset = pos\n\t\t\tfor (let i = 0; i < argument; i++) {\n\t\t\t\tconst keyResult = decodeCbor(data, currentOffset)\n\t\t\t\tconst valResult = decodeCbor(data, keyResult.offset)\n\t\t\t\tmap.set(keyResult.value as string | number, valResult.value)\n\t\t\t\tcurrentOffset = valResult.offset\n\t\t\t}\n\t\t\treturn { value: map, offset: currentOffset }\n\t\t}\n\n\t\tdefault:\n\t\t\tthrow new PasskeyError(\n\t\t\t\t`CBOR decode error: unsupported major type ${majorType} at byte ${pos - 1}. This CBOR decoder only supports types 0-5 (integers, byte/text strings, arrays, maps).`,\n\t\t\t)\n\t}\n}\n\n// Export the CBOR decoder for testing purposes (used by passkey-server too)\nexport { decodeCbor, type CborDecodeResult }\n","import { KoraError } from '@korajs/core'\n\n// --- Key derivation errors ---\n\n/**\n * Thrown when a key derivation operation fails.\n */\nexport class KeyDerivationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'KEY_DERIVATION_ERROR', context)\n\t\tthis.name = 'KeyDerivationError'\n\t}\n}\n\n// --- Internal helpers ---\n\n/** Salt length in bytes. 32 bytes (256 bits) provides sufficient randomness. */\nconst SALT_LENGTH = 32\n\n/**\n * PBKDF2 iteration count. 600,000 iterations is the OWASP-recommended minimum\n * for SHA-256 as of 2024, providing strong resistance against brute-force attacks.\n */\nconst PBKDF2_ITERATIONS = 600_000\n\n/** Derived key length in bits (AES-256). */\nconst DERIVED_KEY_LENGTH = 256\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Key derivation requires crypto.subtle, which is available in modern browsers and Node.js 20+.',\n\t\t)\n\t}\n}\n\n// --- Public API ---\n\n/**\n * Generates a cryptographically random 32-byte salt for key derivation.\n *\n * Each call returns a unique salt. The salt should be stored alongside the\n * encrypted data so that the same passphrase can reproduce the same key later.\n *\n * @returns A random 32-byte Uint8Array\n *\n * @example\n * ```typescript\n * const salt = generateSalt()\n * // salt.length === 32\n * ```\n */\nexport function generateSalt(): Uint8Array {\n\tif (typeof globalThis.crypto === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto) is not available. Cannot generate random salt.',\n\t\t)\n\t}\n\n\treturn globalThis.crypto.getRandomValues(new Uint8Array(SALT_LENGTH))\n}\n\n/**\n * Derives an AES-256-GCM encryption key from a passphrase using PBKDF2.\n *\n * Uses PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended minimum)\n * to derive a 256-bit key from the passphrase. The derived key can be used with\n * the database encryption functions ({@link encryptData}, {@link decryptData}).\n *\n * If no salt is provided, a random 32-byte salt is generated. The salt must be\n * persisted alongside the encrypted data so the key can be re-derived later.\n *\n * **Deterministic:** The same passphrase and salt always produce the same key.\n * This is essential for decryption: the user enters their passphrase, the stored\n * salt is used, and the identical key is re-derived to decrypt the data.\n *\n * @param passphrase - The user's passphrase (any non-empty string)\n * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.\n * @returns An object containing the derived CryptoKey and the salt used\n * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails\n *\n * @example\n * ```typescript\n * // First time: derive key with a new salt\n * const { key, salt } = await deriveEncryptionKey('my-secure-passphrase')\n * // Store the salt alongside the encrypted data\n *\n * // Later: re-derive the same key using the stored salt\n * const { key: sameKey } = await deriveEncryptionKey('my-secure-passphrase', salt)\n * ```\n */\nexport async function deriveEncryptionKey(\n\tpassphrase: string,\n\tsalt?: Uint8Array,\n): Promise<{ key: CryptoKey; salt: Uint8Array }> {\n\tassertCryptoAvailable()\n\n\tif (passphrase.length === 0) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Passphrase must not be empty. Provide a non-empty string for key derivation.',\n\t\t)\n\t}\n\n\tconst usedSalt = salt ?? generateSalt()\n\n\ttry {\n\t\t// Step 1: Import the passphrase as raw key material for PBKDF2\n\t\tconst passphraseBytes = new TextEncoder().encode(passphrase)\n\t\tconst baseKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\tpassphraseBytes,\n\t\t\t'PBKDF2',\n\t\t\tfalse,\n\t\t\t['deriveBits', 'deriveKey'],\n\t\t)\n\n\t\t// Step 2: Derive an AES-256-GCM key using PBKDF2 with SHA-256\n\t\tconst derivedKey = await globalThis.crypto.subtle.deriveKey(\n\t\t\t{\n\t\t\t\tname: 'PBKDF2',\n\t\t\t\tsalt: usedSalt as unknown as ArrayBuffer,\n\t\t\t\titerations: PBKDF2_ITERATIONS,\n\t\t\t\thash: 'SHA-256',\n\t\t\t},\n\t\t\tbaseKey,\n\t\t\t{ name: 'AES-GCM', length: DERIVED_KEY_LENGTH },\n\t\t\t// extractable: true so the derived key can be exported if needed\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\n\t\treturn { key: derivedKey, salt: usedSalt }\n\t} catch (cause) {\n\t\t// Re-throw KeyDerivationError as-is (e.g., from assertCryptoAvailable)\n\t\tif (cause instanceof KeyDerivationError) {\n\t\t\tthrow cause\n\t\t}\n\n\t\tthrow new KeyDerivationError(\n\t\t\t'Failed to derive encryption key from passphrase using PBKDF2. ' +\n\t\t\t\t'Ensure the runtime supports PBKDF2 with SHA-256.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","/**\n * Configuration for the AutoLockManager.\n */\nexport interface AutoLockConfig {\n\t/** Inactivity timeout in milliseconds before auto-locking. */\n\ttimeout: number\n\t/** Callback invoked when the lock engages (either by timeout or manual lock). */\n\tonLock: () => void\n}\n\n/**\n * Manages inactivity-based auto-locking for the encrypted local store.\n *\n * The AutoLockManager starts an inactivity timer when {@link start} is called.\n * If no user activity is reported via {@link reportActivity} within the configured\n * timeout, the manager transitions to the locked state and invokes the `onLock`\n * callback.\n *\n * This class has no DOM dependencies. It uses `setTimeout` and `clearTimeout` for\n * timing and accepts an `onLock` callback for side effects. The consuming code is\n * responsible for wiring DOM events (e.g., visibility changes, user interactions)\n * to {@link reportActivity}.\n *\n * @example\n * ```typescript\n * const manager = new AutoLockManager({\n * timeout: 15 * 60 * 1000, // 15 minutes\n * onLock: () => {\n * // Clear decrypted data from memory\n * // Show lock screen\n * }\n * })\n *\n * manager.start()\n *\n * // Call on user interactions to reset the timer\n * document.addEventListener('click', () => manager.reportActivity())\n * document.addEventListener('keydown', () => manager.reportActivity())\n *\n * // Check lock state\n * if (manager.isLocked) {\n * // Prompt for passphrase\n * }\n * ```\n */\nexport class AutoLockManager {\n\tprivate readonly _timeout: number\n\tprivate readonly _onLock: () => void\n\tprivate _timerId: ReturnType<typeof setTimeout> | null = null\n\tprivate _isLocked = false\n\tprivate _isRunning = false\n\n\tconstructor(config: AutoLockConfig) {\n\t\tif (config.timeout <= 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`AutoLockManager timeout must be a positive number, but received ${config.timeout}.`,\n\t\t\t)\n\t\t}\n\n\t\tthis._timeout = config.timeout\n\t\tthis._onLock = config.onLock\n\t}\n\n\t/**\n\t * Whether the manager is currently in the locked state.\n\t *\n\t * Becomes `true` when the inactivity timeout fires or {@link lock} is called manually.\n\t * Returns to `false` only when {@link unlock} is called.\n\t */\n\tget isLocked(): boolean {\n\t\treturn this._isLocked\n\t}\n\n\t/**\n\t * Starts monitoring for inactivity.\n\t *\n\t * Begins the inactivity countdown. If the manager is already running, this is\n\t * a no-op to prevent creating multiple timers. Calling `start()` also resets\n\t * the locked state if the manager was previously locked.\n\t */\n\tstart(): void {\n\t\tif (this._isRunning) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._isRunning = true\n\t\tthis._isLocked = false\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Stops monitoring for inactivity.\n\t *\n\t * Clears the pending inactivity timer. Does not change the lock state: if\n\t * the manager was locked, it remains locked. If unlocked, it remains unlocked.\n\t * To resume monitoring, call {@link start} again.\n\t */\n\tstop(): void {\n\t\tthis._isRunning = false\n\t\tthis._clearTimer()\n\t}\n\n\t/**\n\t * Reports user activity, resetting the inactivity timer.\n\t *\n\t * Call this whenever the user interacts with the application (clicks, key presses,\n\t * touches, etc.). If the manager is not running or is already locked, this is a no-op.\n\t */\n\treportActivity(): void {\n\t\tif (!this._isRunning || this._isLocked) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._clearTimer()\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Manually locks the manager immediately.\n\t *\n\t * Clears the inactivity timer and transitions to the locked state. The `onLock`\n\t * callback is invoked. The manager remains running but locked: call {@link unlock}\n\t * to return to the unlocked state, which will restart the inactivity timer.\n\t */\n\tlock(): void {\n\t\tthis._clearTimer()\n\n\t\tif (!this._isLocked) {\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}\n\t}\n\n\t/**\n\t * Unlocks the manager, returning to the unlocked state.\n\t *\n\t * If the manager is running, the inactivity timer is restarted. If the manager\n\t * is not running (was stopped), it simply clears the locked state without\n\t * starting a timer.\n\t */\n\tunlock(): void {\n\t\tthis._isLocked = false\n\n\t\tif (this._isRunning) {\n\t\t\tthis._clearTimer()\n\t\t\tthis._startTimer()\n\t\t}\n\t}\n\n\t/**\n\t * Starts the inactivity timer. When it fires, the manager locks.\n\t */\n\tprivate _startTimer(): void {\n\t\tthis._timerId = setTimeout(() => {\n\t\t\tthis._timerId = null\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}, this._timeout)\n\t}\n\n\t/**\n\t * Clears the pending inactivity timer, if any.\n\t */\n\tprivate _clearTimer(): void {\n\t\tif (this._timerId !== null) {\n\t\t\tclearTimeout(this._timerId)\n\t\t\tthis._timerId = null\n\t\t}\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport type { Operation } from '@korajs/core'\nimport { decryptData, encryptData } from './database-encryption'\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Marker field indicating that an operation's data has been encrypted. */\nconst ENCRYPTED_MARKER = '__kora_encrypted' as const\n\n/** Current encryption envelope version for forward compatibility. */\nconst ENCRYPTION_VERSION = 1\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * The envelope structure stored in an operation's `data` or `previousData`\n * field when encrypted. The original field contents are replaced with this\n * envelope, which the server relays opaquely.\n */\ninterface EncryptedFieldEnvelope {\n\t/** Marker flag — always true. Used to detect encrypted fields. */\n\t[ENCRYPTED_MARKER]: true\n\t/** Base64url-encoded AES-256-GCM ciphertext */\n\tciphertext: string\n\t/** Base64url-encoded 12-byte initialization vector */\n\tiv: string\n\t/** Algorithm identifier for forward compatibility */\n\talgorithm: 'AES-256-GCM'\n\t/** Envelope version for schema evolution */\n\tversion: number\n}\n\n/**\n * Configuration for the operation encryptor.\n */\nexport interface OperationEncryptorConfig {\n\t/**\n\t * The AES-256-GCM CryptoKey used to encrypt and decrypt operation data.\n\t *\n\t * This can be:\n\t * - A key derived from a user passphrase via `deriveEncryptionKey`\n\t * - A randomly generated key from `generateEncryptionKey`\n\t * - A key imported from raw bytes via `importKey`\n\t *\n\t * All devices that need to read each other's operations must share\n\t * the same encryption key (or keys, if key rotation is implemented).\n\t */\n\tkey: CryptoKey\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Thrown when operation encryption or decryption fails.\n */\nexport class OperationEncryptionError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'OPERATION_ENCRYPTION_ERROR', context)\n\t\tthis.name = 'OperationEncryptionError'\n\t}\n}\n\n// ============================================================================\n// Base64url encoding helpers\n// ============================================================================\n\nfunction toBase64Url(bytes: Uint8Array): string {\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction fromBase64Url(str: string): Uint8Array {\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Encrypts and decrypts the `data` and `previousData` fields of Kora operations.\n *\n * This provides end-to-end encryption for sync: the server relays operations\n * without being able to read the user's data. Only metadata needed for sync\n * orchestration (id, nodeId, collection, timestamp, causalDeps, etc.) remains\n * in cleartext so the server can route, deduplicate, and order operations.\n *\n * **How it works:**\n * 1. `encryptOperation` replaces `data` and `previousData` with encrypted envelopes\n * containing base64url-encoded AES-256-GCM ciphertext\n * 2. The encrypted operation is sent through the normal sync pipeline\n * 3. The server stores and relays the operation without modification\n * 4. `decryptOperation` on receiving clients restores the original field values\n *\n * **What stays in cleartext (needed for sync):**\n * - `id` (content-addressed hash — used for deduplication)\n * - `nodeId`, `sequenceNumber` (version vectors)\n * - `timestamp` (causal ordering)\n * - `type`, `collection`, `recordId` (routing and storage)\n * - `causalDeps` (dependency tracking)\n * - `schemaVersion` (migration)\n *\n * **What gets encrypted (user data):**\n * - `data` (record field values for inserts/updates)\n * - `previousData` (previous field values for 3-way merge)\n *\n * @example\n * ```typescript\n * import { OperationEncryptor, generateEncryptionKey } from '@korajs/auth'\n *\n * const key = await generateEncryptionKey()\n * const encryptor = new OperationEncryptor({ key })\n *\n * // Before sending via sync\n * const encrypted = await encryptor.encryptOperation(operation)\n * syncEngine.send(encrypted)\n *\n * // After receiving from sync\n * const decrypted = await encryptor.decryptOperation(receivedOp)\n * store.apply(decrypted)\n * ```\n */\nexport class OperationEncryptor {\n\tprivate readonly key: CryptoKey\n\n\tconstructor(config: OperationEncryptorConfig) {\n\t\tthis.key = config.key\n\t}\n\n\t/**\n\t * Encrypt an operation's data fields.\n\t *\n\t * Returns a new Operation with `data` and `previousData` replaced by\n\t * encrypted envelopes. The original operation is not mutated.\n\t *\n\t * If `data` or `previousData` is null (e.g., delete operations),\n\t * that field remains null — there is nothing to encrypt.\n\t *\n\t * @param operation - The operation to encrypt\n\t * @returns A new operation with encrypted data fields\n\t * @throws {OperationEncryptionError} If encryption fails\n\t */\n\tasync encryptOperation(operation: Operation): Promise<Operation> {\n\t\tconst [encryptedData, encryptedPreviousData] = await Promise.all([\n\t\t\tthis.encryptField(operation.data, operation.id, 'data'),\n\t\t\tthis.encryptField(operation.previousData, operation.id, 'previousData'),\n\t\t])\n\n\t\treturn {\n\t\t\t...operation,\n\t\t\tdata: encryptedData,\n\t\t\tpreviousData: encryptedPreviousData,\n\t\t}\n\t}\n\n\t/**\n\t * Decrypt an operation's data fields.\n\t *\n\t * Returns a new Operation with the original `data` and `previousData`\n\t * restored from their encrypted envelopes. The original operation is\n\t * not mutated.\n\t *\n\t * If a field is null or not encrypted (no marker), it passes through unchanged.\n\t * This enables mixed plaintext/encrypted operations during migration.\n\t *\n\t * @param operation - The operation to decrypt\n\t * @returns A new operation with decrypted data fields\n\t * @throws {OperationEncryptionError} If decryption fails (wrong key, tampered data)\n\t */\n\tasync decryptOperation(operation: Operation): Promise<Operation> {\n\t\tconst [decryptedData, decryptedPreviousData] = await Promise.all([\n\t\t\tthis.decryptField(operation.data, operation.id, 'data'),\n\t\t\tthis.decryptField(operation.previousData, operation.id, 'previousData'),\n\t\t])\n\n\t\treturn {\n\t\t\t...operation,\n\t\t\tdata: decryptedData,\n\t\t\tpreviousData: decryptedPreviousData,\n\t\t}\n\t}\n\n\t/**\n\t * Check if an operation's data fields are encrypted.\n\t *\n\t * Returns true if either `data` or `previousData` contains an encrypted\n\t * envelope marker. Useful for determining whether decryption is needed\n\t * before applying an operation.\n\t *\n\t * @param operation - The operation to check\n\t * @returns true if any data field is encrypted\n\t */\n\tisEncrypted(operation: Operation): boolean {\n\t\treturn isEncryptedEnvelope(operation.data) || isEncryptedEnvelope(operation.previousData)\n\t}\n\n\t/**\n\t * Encrypt a batch of operations.\n\t *\n\t * Convenience method for encrypting multiple operations at once.\n\t * Operations are encrypted in parallel for performance.\n\t *\n\t * @param operations - The operations to encrypt\n\t * @returns New operations with encrypted data fields\n\t */\n\tasync encryptBatch(operations: Operation[]): Promise<Operation[]> {\n\t\treturn Promise.all(operations.map((op) => this.encryptOperation(op)))\n\t}\n\n\t/**\n\t * Decrypt a batch of operations.\n\t *\n\t * Convenience method for decrypting multiple operations at once.\n\t * Operations are decrypted in parallel for performance.\n\t *\n\t * @param operations - The operations to decrypt\n\t * @returns New operations with decrypted data fields\n\t */\n\tasync decryptBatch(operations: Operation[]): Promise<Operation[]> {\n\t\treturn Promise.all(operations.map((op) => this.decryptOperation(op)))\n\t}\n\n\t// --- Private helpers ---\n\n\tprivate async encryptField(\n\t\tfield: Record<string, unknown> | null,\n\t\toperationId: string,\n\t\tfieldName: string,\n\t): Promise<Record<string, unknown> | null> {\n\t\tif (field === null) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst plaintext = new TextEncoder().encode(JSON.stringify(field))\n\n\t\ttry {\n\t\t\tconst { ciphertext, iv } = await encryptData(this.key, plaintext)\n\n\t\t\tconst envelope: EncryptedFieldEnvelope = {\n\t\t\t\t[ENCRYPTED_MARKER]: true,\n\t\t\t\tciphertext: toBase64Url(ciphertext),\n\t\t\t\tiv: toBase64Url(iv),\n\t\t\t\talgorithm: 'AES-256-GCM',\n\t\t\t\tversion: ENCRYPTION_VERSION,\n\t\t\t}\n\n\t\t\treturn envelope as unknown as Record<string, unknown>\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof OperationEncryptionError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\t\t\tthrow new OperationEncryptionError(`Failed to encrypt operation ${fieldName} field.`, {\n\t\t\t\toperationId,\n\t\t\t\tfieldName,\n\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t})\n\t\t}\n\t}\n\n\tprivate async decryptField(\n\t\tfield: Record<string, unknown> | null,\n\t\toperationId: string,\n\t\tfieldName: string,\n\t): Promise<Record<string, unknown> | null> {\n\t\tif (field === null) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Pass through unencrypted fields (backward compatibility)\n\t\tif (!isEncryptedEnvelope(field)) {\n\t\t\treturn field\n\t\t}\n\n\t\tconst envelope = field as unknown as EncryptedFieldEnvelope\n\n\t\tif (envelope.version > ENCRYPTION_VERSION) {\n\t\t\tthrow new OperationEncryptionError(\n\t\t\t\t`Encrypted field uses version ${envelope.version}, but this client only supports version ${ENCRYPTION_VERSION}. Update your @korajs/auth package to decrypt this operation.`,\n\t\t\t\t{ operationId, fieldName, version: envelope.version },\n\t\t\t)\n\t\t}\n\n\t\ttry {\n\t\t\tconst ciphertext = fromBase64Url(envelope.ciphertext)\n\t\t\tconst iv = fromBase64Url(envelope.iv)\n\n\t\t\tconst plaintextBytes = await decryptData(this.key, ciphertext, iv)\n\t\t\tconst json = new TextDecoder().decode(plaintextBytes)\n\t\t\tconst parsed: unknown = JSON.parse(json)\n\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\tthrow new OperationEncryptionError(`Decrypted ${fieldName} is not a valid record object.`, {\n\t\t\t\t\toperationId,\n\t\t\t\t\tfieldName,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\treturn parsed as Record<string, unknown>\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof OperationEncryptionError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\t\t\tthrow new OperationEncryptionError(\n\t\t\t\t`Failed to decrypt operation ${fieldName} field. This may indicate a wrong encryption key or tampered data.`,\n\t\t\t\t{\n\t\t\t\t\toperationId,\n\t\t\t\t\tfieldName,\n\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Utility functions\n// ============================================================================\n\n/**\n * Check if a field value is an encrypted envelope.\n *\n * This is a standalone utility function that can be used without constructing\n * an OperationEncryptor instance. Useful for routing logic that needs to\n * detect encrypted operations.\n *\n * @param field - An operation's `data` or `previousData` field\n * @returns true if the field is an encrypted envelope\n */\nexport function isEncryptedField(field: Record<string, unknown> | null): boolean {\n\treturn isEncryptedEnvelope(field)\n}\n\nfunction isEncryptedEnvelope(field: Record<string, unknown> | null): boolean {\n\tif (field === null || typeof field !== 'object') {\n\t\treturn false\n\t}\n\treturn (\n\t\tfield[ENCRYPTED_MARKER] === true &&\n\t\ttypeof field.ciphertext === 'string' &&\n\t\ttypeof field.iv === 'string' &&\n\t\tfield.algorithm === 'AES-256-GCM'\n\t)\n}\n"],"mappings":"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","names":["import_core","import_core","import_core","import_core","import_core","CryptoUnavailableError","assertCryptoAvailable","toBase64Url","fromBase64Url","MemoryStorage","tryGetLocalStorage","DEFAULT_STORAGE_KEY","import_core","import_core","assertCryptoAvailable","import_core","toBase64Url","fromBase64Url"]}
|