@molecule/app-auth 1.5.2 → 1.5.4

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 CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-18T09:07:53.544Z
6
+ Generated: 2026-10-18T20:35:23.952Z
7
7
  -->
8
8
 
9
9
  # @molecule/app-auth
@@ -55,9 +55,19 @@ mirrors the token into the bonded client.
55
55
  **`getState().status` is the session state to render, in one value.**
56
56
  `'loading'` (no decision yet) → `'authenticated'` / `'unauthenticated'`, and
57
57
  `'unavailable'` when a stored session exists but the server could not confirm it.
58
+ **A stored token AND a stored user make the FIRST render `'authenticated'`**:
59
+ `initialize()` sets that state synchronously, before any request, and every
60
+ verification settles behind that render. Never hold the first paint on
61
+ `validateSession()` or a `/me` request of your own — a protected page then pays
62
+ an API round trip on every navigation (xr-molecule-flash-m177: ~30 full page
63
+ loads in one pagination spec; its four tests sat at 15.0–15.9 s of a 15 s
64
+ budget and the grader timed two out). Route guards read this same `status`:
65
+ wait only while it is `'loading'`.
58
66
  `validateSession()` is the background re-check that produces the last two: call
59
- it after the first render — a 4xx clears the token (`'unauthenticated'`), a
60
- network failure or 5xx KEEPS it (`'unavailable'`, retryable). Do not build a
67
+ it after the first render — a 401 or 403 clears the token (`'unauthenticated'`),
68
+ while a network failure, a 5xx, or a 404 from a wrong `currentUserEndpoint`
69
+ KEEPS the token and the user (`'unavailable'`, retryable; the 404 error names
70
+ `currentUserEndpoint`). Do not build a
61
71
  `validating`/`unavailable` flag beside the client: a second module-level state
62
72
  plus its own listener set is a second session store, and it drifts from the
63
73
  client's.
@@ -71,10 +81,27 @@ therefore schedules nothing: the session stays until the API answers 401
71
81
  (`validateSession()` clears it) or the user logs out. `autoRefresh: true`
72
82
  against such an API is harmless but pointless — leave it `false`.
73
83
 
84
+ **Seeding a session from an exchange the app runs itself: `setSession()`.** When an OAuth
85
+ redirect or the app's own sign-in endpoint has already issued the token and returned the
86
+ user, `setSession({ accessToken, user })` stores both and flips the client to
87
+ `authenticated` in one update, so every hook reading the shared client is signed in at once
88
+ (a form's own sign-in code wrote the token and the user with `setAccessToken`/`setUser`,
89
+ which change their field only — the UI stayed signed out and nine browser assertions timed
90
+ out, xr-molecule-flash-m178). `setAccessToken()` and `setUser()` are field setters, not a
91
+ sign-in: neither sets `authenticated`.
92
+
93
+ **A background verification that returns an unchanged user changes nothing.** When
94
+ `validateSession()` (or a reload's restore) answers with the same profile data the client
95
+ already holds, the client keeps the SAME user object and does not notify subscribers — so an
96
+ effect seeded from `user` does not re-run, and a form it fills keeps what has been typed.
97
+ Seed such a form once (on its first load), never on every `user` identity.
98
+
74
99
  **A token another tool reads at its own key is a CONFIG field, not a mirror.**
75
100
  An e2e suite or host page that expects `localStorage.jwtToken` (the RealWorld
76
- specs do) is satisfied by `tokenKey: 'jwtToken'` with
77
- `storage: createBrowserStorageAdapter()`; writing the token into that key from a
101
+ specs do) is satisfied by `tokenKey: 'jwtToken'` — in a browser the client's
102
+ storage default is already `createBrowserStorageAdapter()`, so with an explicit
103
+ `'memory'` store you must also pass
104
+ `storage: createBrowserStorageAdapter()`. Writing the token into that key from a
78
105
  subscription beside the client is a second store. In React, `useAuth()` from
79
106
  `@molecule/app-react` returns the client's `state` (with `status`), `user` and
80
107
  `isAuthenticated`; the token is `useAuthClient().getAccessToken()`.
@@ -341,6 +368,23 @@ interface AuthState<T = UserProfile> {
341
368
  }
