@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/dist/index.js ADDED
@@ -0,0 +1,552 @@
1
+ import {
2
+ CryptoUnavailableError,
3
+ DeviceIdentityError,
4
+ computePublicKeyThumbprint,
5
+ exportPublicKeyJwk,
6
+ fromBase64Url,
7
+ generateDeviceKeyPair,
8
+ signChallenge,
9
+ toBase64Url,
10
+ verifyChallenge
11
+ } from "./chunk-L554ZDPY.js";
12
+
13
+ // src/client/auth-client.ts
14
+ import { KoraError } from "@korajs/core";
15
+ var AuthError = class extends KoraError {
16
+ constructor(message, code, context) {
17
+ super(message, code, context);
18
+ this.name = "AuthError";
19
+ }
20
+ };
21
+ var EXPIRY_BUFFER_SECONDS = 30;
22
+ function decodeJwtPayload(token) {
23
+ const parts = token.split(".");
24
+ if (parts.length !== 3) {
25
+ return null;
26
+ }
27
+ try {
28
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
29
+ const json = atob(base64);
30
+ return JSON.parse(json);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ function isTokenExpired(token) {
36
+ const payload = decodeJwtPayload(token);
37
+ if (!payload || typeof payload["exp"] !== "number") {
38
+ return true;
39
+ }
40
+ const nowSeconds = Math.floor(Date.now() / 1e3);
41
+ return payload["exp"] <= nowSeconds + EXPIRY_BUFFER_SECONDS;
42
+ }
43
+ function getUserIdFromToken(token) {
44
+ const payload = decodeJwtPayload(token);
45
+ if (!payload || typeof payload["sub"] !== "string") {
46
+ return null;
47
+ }
48
+ return payload["sub"];
49
+ }
50
+ function createTokenStorage(prefix) {
51
+ let useLocalStorage = false;
52
+ try {
53
+ if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
54
+ const testKey = `${prefix}_test`;
55
+ window.localStorage.setItem(testKey, "1");
56
+ window.localStorage.removeItem(testKey);
57
+ useLocalStorage = true;
58
+ }
59
+ } catch {
60
+ }
61
+ if (useLocalStorage) {
62
+ const accessKey = `${prefix}_access_token`;
63
+ const refreshKey = `${prefix}_refresh_token`;
64
+ return {
65
+ getAccessToken() {
66
+ return window.localStorage.getItem(accessKey);
67
+ },
68
+ getRefreshToken() {
69
+ return window.localStorage.getItem(refreshKey);
70
+ },
71
+ setTokens(access, refresh) {
72
+ window.localStorage.setItem(accessKey, access);
73
+ window.localStorage.setItem(refreshKey, refresh);
74
+ },
75
+ clear() {
76
+ window.localStorage.removeItem(accessKey);
77
+ window.localStorage.removeItem(refreshKey);
78
+ }
79
+ };
80
+ }
81
+ let accessToken = null;
82
+ let refreshToken = null;
83
+ return {
84
+ getAccessToken() {
85
+ return accessToken;
86
+ },
87
+ getRefreshToken() {
88
+ return refreshToken;
89
+ },
90
+ setTokens(access, refresh) {
91
+ accessToken = access;
92
+ refreshToken = refresh;
93
+ },
94
+ clear() {
95
+ accessToken = null;
96
+ refreshToken = null;
97
+ }
98
+ };
99
+ }
100
+ var AuthClient = class {
101
+ serverUrl;
102
+ storage;
103
+ listeners = /* @__PURE__ */ new Set();
104
+ _state = "loading";
105
+ _user = null;
106
+ _refreshPromise = null;
107
+ /**
108
+ * Creates a new AuthClient.
109
+ *
110
+ * @param config - Auth client configuration
111
+ */
112
+ constructor(config) {
113
+ this.serverUrl = config.serverUrl.replace(/\/+$/, "");
114
+ const prefix = config.storageKey ?? "kora_auth";
115
+ this.storage = createTokenStorage(prefix);
116
+ }
117
+ // -----------------------------------------------------------------------
118
+ // Public getters
119
+ // -----------------------------------------------------------------------
120
+ /** Current authentication state. */
121
+ get state() {
122
+ return this._state;
123
+ }
124
+ /** Current authenticated user, or null if not signed in. */
125
+ get currentUser() {
126
+ return this._user;
127
+ }
128
+ /** Whether the user is currently authenticated. */
129
+ get isAuthenticated() {
130
+ return this._state === "authenticated";
131
+ }
132
+ // -----------------------------------------------------------------------
133
+ // Initialization
134
+ // -----------------------------------------------------------------------
135
+ /**
136
+ * Initialize the auth client by restoring a session from stored tokens.
137
+ *
138
+ * Loads tokens from storage, validates the access token, and attempts a
139
+ * refresh if the access token is expired but a refresh token is available.
140
+ * Safe to call multiple times -- subsequent calls are no-ops once initialized.
141
+ */
142
+ async initialize() {
143
+ const accessToken = this.storage.getAccessToken();
144
+ const refreshToken = this.storage.getRefreshToken();
145
+ if (!accessToken || !refreshToken) {
146
+ this.setState("unauthenticated", null);
147
+ return;
148
+ }
149
+ if (!isTokenExpired(accessToken)) {
150
+ await this.restoreSession(accessToken);
151
+ return;
152
+ }
153
+ try {
154
+ const newAccessToken = await this.refreshAccessToken(refreshToken);
155
+ if (newAccessToken) {
156
+ await this.restoreSession(newAccessToken);
157
+ return;
158
+ }
159
+ } catch {
160
+ }
161
+ this.storage.clear();
162
+ this.setState("unauthenticated", null);
163
+ }
164
+ // -----------------------------------------------------------------------
165
+ // Sign up / Sign in / Sign out
166
+ // -----------------------------------------------------------------------
167
+ /**
168
+ * Register a new user account.
169
+ *
170
+ * @param params - Sign-up credentials
171
+ * @returns The newly created AuthUser
172
+ * @throws {AuthError} If the request fails or the server returns an error
173
+ */
174
+ async signUp(params) {
175
+ const response = await this.request("/auth/signup", {
176
+ method: "POST",
177
+ body: params
178
+ });
179
+ this.storage.setTokens(response.accessToken, response.refreshToken);
180
+ const user = await this.fetchUserProfile(response.accessToken);
181
+ this.setState("authenticated", user);
182
+ return user;
183
+ }
184
+ /**
185
+ * Sign in with email and password.
186
+ *
187
+ * @param params - Sign-in credentials
188
+ * @returns The authenticated AuthUser
189
+ * @throws {AuthError} If the credentials are invalid or the request fails
190
+ */
191
+ async signIn(params) {
192
+ const response = await this.request("/auth/signin", {
193
+ method: "POST",
194
+ body: params
195
+ });
196
+ this.storage.setTokens(response.accessToken, response.refreshToken);
197
+ const user = await this.fetchUserProfile(response.accessToken);
198
+ this.setState("authenticated", user);
199
+ return user;
200
+ }
201
+ /**
202
+ * Sign out the current user.
203
+ *
204
+ * Clears local tokens and state. Does not make a network request to the
205
+ * server -- tokens are simply discarded locally.
206
+ */
207
+ async signOut() {
208
+ this.storage.clear();
209
+ this._refreshPromise = null;
210
+ this.setState("unauthenticated", null);
211
+ }
212
+ // -----------------------------------------------------------------------
213
+ // Token access
214
+ // -----------------------------------------------------------------------
215
+ /**
216
+ * Get a valid access token, automatically refreshing if expired.
217
+ *
218
+ * @returns A valid access token string, or null if the user is not
219
+ * authenticated and refresh is not possible
220
+ */
221
+ async getAccessToken() {
222
+ const accessToken = this.storage.getAccessToken();
223
+ if (accessToken && !isTokenExpired(accessToken)) {
224
+ return accessToken;
225
+ }
226
+ const refreshToken = this.storage.getRefreshToken();
227
+ if (!refreshToken) {
228
+ return null;
229
+ }
230
+ try {
231
+ const newAccessToken = await this.refreshAccessToken(refreshToken);
232
+ return newAccessToken;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+ /**
238
+ * Get a valid token for the sync engine handshake.
239
+ * Alias for {@link getAccessToken}.
240
+ *
241
+ * @returns A valid access token string, or null if unavailable
242
+ */
243
+ async getSyncToken() {
244
+ return this.getAccessToken();
245
+ }
246
+ // -----------------------------------------------------------------------
247
+ // State change subscriptions
248
+ // -----------------------------------------------------------------------
249
+ /**
250
+ * Subscribe to authentication state changes.
251
+ *
252
+ * The callback is invoked whenever the auth state transitions (e.g., from
253
+ * 'unauthenticated' to 'authenticated' on sign-in).
254
+ *
255
+ * @param callback - Function called with the new AuthState on each change
256
+ * @returns An unsubscribe function that removes the listener
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * const unsub = auth.onAuthChange((state) => {
261
+ * console.log('Auth state changed to:', state)
262
+ * })
263
+ * // Later: unsub()
264
+ * ```
265
+ */
266
+ onAuthChange(callback) {
267
+ this.listeners.add(callback);
268
+ return () => {
269
+ this.listeners.delete(callback);
270
+ };
271
+ }
272
+ // -----------------------------------------------------------------------
273
+ // Internal helpers
274
+ // -----------------------------------------------------------------------
275
+ /**
276
+ * Update internal state and notify all listeners.
277
+ */
278
+ setState(state, user) {
279
+ const changed = this._state !== state || this._user !== user;
280
+ this._state = state;
281
+ this._user = user;
282
+ if (changed) {
283
+ for (const listener of this.listeners) {
284
+ try {
285
+ listener(state);
286
+ } catch {
287
+ }
288
+ }
289
+ }
290
+ }
291
+ /**
292
+ * Restore a session from a valid access token by fetching the user profile.
293
+ * Falls back to extracting the user ID from the token payload if the
294
+ * /auth/me request fails (offline scenario).
295
+ */
296
+ async restoreSession(accessToken) {
297
+ try {
298
+ const user = await this.fetchUserProfile(accessToken);
299
+ this.setState("authenticated", user);
300
+ } catch {
301
+ const userId = getUserIdFromToken(accessToken);
302
+ if (userId) {
303
+ this.setState("authenticated", {
304
+ id: userId,
305
+ email: "",
306
+ name: null
307
+ });
308
+ } else {
309
+ this.storage.clear();
310
+ this.setState("unauthenticated", null);
311
+ }
312
+ }
313
+ }
314
+ /**
315
+ * Fetch the current user profile from the server.
316
+ */
317
+ async fetchUserProfile(accessToken) {
318
+ const profile = await this.request("/auth/me", {
319
+ method: "GET",
320
+ token: accessToken
321
+ });
322
+ return {
323
+ id: profile.id,
324
+ email: profile.email,
325
+ name: profile.name ?? null
326
+ };
327
+ }
328
+ /**
329
+ * Refresh the access token using a refresh token.
330
+ * De-duplicates concurrent refresh calls so only one network request is made.
331
+ */
332
+ async refreshAccessToken(refreshToken) {
333
+ if (this._refreshPromise) {
334
+ return this._refreshPromise;
335
+ }
336
+ this._refreshPromise = this.performRefresh(refreshToken);
337
+ try {
338
+ const result = await this._refreshPromise;
339
+ return result;
340
+ } finally {
341
+ this._refreshPromise = null;
342
+ }
343
+ }
344
+ /**
345
+ * Execute the token refresh network request.
346
+ */
347
+ async performRefresh(refreshToken) {
348
+ try {
349
+ const response = await this.request("/auth/refresh", {
350
+ method: "POST",
351
+ body: { refreshToken }
352
+ });
353
+ this.storage.setTokens(response.accessToken, response.refreshToken);
354
+ return response.accessToken;
355
+ } catch {
356
+ this.storage.clear();
357
+ this.setState("unauthenticated", null);
358
+ return null;
359
+ }
360
+ }
361
+ /**
362
+ * Make an HTTP request to the auth server.
363
+ *
364
+ * @param path - URL path relative to serverUrl (e.g. '/auth/signin')
365
+ * @param options - Request options
366
+ * @returns Parsed JSON response body
367
+ * @throws {AuthError} On network failure or non-2xx response
368
+ */
369
+ async request(path, options) {
370
+ const url = `${this.serverUrl}${path}`;
371
+ const headers = {};
372
+ if (options.body) {
373
+ headers["Content-Type"] = "application/json";
374
+ }
375
+ if (options.token) {
376
+ headers["Authorization"] = `Bearer ${options.token}`;
377
+ }
378
+ let response;
379
+ try {
380
+ response = await fetch(url, {
381
+ method: options.method,
382
+ headers,
383
+ body: options.body ? JSON.stringify(options.body) : void 0
384
+ });
385
+ } catch (cause) {
386
+ throw new AuthError(
387
+ `Network request to ${path} failed. The auth server at ${this.serverUrl} may be unreachable. Check your network connection and serverUrl configuration.`,
388
+ "AUTH_NETWORK_ERROR",
389
+ { path, cause: cause instanceof Error ? cause.message : String(cause) }
390
+ );
391
+ }
392
+ if (!response.ok) {
393
+ let errorMessage = `Auth server returned HTTP ${response.status}`;
394
+ let serverError;
395
+ try {
396
+ const body = await response.json();
397
+ if (typeof body["error"] === "string") {
398
+ errorMessage = body["error"];
399
+ serverError = errorMessage;
400
+ } else if (typeof body["message"] === "string") {
401
+ errorMessage = body["message"];
402
+ serverError = errorMessage;
403
+ }
404
+ } catch {
405
+ }
406
+ throw new AuthError(
407
+ errorMessage,
408
+ "AUTH_SERVER_ERROR",
409
+ { path, status: response.status, serverError }
410
+ );
411
+ }
412
+ const data = await response.json();
413
+ return data;
414
+ }
415
+ };
416
+
417
+ // src/tokens/token-store.ts
418
+ var DEFAULT_STORAGE_KEY = "kora_auth_tokens";
419
+ var MemoryStorage = class {
420
+ store = /* @__PURE__ */ new Map();
421
+ getItem(key) {
422
+ return this.store.get(key) ?? null;
423
+ }
424
+ setItem(key, value) {
425
+ this.store.set(key, value);
426
+ }
427
+ removeItem(key) {
428
+ this.store.delete(key);
429
+ }
430
+ };
431
+ function tryGetLocalStorage() {
432
+ try {
433
+ if (typeof globalThis !== "undefined" && "localStorage" in globalThis) {
434
+ const storage = globalThis.localStorage;
435
+ const testKey = "__kora_storage_test__";
436
+ storage.setItem(testKey, "1");
437
+ storage.removeItem(testKey);
438
+ return storage;
439
+ }
440
+ } catch {
441
+ }
442
+ return null;
443
+ }
444
+ var TokenStore = class {
445
+ storageKey;
446
+ storage;
447
+ /**
448
+ * Creates a new TokenStore instance.
449
+ *
450
+ * @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.
451
+ * Use different keys if your app runs multiple Kora instances with separate auth.
452
+ */
453
+ constructor(storageKey) {
454
+ this.storageKey = storageKey ?? DEFAULT_STORAGE_KEY;
455
+ this.storage = tryGetLocalStorage() ?? new MemoryStorage();
456
+ }
457
+ /**
458
+ * Save tokens to persistent storage.
459
+ *
460
+ * Overwrites any previously stored tokens. The tokens are serialized
461
+ * as JSON. Only the `accessToken`, `refreshToken`, and optional
462
+ * `deviceCredential` fields are persisted.
463
+ *
464
+ * @param tokens - The token set to store
465
+ */
466
+ saveTokens(tokens) {
467
+ const serialized = {
468
+ accessToken: tokens.accessToken,
469
+ refreshToken: tokens.refreshToken
470
+ };
471
+ if (tokens.deviceCredential !== void 0) {
472
+ serialized.deviceCredential = tokens.deviceCredential;
473
+ }
474
+ this.storage.setItem(this.storageKey, JSON.stringify(serialized));
475
+ }
476
+ /**
477
+ * Load tokens from storage.
478
+ *
479
+ * @returns The stored {@link AuthTokens}, or null if no tokens have been saved
480
+ */
481
+ loadTokens() {
482
+ const raw = this.storage.getItem(this.storageKey);
483
+ if (raw === null) {
484
+ return null;
485
+ }
486
+ try {
487
+ const parsed = JSON.parse(raw);
488
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
489
+ return null;
490
+ }
491
+ const record = parsed;
492
+ if (typeof record["accessToken"] !== "string" || typeof record["refreshToken"] !== "string") {
493
+ return null;
494
+ }
495
+ const tokens = {
496
+ accessToken: record["accessToken"],
497
+ refreshToken: record["refreshToken"]
498
+ };
499
+ if (typeof record["deviceCredential"] === "string") {
500
+ tokens.deviceCredential = record["deviceCredential"];
501
+ }
502
+ return tokens;
503
+ } catch {
504
+ return null;
505
+ }
506
+ }
507
+ /**
508
+ * Clear all stored tokens.
509
+ *
510
+ * Call this on logout to remove credentials from persistent storage.
511
+ */
512
+ clearTokens() {
513
+ this.storage.removeItem(this.storageKey);
514
+ }
515
+ /**
516
+ * Get the current access token.
517
+ *
518
+ * Returns the raw token string without validating expiration.
519
+ * The caller is responsible for checking whether the token is
520
+ * still valid and initiating a refresh if needed.
521
+ *
522
+ * @returns The access token string, or null if no tokens are stored
523
+ */
524
+ getAccessToken() {
525
+ const tokens = this.loadTokens();
526
+ return tokens?.accessToken ?? null;
527
+ }
528
+ /**
529
+ * Get the current refresh token.
530
+ *
531
+ * @returns The refresh token string, or null if no tokens are stored
532
+ */
533
+ getRefreshToken() {
534
+ const tokens = this.loadTokens();
535
+ return tokens?.refreshToken ?? null;
536
+ }
537
+ };
538
+ export {
539
+ AuthClient,
540
+ AuthError,
541
+ CryptoUnavailableError,
542
+ DeviceIdentityError,
543
+ TokenStore,
544
+ computePublicKeyThumbprint,
545
+ exportPublicKeyJwk,
546
+ fromBase64Url,
547
+ generateDeviceKeyPair,
548
+ signChallenge,
549
+ toBase64Url,
550
+ verifyChallenge
551
+ };
552
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/auth-client.ts","../src/tokens/token-store.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\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 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":[]}