@korajs/auth 0.4.0 → 0.6.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/README.md +81 -28
- package/dist/chunk-7OXBRSJL.js +203 -0
- package/dist/chunk-7OXBRSJL.js.map +1 -0
- package/dist/create-org-session-RsDj9cl4.d.cts +722 -0
- package/dist/create-org-session-RsDj9cl4.d.ts +722 -0
- package/dist/index.cjs +1146 -586
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -118
- package/dist/index.d.ts +67 -118
- package/dist/index.js +654 -258
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +297 -183
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +25 -209
- package/dist/react.d.ts +25 -209
- package/dist/react.js +134 -209
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +1611 -730
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +415 -224
- package/dist/server.d.ts +415 -224
- package/dist/server.js +1565 -704
- package/dist/server.js.map +1 -1
- package/dist/svelte.cjs +512 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.d.cts +100 -0
- package/dist/svelte.d.ts +100 -0
- package/dist/svelte.js +278 -0
- package/dist/svelte.js.map +1 -0
- package/dist/vue.cjs +565 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +136 -0
- package/dist/vue.d.ts +136 -0
- package/dist/vue.js +338 -0
- package/dist/vue.js.map +1 -0
- package/package.json +40 -6
- package/src/svelte/AuthProvider.svelte +37 -0
- package/src/svelte/OrgProvider.svelte +22 -0
- package/dist/org-client-BVTLKcIk.d.cts +0 -355
- package/dist/org-client-BVTLKcIk.d.ts +0 -355
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/auth-client.ts","../src/client/org-client.ts","../src/device/device-store.ts","../src/tokens/token-store.ts","../src/tokens/encrypted-token-store.ts","../src/encryption/key-derivation.ts","../src/encryption/auto-lock.ts"],"sourcesContent":["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// --- 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// --- 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"],"mappings":"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","names":["KoraError","KoraError","KoraError","KoraError","toBase64Url","fromBase64Url","MemoryStorage","tryGetLocalStorage","DEFAULT_STORAGE_KEY","KoraError"]}
|
|
1
|
+
{"version":3,"sources":["../src/client/auth-client.ts","../src/client/device-session.ts","../src/device/device-store.ts","../src/client/storage.ts","../src/client/quickstart.ts","../src/client/auth-sync.ts","../src/client/org-client.ts","../src/tokens/token-store.ts","../src/tokens/encrypted-token-store.ts","../src/encryption/key-derivation.ts","../src/encryption/auto-lock.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\nimport type { AuthDeviceIdentityProvider } from './device-session'\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\nexport interface LinkedOAuthAccount {\n\tid: string\n\tuserId: string\n\tprovider: string\n\tproviderUserId: string\n\temail: string | null\n\tlinkedAt: number\n}\n\nexport interface OAuthAuthorizationResult {\n\turl: string\n\tstate: string\n}\n\nexport interface OAuthAuthorizationOptions {\n\t/**\n\t * Redirect the current browser window to the provider after creating the URL.\n\t * Defaults to true when `window.location.assign` is available.\n\t */\n\tredirect?: boolean\n\t/**\n\t * Optional app-specific return path stored in OAuth state metadata.\n\t */\n\treturnTo?: string\n\t/**\n\t * Optional extra metadata stored in OAuth state. Use this for app handoff data.\n\t */\n\tmetadata?: Record<string, unknown>\n\tdeviceId?: string\n\tdevicePublicKey?: string\n}\n\nexport interface OAuthCallbackParams {\n\tcode: string\n\tstate: string\n\tdeviceId?: string\n\tdevicePublicKey?: string\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\t/**\n\t * Optional token storage adapter.\n\t *\n\t * Use this for runtimes where localStorage is not the right place for\n\t * credentials, such as React Native/Expo SecureStore, iOS Keychain,\n\t * Android Keystore, or a Tauri secure storage plugin.\n\t */\n\tstorage?: AuthTokenStorage\n\n\t/**\n\t * Optional fetch implementation. Defaults to globalThis.fetch.\n\t * Useful for tests, SSR adapters, and mobile runtimes with a custom fetch.\n\t */\n\tfetch?: typeof fetch\n\n\t/**\n\t * Optional local device identity provider.\n\t *\n\t * When configured, sign-up and sign-in automatically include stable\n\t * `deviceId` and `devicePublicKey` fields unless the caller provides them.\n\t */\n\tdeviceIdentity?: AuthDeviceIdentityProvider\n}\n\ntype MaybePromise<T> = T | Promise<T>\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 | null }\n\ttokens: AuthTokensResponse\n}\n\ninterface OAuthSignInResponse extends AuthSignInResponse {\n\tidentity: LinkedOAuthAccount\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\nfunction getDefaultFetch(): typeof fetch {\n\tif (typeof globalThis.fetch !== 'function') {\n\t\treturn async () => {\n\t\t\tthrow new AuthError(\n\t\t\t\t'No fetch implementation is available in this runtime. Pass `fetch` to AuthClientConfig.',\n\t\t\t\t'AUTH_FETCH_UNAVAILABLE',\n\t\t\t)\n\t\t}\n\t}\n\treturn globalThis.fetch.bind(globalThis)\n}\n\nfunction normalizeAuthUser(user: { id: string; email: string; name?: string | null }): AuthUser {\n\treturn {\n\t\tid: user.id,\n\t\temail: user.email,\n\t\tname: user.name ?? null,\n\t}\n}\n\nfunction canRedirectCurrentWindow(): boolean {\n\treturn (\n\t\ttypeof globalThis.window !== 'undefined' &&\n\t\ttypeof globalThis.window.location?.assign === 'function'\n\t)\n}\n\nfunction redirectCurrentWindow(url: string): void {\n\tif (!canRedirectCurrentWindow()) {\n\t\tthrow new AuthError(\n\t\t\t'OAuth redirect is not available in this runtime. Pass redirect: false and open the returned URL with your platform browser API.',\n\t\t\t'AUTH_OAUTH_REDIRECT_UNAVAILABLE',\n\t\t)\n\t}\n\tglobalThis.window.location.assign(url)\n}\n\n// ---------------------------------------------------------------------------\n// Simple token storage backed by localStorage (browser) or in-memory fallback\n// ---------------------------------------------------------------------------\n\nexport interface AuthTokenStorage {\n\tgetAccessToken(): MaybePromise<string | null>\n\tgetRefreshToken(): MaybePromise<string | null>\n\tsetTokens(access: string, refresh: string): MaybePromise<void>\n\tclear(): MaybePromise<void>\n}\n\nfunction createTokenStorage(prefix: string): AuthTokenStorage {\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: AuthTokenStorage\n\tprivate readonly fetchFn: typeof fetch\n\tprivate readonly deviceIdentity: AuthDeviceIdentityProvider | undefined\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 = config.storage ?? createTokenStorage(prefix)\n\t\tthis.fetchFn = config.fetch ?? getDefaultFetch()\n\t\tthis.deviceIdentity = config.deviceIdentity\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 = await this.storage.getAccessToken()\n\t\tconst refreshToken = await 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\tawait this.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: {\n\t\temail: string\n\t\tpassword: string\n\t\tname?: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signup', {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t})\n\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tawait this.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: {\n\t\temail: string\n\t\tpassword: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signin', {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t})\n\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tawait this.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 * Create an OAuth authorization URL and optionally redirect the current window.\n\t *\n\t * For web apps, call this from a button click and keep the default redirect behavior.\n\t * For desktop/mobile, pass `redirect: false`, open the returned URL with the runtime's\n\t * browser API, then call `completeOAuthSignIn()` after receiving the callback.\n\t */\n\tasync signInWithOAuth(\n\t\tprovider: string,\n\t\toptions: OAuthAuthorizationOptions = {},\n\t): Promise<OAuthAuthorizationResult> {\n\t\tconst result = await this.createOAuthAuthorization(provider, options)\n\t\tif (options.redirect ?? canRedirectCurrentWindow()) {\n\t\t\tredirectCurrentWindow(result.url)\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Complete an OAuth sign-in callback and store the issued Kora tokens.\n\t */\n\tasync completeOAuthSignIn(provider: string, params: OAuthCallbackParams): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst response = await this.request<OAuthSignInResponse>(\n\t\t\t`/auth/oauth/${encodeURIComponent(provider)}/callback`,\n\t\t\t{\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { ...body },\n\t\t\t},\n\t\t)\n\n\t\tawait this.storage.setTokens(response.tokens.accessToken, response.tokens.refreshToken)\n\t\tconst user = normalizeAuthUser(response.user)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Create an OAuth authorization URL for linking another provider to the current user.\n\t */\n\tasync getOAuthAuthorizationUrl(\n\t\tprovider: string,\n\t\toptions: OAuthAuthorizationOptions = {},\n\t): Promise<OAuthAuthorizationResult> {\n\t\treturn this.createOAuthAuthorization(provider, { ...options, redirect: false })\n\t}\n\n\t/**\n\t * Link an OAuth provider to the current authenticated user.\n\t */\n\tasync linkOAuth(provider: string, params: OAuthCallbackParams): Promise<LinkedOAuthAccount> {\n\t\tconst token = await this.requireAccessToken()\n\t\treturn this.request<LinkedOAuthAccount>(`/auth/oauth/${encodeURIComponent(provider)}/link`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: {\n\t\t\t\tcode: params.code,\n\t\t\t\tstate: params.state,\n\t\t\t},\n\t\t\ttoken,\n\t\t})\n\t}\n\n\t/**\n\t * List OAuth accounts linked to the current authenticated user.\n\t */\n\tasync listLinkedAccounts(): Promise<LinkedOAuthAccount[]> {\n\t\tconst token = await this.requireAccessToken()\n\t\treturn this.request<LinkedOAuthAccount[]>('/auth/oauth/links', {\n\t\t\tmethod: 'GET',\n\t\t\ttoken,\n\t\t})\n\t}\n\n\t/**\n\t * Unlink an OAuth provider from the current authenticated user.\n\t */\n\tasync unlinkOAuth(provider: string): Promise<void> {\n\t\tconst token = await this.requireAccessToken()\n\t\tawait this.request<{ ok: true }>(`/auth/oauth/${encodeURIComponent(provider)}/link`, {\n\t\t\tmethod: 'DELETE',\n\t\t\ttoken,\n\t\t})\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 = await this.storage.getAccessToken()\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\n\t\t// Clear local state immediately (don't wait for server)\n\t\tawait this.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 = await 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 = await 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\tawait this.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 normalizeAuthUser(profile)\n\t}\n\n\tprivate async createOAuthAuthorization(\n\t\tprovider: string,\n\t\toptions: OAuthAuthorizationOptions,\n\t): Promise<OAuthAuthorizationResult> {\n\t\tconst params = new URLSearchParams()\n\t\tconst withIdentity = await this.withDeviceIdentity({\n\t\t\tdeviceId: options.deviceId,\n\t\t\tdevicePublicKey: options.devicePublicKey,\n\t\t})\n\n\t\tif (options.returnTo) {\n\t\t\tparams.set('returnTo', options.returnTo)\n\t\t}\n\t\tif (withIdentity.deviceId) {\n\t\t\tparams.set('deviceId', withIdentity.deviceId)\n\t\t}\n\t\tif (withIdentity.devicePublicKey) {\n\t\t\tparams.set('devicePublicKey', withIdentity.devicePublicKey)\n\t\t}\n\t\tif (options.metadata) {\n\t\t\tfor (const [key, value] of Object.entries(options.metadata)) {\n\t\t\t\tif (value !== undefined && value !== null) {\n\t\t\t\t\tparams.set(key, String(value))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst query = params.toString()\n\t\treturn this.request<OAuthAuthorizationResult>(\n\t\t\t`/auth/oauth/${encodeURIComponent(provider)}${query ? `?${query}` : ''}`,\n\t\t\t{\n\t\t\t\tmethod: 'GET',\n\t\t\t},\n\t\t)\n\t}\n\n\tprivate async withDeviceIdentity<T extends { deviceId?: string; devicePublicKey?: string }>(\n\t\tparams: T,\n\t): Promise<T> {\n\t\tif (!this.deviceIdentity || (params.deviceId && params.devicePublicKey)) {\n\t\t\treturn params\n\t\t}\n\n\t\tconst identity = await this.deviceIdentity.getDeviceIdentity()\n\t\treturn {\n\t\t\t...params,\n\t\t\tdeviceId: params.deviceId ?? identity.deviceId,\n\t\t\tdevicePublicKey: params.devicePublicKey ?? identity.devicePublicKey,\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\tprivate async requireAccessToken(): Promise<string> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new AuthError('You must be signed in to perform this action.', 'AUTH_REQUIRED')\n\t\t}\n\t\treturn token\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\tawait this.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\tawait this.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' | 'DELETE'\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 this.fetchFn(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'\nimport { exportPublicKeyJwk, generateDeviceKeyPair } from '../device/device-identity'\nimport { type DeviceKeyStore, createDeviceKeyStore } from '../device/device-store'\nimport type { AuthKeyValueStorage } from './storage'\n\nconst DEFAULT_DEVICE_ID_KEY = 'kora_auth_device_id'\n\nexport interface AuthDeviceIdentity {\n\t/** Stable local device ID used in token `dev` claims. */\n\tdeviceId: string\n\t/** Public proof-of-possession key serialized as a JSON Web Key string. */\n\tdevicePublicKey: string\n}\n\nexport interface AuthDeviceIdentityProvider {\n\tgetDeviceIdentity(): Promise<AuthDeviceIdentity>\n}\n\nexport interface PersistentDeviceIdentityOptions {\n\t/** Store for the stable device ID. Use a platform credential store in production. */\n\tstorage: AuthKeyValueStorage\n\t/** Store for the non-extractable device key pair. Defaults to IndexedDB when available. */\n\tkeyStore?: DeviceKeyStore\n\t/** Storage key for the device ID. Defaults to `kora_auth_device_id`. */\n\tdeviceIdKey?: string\n\t/** Optional device ID generator for tests or custom device registries. */\n\tgenerateDeviceId?: () => string\n}\n\nexport class AuthDeviceIdentityError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'AUTH_DEVICE_IDENTITY_ERROR', context)\n\t\tthis.name = 'AuthDeviceIdentityError'\n\t}\n}\n\n/**\n * Creates a persistent device identity provider for `AuthClient`.\n *\n * The provider keeps a stable device ID in the supplied key-value store and a\n * non-extractable ECDSA P-256 key pair in the supplied `DeviceKeyStore`. The\n * public key is returned during sign-up/sign-in so the server can bind tokens\n * to a real offline device instead of a transient browser session.\n */\nexport function createPersistentDeviceIdentity(\n\toptions: PersistentDeviceIdentityOptions,\n): AuthDeviceIdentityProvider {\n\tconst storage = options.storage\n\tconst keyStore = options.keyStore ?? createDefaultPersistentKeyStore()\n\tconst deviceIdKey = options.deviceIdKey ?? DEFAULT_DEVICE_ID_KEY\n\tconst generateDeviceId = options.generateDeviceId ?? defaultDeviceId\n\n\treturn {\n\t\tasync getDeviceIdentity(): Promise<AuthDeviceIdentity> {\n\t\t\tlet deviceId = await storage.getItem(deviceIdKey)\n\t\t\tif (!deviceId) {\n\t\t\t\tdeviceId = generateDeviceId()\n\t\t\t\tawait storage.setItem(deviceIdKey, deviceId)\n\t\t\t}\n\n\t\t\tlet keyPair = await keyStore.loadKeyPair(deviceId)\n\t\t\tif (!keyPair) {\n\t\t\t\tkeyPair = await generateDeviceKeyPair()\n\t\t\t\tawait keyStore.saveKeyPair(deviceId, keyPair)\n\t\t\t}\n\n\t\t\tconst publicKey = await exportPublicKeyJwk(keyPair)\n\t\t\treturn {\n\t\t\t\tdeviceId,\n\t\t\t\tdevicePublicKey: JSON.stringify(publicKey),\n\t\t\t}\n\t\t},\n\t}\n}\n\nfunction createDefaultPersistentKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn createDeviceKeyStore()\n\t}\n\n\tthrow new AuthDeviceIdentityError(\n\t\t'No persistent device key store is available in this runtime. Pass `keyStore` to createPersistentDeviceIdentity().',\n\t)\n}\n\nfunction defaultDeviceId(): string {\n\tif (typeof globalThis.crypto?.randomUUID === 'function') {\n\t\treturn globalThis.crypto.randomUUID()\n\t}\n\n\tconst bytes = new Uint8Array(16)\n\tif (typeof globalThis.crypto?.getRandomValues === 'function') {\n\t\tglobalThis.crypto.getRandomValues(bytes)\n\t} else {\n\t\tfor (let i = 0; i < bytes.length; i++) {\n\t\t\tbytes[i] = Math.floor(Math.random() * 256)\n\t\t}\n\t}\n\n\tbytes[6] = ((bytes[6] as number) & 0x0f) | 0x40\n\tbytes[8] = ((bytes[8] as number) & 0x3f) | 0x80\n\tconst hex = [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\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 { AuthTokenStorage } from './auth-client'\n\ntype MaybePromise<T> = T | Promise<T>\n\n/**\n * Minimal key-value credential storage interface used by Kora auth adapters.\n *\n * This intentionally matches the shape of secure stores across runtimes:\n * browser Storage, Tauri secure storage plugins, Expo SecureStore, iOS Keychain,\n * Android Keystore wrappers, and encrypted desktop stores.\n */\nexport interface AuthKeyValueStorage {\n\tgetItem(key: string): MaybePromise<string | null>\n\tsetItem(key: string, value: string): MaybePromise<void>\n\tremoveItem(key: string): MaybePromise<void>\n}\n\nexport interface AuthTokenStorageOptions {\n\t/** Backing credential store. */\n\tstore: AuthKeyValueStorage\n\t/** Storage key prefix. Defaults to `kora_auth`. */\n\tprefix?: string\n}\n\n/**\n * Creates an `AuthTokenStorage` adapter from a runtime key-value store.\n *\n * Use this with platform credential stores instead of wiring `AuthClient`\n * directly to browser localStorage in desktop and mobile apps.\n */\nexport function createAuthTokenStorage(options: AuthTokenStorageOptions): AuthTokenStorage {\n\tconst prefix = options.prefix ?? 'kora_auth'\n\tconst accessKey = `${prefix}_access_token`\n\tconst refreshKey = `${prefix}_refresh_token`\n\tconst store = options.store\n\n\treturn {\n\t\tgetAccessToken: () => store.getItem(accessKey),\n\t\tgetRefreshToken: () => store.getItem(refreshKey),\n\t\tasync setTokens(accessToken: string, refreshToken: string): Promise<void> {\n\t\t\tawait store.setItem(accessKey, accessToken)\n\t\t\tawait store.setItem(refreshKey, refreshToken)\n\t\t},\n\t\tasync clear(): Promise<void> {\n\t\t\tawait store.removeItem(accessKey)\n\t\t\tawait store.removeItem(refreshKey)\n\t\t},\n\t}\n}\n\n/**\n * Creates an in-memory token storage adapter.\n *\n * Useful for tests, demos, and SSR. Production desktop and mobile apps should\n * prefer a secure platform-backed store.\n */\nexport function createMemoryAuthTokenStorage(): AuthTokenStorage {\n\tlet accessToken: string | null = null\n\tlet refreshToken: string | null = null\n\n\treturn {\n\t\tgetAccessToken: () => accessToken,\n\t\tgetRefreshToken: () => refreshToken,\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 * Adapts Web Storage-compatible APIs such as `localStorage` or `sessionStorage`.\n */\nexport function createWebStorageAuthTokenStorage(\n\tstorage: Storage,\n\tprefix?: string,\n): AuthTokenStorage {\n\treturn createAuthTokenStorage({\n\t\tprefix,\n\t\tstore: {\n\t\t\tgetItem: (key) => storage.getItem(key),\n\t\t\tsetItem: (key, value) => {\n\t\t\t\tstorage.setItem(key, value)\n\t\t\t},\n\t\t\tremoveItem: (key) => {\n\t\t\t\tstorage.removeItem(key)\n\t\t\t},\n\t\t},\n\t})\n}\n","import type { DeviceKeyStore } from '../device/device-store'\nimport { AuthClient, type AuthClientConfig, type AuthTokenStorage } from './auth-client'\nimport type { AuthDeviceIdentityProvider } from './device-session'\nimport { createPersistentDeviceIdentity } from './device-session'\nimport {\n\ttype AuthKeyValueStorage,\n\tcreateAuthTokenStorage,\n\tcreateWebStorageAuthTokenStorage,\n} from './storage'\n\nexport interface CreateKoraAuthOptions\n\textends Omit<AuthClientConfig, 'storage' | 'deviceIdentity'> {\n\t/**\n\t * Complete token storage adapter. Use this for fully custom runtimes.\n\t * If omitted, `credentialStore` is adapted automatically.\n\t */\n\tstorage?: AuthTokenStorage\n\t/**\n\t * Runtime credential store used for tokens and stable device ID.\n\t * Examples: Tauri secure storage, Expo SecureStore, iOS Keychain, Android Keystore.\n\t */\n\tcredentialStore?: AuthKeyValueStorage\n\t/**\n\t * Explicit device identity provider. Set to `false` to disable automatic\n\t * device binding during sign-up/sign-in.\n\t */\n\tdeviceIdentity?: AuthDeviceIdentityProvider | false\n\t/**\n\t * Device key store for runtimes without IndexedDB, such as React Native.\n\t */\n\tdeviceKeyStore?: DeviceKeyStore\n}\n\n/**\n * Create a production-shaped Kora auth client with minimal setup.\n *\n * Defaults:\n * - browser/Tauri WebView: localStorage for tokens, IndexedDB for device keys\n * - desktop/mobile: pass `credentialStore` and optionally `deviceKeyStore`\n * - automatic device identity is enabled when a persistent device ID store exists\n */\nexport function createKoraAuth(options: CreateKoraAuthOptions): AuthClient {\n\tconst storage =\n\t\toptions.storage ??\n\t\t(options.credentialStore\n\t\t\t? createAuthTokenStorage({\n\t\t\t\t\tstore: options.credentialStore,\n\t\t\t\t\tprefix: options.storageKey,\n\t\t\t\t})\n\t\t\t: tryCreateDefaultTokenStorage(options.storageKey))\n\n\tconst deviceIdentity =\n\t\toptions.deviceIdentity === false\n\t\t\t? undefined\n\t\t\t: (options.deviceIdentity ??\n\t\t\t\ttryCreateDefaultDeviceIdentity(options.credentialStore, options.deviceKeyStore))\n\n\treturn new AuthClient({\n\t\tserverUrl: options.serverUrl,\n\t\tstorageKey: options.storageKey,\n\t\tstorage,\n\t\tfetch: options.fetch,\n\t\tdeviceIdentity,\n\t})\n}\n\nfunction tryCreateDefaultTokenStorage(\n\tstorageKey: string | undefined,\n): AuthTokenStorage | undefined {\n\tconst storage = tryGetBrowserStorage()\n\treturn storage ? createWebStorageAuthTokenStorage(storage, storageKey) : undefined\n}\n\nfunction tryCreateDefaultDeviceIdentity(\n\tcredentialStore: AuthKeyValueStorage | undefined,\n\tdeviceKeyStore: DeviceKeyStore | undefined,\n): AuthDeviceIdentityProvider | undefined {\n\tconst storage = credentialStore ?? tryGetBrowserStorage()\n\tif (!storage) {\n\t\treturn undefined\n\t}\n\n\ttry {\n\t\treturn createPersistentDeviceIdentity({\n\t\t\tstorage,\n\t\t\tkeyStore: deviceKeyStore,\n\t\t})\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\nfunction tryGetBrowserStorage(): Storage | null {\n\ttry {\n\t\tif (typeof globalThis.localStorage === 'undefined') {\n\t\t\treturn null\n\t\t}\n\t\tconst key = '__kora_auth_quickstart_test__'\n\t\tglobalThis.localStorage.setItem(key, '1')\n\t\tglobalThis.localStorage.removeItem(key)\n\t\treturn globalThis.localStorage\n\t} catch {\n\t\treturn null\n\t}\n}\n","import {\n\ttype SchemaDefinition,\n\ttype ScopeMap,\n\tbuildScopeMap,\n\textractScopeValuesFromClaims,\n} from '@korajs/core'\nimport type { AuthSyncBinding } from '@korajs/core/bindings'\nimport type { AuthState } from './auth-client'\n\n/**\n * Minimal auth client surface required for sync integration.\n * Matches {@link AuthClient} without importing implementation details.\n */\nexport interface AuthSyncClient {\n\tgetAccessToken(): Promise<string | null>\n\tonAuthChange?(callback: (state: AuthState) => void): () => void\n}\n\n/**\n * Sync binding returned by {@link createKoraAuthSync}.\n * Passed to `createApp({ sync: { authClient } })` in korajs.\n *\n * @deprecated Use {@link AuthSyncBinding} from `@korajs/core/bindings` or `@korajs/auth`.\n */\nexport type KoraAuthSyncBinding = AuthSyncBinding\n\n/**\n * Configuration for {@link createKoraAuthSync}.\n */\nexport interface CreateKoraAuthSyncOptions {\n\t/** Kora auth client from `createKoraAuth()`. */\n\tauthClient: AuthSyncClient\n\t/**\n\t * Application schema. When provided, scope maps are built automatically\n\t * from JWT claims and schema scope declarations.\n\t */\n\tschema?: SchemaDefinition\n\t/**\n\t * Custom claim → flat scope value mapping.\n\t * Defaults to {@link extractScopeValuesFromClaims}.\n\t */\n\tscopeFromClaims?: (claims: Record<string, unknown>) => Record<string, unknown>\n}\n\n/**\n * Decode JWT payload without signature verification (client-side scope hints only).\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\tconst payloadSegment = parts[1]\n\tif (payloadSegment === undefined) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\tconst base64 = payloadSegment.replace(/-/g, '+').replace(/_/g, '/')\n\t\tconst padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')\n\t\tconst json =\n\t\t\ttypeof atob === 'function' ? atob(padded) : Buffer.from(padded, 'base64').toString('utf-8')\n\t\tconst parsed: unknown = JSON.parse(json)\n\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn null\n\t\t}\n\t\treturn parsed as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction readDeviceIdFromClaims(claims: Record<string, unknown>): string | undefined {\n\tconst dev = claims.dev\n\treturn typeof dev === 'string' && dev.length > 0 ? dev : undefined\n}\n\n/**\n * Creates a sync auth binding for `createApp({ sync: { authClient: binding } })`.\n *\n * Wires token refresh, automatic scope maps from JWT claims, and device-bound\n * sync node ids (`dev` claim) separate from the user id (`sub`).\n *\n * @example\n * ```typescript\n * import { createKoraAuth, createKoraAuthSync } from '@korajs/auth'\n * import { createApp } from 'korajs'\n *\n * const authClient = createKoraAuth({ serverUrl: 'https://api.example.com' })\n *\n * const app = createApp({\n * schema,\n * sync: {\n * url: 'wss://api.example.com/kora-sync',\n * authClient: createKoraAuthSync({ authClient, schema }),\n * },\n * })\n * ```\n */\nexport function createKoraAuthSync(options: CreateKoraAuthSyncOptions): AuthSyncBinding {\n\tconst { authClient, schema, scopeFromClaims } = options\n\n\tconst binding: AuthSyncBinding = {\n\t\tauth: async () => {\n\t\t\tconst token = await authClient.getAccessToken()\n\t\t\treturn { token: token ?? '' }\n\t\t},\n\t}\n\n\tif (schema) {\n\t\tbinding.resolveScopeMap = async () => {\n\t\t\tconst token = await authClient.getAccessToken()\n\t\t\tif (!token) {\n\t\t\t\treturn undefined\n\t\t\t}\n\n\t\t\tconst claims = decodeJwtPayload(token)\n\t\t\tif (!claims) {\n\t\t\t\treturn undefined\n\t\t\t}\n\n\t\t\tconst scopeValues = scopeFromClaims\n\t\t\t\t? scopeFromClaims(claims)\n\t\t\t\t: extractScopeValuesFromClaims(schema, claims)\n\n\t\t\treturn buildScopeMap(schema, scopeValues)\n\t\t}\n\t}\n\n\tbinding.resolveNodeId = async () => {\n\t\tconst token = await authClient.getAccessToken()\n\t\tif (!token) {\n\t\t\treturn undefined\n\t\t}\n\n\t\tconst claims = decodeJwtPayload(token)\n\t\tif (!claims) {\n\t\t\treturn undefined\n\t\t}\n\n\t\treturn readDeviceIdFromClaims(claims)\n\t}\n\n\tif (authClient.onAuthChange) {\n\t\tbinding.subscribe = (listener) => authClient.onAuthChange?.(() => listener()) ?? (() => {})\n\t}\n\n\treturn binding\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 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// --- 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"],"mappings":"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","names":["KoraError","KoraError","KoraError","decodeJwtPayload","KoraError","KoraError","KoraError","toBase64Url","fromBase64Url","MemoryStorage","tryGetLocalStorage","DEFAULT_STORAGE_KEY","KoraError"]}
|