342
369
  ```
343
370
 
371
+ #### `AuthSessionSeed`
372
+
373
+ A complete session an exchange outside the client has already established — an OAuth
374
+ code→token redirect, or the app's own sign-in endpoint. {@link AuthClient.setSession} adopts
375
+ it in one update: the token and the user are stored and the client becomes `authenticated`.
376
+
377
+ ```typescript
378
+ interface AuthSessionSeed<T = UserProfile> {
379
+ /** The access token the exchange issued. */
380
+ accessToken: string
381
+ /** The profile that token belongs to, as the API returned it. */
382
+ user: T
383
+ /** A refresh token, when the exchange issued one; omitted leaves the stored one untouched. */
384
+ refreshToken?: string | null
385
+ }
386
+ ```
387
+
344
388
  #### `AuthClient`
345
389
 
346
390
  Auth client interface that all auth bond packages must implement.
@@ -372,6 +416,11 @@ interface AuthClient<
372
416
  * hitting the network. Intended for local refreshes after a per-app
373
417
  * mutation (e.g., the user just PATCHed their own profile and the
374
418
  * server returned the canonical row). Does NOT change tokens.
419
+ *
420
+ * This does not sign the client in: `authenticated` and `status` are
421
+ * unchanged, so an anonymous client stays anonymous. Use
422
+ * {@link login} / {@link register} for a credential exchange, or
423
+ * {@link setSession} when the token was issued outside the client.
375
424
  */
376
425
  setUser(user: T | null): void
377
426
 
@@ -381,14 +430,34 @@ interface AuthClient<
381
430
  getAccessToken(): string | null
382
431
 
383
432
  /**
384
- * Stores the access token in the configured token storage adapter (in-memory
385
- * by default). Use this to seed the token after an out-of-band exchange (e.g.
386
- * the OAuth code→token redirect) instead of writing to `localStorage` directly,
387
- * which would violate the in-memory-default storage contract and make the bearer
388
- * token JS-readable (XSS-exfiltratable). Pass `null` to clear it.
433
+ * Stores the access token in the configured token storage adapter (in a
434
+ * browser, Web Storage; `'memory'` where no browser store exists). Use this
435
+ * to seed the token after an out-of-band exchange (e.g. the OAuth
436
+ * code→token redirect) instead of writing to `localStorage` directly,
437
+ * which would violate the storage contract and make a stored bearer token
438
+ * JS-readable (XSS-exfiltratable). Pass `null` to clear it.
439
+ *
440
+ * This changes token storage only; it does not sign the client in
441
+ * (`authenticated` and `status` are unchanged). Use {@link login} /
442
+ * {@link register} for a credential exchange, or call {@link setSession}
443
+ * with the token and the user to establish an already-issued session.
389
444
  */
390
445
  setAccessToken(token: string | null): void
391
446
 
447
+ /**
448
+ * Adopts a session an out-of-band exchange already established: stores the
449
+ * access token (and the refresh token when given), caches the user, and
450
+ * sets the COMPLETE state — `initialized`, `authenticated`, `status:
451
+ * 'authenticated'`, no error — in one update, with one subscriber
452
+ * notification. Every hook reading the shared client sees the user as
453
+ * signed in immediately.
454
+ *
455
+ * This is the way to seed an OAuth/custom sign-in. Calling
456
+ * {@link setAccessToken} and {@link setUser} instead does NOT sign the
457
+ * client in.
458
+ */
459
+ setSession(session: AuthSessionSeed<T>): void
460
+
392
461
  /**
393
462
  * Gets the refresh token.
394
463
  */
@@ -437,6 +506,19 @@ interface AuthClient<
437
506
 
438
507
  /**
439
508
  * Initializes auth state (e.g., from stored tokens).
509
+ *
510
+ * With a stored token AND a stored user it marks the session `'authenticated'`
511
+ * SYNCHRONOUSLY — before its first `await` — so the first render is the
512
+ * signed-in UI and no page waits on a verification round trip. Any check
513
+ * (a refresh, the cookie-restore probe) settles behind that render, and only an
514
+ * explicit 401/403 ends the session; a network/5xx/404 failure keeps it and
515
+ * reports `'unavailable'`. With no stored user the client stays `'loading'`
516
+ * until the cookie restore decides.
517
+ *
518
+ * Do not put this call (or a `/me` request of your own) in front of the first
519
+ * render, and do not render a loading screen over a session it just restored:
520
+ * `whenRestored()` already resolves as soon as the restore has settled, and the
521
+ * HTTP gate built on it holds requests for exactly that window.
440
522
  */
441
523
  initialize(): Promise<void>
442
524
 
@@ -448,11 +530,13 @@ interface AuthClient<
448
530
  *
449
531
  * - The server confirms the user: the session is refreshed and {@link AuthState.status}
450
532
  * stays `'authenticated'`.
451
- * - The server answers 4xx: the token is dead — the stored session is cleared and
452
- * the status becomes `'unauthenticated'`.
453
- * - The server cannot answer (network failure or 5xx): the session is KEPT and the
454
- * status becomes `'unavailable'`, so the app can retry instead of signing the
455
- * user out over a bad connection.
533
+ * - The server answers 401 or 403: the token is dead — the stored session is
534
+ * cleared and the status becomes `'unauthenticated'`.
535
+ * - The server cannot confirm the session — a network failure, a 5xx, or a 404
536
+ * from a misconfigured {@link AuthClientConfig.currentUserEndpoint}: the
537
+ * session (token AND user) is KEPT and the status becomes `'unavailable'`, so
538
+ * the app can retry instead of signing the user out over a bad connection or a
539
+ * route that does not exist. The 404 error names `currentUserEndpoint`.
456
540
  *
457
541
  * Do not hand-write this beside the client: a parallel token/user store for a
458
542
  * "validating"/"unavailable" flag is the race this method removes.
@@ -561,11 +645,17 @@ interface AuthClientConfig {
561
645
  profileEndpoint?: string
562
646
 
563
647
  /**
564
- * Current-user endpoint for session restore (default `/users/me`). Called by
565
- * `initialize()` with credentials to re-establish the session after a full page
566
- * load when the token did not survive the reload itself — i.e. when `storage` is
567
- * `'memory'` and the API sets an httpOnly session cookie. Must return the user
568
- * (as `user` or `props`).
648
+ * The API route that returns the signed-in user (default `/users/me`). Called
649
+ * with credentials by `initialize()` to re-establish the session after a full
650
+ * page load when the token did not survive the reload itself — i.e. when
651
+ * `storage` is `'memory'` and the API sets an httpOnly session cookie — and by
652
+ * {@link AuthClient.validateSession} on every reload to confirm a stored token
653
+ * is still valid. Must return the user (as `user` or `props`).
654
+ *
655
+ * Set it whenever the app serves its own current-user route: a wrong value
656
+ * answers 404, which `validateSession` reports as `'unavailable'` and keeps the
657
+ * stored token instead of signing the user out — but the session then stays
658
+ * unconfirmed.
569
659
  */
570
660
  currentUserEndpoint?: string
571
661
 
@@ -583,6 +673,12 @@ interface AuthClientConfig {
583
673
  * second library, a host page — instead of mirroring the token into
584
674
  * `localStorage` beside the client. The refresh token and the cached user keep
585
675
  * the prefix.
676
+ *
677
+ * In a browser the client's storage default is {@link createBrowserStorageAdapter},
678
+ * so `tokenKey` alone writes the token to exactly that key and a reload keeps
679
+ * the session. An EXPLICIT `storage: 'memory'` beside it warns at construction:
680
+ * the value then lives in a process-local Map nothing outside this client can
681
+ * read, and a reload signs the user out.
586
682
  */
587
683
  tokenKey?: string
588
684
 
@@ -622,9 +718,23 @@ interface AuthClientConfig {
622
718
  requestBody?: (kind: 'login' | 'register', payload: Record<string, unknown>) => unknown
623
719
 
624
720
  /**
625
- * Turn the API's login/register answer into the client's
721
+ * Turn an answer that carries a user into the client's
626
722
  * `{ accessToken, refreshToken?, user }` (default: the answer as given).
627
723
  * RealWorld, for example: `(data) => ({ accessToken: data.user.token, user: data.user })`.
724
+ *
725
+ * It is the ONE user-shape adapter, and the client applies it on EVERY path
726
+ * that adopts a user from an API answer — `login()`, `register()`,
727
+ * `validateSession()`, the cookie restore `initialize()`/`refresh()` run, the
728
+ * token-refresh answer, and `updateProfile()`. Those answers carry the raw wire
729
+ * user (a current-user route typically answers a bare `{ user }`), so applying
730
+ * the same mapping is what keeps the adapted profile after a reload instead of
731
+ * replacing it with the unadapted one (xr-molecule-flash-m184). A mapper that
732
+ * cannot read a given answer (it throws, or returns no `user`) leaves that
733
+ * answer's user as it came.
734
+ *
735
+ * It is NOT applied to `setSession()` or `setUser()`: those receive a profile
736
+ * the caller already built in this client's shape, not a wire answer.
737
+ *
628
738
  * @param data - The parsed response body.
629
739
  * @returns The auth result.
630
740
  */
@@ -634,19 +744,21 @@ interface AuthClientConfig {
634
744
  * Where the access/refresh token and the cached user are kept — the field that
635
745
  * decides whether a page reload keeps the user signed in.
636
746
  *
637
- * **In-memory is the secure default.** `'memory'` is a process-local store, lost
638
- * on reload. It is correct when the API sets an httpOnly session cookie, which
639
- * `initialize()` then restores through {@link AuthClientConfig.currentUserEndpoint}
640
- * — the strongest configuration, because no script can read the cookie.
747
+ * **In a browser the default is `createBrowserStorageAdapter()` (Web Storage)**,
748
+ * so a reload keeps the session with no wiring of its own; outside a browser
749
+ * (SSR, a Node test) the default is `'memory'`. Pass `'memory'` EXPLICITLY when
750
+ * the API sets an httpOnly session cookie and you do not want a script-readable
751
+ * copy: `'memory'` is a process-local store, and `initialize()` then restores
752
+ * through {@link AuthClientConfig.currentUserEndpoint} — the strongest
753
+ * configuration, because no script can read the cookie.
641
754
  *
642
- * **An app whose external contract requires a token in storage opts in
643
- * EXPLICITLY, with these two fields together.** A supplied e2e suite (the
644
- * RealWorld specs read `localStorage.jwtToken` in `SELECTORS.md`), a host page
645
- * or a second library can require the token at its own key: then set
646
- * `storage: createBrowserStorageAdapter()` and `tokenKey: 'jwtToken'`, and the
647
- * session (token AND user) is restored synchronously in `initialize()` — with no
648
- * store of your own. `tokenKey` places the access token at that exact key; the
649
- * refresh token and the cached user keep `storagePrefix`.
755
+ * **An app whose external contract requires the token at a specific key** — a
756
+ * supplied e2e suite (the RealWorld specs read `localStorage.jwtToken` in
757
+ * `SELECTORS.md`), a host page, a second library — sets `tokenKey: 'jwtToken'`;
758
+ * in a browser that is enough, because the default store is Web Storage. The
759
+ * session (token AND user) is then restored synchronously in `initialize()` —
760
+ * with no store of your own. `tokenKey` places the access token at that exact
761
+ * key; the refresh token and the cached user keep `storagePrefix`.
650
762
  *
651
763
  * A bearer token in Web Storage is readable by any script on the origin, so an
652
764
  * XSS can steal it; see {@link createBrowserStorageAdapter}'s `@remarks` for the
@@ -664,14 +776,13 @@ interface AuthClientConfig {
664
776
  * ```typescript
