@douvery/auth 0.3.2 → 0.3.3

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.d.ts CHANGED
@@ -454,5 +454,65 @@ declare class TokenManager {
454
454
  clearReturnTo(): Promise<void>;
455
455
  clearAll(): Promise<void>;
456
456
  }
457
+ /**
458
+ * Options for createServerBridgedStorage.
459
+ *
460
+ * Use this when tokens are managed server-side (httpOnly cookies)
461
+ * but the OAuth/PKCE flow needs client-side ephemeral storage.
462
+ */
463
+ interface ServerBridgedStorageOptions {
464
+ /**
465
+ * Name of a **non-httpOnly** cookie that holds the access token
466
+ * expiration timestamp (in milliseconds). Used to infer whether
467
+ * a valid session exists without exposing the actual tokens.
468
+ */
469
+ tokenExpirationCookie: string;
470
+ /**
471
+ * Placeholder value returned by `get()` for server-managed keys
472
+ * (accessToken, refreshToken). Signals to the caller that the
473
+ * real token exists but is not readable from JS.
474
+ * @default "__server_managed__"
475
+ */
476
+ serverManagedPlaceholder?: string;
477
+ /**
478
+ * Enable debug logging.
479
+ * @default false
480
+ */
481
+ debug?: boolean;
482
+ }
483
+ /**
484
+ * Creates a `TokenStorage` adapter for apps where **tokens are
485
+ * managed server-side** (e.g. httpOnly cookies set by routeLoader$/
486
+ * routeAction$) but the OAuth PKCE flow still needs ephemeral
487
+ * client-side storage for state, nonce, codeVerifier and returnTo.
488
+ *
489
+ * Behaviour per key category:
490
+ *
491
+ * | Category | get() | set() / remove() |
492
+ * |-------------------|------------------------------------------|-------------------|
493
+ * | accessToken | returns placeholder if session is active | no-op (server) |
494
+ * | refreshToken | returns placeholder if session exists | no-op (server) |
495
+ * | idToken | always null | no-op |
496
+ * | expiresAt | reads from expiration cookie | no-op (server) |
497
+ * | state/nonce/etc. | sessionStorage | sessionStorage |
498
+ *
499
+ * @example
500
+ * ```ts
501
+ * import { createServerBridgedStorage } from '@douvery/auth';
502
+ *
503
+ * const bridgedStorage = createServerBridgedStorage({
504
+ * tokenExpirationCookie: 'dou_token_exp',
505
+ * debug: import.meta.env.DEV,
506
+ * });
507
+ *
508
+ * const config: DouveryAuthConfig = {
509
+ * clientId: 'my-app',
510
+ * redirectUri: '/callback',
511
+ * customStorage: bridgedStorage,
512
+ * autoRefresh: false, // server handles refresh
513
+ * };
514
+ * ```
515
+ */
516
+ declare function createServerBridgedStorage(options: ServerBridgedStorageOptions): TokenStorage;
457
517
 
458
- export { type AddAccountOptions, AuthError, type AuthErrorCode, type AuthEvent, type AuthEventHandler, type AuthNavigationOptions, type AuthState, type AuthStatus, type AuthUrl, type CallbackResult, CookieStorage, type DecodedIdToken, DouveryAuthClient, type DouveryAuthConfig, LocalStorage, type LoginOptions, type LogoutOptions, MemoryStorage, type OIDCDiscovery, type PKCEPair, type RecoverAccountOptions, type RegisterOptions, type RevokeTokenOptions, STORAGE_KEYS, type SelectAccountOptions, SessionStorage, type SetupAddressOptions, type SetupPasskeyOptions, type StorageKeys, type TokenInfo, TokenManager, type TokenSet, type TokenStorage, type UpgradeAccountOptions, type User, type VerifyAccountOptions, base64UrlDecode, base64UrlEncode, createDouveryAuth, createStorage, decodeJWT, generateCodeChallenge, generateCodeVerifier, generateNonce, generatePKCEPair, generateState, getTokenExpiration, isTokenExpired, verifyCodeChallenge };
518
+ export { type AddAccountOptions, AuthError, type AuthErrorCode, type AuthEvent, type AuthEventHandler, type AuthNavigationOptions, type AuthState, type AuthStatus, type AuthUrl, type CallbackResult, CookieStorage, type DecodedIdToken, DouveryAuthClient, type DouveryAuthConfig, LocalStorage, type LoginOptions, type LogoutOptions, MemoryStorage, type OIDCDiscovery, type PKCEPair, type RecoverAccountOptions, type RegisterOptions, type RevokeTokenOptions, STORAGE_KEYS, type SelectAccountOptions, type ServerBridgedStorageOptions, SessionStorage, type SetupAddressOptions, type SetupPasskeyOptions, type StorageKeys, type TokenInfo, TokenManager, type TokenSet, type TokenStorage, type UpgradeAccountOptions, type User, type VerifyAccountOptions, base64UrlDecode, base64UrlEncode, createDouveryAuth, createServerBridgedStorage, createStorage, decodeJWT, generateCodeChallenge, generateCodeVerifier, generateNonce, generatePKCEPair, generateState, getTokenExpiration, isTokenExpired, verifyCodeChallenge };
package/dist/index.js CHANGED
@@ -286,6 +286,113 @@ var TokenManager = class {
286
286
  await this.storage.clear();
287
287
  }
