@korajs/auth 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +8 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +403 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +208 -11
- package/dist/server.d.ts +208 -11
- package/dist/server.js +381 -0
- package/dist/server.js.map +1 -1
- package/package.json +15 -6
package/dist/index.js
CHANGED
|
@@ -196,8 +196,9 @@ var AuthClient = class {
|
|
|
196
196
|
method: "POST",
|
|
197
197
|
body: params
|
|
198
198
|
});
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
const tokens = "tokens" in response ? response.tokens : response;
|
|
200
|
+
this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
201
|
+
const user = await this.fetchUserProfile(tokens.accessToken);
|
|
201
202
|
this.setState("authenticated", user);
|
|
202
203
|
return user;
|
|
203
204
|
}
|
|
@@ -213,8 +214,9 @@ var AuthClient = class {
|
|
|
213
214
|
method: "POST",
|
|
214
215
|
body: params
|
|
215
216
|
});
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
const tokens = "tokens" in response ? response.tokens : response;
|
|
218
|
+
this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
219
|
+
const user = await this.fetchUserProfile(tokens.accessToken);
|
|
218
220
|
this.setState("authenticated", user);
|
|
219
221
|
return user;
|
|
220
222
|
}
|
|
@@ -442,7 +444,8 @@ var AuthClient = class {
|
|
|
442
444
|
{ path, status: response.status, serverError }
|
|
443
445
|
);
|
|
444
446
|
}
|
|
445
|
-
const
|
|
447
|
+
const json = await response.json();
|
|
448
|
+
const data = json["data"] !== void 0 ? json["data"] : json;
|
|
446
449
|
return data;
|
|
447
450
|
}
|
|
448
451
|
};
|
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 * 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<AuthTokensResponse>('/auth/signup', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\n\t\tthis.storage.setTokens(response.accessToken, response.refreshToken)\n\n\t\tconst user = await this.fetchUserProfile(response.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<AuthTokensResponse>('/auth/signin', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\n\t\tthis.storage.setTokens(response.accessToken, response.refreshToken)\n\n\t\tconst user = await this.fetchUserProfile(response.accessToken)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Sign out the current user.\n\t *\n\t * Clears local tokens and attempts to revoke the refresh token on the server\n\t * (best-effort — succeeds even if the server is unreachable). This ensures that\n\t * stolen refresh tokens cannot be used after the user explicitly signs out.\n\t */\n\tasync signOut(): Promise<void> {\n\t\tconst accessToken = this.storage.getAccessToken()\n\t\tconst refreshToken = this.storage.getRefreshToken()\n\n\t\t// Clear local state immediately (don't wait for server)\n\t\tthis.storage.clear()\n\t\tthis._refreshPromise = null\n\t\tthis.setState('unauthenticated', null)\n\n\t\t// Best-effort server-side revocation\n\t\tif (accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.request('/auth/signout', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: { refreshToken: refreshToken ?? undefined },\n\t\t\t\t\ttoken: accessToken,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\t// Server may be unreachable (offline) — local sign-out still succeeds\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Token access\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Get a valid access token, automatically refreshing if expired.\n\t *\n\t * @returns A valid access token string, or null if the user is not\n\t * authenticated and refresh is not possible\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst accessToken = this.storage.getAccessToken()\n\n\t\tif (accessToken && !isTokenExpired(accessToken)) {\n\t\t\treturn accessToken\n\t\t}\n\n\t\t// Attempt refresh\n\t\tconst refreshToken = this.storage.getRefreshToken()\n\t\tif (!refreshToken) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst newAccessToken = await this.refreshAccessToken(refreshToken)\n\t\t\treturn newAccessToken\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Get a valid token for the sync engine handshake.\n\t * Alias for {@link getAccessToken}.\n\t *\n\t * @returns A valid access token string, or null if unavailable\n\t */\n\tasync getSyncToken(): Promise<string | null> {\n\t\treturn this.getAccessToken()\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// State change subscriptions\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Subscribe to authentication state changes.\n\t *\n\t * The callback is invoked whenever the auth state transitions (e.g., from\n\t * 'unauthenticated' to 'authenticated' on sign-in).\n\t *\n\t * @param callback - Function called with the new AuthState on each change\n\t * @returns An unsubscribe function that removes the listener\n\t *\n\t * @example\n\t * ```typescript\n\t * const unsub = auth.onAuthChange((state) => {\n\t * console.log('Auth state changed to:', state)\n\t * })\n\t * // Later: unsub()\n\t * ```\n\t */\n\tonAuthChange(callback: (state: AuthState) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Update internal state and notify all listeners.\n\t */\n\tprivate setState(state: AuthState, user: AuthUser | null): void {\n\t\tconst changed = this._state !== state || this._user !== user\n\t\tthis._state = state\n\t\tthis._user = user\n\n\t\tif (changed) {\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\ttry {\n\t\t\t\t\tlistener(state)\n\t\t\t\t} catch {\n\t\t\t\t\t// Listeners should not throw, but if they do, do not let it\n\t\t\t\t\t// break the notification loop for other listeners.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Restore a session from a valid access token by fetching the user profile.\n\t * Falls back to extracting the user ID from the token payload if the\n\t * /auth/me request fails (offline scenario).\n\t */\n\tprivate async restoreSession(accessToken: string): Promise<void> {\n\t\ttry {\n\t\t\tconst user = await this.fetchUserProfile(accessToken)\n\t\t\tthis.setState('authenticated', user)\n\t\t} catch {\n\t\t\t// Network may be unavailable -- extract minimal user info from the token\n\t\t\tconst userId = getUserIdFromToken(accessToken)\n\t\t\tif (userId) {\n\t\t\t\tthis.setState('authenticated', {\n\t\t\t\t\tid: userId,\n\t\t\t\t\temail: '',\n\t\t\t\t\tname: null,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tthis.storage.clear()\n\t\t\t\tthis.setState('unauthenticated', null)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Fetch the current user profile from the server.\n\t */\n\tprivate async fetchUserProfile(accessToken: string): Promise<AuthUser> {\n\t\tconst profile = await this.request<UserProfileResponse>('/auth/me', {\n\t\t\tmethod: 'GET',\n\t\t\ttoken: accessToken,\n\t\t})\n\t\treturn {\n\t\t\tid: profile.id,\n\t\t\temail: profile.email,\n\t\t\tname: profile.name ?? null,\n\t\t}\n\t}\n\n\t/**\n\t * Refresh the access token using a refresh token.\n\t * De-duplicates concurrent refresh calls so only one network request is made.\n\t */\n\tprivate async refreshAccessToken(refreshToken: string): Promise<string | null> {\n\t\t// De-duplicate: if a refresh is already in progress, return the same promise\n\t\tif (this._refreshPromise) {\n\t\t\treturn this._refreshPromise\n\t\t}\n\n\t\tthis._refreshPromise = this.performRefresh(refreshToken)\n\n\t\ttry {\n\t\t\tconst result = await this._refreshPromise\n\t\t\treturn result\n\t\t} finally {\n\t\t\tthis._refreshPromise = null\n\t\t}\n\t}\n\n\t/**\n\t * Execute the token refresh network request.\n\t */\n\tprivate async performRefresh(refreshToken: string): Promise<string | null> {\n\t\ttry {\n\t\t\tconst response = await this.request<AuthTokensResponse>('/auth/refresh', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { refreshToken },\n\t\t\t})\n\n\t\t\tthis.storage.setTokens(response.accessToken, response.refreshToken)\n\t\t\treturn response.accessToken\n\t\t} catch {\n\t\t\t// Refresh failed -- clear tokens to avoid infinite retry loops\n\t\t\tthis.storage.clear()\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Make an HTTP request to the auth server.\n\t *\n\t * @param path - URL path relative to serverUrl (e.g. '/auth/signin')\n\t * @param options - Request options\n\t * @returns Parsed JSON response body\n\t * @throws {AuthError} On network failure or non-2xx response\n\t */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST'\n\t\t\tbody?: Record<string, unknown>\n\t\t\ttoken?: string\n\t\t},\n\t): Promise<T> {\n\t\tconst url = `${this.serverUrl}${path}`\n\n\t\tconst headers: Record<string, string> = {}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\t\tif (options.token) {\n\t\t\theaders['Authorization'] = `Bearer ${options.token}`\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new AuthError(\n\t\t\t\t`Network request to ${path} failed. The auth server at ${this.serverUrl} may be unreachable. ` +\n\t\t\t\t\t'Check your network connection and serverUrl configuration.',\n\t\t\t\t'AUTH_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Auth server returned HTTP ${response.status}`\n\t\t\tlet serverError: string | undefined\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body['error'] === 'string') {\n\t\t\t\t\terrorMessage = body['error'] as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t} else if (typeof body['message'] === 'string') {\n\t\t\t\t\terrorMessage = body['message'] as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Response body is not JSON -- use the status text\n\t\t\t}\n\n\t\t\tthrow new AuthError(\n\t\t\t\terrorMessage,\n\t\t\t\t'AUTH_SERVER_ERROR',\n\t\t\t\t{ path, status: response.status, serverError },\n\t\t\t)\n\t\t}\n\n\t\tconst data = (await response.json()) as T\n\t\treturn data\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Organization info returned by the server.\n */\nexport interface ClientOrganization {\n\tid: string\n\tname: string\n\tslug: string\n\townerId: string\n\tcreatedAt: number\n\tupdatedAt: number\n\tmetadata: Record<string, unknown>\n}\n\n/**\n * Membership info returned by the server.\n */\nexport interface ClientMembership {\n\tid: string\n\torgId: string\n\tuserId: string\n\trole: string\n\tinvitedBy: string | null\n\tjoinedAt: number\n}\n\n/**\n * Invitation info returned by the server.\n */\nexport interface ClientInvitation {\n\tid: string\n\torgId: string\n\temail: string\n\trole: string\n\tinvitedBy: string\n\ttoken: string\n\tcreatedAt: number\n\texpiresAt: number\n\tstatus: string\n}\n\n/**\n * Configuration for the OrgClient.\n */\nexport interface OrgClientConfig {\n\t/** Base URL of the auth/org server */\n\tserverUrl: string\n\t/** Function that returns a valid access token for authenticated requests */\n\tgetAccessToken: () => Promise<string | null>\n}\n\n/**\n * Thrown when an org operation fails.\n */\nexport class OrgClientError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OrgClientError'\n\t}\n}\n\n// ============================================================================\n// OrgClient\n// ============================================================================\n\n/**\n * Client-side organization manager.\n *\n * Handles org CRUD, member management, invitations, and active org switching.\n * Framework-agnostic — works in any JavaScript environment with `fetch`.\n *\n * @example\n * ```typescript\n * const orgClient = new OrgClient({\n * serverUrl: 'http://localhost:3001',\n * getAccessToken: () => authClient.getAccessToken(),\n * })\n *\n * const org = await orgClient.createOrg({ name: 'Acme Inc', slug: 'acme' })\n * orgClient.switchOrg(org.id)\n * ```\n */\nexport class OrgClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly getAccessToken: () => Promise<string | null>\n\tprivate readonly listeners = new Set<(orgId: string | null) => void>()\n\n\tprivate _activeOrgId: string | null = null\n\tprivate _activeOrg: ClientOrganization | null = null\n\tprivate _activeRole: string | null = null\n\n\tconstructor(config: OrgClientConfig) {\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tthis.getAccessToken = config.getAccessToken\n\t}\n\n\t// --- Getters ---\n\n\t/** Currently active organization ID */\n\tget activeOrgId(): string | null {\n\t\treturn this._activeOrgId\n\t}\n\n\t/** Currently active organization */\n\tget activeOrg(): ClientOrganization | null {\n\t\treturn this._activeOrg\n\t}\n\n\t/** Current user's role in the active organization */\n\tget activeRole(): string | null {\n\t\treturn this._activeRole\n\t}\n\n\t// --- Organization Operations ---\n\n\t/**\n\t * Create a new organization.\n\t */\n\tasync createOrg(params: {\n\t\tname: string\n\t\tslug?: string\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>('/orgs', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * List all organizations the current user belongs to.\n\t */\n\tasync listOrgs(): Promise<ClientOrganization[]> {\n\t\treturn this.request<ClientOrganization[]>('/orgs', { method: 'GET' })\n\t}\n\n\t/**\n\t * Get an organization by ID.\n\t */\n\tasync getOrg(orgId: string): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Update an organization.\n\t */\n\tasync updateOrg(\n\t\torgId: string,\n\t\tparams: { name?: string; slug?: string; metadata?: Record<string, unknown> },\n\t): Promise<ClientOrganization> {\n\t\tconst result = await this.request<ClientOrganization>(`/orgs/${orgId}`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: params,\n\t\t})\n\t\t// Update cached active org if this is the active one\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrg = result\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Delete an organization.\n\t */\n\tasync deleteOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}`, { method: 'DELETE' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Org Switching ---\n\n\t/**\n\t * Switch the active organization context.\n\t * Fetches the org details and the user's membership/role.\n\t */\n\tasync switchOrg(orgId: string): Promise<void> {\n\t\tconst org = await this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t\tconst membership = await this.request<ClientMembership>(`/orgs/${orgId}/membership`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\n\t\tthis._activeOrgId = orgId\n\t\tthis._activeOrg = org\n\t\tthis._activeRole = membership.role\n\t\tthis.notifyListeners()\n\t}\n\n\t/**\n\t * Clear the active organization (no org selected).\n\t */\n\tclearActiveOrg(): void {\n\t\tthis._activeOrgId = null\n\t\tthis._activeOrg = null\n\t\tthis._activeRole = null\n\t\tthis.notifyListeners()\n\t}\n\n\t// --- Member Management ---\n\n\t/**\n\t * List members of an organization.\n\t */\n\tasync listMembers(orgId: string): Promise<ClientMembership[]> {\n\t\treturn this.request<ClientMembership[]>(`/orgs/${orgId}/members`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Remove a member from an organization.\n\t */\n\tasync removeMember(orgId: string, userId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/members/${userId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * Update a member's role.\n\t */\n\tasync updateMemberRole(\n\t\torgId: string,\n\t\tuserId: string,\n\t\trole: string,\n\t): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>(`/orgs/${orgId}/members/${userId}/role`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: { role },\n\t\t})\n\t}\n\n\t/**\n\t * Transfer ownership to another member.\n\t */\n\tasync transferOwnership(orgId: string, newOwnerId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/transfer`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { newOwnerId },\n\t\t})\n\t}\n\n\t/**\n\t * Leave an organization (remove yourself).\n\t */\n\tasync leaveOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/leave`, { method: 'POST' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Invitations ---\n\n\t/**\n\t * Invite a user to an organization by email.\n\t */\n\tasync inviteMember(\n\t\torgId: string,\n\t\tparams: { email: string; role: string },\n\t): Promise<ClientInvitation> {\n\t\treturn this.request<ClientInvitation>(`/orgs/${orgId}/invitations`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * Accept an invitation by token.\n\t */\n\tasync acceptInvitation(token: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>('/invitations/accept', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { token },\n\t\t})\n\t}\n\n\t/**\n\t * List pending invitations for an organization.\n\t */\n\tasync listInvitations(orgId: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(`/orgs/${orgId}/invitations`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Revoke a pending invitation.\n\t */\n\tasync revokeInvitation(orgId: string, invitationId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * List pending invitations for the current user's email.\n\t */\n\tasync listMyInvitations(email: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(\n\t\t\t`/invitations?email=${encodeURIComponent(email)}`,\n\t\t\t{ method: 'GET' },\n\t\t)\n\t}\n\n\t// --- Subscriptions ---\n\n\t/**\n\t * Subscribe to active org changes.\n\t * @returns Unsubscribe function\n\t */\n\tonOrgChange(callback: (orgId: string | null) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// --- Internal ---\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(this._activeOrgId)\n\t\t\t} catch {\n\t\t\t\t// Don't let listener errors break the notification loop\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async request<T = void>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t},\n\t): Promise<T> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t'Not authenticated. Sign in before performing organization operations.',\n\t\t\t\t'ORG_NOT_AUTHENTICATED',\n\t\t\t)\n\t\t}\n\n\t\tconst url = `${this.serverUrl}${path}`\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t`Network request to ${path} failed.`,\n\t\t\t\t'ORG_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Server returned HTTP ${response.status}`\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body['error'] === 'string') {\n\t\t\t\t\terrorMessage = body['error'] as string\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// not JSON\n\t\t\t}\n\t\t\tthrow new OrgClientError(errorMessage, 'ORG_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t})\n\t\t}\n\n\t\t// Handle empty responses (DELETE, etc.)\n\t\tconst text = await response.text()\n\t\tif (text.length === 0) return undefined as T\n\n\t\ttry {\n\t\t\tconst body = JSON.parse(text)\n\t\t\t// Unwrap { data: ... } envelope if present\n\t\t\tif (body && typeof body === 'object' && 'data' in body) {\n\t\t\t\treturn body.data as T\n\t\t\t}\n\t\t\treturn body as T\n\t\t} catch {\n\t\t\treturn undefined as T\n\t\t}\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Device key store errors ---\n\n/**\n * Thrown when a device key store operation fails.\n * Provides context about the operation and the underlying cause.\n */\nexport class DeviceKeyStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_KEY_STORE_ERROR', context)\n\t\tthis.name = 'DeviceKeyStoreError'\n\t}\n}\n\n// --- Interface ---\n\n/**\n * Persistent storage interface for ECDSA P-256 device key pairs.\n *\n * Device key pairs are used for proof-of-possession authentication:\n * the private key never leaves the device, and the public key is\n * registered with the server. This store persists key pairs across\n * page reloads and app restarts.\n *\n * In the browser, CryptoKey objects are structured-cloneable, so they\n * can be stored directly in IndexedDB without serialization. In Node.js\n * or test environments, an in-memory implementation is used instead.\n */\nexport interface DeviceKeyStore {\n\t/**\n\t * Persist a key pair for the given device.\n\t *\n\t * Overwrites any previously stored key pair for the same device ID.\n\t *\n\t * @param deviceId - The unique device identifier (typically a UUID v7)\n\t * @param keyPair - The ECDSA P-256 CryptoKeyPair to store\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tsaveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>\n\n\t/**\n\t * Load a previously stored key pair for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns The stored CryptoKeyPair, or null if no key pair exists for the device\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tloadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>\n\n\t/**\n\t * Delete a stored key pair for the given device.\n\t *\n\t * No-op if no key pair exists for the device ID.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tdeleteKeyPair(deviceId: string): Promise<void>\n\n\t/**\n\t * Check whether a key pair exists for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns True if a key pair is stored for the device, false otherwise\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\thasKeyPair(deviceId: string): Promise<boolean>\n}\n\n// --- IndexedDB constants ---\n\n/** IndexedDB database name for device key storage. */\nconst IDB_DATABASE_NAME = 'kora_device_keys'\n\n/** IndexedDB object store name within the database. */\nconst IDB_STORE_NAME = 'keypairs'\n\n/** Current database schema version. */\nconst IDB_VERSION = 1\n\n// --- IndexedDB implementation ---\n\n/**\n * Browser-based device key store backed by IndexedDB.\n *\n * CryptoKey objects are structured-cloneable, so they can be stored\n * directly in IndexedDB without needing to export/import them as JWK.\n * This preserves the non-extractable flag on private keys, ensuring\n * they cannot be read even from storage.\n *\n * The database uses a single object store (`keypairs`) with the device ID\n * as the key and the full CryptoKeyPair as the value.\n *\n * @example\n * ```typescript\n * const store = new IndexedDBDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * // loaded.privateKey is still non-extractable\n * ```\n */\nexport class IndexedDBDeviceKeyStore implements DeviceKeyStore {\n\tprivate dbPromise: Promise<IDBDatabase> | null = null\n\n\t/**\n\t * Opens (or creates) the IndexedDB database.\n\t *\n\t * The database connection is lazily initialized on first use and\n\t * reused for subsequent operations. If the database does not exist,\n\t * it is created with the `keypairs` object store.\n\t */\n\tprivate openDatabase(): Promise<IDBDatabase> {\n\t\t// Reuse the database connection if already opened.\n\t\t// This avoids repeatedly opening the database on every operation.\n\t\tif (this.dbPromise !== null) {\n\t\t\treturn this.dbPromise\n\t\t}\n\n\t\tthis.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n\t\t\tlet request: IDBOpenDBRequest\n\n\t\t\ttry {\n\t\t\t\trequest = globalThis.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\t\t} catch (cause) {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage. ' +\n\t\t\t\t\t\t\t'IndexedDB may be unavailable or access may be denied.',\n\t\t\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequest.onupgradeneeded = () => {\n\t\t\t\tconst db = request.result\n\t\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve(request.result)\n\t\t\t}\n\n\t\t\trequest.onerror = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage.',\n\t\t\t\t\t\t{ error: request.error?.message },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\trequest.onblocked = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'IndexedDB database open was blocked. ' +\n\t\t\t\t\t\t\t'Another tab may have an older version of the database open. ' +\n\t\t\t\t\t\t\t'Close other tabs and try again.',\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\treturn this.dbPromise\n\t}\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Store the CryptoKeyPair as a structured clone with the deviceId as the key.\n\t\t\t\t// IndexedDB handles structured cloning of CryptoKey objects natively.\n\t\t\t\tstore.put(keyPair, deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to save key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: tx.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to save key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<CryptoKeyPair | null>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tconst request = store.get(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst result = request.result as CryptoKeyPair | undefined\n\t\t\t\t\tresolve(result ?? null)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to load key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to load key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tstore.delete(deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to delete key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: tx.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to delete key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<boolean>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Use count() with the key to check existence without loading the value.\n\t\t\t\t// This is more efficient than get() for large CryptoKeyPair objects.\n\t\t\t\tconst request = store.count(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tresolve(request.result > 0)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// --- In-memory implementation ---\n\n/**\n * In-memory device key store for Node.js and testing environments.\n *\n * Key pairs are stored in a plain Map and do not survive process restarts.\n * This is suitable for server-side rendering, Node.js scripts, and unit tests\n * where IndexedDB is not available.\n *\n * @example\n * ```typescript\n * const store = new InMemoryDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * ```\n */\nexport class InMemoryDeviceKeyStore implements DeviceKeyStore {\n\tprivate readonly store = new Map<string, CryptoKeyPair>()\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tthis.store.set(deviceId, keyPair)\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\treturn this.store.get(deviceId) ?? null\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tthis.store.delete(deviceId)\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\treturn this.store.has(deviceId)\n\t}\n}\n\n// --- Factory ---\n\n/**\n * Creates a DeviceKeyStore appropriate for the current environment.\n *\n * In browsers where IndexedDB is available, returns an {@link IndexedDBDeviceKeyStore}\n * that persists CryptoKeyPair objects as structured clones. In Node.js, SSR,\n * or environments without IndexedDB, returns an {@link InMemoryDeviceKeyStore}.\n *\n * @returns A DeviceKeyStore instance for the current environment\n *\n * @example\n * ```typescript\n * const store = createDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair(deviceId, keyPair)\n * ```\n */\nexport function createDeviceKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn new IndexedDBDeviceKeyStore()\n\t}\n\treturn new InMemoryDeviceKeyStore()\n}\n","import type { AuthTokens } from '../types'\n\n/** Default key used for localStorage persistence. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_tokens'\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that TokenStore needs. This allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\t// Probe that read/write actually works (some browsers throw on access)\n\t\t\tconst testKey = '__kora_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied (e.g., private mode in some browsers)\n\t}\n\treturn null\n}\n\n/**\n * Client-side token storage for Kora auth tokens.\n *\n * Persists tokens to localStorage when available, falling back to in-memory\n * storage in environments where localStorage is unavailable (Node.js, SSR,\n * restricted browser contexts). Encrypted storage is planned for Phase 2.\n *\n * All operations are synchronous since both localStorage and in-memory\n * storage are synchronous.\n *\n * @example\n * ```typescript\n * const store = new TokenStore()\n *\n * // After login\n * store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls\n * const token = store.getAccessToken()\n *\n * // On logout\n * store.clearTokens()\n * ```\n */\nexport class TokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new TokenStore instance.\n\t *\n\t * @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tconstructor(storageKey?: string) {\n\t\tthis.storageKey = storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Save tokens to persistent storage.\n\t *\n\t * Overwrites any previously stored tokens. The tokens are serialized\n\t * as JSON. Only the `accessToken`, `refreshToken`, and optional\n\t * `deviceCredential` fields are persisted.\n\t *\n\t * @param tokens - The token set to store\n\t */\n\tsaveTokens(tokens: AuthTokens): void {\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\t\tthis.storage.setItem(this.storageKey, JSON.stringify(serialized))\n\t}\n\n\t/**\n\t * Load tokens from storage.\n\t *\n\t * @returns The stored {@link AuthTokens}, or null if no tokens have been saved\n\t */\n\tloadTokens(): AuthTokens | null {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst record = parsed as Record<string, unknown>\n\n\t\t\t// Validate required fields are present and are strings\n\t\t\tif (\n\t\t\t\ttypeof record['accessToken'] !== 'string'\n\t\t\t\t|| typeof record['refreshToken'] !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: record['accessToken'],\n\t\t\t\trefreshToken: record['refreshToken'],\n\t\t\t}\n\n\t\t\tif (typeof record['deviceCredential'] === 'string') {\n\t\t\t\ttokens.deviceCredential = record['deviceCredential']\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Corrupted data in storage; treat as empty\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored tokens.\n\t *\n\t * Call this on logout to remove credentials from persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The access token string, or null if no tokens are stored\n\t */\n\tgetAccessToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token.\n\t *\n\t * @returns The refresh token string, or null if no tokens are stored\n\t */\n\tgetRefreshToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { encryptData, decryptData } from '../encryption/database-encryption'\nimport type { AuthTokens } from '../types'\n\n// --- Errors ---\n\n/**\n * Thrown when encrypted token storage operations fail.\n * Includes context about what went wrong to aid debugging without\n * exposing sensitive key or token material.\n */\nexport class EncryptedTokenStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ENCRYPTED_TOKEN_STORE_ERROR', context)\n\t\tthis.name = 'EncryptedTokenStoreError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes a Uint8Array as a base64url string (no padding).\n * Implemented locally to avoid coupling to the device-identity module.\n *\n * @param bytes - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nfunction toBase64Url(bytes: Uint8Array): string {\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nfunction fromBase64Url(str: string): Uint8Array {\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Storage helpers ---\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that EncryptedTokenStore needs. Allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\tconst testKey = '__kora_encrypted_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied\n\t}\n\treturn null\n}\n\n// --- Types ---\n\n/** Default storage key prefix for encrypted tokens. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_encrypted'\n\n/**\n * The serialized format stored in localStorage.\n * Both `iv` and `data` are base64url-encoded strings.\n */\ninterface EncryptedPayload {\n\t/** Base64url-encoded initialization vector (12 bytes for AES-GCM) */\n\tiv: string\n\t/** Base64url-encoded AES-256-GCM ciphertext of the JSON-serialized AuthTokens */\n\tdata: string\n}\n\n/**\n * Configuration for the encrypted token store.\n */\nexport interface EncryptedTokenStoreConfig {\n\t/**\n\t * Storage key prefix. Defaults to 'kora_auth_encrypted'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tstorageKey?: string\n\t/**\n\t * The AES-256-GCM CryptoKey used to encrypt and decrypt tokens.\n\t *\n\t * This can be:\n\t * - A key derived from a user passphrase via {@link deriveEncryptionKey}\n\t * - A device-derived key (e.g., from secure hardware or biometric unlock)\n\t * - A randomly generated key from {@link generateEncryptionKey}\n\t *\n\t * The caller is responsible for obtaining the key before constructing the store.\n\t */\n\tkey: CryptoKey\n}\n\n// --- Implementation ---\n\n/**\n * Encrypted token store that protects auth tokens at rest.\n *\n * Addresses the security vulnerability of storing tokens in plaintext localStorage,\n * which is accessible to any JavaScript running on the page (XSS attack surface).\n * Tokens are encrypted with AES-256-GCM before being written to storage.\n *\n * The stored format is a JSON string with two base64url-encoded fields:\n * - `iv`: the 12-byte initialization vector (unique per encryption)\n * - `data`: the AES-256-GCM ciphertext of the JSON-serialized tokens\n *\n * AES-GCM provides both confidentiality (tokens are unreadable without the key)\n * and integrity (tampered ciphertext is detected and rejected).\n *\n * @example\n * ```typescript\n * import { deriveEncryptionKey } from '@korajs/auth'\n *\n * // Derive a key from the user's passphrase\n * const { key } = await deriveEncryptionKey('user-passphrase', storedSalt)\n *\n * const store = new EncryptedTokenStore({ key })\n *\n * // After login: encrypt and persist tokens\n * await store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls: decrypt and retrieve\n * const accessToken = await store.getAccessToken()\n *\n * // On logout: remove encrypted data\n * store.clearTokens()\n * ```\n */\nexport class EncryptedTokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly key: CryptoKey\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new EncryptedTokenStore instance.\n\t *\n\t * @param config - Configuration including the encryption key and optional storage key\n\t */\n\tconstructor(config: EncryptedTokenStoreConfig) {\n\t\tthis.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.key = config.key\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Encrypt and save tokens to persistent storage.\n\t *\n\t * Serializes the tokens as JSON, encrypts with AES-256-GCM using a fresh\n\t * random IV, then stores the result as a JSON object containing the\n\t * base64url-encoded IV and ciphertext.\n\t *\n\t * Overwrites any previously stored tokens.\n\t *\n\t * @param tokens - The token set to encrypt and store\n\t * @throws {EncryptedTokenStoreError} If encryption fails\n\t */\n\tasync saveTokens(tokens: AuthTokens): Promise<void> {\n\t\t// Build a clean token object with only the expected fields\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\n\t\tconst plaintext = new TextEncoder().encode(JSON.stringify(serialized))\n\n\t\ttry {\n\t\t\tconst { ciphertext, iv } = await encryptData(this.key, plaintext)\n\n\t\t\tconst payload: EncryptedPayload = {\n\t\t\t\tiv: toBase64Url(iv),\n\t\t\t\tdata: toBase64Url(ciphertext),\n\t\t\t}\n\n\t\t\tthis.storage.setItem(this.storageKey, JSON.stringify(payload))\n\t\t} catch (cause) {\n\t\t\t// Re-throw EncryptedTokenStoreError as-is\n\t\t\tif (cause instanceof EncryptedTokenStoreError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\n\t\t\tthrow new EncryptedTokenStoreError(\n\t\t\t\t'Failed to encrypt and save auth tokens. ' +\n\t\t\t\t\t'Ensure the encryption key is a valid AES-256-GCM CryptoKey.',\n\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Load and decrypt tokens from storage.\n\t *\n\t * Reads the encrypted payload from localStorage, decodes the base64url IV\n\t * and ciphertext, decrypts with AES-256-GCM, and parses the resulting JSON.\n\t *\n\t * Returns null (without throwing) if:\n\t * - No tokens have been saved\n\t * - The stored data is corrupted or not valid JSON\n\t * - Decryption fails (wrong key, tampered ciphertext, or wrong IV)\n\t * - The decrypted data does not contain valid token fields\n\t *\n\t * This fail-silent design prevents decryption errors from crashing the\n\t * application. The caller should treat null as \"no valid tokens available\"\n\t * and initiate a re-authentication flow.\n\t *\n\t * @returns The decrypted {@link AuthTokens}, or null if unavailable or decryption fails\n\t */\n\tasync loadTokens(): Promise<AuthTokens | null> {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\t// Parse the stored encrypted payload\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst record = parsed as Record<string, unknown>\n\t\t\tif (typeof record['iv'] !== 'string' || typeof record['data'] !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Decode the base64url-encoded IV and ciphertext\n\t\t\tconst iv = fromBase64Url(record['iv'])\n\t\t\tconst ciphertext = fromBase64Url(record['data'])\n\n\t\t\t// Decrypt with AES-256-GCM\n\t\t\tconst plaintextBytes = await decryptData(this.key, ciphertext, iv)\n\t\t\tconst json = new TextDecoder().decode(plaintextBytes)\n\n\t\t\t// Parse the decrypted JSON and validate token structure\n\t\t\tconst tokenData: unknown = JSON.parse(json)\n\t\t\tif (typeof tokenData !== 'object' || tokenData === null || Array.isArray(tokenData)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokenRecord = tokenData as Record<string, unknown>\n\t\t\tif (\n\t\t\t\ttypeof tokenRecord['accessToken'] !== 'string'\n\t\t\t\t|| typeof tokenRecord['refreshToken'] !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: tokenRecord['accessToken'],\n\t\t\t\trefreshToken: tokenRecord['refreshToken'],\n\t\t\t}\n\n\t\t\tif (typeof tokenRecord['deviceCredential'] === 'string') {\n\t\t\t\ttokens.deviceCredential = tokenRecord['deviceCredential']\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Decryption failure, corrupted data, or JSON parse error.\n\t\t\t// Return null instead of throwing to allow graceful fallback\n\t\t\t// to re-authentication.\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored encrypted tokens.\n\t *\n\t * Removes the encrypted payload from storage. This is a synchronous\n\t * operation since it only removes the localStorage entry.\n\t *\n\t * Call this on logout to ensure no encrypted credential material\n\t * remains in persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token by decrypting stored tokens.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The decrypted access token string, or null if no valid tokens are stored\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token by decrypting stored tokens.\n\t *\n\t * @returns The decrypted refresh token string, or null if no valid tokens are stored\n\t */\n\tasync getRefreshToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Key derivation errors ---\n\n/**\n * Thrown when a key derivation operation fails.\n */\nexport class KeyDerivationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'KEY_DERIVATION_ERROR', context)\n\t\tthis.name = 'KeyDerivationError'\n\t}\n}\n\n// --- Internal helpers ---\n\n/** Salt length in bytes. 32 bytes (256 bits) provides sufficient randomness. */\nconst SALT_LENGTH = 32\n\n/**\n * PBKDF2 iteration count. 600,000 iterations is the OWASP-recommended minimum\n * for SHA-256 as of 2024, providing strong resistance against brute-force attacks.\n */\nconst PBKDF2_ITERATIONS = 600_000\n\n/** Derived key length in bits (AES-256). */\nconst DERIVED_KEY_LENGTH = 256\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (\n\t\ttypeof globalThis.crypto === 'undefined' ||\n\t\ttypeof globalThis.crypto.subtle === 'undefined'\n\t) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Key derivation requires crypto.subtle, which is available in modern browsers and Node.js 20+.',\n\t\t)\n\t}\n}\n\n// --- Public API ---\n\n/**\n * Generates a cryptographically random 32-byte salt for key derivation.\n *\n * Each call returns a unique salt. The salt should be stored alongside the\n * encrypted data so that the same passphrase can reproduce the same key later.\n *\n * @returns A random 32-byte Uint8Array\n *\n * @example\n * ```typescript\n * const salt = generateSalt()\n * // salt.length === 32\n * ```\n */\nexport function generateSalt(): Uint8Array {\n\tif (typeof globalThis.crypto === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto) is not available. Cannot generate random salt.',\n\t\t)\n\t}\n\n\treturn globalThis.crypto.getRandomValues(new Uint8Array(SALT_LENGTH))\n}\n\n/**\n * Derives an AES-256-GCM encryption key from a passphrase using PBKDF2.\n *\n * Uses PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended minimum)\n * to derive a 256-bit key from the passphrase. The derived key can be used with\n * the database encryption functions ({@link encryptData}, {@link decryptData}).\n *\n * If no salt is provided, a random 32-byte salt is generated. The salt must be\n * persisted alongside the encrypted data so the key can be re-derived later.\n *\n * **Deterministic:** The same passphrase and salt always produce the same key.\n * This is essential for decryption: the user enters their passphrase, the stored\n * salt is used, and the identical key is re-derived to decrypt the data.\n *\n * @param passphrase - The user's passphrase (any non-empty string)\n * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.\n * @returns An object containing the derived CryptoKey and the salt used\n * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails\n *\n * @example\n * ```typescript\n * // First time: derive key with a new salt\n * const { key, salt } = await deriveEncryptionKey('my-secure-passphrase')\n * // Store the salt alongside the encrypted data\n *\n * // Later: re-derive the same key using the stored salt\n * const { key: sameKey } = await deriveEncryptionKey('my-secure-passphrase', salt)\n * ```\n */\nexport async function deriveEncryptionKey(\n\tpassphrase: string,\n\tsalt?: Uint8Array,\n): Promise<{ key: CryptoKey; salt: Uint8Array }> {\n\tassertCryptoAvailable()\n\n\tif (passphrase.length === 0) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Passphrase must not be empty. Provide a non-empty string for key derivation.',\n\t\t)\n\t}\n\n\tconst usedSalt = salt ?? generateSalt()\n\n\ttry {\n\t\t// Step 1: Import the passphrase as raw key material for PBKDF2\n\t\tconst passphraseBytes = new TextEncoder().encode(passphrase)\n\t\tconst baseKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\tpassphraseBytes,\n\t\t\t'PBKDF2',\n\t\t\tfalse,\n\t\t\t['deriveBits', 'deriveKey'],\n\t\t)\n\n\t\t// Step 2: Derive an AES-256-GCM key using PBKDF2 with SHA-256\n\t\tconst derivedKey = await globalThis.crypto.subtle.deriveKey(\n\t\t\t{\n\t\t\t\tname: 'PBKDF2',\n\t\t\t\tsalt: usedSalt as unknown as ArrayBuffer,\n\t\t\t\titerations: PBKDF2_ITERATIONS,\n\t\t\t\thash: 'SHA-256',\n\t\t\t},\n\t\t\tbaseKey,\n\t\t\t{ name: 'AES-GCM', length: DERIVED_KEY_LENGTH },\n\t\t\t// extractable: true so the derived key can be exported if needed\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\n\t\treturn { key: derivedKey, salt: usedSalt }\n\t} catch (cause) {\n\t\t// Re-throw KeyDerivationError as-is (e.g., from assertCryptoAvailable)\n\t\tif (cause instanceof KeyDerivationError) {\n\t\t\tthrow cause\n\t\t}\n\n\t\tthrow new KeyDerivationError(\n\t\t\t'Failed to derive encryption key from passphrase using PBKDF2. ' +\n\t\t\t\t'Ensure the runtime supports PBKDF2 with SHA-256.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","/**\n * Configuration for the AutoLockManager.\n */\nexport interface AutoLockConfig {\n\t/** Inactivity timeout in milliseconds before auto-locking. */\n\ttimeout: number\n\t/** Callback invoked when the lock engages (either by timeout or manual lock). */\n\tonLock: () => void\n}\n\n/**\n * Manages inactivity-based auto-locking for the encrypted local store.\n *\n * The AutoLockManager starts an inactivity timer when {@link start} is called.\n * If no user activity is reported via {@link reportActivity} within the configured\n * timeout, the manager transitions to the locked state and invokes the `onLock`\n * callback.\n *\n * This class has no DOM dependencies. It uses `setTimeout` and `clearTimeout` for\n * timing and accepts an `onLock` callback for side effects. The consuming code is\n * responsible for wiring DOM events (e.g., visibility changes, user interactions)\n * to {@link reportActivity}.\n *\n * @example\n * ```typescript\n * const manager = new AutoLockManager({\n * timeout: 15 * 60 * 1000, // 15 minutes\n * onLock: () => {\n * // Clear decrypted data from memory\n * // Show lock screen\n * }\n * })\n *\n * manager.start()\n *\n * // Call on user interactions to reset the timer\n * document.addEventListener('click', () => manager.reportActivity())\n * document.addEventListener('keydown', () => manager.reportActivity())\n *\n * // Check lock state\n * if (manager.isLocked) {\n * // Prompt for passphrase\n * }\n * ```\n */\nexport class AutoLockManager {\n\tprivate readonly _timeout: number\n\tprivate readonly _onLock: () => void\n\tprivate _timerId: ReturnType<typeof setTimeout> | null = null\n\tprivate _isLocked: boolean = false\n\tprivate _isRunning: boolean = false\n\n\tconstructor(config: AutoLockConfig) {\n\t\tif (config.timeout <= 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`AutoLockManager timeout must be a positive number, but received ${config.timeout}.`,\n\t\t\t)\n\t\t}\n\n\t\tthis._timeout = config.timeout\n\t\tthis._onLock = config.onLock\n\t}\n\n\t/**\n\t * Whether the manager is currently in the locked state.\n\t *\n\t * Becomes `true` when the inactivity timeout fires or {@link lock} is called manually.\n\t * Returns to `false` only when {@link unlock} is called.\n\t */\n\tget isLocked(): boolean {\n\t\treturn this._isLocked\n\t}\n\n\t/**\n\t * Starts monitoring for inactivity.\n\t *\n\t * Begins the inactivity countdown. If the manager is already running, this is\n\t * a no-op to prevent creating multiple timers. Calling `start()` also resets\n\t * the locked state if the manager was previously locked.\n\t */\n\tstart(): void {\n\t\tif (this._isRunning) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._isRunning = true\n\t\tthis._isLocked = false\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Stops monitoring for inactivity.\n\t *\n\t * Clears the pending inactivity timer. Does not change the lock state: if\n\t * the manager was locked, it remains locked. If unlocked, it remains unlocked.\n\t * To resume monitoring, call {@link start} again.\n\t */\n\tstop(): void {\n\t\tthis._isRunning = false\n\t\tthis._clearTimer()\n\t}\n\n\t/**\n\t * Reports user activity, resetting the inactivity timer.\n\t *\n\t * Call this whenever the user interacts with the application (clicks, key presses,\n\t * touches, etc.). If the manager is not running or is already locked, this is a no-op.\n\t */\n\treportActivity(): void {\n\t\tif (!this._isRunning || this._isLocked) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._clearTimer()\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Manually locks the manager immediately.\n\t *\n\t * Clears the inactivity timer and transitions to the locked state. The `onLock`\n\t * callback is invoked. The manager remains running but locked: call {@link unlock}\n\t * to return to the unlocked state, which will restart the inactivity timer.\n\t */\n\tlock(): void {\n\t\tthis._clearTimer()\n\n\t\tif (!this._isLocked) {\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}\n\t}\n\n\t/**\n\t * Unlocks the manager, returning to the unlocked state.\n\t *\n\t * If the manager is running, the inactivity timer is restarted. If the manager\n\t * is not running (was stopped), it simply clears the locked state without\n\t * starting a timer.\n\t */\n\tunlock(): void {\n\t\tthis._isLocked = false\n\n\t\tif (this._isRunning) {\n\t\t\tthis._clearTimer()\n\t\t\tthis._startTimer()\n\t\t}\n\t}\n\n\t/**\n\t * Starts the inactivity timer. When it fires, the manager locks.\n\t */\n\tprivate _startTimer(): void {\n\t\tthis._timerId = setTimeout(() => {\n\t\t\tthis._timerId = null\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}, this._timeout)\n\t}\n\n\t/**\n\t * Clears the pending inactivity timer, if any.\n\t */\n\tprivate _clearTimer(): void {\n\t\tif (this._timerId !== null) {\n\t\t\tclearTimeout(this._timerId)\n\t\t\tthis._timerId = null\n\t\t}\n\t}\n}\n"],"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/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. ` +\n\t\t\t\t\t'Check your network connection and serverUrl configuration.',\n\t\t\t\t'AUTH_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Auth server returned HTTP ${response.status}`\n\t\t\tlet serverError: string | undefined\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body['error'] === 'string') {\n\t\t\t\t\terrorMessage = body['error'] as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t} else if (typeof body['message'] === 'string') {\n\t\t\t\t\terrorMessage = body['message'] as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Response body is not JSON -- use the status text\n\t\t\t}\n\n\t\t\tthrow new AuthError(\n\t\t\t\terrorMessage,\n\t\t\t\t'AUTH_SERVER_ERROR',\n\t\t\t\t{ path, status: response.status, serverError },\n\t\t\t)\n\t\t}\n\n\t\tconst json = (await response.json()) as Record<string, unknown>\n\n\t\t// The BuiltInAuthRoutes server wraps success responses in { data: T }.\n\t\t// Unwrap the envelope so callers get the inner payload directly.\n\t\tconst data = (json['data'] !== undefined ? json['data'] : json) as T\n\t\treturn data\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Organization info returned by the server.\n */\nexport interface ClientOrganization {\n\tid: string\n\tname: string\n\tslug: string\n\townerId: string\n\tcreatedAt: number\n\tupdatedAt: number\n\tmetadata: Record<string, unknown>\n}\n\n/**\n * Membership info returned by the server.\n */\nexport interface ClientMembership {\n\tid: string\n\torgId: string\n\tuserId: string\n\trole: string\n\tinvitedBy: string | null\n\tjoinedAt: number\n}\n\n/**\n * Invitation info returned by the server.\n */\nexport interface ClientInvitation {\n\tid: string\n\torgId: string\n\temail: string\n\trole: string\n\tinvitedBy: string\n\ttoken: string\n\tcreatedAt: number\n\texpiresAt: number\n\tstatus: string\n}\n\n/**\n * Configuration for the OrgClient.\n */\nexport interface OrgClientConfig {\n\t/** Base URL of the auth/org server */\n\tserverUrl: string\n\t/** Function that returns a valid access token for authenticated requests */\n\tgetAccessToken: () => Promise<string | null>\n}\n\n/**\n * Thrown when an org operation fails.\n */\nexport class OrgClientError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OrgClientError'\n\t}\n}\n\n// ============================================================================\n// OrgClient\n// ============================================================================\n\n/**\n * Client-side organization manager.\n *\n * Handles org CRUD, member management, invitations, and active org switching.\n * Framework-agnostic — works in any JavaScript environment with `fetch`.\n *\n * @example\n * ```typescript\n * const orgClient = new OrgClient({\n * serverUrl: 'http://localhost:3001',\n * getAccessToken: () => authClient.getAccessToken(),\n * })\n *\n * const org = await orgClient.createOrg({ name: 'Acme Inc', slug: 'acme' })\n * orgClient.switchOrg(org.id)\n * ```\n */\nexport class OrgClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly getAccessToken: () => Promise<string | null>\n\tprivate readonly listeners = new Set<(orgId: string | null) => void>()\n\n\tprivate _activeOrgId: string | null = null\n\tprivate _activeOrg: ClientOrganization | null = null\n\tprivate _activeRole: string | null = null\n\n\tconstructor(config: OrgClientConfig) {\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tthis.getAccessToken = config.getAccessToken\n\t}\n\n\t// --- Getters ---\n\n\t/** Currently active organization ID */\n\tget activeOrgId(): string | null {\n\t\treturn this._activeOrgId\n\t}\n\n\t/** Currently active organization */\n\tget activeOrg(): ClientOrganization | null {\n\t\treturn this._activeOrg\n\t}\n\n\t/** Current user's role in the active organization */\n\tget activeRole(): string | null {\n\t\treturn this._activeRole\n\t}\n\n\t// --- Organization Operations ---\n\n\t/**\n\t * Create a new organization.\n\t */\n\tasync createOrg(params: {\n\t\tname: string\n\t\tslug?: string\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>('/orgs', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * List all organizations the current user belongs to.\n\t */\n\tasync listOrgs(): Promise<ClientOrganization[]> {\n\t\treturn this.request<ClientOrganization[]>('/orgs', { method: 'GET' })\n\t}\n\n\t/**\n\t * Get an organization by ID.\n\t */\n\tasync getOrg(orgId: string): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Update an organization.\n\t */\n\tasync updateOrg(\n\t\torgId: string,\n\t\tparams: { name?: string; slug?: string; metadata?: Record<string, unknown> },\n\t): Promise<ClientOrganization> {\n\t\tconst result = await this.request<ClientOrganization>(`/orgs/${orgId}`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: params,\n\t\t})\n\t\t// Update cached active org if this is the active one\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrg = result\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Delete an organization.\n\t */\n\tasync deleteOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}`, { method: 'DELETE' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Org Switching ---\n\n\t/**\n\t * Switch the active organization context.\n\t * Fetches the org details and the user's membership/role.\n\t */\n\tasync switchOrg(orgId: string): Promise<void> {\n\t\tconst org = await this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t\tconst membership = await this.request<ClientMembership>(`/orgs/${orgId}/membership`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\n\t\tthis._activeOrgId = orgId\n\t\tthis._activeOrg = org\n\t\tthis._activeRole = membership.role\n\t\tthis.notifyListeners()\n\t}\n\n\t/**\n\t * Clear the active organization (no org selected).\n\t */\n\tclearActiveOrg(): void {\n\t\tthis._activeOrgId = null\n\t\tthis._activeOrg = null\n\t\tthis._activeRole = null\n\t\tthis.notifyListeners()\n\t}\n\n\t// --- Member Management ---\n\n\t/**\n\t * List members of an organization.\n\t */\n\tasync listMembers(orgId: string): Promise<ClientMembership[]> {\n\t\treturn this.request<ClientMembership[]>(`/orgs/${orgId}/members`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Remove a member from an organization.\n\t */\n\tasync removeMember(orgId: string, userId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/members/${userId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * Update a member's role.\n\t */\n\tasync updateMemberRole(\n\t\torgId: string,\n\t\tuserId: string,\n\t\trole: string,\n\t): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>(`/orgs/${orgId}/members/${userId}/role`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: { role },\n\t\t})\n\t}\n\n\t/**\n\t * Transfer ownership to another member.\n\t */\n\tasync transferOwnership(orgId: string, newOwnerId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/transfer`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { newOwnerId },\n\t\t})\n\t}\n\n\t/**\n\t * Leave an organization (remove yourself).\n\t */\n\tasync leaveOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/leave`, { method: 'POST' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Invitations ---\n\n\t/**\n\t * Invite a user to an organization by email.\n\t */\n\tasync inviteMember(\n\t\torgId: string,\n\t\tparams: { email: string; role: string },\n\t): Promise<ClientInvitation> {\n\t\treturn this.request<ClientInvitation>(`/orgs/${orgId}/invitations`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * Accept an invitation by token.\n\t */\n\tasync acceptInvitation(token: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>('/invitations/accept', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { token },\n\t\t})\n\t}\n\n\t/**\n\t * List pending invitations for an organization.\n\t */\n\tasync listInvitations(orgId: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(`/orgs/${orgId}/invitations`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Revoke a pending invitation.\n\t */\n\tasync revokeInvitation(orgId: string, invitationId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * List pending invitations for the current user's email.\n\t */\n\tasync listMyInvitations(email: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(\n\t\t\t`/invitations?email=${encodeURIComponent(email)}`,\n\t\t\t{ method: 'GET' },\n\t\t)\n\t}\n\n\t// --- Subscriptions ---\n\n\t/**\n\t * Subscribe to active org changes.\n\t * @returns Unsubscribe function\n\t */\n\tonOrgChange(callback: (orgId: string | null) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// --- Internal ---\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(this._activeOrgId)\n\t\t\t} catch {\n\t\t\t\t// Don't let listener errors break the notification loop\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async request<T = void>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t},\n\t): Promise<T> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t'Not authenticated. Sign in before performing organization operations.',\n\t\t\t\t'ORG_NOT_AUTHENTICATED',\n\t\t\t)\n\t\t}\n\n\t\tconst url = `${this.serverUrl}${path}`\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t`Network request to ${path} failed.`,\n\t\t\t\t'ORG_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Server returned HTTP ${response.status}`\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body['error'] === 'string') {\n\t\t\t\t\terrorMessage = body['error'] as string\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// not JSON\n\t\t\t}\n\t\t\tthrow new OrgClientError(errorMessage, 'ORG_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t})\n\t\t}\n\n\t\t// Handle empty responses (DELETE, etc.)\n\t\tconst text = await response.text()\n\t\tif (text.length === 0) return undefined as T\n\n\t\ttry {\n\t\t\tconst body = JSON.parse(text)\n\t\t\t// Unwrap { data: ... } envelope if present\n\t\t\tif (body && typeof body === 'object' && 'data' in body) {\n\t\t\t\treturn body.data as T\n\t\t\t}\n\t\t\treturn body as T\n\t\t} catch {\n\t\t\treturn undefined as T\n\t\t}\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Device key store errors ---\n\n/**\n * Thrown when a device key store operation fails.\n * Provides context about the operation and the underlying cause.\n */\nexport class DeviceKeyStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_KEY_STORE_ERROR', context)\n\t\tthis.name = 'DeviceKeyStoreError'\n\t}\n}\n\n// --- Interface ---\n\n/**\n * Persistent storage interface for ECDSA P-256 device key pairs.\n *\n * Device key pairs are used for proof-of-possession authentication:\n * the private key never leaves the device, and the public key is\n * registered with the server. This store persists key pairs across\n * page reloads and app restarts.\n *\n * In the browser, CryptoKey objects are structured-cloneable, so they\n * can be stored directly in IndexedDB without serialization. In Node.js\n * or test environments, an in-memory implementation is used instead.\n */\nexport interface DeviceKeyStore {\n\t/**\n\t * Persist a key pair for the given device.\n\t *\n\t * Overwrites any previously stored key pair for the same device ID.\n\t *\n\t * @param deviceId - The unique device identifier (typically a UUID v7)\n\t * @param keyPair - The ECDSA P-256 CryptoKeyPair to store\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tsaveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>\n\n\t/**\n\t * Load a previously stored key pair for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns The stored CryptoKeyPair, or null if no key pair exists for the device\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tloadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>\n\n\t/**\n\t * Delete a stored key pair for the given device.\n\t *\n\t * No-op if no key pair exists for the device ID.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tdeleteKeyPair(deviceId: string): Promise<void>\n\n\t/**\n\t * Check whether a key pair exists for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns True if a key pair is stored for the device, false otherwise\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\thasKeyPair(deviceId: string): Promise<boolean>\n}\n\n// --- IndexedDB constants ---\n\n/** IndexedDB database name for device key storage. */\nconst IDB_DATABASE_NAME = 'kora_device_keys'\n\n/** IndexedDB object store name within the database. */\nconst IDB_STORE_NAME = 'keypairs'\n\n/** Current database schema version. */\nconst IDB_VERSION = 1\n\n// --- IndexedDB implementation ---\n\n/**\n * Browser-based device key store backed by IndexedDB.\n *\n * CryptoKey objects are structured-cloneable, so they can be stored\n * directly in IndexedDB without needing to export/import them as JWK.\n * This preserves the non-extractable flag on private keys, ensuring\n * they cannot be read even from storage.\n *\n * The database uses a single object store (`keypairs`) with the device ID\n * as the key and the full CryptoKeyPair as the value.\n *\n * @example\n * ```typescript\n * const store = new IndexedDBDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * // loaded.privateKey is still non-extractable\n * ```\n */\nexport class IndexedDBDeviceKeyStore implements DeviceKeyStore {\n\tprivate dbPromise: Promise<IDBDatabase> | null = null\n\n\t/**\n\t * Opens (or creates) the IndexedDB database.\n\t *\n\t * The database connection is lazily initialized on first use and\n\t * reused for subsequent operations. If the database does not exist,\n\t * it is created with the `keypairs` object store.\n\t */\n\tprivate openDatabase(): Promise<IDBDatabase> {\n\t\t// Reuse the database connection if already opened.\n\t\t// This avoids repeatedly opening the database on every operation.\n\t\tif (this.dbPromise !== null) {\n\t\t\treturn this.dbPromise\n\t\t}\n\n\t\tthis.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n\t\t\tlet request: IDBOpenDBRequest\n\n\t\t\ttry {\n\t\t\t\trequest = globalThis.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\t\t} catch (cause) {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage. ' +\n\t\t\t\t\t\t\t'IndexedDB may be unavailable or access may be denied.',\n\t\t\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequest.onupgradeneeded = () => {\n\t\t\t\tconst db = request.result\n\t\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve(request.result)\n\t\t\t}\n\n\t\t\trequest.onerror = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage.',\n\t\t\t\t\t\t{ error: request.error?.message },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\trequest.onblocked = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'IndexedDB database open was blocked. ' +\n\t\t\t\t\t\t\t'Another tab may have an older version of the database open. ' +\n\t\t\t\t\t\t\t'Close other tabs and try again.',\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\treturn this.dbPromise\n\t}\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Store the CryptoKeyPair as a structured clone with the deviceId as the key.\n\t\t\t\t// IndexedDB handles structured cloning of CryptoKey objects natively.\n\t\t\t\tstore.put(keyPair, deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to save key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: tx.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to save key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<CryptoKeyPair | null>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tconst request = store.get(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst result = request.result as CryptoKeyPair | undefined\n\t\t\t\t\tresolve(result ?? null)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to load key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to load key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tstore.delete(deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to delete key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: tx.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to delete key pair for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<boolean>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Use count() with the key to check existence without loading the value.\n\t\t\t\t// This is more efficient than get() for large CryptoKeyPair objects.\n\t\t\t\tconst request = store.count(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tresolve(request.result > 0)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// --- In-memory implementation ---\n\n/**\n * In-memory device key store for Node.js and testing environments.\n *\n * Key pairs are stored in a plain Map and do not survive process restarts.\n * This is suitable for server-side rendering, Node.js scripts, and unit tests\n * where IndexedDB is not available.\n *\n * @example\n * ```typescript\n * const store = new InMemoryDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * ```\n */\nexport class InMemoryDeviceKeyStore implements DeviceKeyStore {\n\tprivate readonly store = new Map<string, CryptoKeyPair>()\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tthis.store.set(deviceId, keyPair)\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\treturn this.store.get(deviceId) ?? null\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tthis.store.delete(deviceId)\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\treturn this.store.has(deviceId)\n\t}\n}\n\n// --- Factory ---\n\n/**\n * Creates a DeviceKeyStore appropriate for the current environment.\n *\n * In browsers where IndexedDB is available, returns an {@link IndexedDBDeviceKeyStore}\n * that persists CryptoKeyPair objects as structured clones. In Node.js, SSR,\n * or environments without IndexedDB, returns an {@link InMemoryDeviceKeyStore}.\n *\n * @returns A DeviceKeyStore instance for the current environment\n *\n * @example\n * ```typescript\n * const store = createDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair(deviceId, keyPair)\n * ```\n */\nexport function createDeviceKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn new IndexedDBDeviceKeyStore()\n\t}\n\treturn new InMemoryDeviceKeyStore()\n}\n","import type { AuthTokens } from '../types'\n\n/** Default key used for localStorage persistence. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_tokens'\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that TokenStore needs. This allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\t// Probe that read/write actually works (some browsers throw on access)\n\t\t\tconst testKey = '__kora_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied (e.g., private mode in some browsers)\n\t}\n\treturn null\n}\n\n/**\n * Client-side token storage for Kora auth tokens.\n *\n * Persists tokens to localStorage when available, falling back to in-memory\n * storage in environments where localStorage is unavailable (Node.js, SSR,\n * restricted browser contexts). Encrypted storage is planned for Phase 2.\n *\n * All operations are synchronous since both localStorage and in-memory\n * storage are synchronous.\n *\n * @example\n * ```typescript\n * const store = new TokenStore()\n *\n * // After login\n * store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls\n * const token = store.getAccessToken()\n *\n * // On logout\n * store.clearTokens()\n * ```\n */\nexport class TokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new TokenStore instance.\n\t *\n\t * @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tconstructor(storageKey?: string) {\n\t\tthis.storageKey = storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Save tokens to persistent storage.\n\t *\n\t * Overwrites any previously stored tokens. The tokens are serialized\n\t * as JSON. Only the `accessToken`, `refreshToken`, and optional\n\t * `deviceCredential` fields are persisted.\n\t *\n\t * @param tokens - The token set to store\n\t */\n\tsaveTokens(tokens: AuthTokens): void {\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\t\tthis.storage.setItem(this.storageKey, JSON.stringify(serialized))\n\t}\n\n\t/**\n\t * Load tokens from storage.\n\t *\n\t * @returns The stored {@link AuthTokens}, or null if no tokens have been saved\n\t */\n\tloadTokens(): AuthTokens | null {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst record = parsed as Record<string, unknown>\n\n\t\t\t// Validate required fields are present and are strings\n\t\t\tif (\n\t\t\t\ttypeof record['accessToken'] !== 'string'\n\t\t\t\t|| typeof record['refreshToken'] !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: record['accessToken'],\n\t\t\t\trefreshToken: record['refreshToken'],\n\t\t\t}\n\n\t\t\tif (typeof record['deviceCredential'] === 'string') {\n\t\t\t\ttokens.deviceCredential = record['deviceCredential']\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Corrupted data in storage; treat as empty\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored tokens.\n\t *\n\t * Call this on logout to remove credentials from persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The access token string, or null if no tokens are stored\n\t */\n\tgetAccessToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token.\n\t *\n\t * @returns The refresh token string, or null if no tokens are stored\n\t */\n\tgetRefreshToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { encryptData, decryptData } from '../encryption/database-encryption'\nimport type { AuthTokens } from '../types'\n\n// --- Errors ---\n\n/**\n * Thrown when encrypted token storage operations fail.\n * Includes context about what went wrong to aid debugging without\n * exposing sensitive key or token material.\n */\nexport class EncryptedTokenStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ENCRYPTED_TOKEN_STORE_ERROR', context)\n\t\tthis.name = 'EncryptedTokenStoreError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes a Uint8Array as a base64url string (no padding).\n * Implemented locally to avoid coupling to the device-identity module.\n *\n * @param bytes - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nfunction toBase64Url(bytes: Uint8Array): string {\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nfunction fromBase64Url(str: string): Uint8Array {\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Storage helpers ---\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that EncryptedTokenStore needs. Allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\tconst testKey = '__kora_encrypted_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied\n\t}\n\treturn null\n}\n\n// --- Types ---\n\n/** Default storage key prefix for encrypted tokens. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_encrypted'\n\n/**\n * The serialized format stored in localStorage.\n * Both `iv` and `data` are base64url-encoded strings.\n */\ninterface EncryptedPayload {\n\t/** Base64url-encoded initialization vector (12 bytes for AES-GCM) */\n\tiv: string\n\t/** Base64url-encoded AES-256-GCM ciphertext of the JSON-serialized AuthTokens */\n\tdata: string\n}\n\n/**\n * Configuration for the encrypted token store.\n */\nexport interface EncryptedTokenStoreConfig {\n\t/**\n\t * Storage key prefix. Defaults to 'kora_auth_encrypted'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tstorageKey?: string\n\t/**\n\t * The AES-256-GCM CryptoKey used to encrypt and decrypt tokens.\n\t *\n\t * This can be:\n\t * - A key derived from a user passphrase via {@link deriveEncryptionKey}\n\t * - A device-derived key (e.g., from secure hardware or biometric unlock)\n\t * - A randomly generated key from {@link generateEncryptionKey}\n\t *\n\t * The caller is responsible for obtaining the key before constructing the store.\n\t */\n\tkey: CryptoKey\n}\n\n// --- Implementation ---\n\n/**\n * Encrypted token store that protects auth tokens at rest.\n *\n * Addresses the security vulnerability of storing tokens in plaintext localStorage,\n * which is accessible to any JavaScript running on the page (XSS attack surface).\n * Tokens are encrypted with AES-256-GCM before being written to storage.\n *\n * The stored format is a JSON string with two base64url-encoded fields:\n * - `iv`: the 12-byte initialization vector (unique per encryption)\n * - `data`: the AES-256-GCM ciphertext of the JSON-serialized tokens\n *\n * AES-GCM provides both confidentiality (tokens are unreadable without the key)\n * and integrity (tampered ciphertext is detected and rejected).\n *\n * @example\n * ```typescript\n * import { deriveEncryptionKey } from '@korajs/auth'\n *\n * // Derive a key from the user's passphrase\n * const { key } = await deriveEncryptionKey('user-passphrase', storedSalt)\n *\n * const store = new EncryptedTokenStore({ key })\n *\n * // After login: encrypt and persist tokens\n * await store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls: decrypt and retrieve\n * const accessToken = await store.getAccessToken()\n *\n * // On logout: remove encrypted data\n * store.clearTokens()\n * ```\n */\nexport class EncryptedTokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly key: CryptoKey\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new EncryptedTokenStore instance.\n\t *\n\t * @param config - Configuration including the encryption key and optional storage key\n\t */\n\tconstructor(config: EncryptedTokenStoreConfig) {\n\t\tthis.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.key = config.key\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Encrypt and save tokens to persistent storage.\n\t *\n\t * Serializes the tokens as JSON, encrypts with AES-256-GCM using a fresh\n\t * random IV, then stores the result as a JSON object containing the\n\t * base64url-encoded IV and ciphertext.\n\t *\n\t * Overwrites any previously stored tokens.\n\t *\n\t * @param tokens - The token set to encrypt and store\n\t * @throws {EncryptedTokenStoreError} If encryption fails\n\t */\n\tasync saveTokens(tokens: AuthTokens): Promise<void> {\n\t\t// Build a clean token object with only the expected fields\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\n\t\tconst plaintext = new TextEncoder().encode(JSON.stringify(serialized))\n\n\t\ttry {\n\t\t\tconst { ciphertext, iv } = await encryptData(this.key, plaintext)\n\n\t\t\tconst payload: EncryptedPayload = {\n\t\t\t\tiv: toBase64Url(iv),\n\t\t\t\tdata: toBase64Url(ciphertext),\n\t\t\t}\n\n\t\t\tthis.storage.setItem(this.storageKey, JSON.stringify(payload))\n\t\t} catch (cause) {\n\t\t\t// Re-throw EncryptedTokenStoreError as-is\n\t\t\tif (cause instanceof EncryptedTokenStoreError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\n\t\t\tthrow new EncryptedTokenStoreError(\n\t\t\t\t'Failed to encrypt and save auth tokens. ' +\n\t\t\t\t\t'Ensure the encryption key is a valid AES-256-GCM CryptoKey.',\n\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Load and decrypt tokens from storage.\n\t *\n\t * Reads the encrypted payload from localStorage, decodes the base64url IV\n\t * and ciphertext, decrypts with AES-256-GCM, and parses the resulting JSON.\n\t *\n\t * Returns null (without throwing) if:\n\t * - No tokens have been saved\n\t * - The stored data is corrupted or not valid JSON\n\t * - Decryption fails (wrong key, tampered ciphertext, or wrong IV)\n\t * - The decrypted data does not contain valid token fields\n\t *\n\t * This fail-silent design prevents decryption errors from crashing the\n\t * application. The caller should treat null as \"no valid tokens available\"\n\t * and initiate a re-authentication flow.\n\t *\n\t * @returns The decrypted {@link AuthTokens}, or null if unavailable or decryption fails\n\t */\n\tasync loadTokens(): Promise<AuthTokens | null> {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\t// Parse the stored encrypted payload\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst record = parsed as Record<string, unknown>\n\t\t\tif (typeof record['iv'] !== 'string' || typeof record['data'] !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Decode the base64url-encoded IV and ciphertext\n\t\t\tconst iv = fromBase64Url(record['iv'])\n\t\t\tconst ciphertext = fromBase64Url(record['data'])\n\n\t\t\t// Decrypt with AES-256-GCM\n\t\t\tconst plaintextBytes = await decryptData(this.key, ciphertext, iv)\n\t\t\tconst json = new TextDecoder().decode(plaintextBytes)\n\n\t\t\t// Parse the decrypted JSON and validate token structure\n\t\t\tconst tokenData: unknown = JSON.parse(json)\n\t\t\tif (typeof tokenData !== 'object' || tokenData === null || Array.isArray(tokenData)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokenRecord = tokenData as Record<string, unknown>\n\t\t\tif (\n\t\t\t\ttypeof tokenRecord['accessToken'] !== 'string'\n\t\t\t\t|| typeof tokenRecord['refreshToken'] !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: tokenRecord['accessToken'],\n\t\t\t\trefreshToken: tokenRecord['refreshToken'],\n\t\t\t}\n\n\t\t\tif (typeof tokenRecord['deviceCredential'] === 'string') {\n\t\t\t\ttokens.deviceCredential = tokenRecord['deviceCredential']\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Decryption failure, corrupted data, or JSON parse error.\n\t\t\t// Return null instead of throwing to allow graceful fallback\n\t\t\t// to re-authentication.\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored encrypted tokens.\n\t *\n\t * Removes the encrypted payload from storage. This is a synchronous\n\t * operation since it only removes the localStorage entry.\n\t *\n\t * Call this on logout to ensure no encrypted credential material\n\t * remains in persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token by decrypting stored tokens.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The decrypted access token string, or null if no valid tokens are stored\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token by decrypting stored tokens.\n\t *\n\t * @returns The decrypted refresh token string, or null if no valid tokens are stored\n\t */\n\tasync getRefreshToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Key derivation errors ---\n\n/**\n * Thrown when a key derivation operation fails.\n */\nexport class KeyDerivationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'KEY_DERIVATION_ERROR', context)\n\t\tthis.name = 'KeyDerivationError'\n\t}\n}\n\n// --- Internal helpers ---\n\n/** Salt length in bytes. 32 bytes (256 bits) provides sufficient randomness. */\nconst SALT_LENGTH = 32\n\n/**\n * PBKDF2 iteration count. 600,000 iterations is the OWASP-recommended minimum\n * for SHA-256 as of 2024, providing strong resistance against brute-force attacks.\n */\nconst PBKDF2_ITERATIONS = 600_000\n\n/** Derived key length in bits (AES-256). */\nconst DERIVED_KEY_LENGTH = 256\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (\n\t\ttypeof globalThis.crypto === 'undefined' ||\n\t\ttypeof globalThis.crypto.subtle === 'undefined'\n\t) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Key derivation requires crypto.subtle, which is available in modern browsers and Node.js 20+.',\n\t\t)\n\t}\n}\n\n// --- Public API ---\n\n/**\n * Generates a cryptographically random 32-byte salt for key derivation.\n *\n * Each call returns a unique salt. The salt should be stored alongside the\n * encrypted data so that the same passphrase can reproduce the same key later.\n *\n * @returns A random 32-byte Uint8Array\n *\n * @example\n * ```typescript\n * const salt = generateSalt()\n * // salt.length === 32\n * ```\n */\nexport function generateSalt(): Uint8Array {\n\tif (typeof globalThis.crypto === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto) is not available. Cannot generate random salt.',\n\t\t)\n\t}\n\n\treturn globalThis.crypto.getRandomValues(new Uint8Array(SALT_LENGTH))\n}\n\n/**\n * Derives an AES-256-GCM encryption key from a passphrase using PBKDF2.\n *\n * Uses PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended minimum)\n * to derive a 256-bit key from the passphrase. The derived key can be used with\n * the database encryption functions ({@link encryptData}, {@link decryptData}).\n *\n * If no salt is provided, a random 32-byte salt is generated. The salt must be\n * persisted alongside the encrypted data so the key can be re-derived later.\n *\n * **Deterministic:** The same passphrase and salt always produce the same key.\n * This is essential for decryption: the user enters their passphrase, the stored\n * salt is used, and the identical key is re-derived to decrypt the data.\n *\n * @param passphrase - The user's passphrase (any non-empty string)\n * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.\n * @returns An object containing the derived CryptoKey and the salt used\n * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails\n *\n * @example\n * ```typescript\n * // First time: derive key with a new salt\n * const { key, salt } = await deriveEncryptionKey('my-secure-passphrase')\n * // Store the salt alongside the encrypted data\n *\n * // Later: re-derive the same key using the stored salt\n * const { key: sameKey } = await deriveEncryptionKey('my-secure-passphrase', salt)\n * ```\n */\nexport async function deriveEncryptionKey(\n\tpassphrase: string,\n\tsalt?: Uint8Array,\n): Promise<{ key: CryptoKey; salt: Uint8Array }> {\n\tassertCryptoAvailable()\n\n\tif (passphrase.length === 0) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Passphrase must not be empty. Provide a non-empty string for key derivation.',\n\t\t)\n\t}\n\n\tconst usedSalt = salt ?? generateSalt()\n\n\ttry {\n\t\t// Step 1: Import the passphrase as raw key material for PBKDF2\n\t\tconst passphraseBytes = new TextEncoder().encode(passphrase)\n\t\tconst baseKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\tpassphraseBytes,\n\t\t\t'PBKDF2',\n\t\t\tfalse,\n\t\t\t['deriveBits', 'deriveKey'],\n\t\t)\n\n\t\t// Step 2: Derive an AES-256-GCM key using PBKDF2 with SHA-256\n\t\tconst derivedKey = await globalThis.crypto.subtle.deriveKey(\n\t\t\t{\n\t\t\t\tname: 'PBKDF2',\n\t\t\t\tsalt: usedSalt as unknown as ArrayBuffer,\n\t\t\t\titerations: PBKDF2_ITERATIONS,\n\t\t\t\thash: 'SHA-256',\n\t\t\t},\n\t\t\tbaseKey,\n\t\t\t{ name: 'AES-GCM', length: DERIVED_KEY_LENGTH },\n\t\t\t// extractable: true so the derived key can be exported if needed\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\n\t\treturn { key: derivedKey, salt: usedSalt }\n\t} catch (cause) {\n\t\t// Re-throw KeyDerivationError as-is (e.g., from assertCryptoAvailable)\n\t\tif (cause instanceof KeyDerivationError) {\n\t\t\tthrow cause\n\t\t}\n\n\t\tthrow new KeyDerivationError(\n\t\t\t'Failed to derive encryption key from passphrase using PBKDF2. ' +\n\t\t\t\t'Ensure the runtime supports PBKDF2 with SHA-256.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","/**\n * Configuration for the AutoLockManager.\n */\nexport interface AutoLockConfig {\n\t/** Inactivity timeout in milliseconds before auto-locking. */\n\ttimeout: number\n\t/** Callback invoked when the lock engages (either by timeout or manual lock). */\n\tonLock: () => void\n}\n\n/**\n * Manages inactivity-based auto-locking for the encrypted local store.\n *\n * The AutoLockManager starts an inactivity timer when {@link start} is called.\n * If no user activity is reported via {@link reportActivity} within the configured\n * timeout, the manager transitions to the locked state and invokes the `onLock`\n * callback.\n *\n * This class has no DOM dependencies. It uses `setTimeout` and `clearTimeout` for\n * timing and accepts an `onLock` callback for side effects. The consuming code is\n * responsible for wiring DOM events (e.g., visibility changes, user interactions)\n * to {@link reportActivity}.\n *\n * @example\n * ```typescript\n * const manager = new AutoLockManager({\n * timeout: 15 * 60 * 1000, // 15 minutes\n * onLock: () => {\n * // Clear decrypted data from memory\n * // Show lock screen\n * }\n * })\n *\n * manager.start()\n *\n * // Call on user interactions to reset the timer\n * document.addEventListener('click', () => manager.reportActivity())\n * document.addEventListener('keydown', () => manager.reportActivity())\n *\n * // Check lock state\n * if (manager.isLocked) {\n * // Prompt for passphrase\n * }\n * ```\n */\nexport class AutoLockManager {\n\tprivate readonly _timeout: number\n\tprivate readonly _onLock: () => void\n\tprivate _timerId: ReturnType<typeof setTimeout> | null = null\n\tprivate _isLocked: boolean = false\n\tprivate _isRunning: boolean = false\n\n\tconstructor(config: AutoLockConfig) {\n\t\tif (config.timeout <= 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`AutoLockManager timeout must be a positive number, but received ${config.timeout}.`,\n\t\t\t)\n\t\t}\n\n\t\tthis._timeout = config.timeout\n\t\tthis._onLock = config.onLock\n\t}\n\n\t/**\n\t * Whether the manager is currently in the locked state.\n\t *\n\t * Becomes `true` when the inactivity timeout fires or {@link lock} is called manually.\n\t * Returns to `false` only when {@link unlock} is called.\n\t */\n\tget isLocked(): boolean {\n\t\treturn this._isLocked\n\t}\n\n\t/**\n\t * Starts monitoring for inactivity.\n\t *\n\t * Begins the inactivity countdown. If the manager is already running, this is\n\t * a no-op to prevent creating multiple timers. Calling `start()` also resets\n\t * the locked state if the manager was previously locked.\n\t */\n\tstart(): void {\n\t\tif (this._isRunning) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._isRunning = true\n\t\tthis._isLocked = false\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Stops monitoring for inactivity.\n\t *\n\t * Clears the pending inactivity timer. Does not change the lock state: if\n\t * the manager was locked, it remains locked. If unlocked, it remains unlocked.\n\t * To resume monitoring, call {@link start} again.\n\t */\n\tstop(): void {\n\t\tthis._isRunning = false\n\t\tthis._clearTimer()\n\t}\n\n\t/**\n\t * Reports user activity, resetting the inactivity timer.\n\t *\n\t * Call this whenever the user interacts with the application (clicks, key presses,\n\t * touches, etc.). If the manager is not running or is already locked, this is a no-op.\n\t */\n\treportActivity(): void {\n\t\tif (!this._isRunning || this._isLocked) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._clearTimer()\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Manually locks the manager immediately.\n\t *\n\t * Clears the inactivity timer and transitions to the locked state. The `onLock`\n\t * callback is invoked. The manager remains running but locked: call {@link unlock}\n\t * to return to the unlocked state, which will restart the inactivity timer.\n\t */\n\tlock(): void {\n\t\tthis._clearTimer()\n\n\t\tif (!this._isLocked) {\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}\n\t}\n\n\t/**\n\t * Unlocks the manager, returning to the unlocked state.\n\t *\n\t * If the manager is running, the inactivity timer is restarted. If the manager\n\t * is not running (was stopped), it simply clears the locked state without\n\t * starting a timer.\n\t */\n\tunlock(): void {\n\t\tthis._isLocked = false\n\n\t\tif (this._isRunning) {\n\t\t\tthis._clearTimer()\n\t\t\tthis._startTimer()\n\t\t}\n\t}\n\n\t/**\n\t * Starts the inactivity timer. When it fires, the manager locks.\n\t */\n\tprivate _startTimer(): void {\n\t\tthis._timerId = setTimeout(() => {\n\t\t\tthis._timerId = null\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}, this._timeout)\n\t}\n\n\t/**\n\t * Clears the pending inactivity timer, if any.\n\t */\n\tprivate _clearTimer(): void {\n\t\tif (this._timerId !== null) {\n\t\t\tclearTimeout(this._timerId)\n\t\t\tthis._timerId = null\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["KoraError","KoraError","KoraError","KoraError","toBase64Url","fromBase64Url","MemoryStorage","tryGetLocalStorage","DEFAULT_STORAGE_KEY","KoraError"]}
|