665
777
  * import { createBrowserStorageAdapter } from '@molecule/app-auth'
666
778
  *
667
- * // A suite/host page reads the token at its own key — the opt-in:
779
+ * // A suite/host page reads the token at its own key (the browser default
780
+ * // already stores it; `storage` is shown for an explicit choice):
668
781
  * storage: createBrowserStorageAdapter()
669
782
  * tokenKey: 'jwtToken'
670
- * // A reload keeps the session at this client's own keys:
671
- * storage: createBrowserStorageAdapter()
672
783
  * // Only this tab's lifetime, at a narrower exposure than localStorage:
673
784
  * storage: createBrowserStorageAdapter({ kind: 'session' })
674
- * // An httpOnly cookie session instead — memory only, the stronger route:
785
+ * // An httpOnly cookie session — keep the bearer out of script-reachable storage:
675
786
  * storage: 'memory'
676
787
  * ```
677
788
  */
@@ -686,50 +797,7 @@ interface AuthClientConfig {
686
797
  * Use this when the server also issues sessions that must never appear
687
798
  * signed-in client-side — e.g. an anonymous/guest session whose user row
688
799
  * (`isAnonymous`) would otherwise hydrate as a logged-in user. Defaults to
689
- * accepting every restored user.
690
- * @param user - The user returned by the restore probe.
691
- * @returns Whether to establish the client session from this user.
692
- */
693
- shouldRestoreUser?: (user: UserProfile) => boolean
694
-
695
- /**
696
- * Refresh the access token before it expires. A no-op unless the API issued a
697
- * refresh token at login/register — with none stored, nothing is scheduled and
698
- * the session is never ended by the refresh timer; an API that rejects the
699
- * access token (401) or an explicit logout ends it instead. Defaults to `true`.
700
- */
701
- autoRefresh?: boolean
702
-
703
- /**
704
- * Refresh tokens this many seconds before expiry.
705
- */
706
- refreshBuffer?: number
707
-
708
- /**
709
- * Forgot password / password reset request endpoint.
710
- */
711
- forgotPasswordEndpoint?: string
712
-
713
- /**
714
- * Password reset confirmation endpoint.
715
- */
716
- resetPasswordEndpoint?: string
717
-
718
- /**
719
- * Change password endpoint.
720
- */
721
- changePasswordEndpoint?: string
722
-
723
- /**
724
- * Available OAuth providers (e.g., ['github', 'google', 'gitlab']).
725
- */
726
- oauthProviders?: string[]
727
-
728
- /**
729
- * OAuth endpoint path (default: '/oauth').
730
- */
731
- oauthEndpoint?: string
732
- }
800
+ // … truncated — the full declaration is in node_modules/@molecule/app-auth/dist/types.d.ts
733
801
  ````
734
802
 
735
803
  #### `TokenStorage`
@@ -857,10 +925,11 @@ token as a background request — never in front of the first render. A page tha
857
925
  own `/me` call pays a round trip on EVERY navigation (xr-molecule-flash-m122: a 1–5 s restore
858
926
  window, eight `waitForURL` timeouts).
859
927
 
860
- Pass it as `AuthClientConfig.storage` to keep the session across a page reload:
861
- the client's default (`'memory'`) is lost on reload, and re-establishing the
862
- session then needs a credential that survived — this adapter, or an httpOnly
863
- session cookie the API sets (see the security note below).
928
+ In a browser this IS the auth client's default `AuthClientConfig.storage`, so a scaffolded
929
+ app keeps its session across a page reload with no extra wiring. Outside a browser (SSR, a Node
930
+ test) the client defaults to `'memory'` instead, because there is no Web Storage to use. Pass
931
+ `'memory'` explicitly to keep the bearer token out of script-reachable storage when the API sets
932
+ an httpOnly session cookie (see the security note below).
864
933
 
865
934
  ```ts
