@korajs/auth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -0
- package/dist/auth-client-CrDNuh10.d.cts +181 -0
- package/dist/auth-client-CrDNuh10.d.ts +181 -0
- package/dist/chunk-L554ZDPY.js +174 -0
- package/dist/chunk-L554ZDPY.js.map +1 -0
- package/dist/device-identity-DiwdLsUB.d.cts +247 -0
- package/dist/device-identity-DiwdLsUB.d.ts +247 -0
- package/dist/index.cjs +740 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +552 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +206 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +189 -0
- package/dist/react.d.ts +189 -0
- package/dist/react.js +175 -0
- package/dist/react.js.map +1 -0
- package/dist/server.cjs +1040 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +780 -0
- package/dist/server.d.ts +780 -0
- package/dist/server.js +890 -0
- package/dist/server.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client/auth-client.ts","../src/device/device-identity.ts","../src/tokens/token-store.ts"],"sourcesContent":["// @korajs/auth — public API\n// Every export here is a public API commitment. Be explicit.\n\n// === Client ===\nexport { AuthClient, AuthError } from './client/auth-client'\nexport type { AuthClientConfig, AuthUser, AuthState } from './client/auth-client'\n\n// === Token Types ===\nexport type {\n\tAuthTokens,\n\tTokenPayload,\n\tTokenType,\n\tAuthConfig,\n\tAuthStatus,\n\tAuthEvent,\n\tAuthEventType,\n} from './types'\n\n// === Device Identity ===\nexport {\n\tgenerateDeviceKeyPair,\n\texportPublicKeyJwk,\n\tsignChallenge,\n\tverifyChallenge,\n\tcomputePublicKeyThumbprint,\n\ttoBase64Url,\n\tfromBase64Url,\n\tCryptoUnavailableError,\n\tDeviceIdentityError,\n} from './device/device-identity'\n\n// === Token Store (client-side persistence) ===\nexport { TokenStore } from './tokens/token-store'\n","import { KoraError } from '@korajs/core'\n\n// ---------------------------------------------------------------------------\n// Auth-specific error\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when an authentication operation fails.\n * Includes a machine-readable code and optional context for debugging.\n */\nexport class AuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuthError'\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Possible authentication states for the client.\n * - 'loading': Initial state while restoring tokens from storage\n * - 'authenticated': User is signed in with a valid session\n * - 'unauthenticated': No valid session exists\n */\nexport type AuthState = 'loading' | 'authenticated' | 'unauthenticated'\n\n/**\n * Authenticated user information.\n */\nexport interface AuthUser {\n\t/** Unique user identifier */\n\tid: string\n\n\t/** User email address */\n\temail: string\n\n\t/** Display name (may be absent if user did not provide one) */\n\tname: string | null\n}\n\n/**\n * Configuration for the AuthClient.\n */\nexport interface AuthClientConfig {\n\t/** Base URL of the auth server (e.g. 'http://localhost:3001') */\n\tserverUrl: string\n\n\t/** Storage key prefix for tokens. Defaults to 'kora_auth' */\n\tstorageKey?: string\n}\n\n/**\n * Token pair returned by the auth server on sign-up, sign-in, and refresh.\n */\ninterface AuthTokensResponse {\n\taccessToken: string\n\trefreshToken: string\n}\n\n/**\n * 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\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\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 state. Does not make a network request to the\n\t * server -- tokens are simply discarded locally.\n\t */\n\tasync signOut(): Promise<void> {\n\t\tthis.storage.clear()\n\t\tthis._refreshPromise = null\n\t\tthis.setState('unauthenticated', null)\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// --- Auth-specific errors ---\n\n/**\n * Thrown when the Web Crypto API is not available in the current environment.\n * This can happen in older Node.js versions or SSR environments without crypto support.\n */\nexport class CryptoUnavailableError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Device identity requires crypto.subtle, which is available in modern browsers and Node.js 20+. ' +\n\t\t\t\t'If running in SSR, ensure your runtime provides the Web Crypto API.',\n\t\t\t'CRYPTO_UNAVAILABLE',\n\t\t)\n\t\tthis.name = 'CryptoUnavailableError'\n\t}\n}\n\n/**\n * Thrown when a device identity operation fails (key generation, signing, verification).\n */\nexport class DeviceIdentityError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_IDENTITY_ERROR', context)\n\t\tthis.name = 'DeviceIdentityError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes an ArrayBuffer as a base64url string (no padding).\n *\n * @param buffer - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nexport function toBase64Url(buffer: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(buffer)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\t// Standard base64, then convert to base64url (no padding)\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nexport function fromBase64Url(str: string): Uint8Array {\n\t// Convert base64url back to standard base64\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\t// Add padding if necessary\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Internal helpers ---\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (\n\t\ttypeof globalThis.crypto === 'undefined' ||\n\t\ttypeof globalThis.crypto.subtle === 'undefined'\n\t) {\n\t\tthrow new CryptoUnavailableError()\n\t}\n}\n\n/** ECDSA algorithm parameters used throughout the module. */\nconst ECDSA_ALGORITHM: EcKeyGenParams = {\n\tname: 'ECDSA',\n\tnamedCurve: 'P-256',\n}\n\n/** Signing algorithm parameters: ECDSA with SHA-256. */\nconst ECDSA_SIGN_ALGORITHM: EcdsaParams = {\n\tname: 'ECDSA',\n\thash: { name: 'SHA-256' },\n}\n\n// --- Public API ---\n\n/**\n * Generates an ECDSA P-256 key pair for device identity.\n *\n * The private key is marked as non-extractable, ensuring it cannot be\n * exported from the browser's crypto subsystem. This provides\n * proof-of-possession: only code running on this device can sign with the key.\n *\n * @returns A CryptoKeyPair containing the public and private ECDSA P-256 keys\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If key generation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * // keyPair.publicKey can be exported; keyPair.privateKey stays on device\n * ```\n */\nexport async function generateDeviceKeyPair(): Promise<CryptoKeyPair> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst keyPair = await globalThis.crypto.subtle.generateKey(\n\t\t\tECDSA_ALGORITHM,\n\t\t\t// extractable: false makes the private key non-extractable.\n\t\t\t// The public key is always extractable regardless of this flag.\n\t\t\tfalse,\n\t\t\t['sign', 'verify'],\n\t\t)\n\t\treturn keyPair\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to generate ECDSA P-256 device key pair. ' +\n\t\t\t\t'Ensure the runtime supports the ECDSA algorithm with the P-256 curve.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Exports the public key from a key pair as a JSON Web Key (JWK).\n *\n * The JWK can be safely transmitted to a server or other devices to identify\n * this device. It contains only the public component of the key pair.\n *\n * @param keyPair - The CryptoKeyPair whose public key should be exported\n * @returns The public key in JWK format\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the export operation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const jwk = await exportPublicKeyJwk(keyPair)\n * // jwk contains { kty: 'EC', crv: 'P-256', x: '...', y: '...' }\n * ```\n */\nexport async function exportPublicKeyJwk(keyPair: CryptoKeyPair): Promise<JsonWebKey> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst jwk = await globalThis.crypto.subtle.exportKey('jwk', keyPair.publicKey)\n\t\treturn jwk\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to export public key as JWK. ' +\n\t\t\t\t'The key pair may be invalid or the public key may not support JWK export.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Signs a challenge string with the device's private key.\n *\n * Used for proof-of-possession during authentication: the server sends a\n * random challenge, and the device proves it holds the private key by signing it.\n *\n * @param privateKey - The device's private CryptoKey (ECDSA P-256)\n * @param challenge - The challenge string to sign (typically a random nonce from the server)\n * @returns A base64url-encoded ECDSA signature (no padding)\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the signing operation fails\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const signature = await signChallenge(keyPair.privateKey, 'server-nonce-abc123')\n * // signature is a base64url string like 'MEUCIQDx...'\n * ```\n */\nexport async function signChallenge(privateKey: CryptoKey, challenge: string): Promise<string> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst encoded = new TextEncoder().encode(challenge)\n\t\tconst signatureBuffer = await globalThis.crypto.subtle.sign(\n\t\t\tECDSA_SIGN_ALGORITHM,\n\t\t\tprivateKey,\n\t\t\tencoded,\n\t\t)\n\t\treturn toBase64Url(signatureBuffer)\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to sign challenge. ' +\n\t\t\t\t'Ensure the key is a valid ECDSA P-256 private key with \"sign\" usage.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n\n/**\n * Verifies a challenge signature against a public key.\n *\n * Used server-side (or on any verifying party) to confirm that a device\n * holds the private key corresponding to the given public key.\n *\n * @param publicKeyJwk - The device's public key in JWK format\n * @param challenge - The original challenge string that was signed\n * @param signature - The base64url-encoded signature to verify\n * @returns `true` if the signature is valid, `false` otherwise\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the verification operation fails due to an invalid key or format\n *\n * @example\n * ```typescript\n * const isValid = await verifyChallenge(publicKeyJwk, 'server-nonce-abc123', signature)\n * if (isValid) {\n * // Device proved possession of the private key\n * }\n * ```\n */\nexport async function verifyChallenge(\n\tpublicKeyJwk: JsonWebKey,\n\tchallenge: string,\n\tsignature: string,\n): Promise<boolean> {\n\tassertCryptoAvailable()\n\n\ttry {\n\t\tconst publicKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'jwk',\n\t\t\tpublicKeyJwk,\n\t\t\tECDSA_ALGORITHM,\n\t\t\ttrue,\n\t\t\t['verify'],\n\t\t)\n\n\t\tconst encoded = new TextEncoder().encode(challenge)\n\t\tconst signatureBytes = fromBase64Url(signature)\n\n\t\tconst isValid = await globalThis.crypto.subtle.verify(\n\t\t\tECDSA_SIGN_ALGORITHM,\n\t\t\tpublicKey,\n\t\t\tsignatureBytes as unknown as ArrayBuffer,\n\t\t\tencoded,\n\t\t)\n\t\treturn isValid\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to verify challenge signature. ' +\n\t\t\t\t'The public key JWK or signature format may be invalid.',\n\t\t\t{\n\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\tpublicKeyKty: publicKeyJwk.kty,\n\t\t\t\tpublicKeyCrv: publicKeyJwk.crv,\n\t\t\t},\n\t\t)\n\t}\n}\n\n/**\n * Computes a SHA-256 thumbprint of a JWK public key.\n *\n * The thumbprint is computed per RFC 7638: the JWK members required for the key\n * type are serialized in lexicographic order, then hashed with SHA-256. For EC keys\n * (kty: \"EC\"), the required members are `crv`, `kty`, `x`, and `y`.\n *\n * This thumbprint serves as a compact, stable identifier for the device's public key\n * (used as the `dpk` claim in device credentials).\n *\n * @param publicKeyJwk - The public key in JWK format (must be an EC P-256 key)\n * @returns A base64url-encoded SHA-256 thumbprint (no padding)\n * @throws {CryptoUnavailableError} If `crypto.subtle` is not available\n * @throws {DeviceIdentityError} If the thumbprint computation fails or the JWK is missing required fields\n *\n * @example\n * ```typescript\n * const keyPair = await generateDeviceKeyPair()\n * const jwk = await exportPublicKeyJwk(keyPair)\n * const thumbprint = await computePublicKeyThumbprint(jwk)\n * // thumbprint is a base64url string, e.g., 'NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs'\n * ```\n */\nexport async function computePublicKeyThumbprint(publicKeyJwk: JsonWebKey): Promise<string> {\n\tassertCryptoAvailable()\n\n\t// RFC 7638 requires specific members in lexicographic order for each key type.\n\t// For EC (kty: \"EC\"), the required members are: crv, kty, x, y.\n\tif (publicKeyJwk.kty !== 'EC') {\n\t\tthrow new DeviceIdentityError(\n\t\t\t`Expected JWK key type \"EC\" but received \"${publicKeyJwk.kty ?? 'undefined'}\". ` +\n\t\t\t\t'Only ECDSA public keys are supported for device identity.',\n\t\t\t{ kty: publicKeyJwk.kty },\n\t\t)\n\t}\n\n\tif (!publicKeyJwk.crv || !publicKeyJwk.x || !publicKeyJwk.y) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'JWK is missing required EC fields. ' +\n\t\t\t\t'An EC public key JWK must include \"crv\", \"x\", and \"y\" members.',\n\t\t\t{\n\t\t\t\thasCrv: Boolean(publicKeyJwk.crv),\n\t\t\t\thasX: Boolean(publicKeyJwk.x),\n\t\t\t\thasY: Boolean(publicKeyJwk.y),\n\t\t\t},\n\t\t)\n\t}\n\n\t// Build the canonical JSON with only the required members in lexicographic order.\n\t// Per RFC 7638, no whitespace, keys in sorted order.\n\tconst canonicalJson = JSON.stringify({\n\t\tcrv: publicKeyJwk.crv,\n\t\tkty: publicKeyJwk.kty,\n\t\tx: publicKeyJwk.x,\n\t\ty: publicKeyJwk.y,\n\t})\n\n\ttry {\n\t\tconst encoded = new TextEncoder().encode(canonicalJson)\n\t\tconst hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', encoded)\n\t\treturn toBase64Url(hashBuffer)\n\t} catch (cause) {\n\t\tthrow new DeviceIdentityError(\n\t\t\t'Failed to compute SHA-256 thumbprint of the public key JWK.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","import 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"],"mappings":"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","names":["import_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export { A as AuthClient, a as AuthClientConfig, b as AuthError, c as AuthState, d as AuthUser } from './auth-client-CrDNuh10.cjs';
|
|
2
|
+
import { A as AuthTokens } from './device-identity-DiwdLsUB.cjs';
|
|
3
|
+
export { a as AuthConfig, b as AuthEvent, c as AuthEventType, d as AuthStatus, C as CryptoUnavailableError, D as DeviceIdentityError, T as TokenPayload, e as TokenType, f as computePublicKeyThumbprint, g as exportPublicKeyJwk, h as fromBase64Url, i as generateDeviceKeyPair, s as signChallenge, t as toBase64Url, v as verifyChallenge } from './device-identity-DiwdLsUB.cjs';
|
|
4
|
+
import '@korajs/core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Client-side token storage for Kora auth tokens.
|
|
8
|
+
*
|
|
9
|
+
* Persists tokens to localStorage when available, falling back to in-memory
|
|
10
|
+
* storage in environments where localStorage is unavailable (Node.js, SSR,
|
|
11
|
+
* restricted browser contexts). Encrypted storage is planned for Phase 2.
|
|
12
|
+
*
|
|
13
|
+
* All operations are synchronous since both localStorage and in-memory
|
|
14
|
+
* storage are synchronous.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const store = new TokenStore()
|
|
19
|
+
*
|
|
20
|
+
* // After login
|
|
21
|
+
* store.saveTokens({ accessToken: '...', refreshToken: '...' })
|
|
22
|
+
*
|
|
23
|
+
* // Before API calls
|
|
24
|
+
* const token = store.getAccessToken()
|
|
25
|
+
*
|
|
26
|
+
* // On logout
|
|
27
|
+
* store.clearTokens()
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare class TokenStore {
|
|
31
|
+
private readonly storageKey;
|
|
32
|
+
private readonly storage;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new TokenStore instance.
|
|
35
|
+
*
|
|
36
|
+
* @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.
|
|
37
|
+
* Use different keys if your app runs multiple Kora instances with separate auth.
|
|
38
|
+
*/
|
|
39
|
+
constructor(storageKey?: string);
|
|
40
|
+
/**
|
|
41
|
+
* Save tokens to persistent storage.
|
|
42
|
+
*
|
|
43
|
+
* Overwrites any previously stored tokens. The tokens are serialized
|
|
44
|
+
* as JSON. Only the `accessToken`, `refreshToken`, and optional
|
|
45
|
+
* `deviceCredential` fields are persisted.
|
|
46
|
+
*
|
|
47
|
+
* @param tokens - The token set to store
|
|
48
|
+
*/
|
|
49
|
+
saveTokens(tokens: AuthTokens): void;
|
|
50
|
+
/**
|
|
51
|
+
* Load tokens from storage.
|
|
52
|
+
*
|
|
53
|
+
* @returns The stored {@link AuthTokens}, or null if no tokens have been saved
|
|
54
|
+
*/
|
|
55
|
+
loadTokens(): AuthTokens | null;
|
|
56
|
+
/**
|
|
57
|
+
* Clear all stored tokens.
|
|
58
|
+
*
|
|
59
|
+
* Call this on logout to remove credentials from persistent storage.
|
|
60
|
+
*/
|
|
61
|
+
clearTokens(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Get the current access token.
|
|
64
|
+
*
|
|
65
|
+
* Returns the raw token string without validating expiration.
|
|
66
|
+
* The caller is responsible for checking whether the token is
|
|
67
|
+
* still valid and initiating a refresh if needed.
|
|
68
|
+
*
|
|
69
|
+
* @returns The access token string, or null if no tokens are stored
|
|
70
|
+
*/
|
|
71
|
+
getAccessToken(): string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Get the current refresh token.
|
|
74
|
+
*
|
|
75
|
+
* @returns The refresh token string, or null if no tokens are stored
|
|
76
|
+
*/
|
|
77
|
+
getRefreshToken(): string | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { AuthTokens, TokenStore };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export { A as AuthClient, a as AuthClientConfig, b as AuthError, c as AuthState, d as AuthUser } from './auth-client-CrDNuh10.js';
|
|
2
|
+
import { A as AuthTokens } from './device-identity-DiwdLsUB.js';
|
|
3
|
+
export { a as AuthConfig, b as AuthEvent, c as AuthEventType, d as AuthStatus, C as CryptoUnavailableError, D as DeviceIdentityError, T as TokenPayload, e as TokenType, f as computePublicKeyThumbprint, g as exportPublicKeyJwk, h as fromBase64Url, i as generateDeviceKeyPair, s as signChallenge, t as toBase64Url, v as verifyChallenge } from './device-identity-DiwdLsUB.js';
|
|
4
|
+
import '@korajs/core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Client-side token storage for Kora auth tokens.
|
|
8
|
+
*
|
|
9
|
+
* Persists tokens to localStorage when available, falling back to in-memory
|
|
10
|
+
* storage in environments where localStorage is unavailable (Node.js, SSR,
|
|
11
|
+
* restricted browser contexts). Encrypted storage is planned for Phase 2.
|
|
12
|
+
*
|
|
13
|
+
* All operations are synchronous since both localStorage and in-memory
|
|
14
|
+
* storage are synchronous.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const store = new TokenStore()
|
|
19
|
+
*
|
|
20
|
+
* // After login
|
|
21
|
+
* store.saveTokens({ accessToken: '...', refreshToken: '...' })
|
|
22
|
+
*
|
|
23
|
+
* // Before API calls
|
|
24
|
+
* const token = store.getAccessToken()
|
|
25
|
+
*
|
|
26
|
+
* // On logout
|
|
27
|
+
* store.clearTokens()
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare class TokenStore {
|
|
31
|
+
private readonly storageKey;
|
|
32
|
+
private readonly storage;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new TokenStore instance.
|
|
35
|
+
*
|
|
36
|
+
* @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.
|
|
37
|
+
* Use different keys if your app runs multiple Kora instances with separate auth.
|
|
38
|
+
*/
|
|
39
|
+
constructor(storageKey?: string);
|
|
40
|
+
/**
|
|
41
|
+
* Save tokens to persistent storage.
|
|
42
|
+
*
|
|
43
|
+
* Overwrites any previously stored tokens. The tokens are serialized
|
|
44
|
+
* as JSON. Only the `accessToken`, `refreshToken`, and optional
|
|
45
|
+
* `deviceCredential` fields are persisted.
|
|
46
|
+
*
|
|
47
|
+
* @param tokens - The token set to store
|
|
48
|
+
*/
|
|
49
|
+
saveTokens(tokens: AuthTokens): void;
|
|
50
|
+
/**
|
|
51
|
+
* Load tokens from storage.
|
|
52
|
+
*
|
|
53
|
+
* @returns The stored {@link AuthTokens}, or null if no tokens have been saved
|
|
54
|
+
*/
|
|
55
|
+
loadTokens(): AuthTokens | null;
|
|
56
|
+
/**
|
|
57
|
+
* Clear all stored tokens.
|
|
58
|
+
*
|
|
59
|
+
* Call this on logout to remove credentials from persistent storage.
|
|
60
|
+
*/
|
|
61
|
+
clearTokens(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Get the current access token.
|
|
64
|
+
*
|
|
65
|
+
* Returns the raw token string without validating expiration.
|
|
66
|
+
* The caller is responsible for checking whether the token is
|
|
67
|
+
* still valid and initiating a refresh if needed.
|
|
68
|
+
*
|
|
69
|
+
* @returns The access token string, or null if no tokens are stored
|
|
70
|
+
*/
|
|
71
|
+
getAccessToken(): string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Get the current refresh token.
|
|
74
|
+
*
|
|
75
|
+
* @returns The refresh token string, or null if no tokens are stored
|
|
76
|
+
*/
|
|
77
|
+
getRefreshToken(): string | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { AuthTokens, TokenStore };
|