288
288
  };
289
+ var SERVER_TOKEN_KEYS = /* @__PURE__ */ new Set([
290
+ STORAGE_KEYS.accessToken,
291
+ STORAGE_KEYS.refreshToken,
292
+ STORAGE_KEYS.idToken
293
+ ]);
294
+ var PKCE_KEYS = /* @__PURE__ */ new Set([
295
+ STORAGE_KEYS.state,
296
+ STORAGE_KEYS.nonce,
297
+ STORAGE_KEYS.codeVerifier,
298
+ STORAGE_KEYS.returnTo
299
+ ]);
300
+ function readClientCookie(name) {
301
+ if (typeof document === "undefined") return null;
302
+ const cookies = document.cookie.split(";");
303
+ for (const c of cookies) {
304
+ const [key, ...parts] = c.trim().split("=");
305
+ if (key === name) return decodeURIComponent(parts.join("="));
306
+ }
307
+ return null;
308
+ }
309
+ function safeSessionStorage() {
310
+ if (typeof window === "undefined" || typeof sessionStorage === "undefined") {
311
+ return null;
312
+ }
313
+ return sessionStorage;
314
+ }
315
+ function createServerBridgedStorage(options) {
316
+ const {
317
+ tokenExpirationCookie,
318
+ serverManagedPlaceholder = "__server_managed__",
319
+ debug = false
320
+ } = options;
321
+ function log(msg) {
322
+ if (debug) console.debug(`[ServerBridgedStorage] ${msg}`);
323
+ }
324
+ return {
325
+ get(key) {
326
+ if (key === STORAGE_KEYS.accessToken) {
327
+ const exp = readClientCookie(tokenExpirationCookie);
328
+ if (exp) {
329
+ const ms = parseInt(exp, 10);
330
+ if (!isNaN(ms) && ms > Date.now()) return serverManagedPlaceholder;
331
+ }
332
+ return null;
333
+ }
334
+ if (key === STORAGE_KEYS.refreshToken) {
335
+ const exp = readClientCookie(tokenExpirationCookie);
336
+ return exp ? serverManagedPlaceholder : null;
337
+ }
338
+ if (key === STORAGE_KEYS.idToken) return null;
339
+ if (key === STORAGE_KEYS.expiresAt) {
340
+ return readClientCookie(tokenExpirationCookie);
341
+ }
342
+ if (PKCE_KEYS.has(key)) {
343
+ return safeSessionStorage()?.getItem(key) ?? null;
344
+ }
345
+ return safeSessionStorage()?.getItem(key) ?? null;
346
+ },
347
+ set(key, value) {
348
+ if (SERVER_TOKEN_KEYS.has(key) || key === STORAGE_KEYS.expiresAt) {
349
+ log(`Ignoring set("${key}") \u2013 managed by server`);
350
+ return;
351
+ }
352
+ const ss = safeSessionStorage();
353
+ if (ss) {
354
+ try {
355
+ ss.setItem(key, value);
356
+ } catch (e) {
357
+ if (debug)
358
+ console.warn(
359
+ "[ServerBridgedStorage] sessionStorage.setItem failed:",
360
+ e
361
+ );
362
+ }
363
+ }
364
+ },
365
+ remove(key) {
366
+ if (SERVER_TOKEN_KEYS.has(key) || key === STORAGE_KEYS.expiresAt) {
367
+ log(`Ignoring remove("${key}") \u2013 managed by server`);
368
+ return;
369
+ }
370
+ const ss = safeSessionStorage();
371
+ if (ss) {
372
+ try {
373
+ ss.removeItem(key);
374
+ } catch (e) {
375
+ if (debug)
376
+ console.warn(
377
+ "[ServerBridgedStorage] sessionStorage.removeItem failed:",
378
+ e
379
+ );
380
+ }
381
+ }
382
+ },
383
+ clear() {
384
+ const ss = safeSessionStorage();
385
+ if (!ss) return;
386
+ for (const key of PKCE_KEYS) {
387
+ try {
388
+ ss.removeItem(key);
389
+ } catch {
390
+ }
391
+ }
392
+ log("PKCE ephemeral data cleared from sessionStorage");
393
+ }
394
+ };
395
+ }
289
396
 