866
935
  import { createJWTAuthClient, createBrowserStorageAdapter } from '@molecule/app-auth'
@@ -871,18 +940,18 @@ const authClient = createJWTAuthClient({
871
940
  })
872
941
  ```
873
942
 
874
- The scaffold's `authConfig` already passes it, so a scaffolded app survives a
943
+ The browser client already defaults to it, so a scaffolded app survives a
875
944
  reload out of the box; each of these replaces that default:
876
945
 
877
- ````ts
946
+ ```ts
878
947
  storage: 'memory' // memory only: pair it with an httpOnly cookie session
879
- storage: createBrowserStorageAdapter({ kind: 'session' }) // survives a reload, dies with the tab
948
+ ```
880
949
 
881
- *(description truncated — read the full text in `node_modules/@molecule/app-auth/dist/browser-storage.d.ts`.)*
950
+ _(description truncated — read the full text in `node_modules/@molecule/app-auth/dist/browser-storage.d.ts`.)_
882
951
 
883
952
  ```typescript
884
953
  function createBrowserStorageAdapter(options?: BrowserStorageAdapterOptions): StorageAdapter
885
- ````
954
+ ```
886
955
 
887
956
  - `options` — Which Web Storage area to use, or a store to use directly.
888
957
 
@@ -1064,7 +1133,11 @@ Peer dependencies:
1064
1133
  - **An API with its own auth envelope keeps this client.** `requestBody` wraps the
1065
1134
  login/register payload (`(kind, p) => ({ user: p })`), `mapAuthResponse` reads the
1066
1135
  token and user out of the answer (`(d) => ({ accessToken: d.user.token, user: d.user })`),
1067
- and `authScheme: 'Token'` sends `Authorization: Token <jwt>` — both are fields of the
1136
+ and `authScheme: 'Token'` sends `Authorization: Token <jwt>`. `mapAuthResponse` is the
1137
+ ONE user-shape adapter: the client applies it to every answer that carries a user (login,
1138
+ register, `validateSession()`, the cookie restore, a token refresh, `updateProfile()`), so
1139
+ the adapted profile survives a reload instead of being replaced by the raw wire user.
1140
+ Both are fields of the
1068
1141
  `AuthClientConfig` the client is CONSTRUCTED with
1069
1142
  (`setClient(createJWTAuthClient({ authScheme: 'Token', … }))` in the auth bond), not
1070
1143
  properties of a client that already exists. Do not hand-write an auth client for a spec
@@ -1109,12 +1182,12 @@ by one. A box you can't check is an integration bug to fix — not a skip:
1109
1182
  - [ ] The session survives a FULL page reload: after login, hard-reload → you
1110
1183
  stay signed in (the profile/sign-out UI a signed-in visitor sees is still
1111
1184
  there), and you are NOT bounced to login. Which route does it is the app's
1112
- `AuthClientConfig.storage`: the scaffold's `createBrowserStorageAdapter()` keeps
1113
- the token in the browser, an httpOnly session cookie is restored by
1114
- `initialize()` through `currentUserEndpoint` (default `/users/me`). If the
1115
- app has neither — `storage` left at `'memory'` against an API that sets no
1116
- cookie — this box cannot be checked, and the fix is the config, not a token
1117
- store written beside the client.
1185
+ `AuthClientConfig.storage`: in a browser the client defaults to
1186
+ `createBrowserStorageAdapter()`, which keeps the token there; an httpOnly
1187
+ session cookie is restored by `initialize()` through `currentUserEndpoint`
1188
+ (default `/users/me`). If the app sets `storage: 'memory'` explicitly against
1189
+ an API that sets no cookie, this box cannot be checked, and the fix is the
1190
+ config, not a token store written beside the client.
1118
1191
  - [ ] Password reset round-trips: `requestPasswordReset({ email })` then
1119
1192
  `confirmPasswordReset({ token, password })` with the emailed token, after
1120
1193
  which `login()` with the NEW password succeeds and the OLD password no
@@ -38,10 +38,11 @@ export interface BrowserStorageAdapterOptions {
38
38
  * own `/me` call pays a round trip on EVERY navigation (xr-molecule-flash-m122: a 1–5 s restore
39
39
  * window, eight `waitForURL` timeouts).
40
40
  *
41
- * Pass it as `AuthClientConfig.storage` to keep the session across a page reload:
42
- * the client's default (`'memory'`) is lost on reload, and re-establishing the
43
- * session then needs a credential that survived — this adapter, or an httpOnly
44
- * session cookie the API sets (see the security note below).
41
+ * In a browser this IS the auth client's default `AuthClientConfig.storage`, so a scaffolded
42
+ * app keeps its session across a page reload with no extra wiring. Outside a browser (SSR, a Node
43
+ * test) the client defaults to `'memory'` instead, because there is no Web Storage to use. Pass
44
+ * `'memory'` explicitly to keep the bearer token out of script-reachable storage when the API sets
45
+ * an httpOnly session cookie (see the security note below).
45
46
  *
46
47
  * ```ts