290
397
  // src/client.ts
291
398
  var DEFAULT_ISSUER = "https://auth.douvery.com";
@@ -866,6 +973,7 @@ var DouveryAuthClient = class {
866
973
  async getDiscovery() {
867
974
  if (this.discovery) return this.discovery;
868
975
  const discoveryUrl = `${this.config.issuer}/.well-known/openid-configuration`;
976
+ this.log("Fetching discovery from:", discoveryUrl);
869
977
  const response = await fetch(discoveryUrl);
870
978
  if (!response.ok) {
871
979
  throw new AuthError(
@@ -873,7 +981,29 @@ var DouveryAuthClient = class {
873
981
  "Failed to fetch discovery document"
874
982
  );
875
983
  }
876
- this.discovery = await response.json();
984
+ const doc = await response.json();
985
+ const docIssuer = doc.issuer;
986
+ const configIssuer = this.config.issuer;
987
+ if (docIssuer && docIssuer !== configIssuer) {
988
+ this.log(
989
+ `Rewriting discovery endpoints: "${docIssuer}" -> "${configIssuer}"`
990
+ );
991
+ const rewrite = (url) => url.replace(docIssuer, configIssuer);
992
+ doc.issuer = configIssuer;
993
+ doc.authorization_endpoint = rewrite(doc.authorization_endpoint);
994
+ doc.token_endpoint = rewrite(doc.token_endpoint);
995
+ if (doc.userinfo_endpoint)
996
+ doc.userinfo_endpoint = rewrite(doc.userinfo_endpoint);
997
+ if (doc.end_session_endpoint)
998
+ doc.end_session_endpoint = rewrite(doc.end_session_endpoint);
999
+ if (doc.jwks_uri) doc.jwks_uri = rewrite(doc.jwks_uri);
1000
+ if (doc.revocation_endpoint)
1001
+ doc.revocation_endpoint = rewrite(doc.revocation_endpoint);
1002
+ if (doc.introspection_endpoint)
1003
+ doc.introspection_endpoint = rewrite(doc.introspection_endpoint);
1004
+ }
1005
+ this.log("authorization_endpoint:", doc.authorization_endpoint);
1006
+ this.discovery = doc;
877
1007
  return this.discovery;
878
1008
  }
879
1009
  setupAutoRefresh() {
@@ -931,6 +1061,6 @@ function createDouveryAuth(config) {
931
1061
  return new DouveryAuthClient(config);
932
1062
  }
933
1063
 
934
- export { AuthError, CookieStorage, DouveryAuthClient, LocalStorage, MemoryStorage, STORAGE_KEYS, SessionStorage, TokenManager, base64UrlDecode, base64UrlEncode, createDouveryAuth, createStorage, decodeJWT, generateCodeChallenge, generateCodeVerifier, generateNonce, generatePKCEPair, generateState, getTokenExpiration, isTokenExpired, verifyCodeChallenge };
1064
+ export { AuthError, CookieStorage, DouveryAuthClient, LocalStorage, MemoryStorage, STORAGE_KEYS, SessionStorage, TokenManager, base64UrlDecode, base64UrlEncode, createDouveryAuth, createServerBridgedStorage, createStorage, decodeJWT, generateCodeChallenge, generateCodeVerifier, generateNonce, generatePKCEPair, generateState, getTokenExpiration, isTokenExpired, verifyCodeChallenge };
935
1065
  //# sourceMappingURL=index.js.map
936
1066
  //# sourceMappingURL=index.js.map