47
48
  * import { createJWTAuthClient, createBrowserStorageAdapter } from '@molecule/app-auth'
@@ -52,7 +53,7 @@ export interface BrowserStorageAdapterOptions {
52
53
  * })
53
54
  * ```
54
55
  *
55
- * The scaffold's `authConfig` already passes it, so a scaffolded app survives a
56
+ * The browser client already defaults to it, so a scaffolded app survives a
56
57
  * reload out of the box; each of these replaces that default:
57
58
  *
58
59
  * ```ts
@@ -72,10 +73,11 @@ export interface BrowserStorageAdapterOptions {
72
73
  * your API supports it.** Script cannot read an httpOnly cookie, so an XSS
73
74
  * cannot exfiltrate the credential; the token stays in memory and
74
75
  * `initialize()` restores the session through `currentUserEndpoint`
75
- * (`@molecule/api-resource-user` issues exactly this cookie — that is why the
76
- * client's default is `'memory'`). If the API sets one, `storage: 'memory'` is
77
- * the strongest configuration. If it does not and you cannot change it, keep
78
- * the token's lifetime short and rotate it on refresh.
76
+ * (`@molecule/api-resource-user` issues exactly this cookie — pass
77
+ * `storage: 'memory'` when it does). `'memory'` is then the strongest
78
+ * configuration. If the API sets no such cookie and you cannot change that,
79
+ * this adapter is the alternative — keep the token's lifetime short and rotate
80
+ * it on refresh.
79
81
  * - **`kind: 'session'` narrows the window** to the tab's lifetime at the cost of
80
82
  * losing the session in a new tab; `'local'` is the default because a reload is
81
83
  * the requirement it exists for, and a fresh tab is the same person.
@@ -1 +1 @@
1
- {"version":3,"file":"browser-storage.d.ts","sourceRoot":"","sources":["../src/browser-storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,CAAA;AAEpD,4EAA4E;AAC5E,MAAM,WAAW,4BAA4B;IAC3C;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,kBAAkB,CAAA;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,eAAO,MAAM,2BAA2B,GACtC,UAAS,4BAAiC,KACzC,cA6EF,CAAA"}
1
+ {"version":3,"file":"browser-storage.d.ts","sourceRoot":"","sources":["../src/browser-storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,CAAA;AAEpD,4EAA4E;AAC5E,MAAM,WAAW,4BAA4B;IAC3C;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,kBAAkB,CAAA;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,eAAO,MAAM,2BAA2B,GACtC,UAAS,4BAAiC,KACzC,cA6EF,CAAA"}
@@ -29,10 +29,11 @@ const warn = (message, error) => {
29
29
  * own `/me` call pays a round trip on EVERY navigation (xr-molecule-flash-m122: a 1–5 s restore
30
30
  * window, eight `waitForURL` timeouts).
31
31
  *
32
- * Pass it as `AuthClientConfig.storage` to keep the session across a page reload:
33
- * the client's default (`'memory'`) is lost on reload, and re-establishing the
34
- * session then needs a credential that survived — this adapter, or an httpOnly
35
- * session cookie the API sets (see the security note below).
32
+ * In a browser this IS the auth client's default `AuthClientConfig.storage`, so a scaffolded
33
+ * app keeps its session across a page reload with no extra wiring. Outside a browser (SSR, a Node
34
+ * test) the client defaults to `'memory'` instead, because there is no Web Storage to use. Pass
35
+ * `'memory'` explicitly to keep the bearer token out of script-reachable storage when the API sets
36
+ * an httpOnly session cookie (see the security note below).
36
37
  *
37
38
  * ```ts
38
39
  * import { createJWTAuthClient, createBrowserStorageAdapter } from '@molecule/app-auth'
@@ -43,7 +44,7 @@ const warn = (message, error) => {
43
44
  * })
44
45
  * ```
45
46
  *
46
- * The scaffold's `authConfig` already passes it, so a scaffolded app survives a
47
+ * The browser client already defaults to it, so a scaffolded app survives a
47
48
  * reload out of the box; each of these replaces that default:
48
49
  *
49
50
  * ```ts
@@ -63,10 +64,11 @@ const warn = (message, error) => {
63
64
  * your API supports it.** Script cannot read an httpOnly cookie, so an XSS
64
65
  * cannot exfiltrate the credential; the token stays in memory and
65
66
  * `initialize()` restores the session through `currentUserEndpoint`
66
- * (`@molecule/api-resource-user` issues exactly this cookie — that is why the
67
- * client's default is `'memory'`). If the API sets one, `storage: 'memory'` is
68
- * the strongest configuration. If it does not and you cannot change it, keep
69
- * the token's lifetime short and rotate it on refresh.
67
+ * (`@molecule/api-resource-user` issues exactly this cookie — pass
68
+ * `storage: 'memory'` when it does). `'memory'` is then the strongest
69
+ * configuration. If the API sets no such cookie and you cannot change that,
70
+ * this adapter is the alternative — keep the token's lifetime short and rotate
71
+ * it on refresh.
70
72
  * - **`kind: 'session'` narrows the window** to the tab's lifetime at the cost of
71
73
  * losing the session in a new tab; `'local'` is the default because a reload is
72
74
  * the requirement it exists for, and a fresh tab is the same person.
@@ -1 +1 @@
1
- {"version":3,"file":"browser-storage.js","sourceRoot":"","sources":["../src/browser-storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AA8BhD,gFAAgF;AAChF,MAAM,IAAI,GAAG,CAAC,OAAe,EAAE,KAAe,EAAQ,EAAE;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;QAChC,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;;YACxC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IACtC,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,2EAA2E;QAC3E,2EAA2E;QAC3E,wBAAwB;IAC1B,CAAC;AACH,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,UAAwC,EAAE,EAC1B,EAAE;IAClB,MAAM,EAAE,IAAI,GAAG,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAC3C,2EAA2E;IAC3E,2DAA2D;IAC3D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,yFAAyF;IACzF,IAAI,QAA2C,CAAA;IAC/C,gFAAgF;IAChF,IAAI,MAAM,GAAG,KAAK,CAAA;IAElB,MAAM,OAAO,GAAG,GAA0B,EAAE;QAC1C,IAAI,OAAO;YAAE,OAAO,OAAO,CAAA;QAC3B,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,QAAQ,CAAA;QAChE,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,UAAmE,CAAA;YACjF,MAAM,GAAG,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAA;YAC1E,IAAI,GAAG,EAAE,CAAC;gBACR,mEAAmE;gBACnE,6EAA6E;gBAC7E,MAAM,KAAK,GAAG,yBAAyB,CAAA;gBACvC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;gBACzB,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;gBACrB,QAAQ,GAAG,GAAG,CAAA;gBACd,OAAO,GAAG,CAAA;YACZ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,GAAG,IAAI,CAAA;gBACb,IAAI,CAAC,OAAO,IAAI,gEAAgE,EAAE,KAAK,CAAC,CAAA;YAC1F,CAAC;QACH,CAAC;QACD,4EAA4E;QAC5E,8CAA8C;QAC9C,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,OAAO;QACL,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;YACvB,IAAI,CAAC,KAAK;gBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAA;YAC1C,IAAI,CAAC;gBACH,sEAAsE;gBACtE,4DAA4D;gBAC5D,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAA;YACtD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,4DAA4D,EAAE,KAAK,CAAC,CAAA;gBACzE,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAA;YAChC,CAAC;QACH,CAAC;QACD,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YACtB,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;YACvB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;gBACtB,OAAM;YACR,CAAC;YACD,IAAI,CAAC;gBACH,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;gBACzB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACpB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,0EAA0E;gBAC1E,2EAA2E;gBAC3E,wDAAwD;gBACxD,IAAI,CAAC,kEAAkE,EAAE,KAAK,CAAC,CAAA;gBAC/E,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YACxB,CAAC;QACH,CAAC;QACD,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE;YAClB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAClB,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;YACvB,IAAI,CAAC,KAAK;gBAAE,OAAM;YAClB,IAAI,CAAC;gBACH,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,qCAAqC,EAAE,KAAK,CAAC,CAAA;YACpD,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"browser-storage.js","sourceRoot":"","sources":["../src/browser-storage.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAUhB,WAAW,EACZ,MAAM,YAAY,CAAA;AA4CnB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAC9B,CAAC,SAAS,WAAW,GAAG,WAAW,EACnC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAEhE,SAAQ,gBAAqB,KAC5B,UAAU,CAAC,CAAC,EAAE,SAAS,CAokBzB,CAAA"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAUhB,WAAW,EACZ,MAAM,YAAY,CAAA;AAmHnB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAC9B,CAAC,SAAS,WAAW,GAAG,WAAW,EACnC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAEhE,SAAQ,gBAAqB,KAC5B,UAAU,CAAC,CAAC,EAAE,SAAS,CA+wBzB,CAAA"}