@djangocfg/api 2.1.484 → 2.1.487

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/api",
3
- "version": "2.1.484",
3
+ "version": "2.1.487",
4
4
  "description": "Auto-generated TypeScript API client with React hooks, SWR integration, and Zod validation for Django REST Framework backends",
5
5
  "keywords": [
6
6
  "django",
@@ -84,7 +84,7 @@
84
84
  "zod": "^4.3.6"
85
85
  },
86
86
  "devDependencies": {
87
- "@djangocfg/typescript-config": "^2.1.484",
87
+ "@djangocfg/typescript-config": "^2.1.487",
88
88
  "@testing-library/react": "^16.3.2",
89
89
  "@types/node": "^25.9.5",
90
90
  "@types/react": "19.2.15",
@@ -77,8 +77,30 @@ const cookieBackend: KVStore = {
77
77
  },
78
78
  };
79
79
 
80
- let _storage: KVStore = localStorageBackend;
80
+ function backendFor(mode: StorageMode): KVStore {
81
+ return mode === 'cookie'
82
+ ? cookieBackend
83
+ : mode === 'sessionStorage'
84
+ ? sessionStorageBackend
85
+ : localStorageBackend;
86
+ }
87
+
88
+ /**
89
+ * The session backend. `localStorage` by default and for every login path.
90
+ *
91
+ * `setStorageMode` exists for a host that must relocate the whole session once
92
+ * at bootstrap — e.g. 'cookie' so Next.js `cookies()` can read the token during
93
+ * SSR. It is NOT a per-login switch: this value lives in module memory and
94
+ * resets to the default on every page load, so tokens written to a non-default
95
+ * backend after startup would be stranded — present in the browser, invisible
96
+ * to the client, and every request would go out unauthenticated.
97
+ *
98
+ * Session LENGTH is therefore not a client concern. The server decides it when
99
+ * minting the refresh token (`remember_me`); the client just stores whatever it
100
+ * was given, in one place.
101
+ */
81
102
  let _storageMode: StorageMode = 'localStorage';
103
+ let _storage: KVStore = backendFor(_storageMode);
82
104
 
83
105
  /** Detect locale from `NEXT_LOCALE` cookie or `navigator.language`. */
84
106
  function detectLocale(): string | null {
@@ -324,13 +346,12 @@ function pushClientConfig(): void {
324
346
  export const auth = {
325
347
  // ── Storage mode ──────────────────────────────────────────────────
326
348
  getStorageMode(): StorageMode { return _storageMode; },
349
+ /** Relocate the session backend. Call once at bootstrap, before any sign-in:
350
+ * the mode is module state and resets on reload, so a later switch strands
351
+ * whatever was written after it. */
327
352
  setStorageMode(mode: StorageMode): void {
328
353
  _storageMode = mode;
329
- _storage = mode === 'cookie'
330
- ? cookieBackend
331
- : mode === 'sessionStorage'
332
- ? sessionStorageBackend
333
- : localStorageBackend;
354
+ _storage = backendFor(mode);
334
355
  notifySessionChanged();
335
356
  },
336
357
 
@@ -346,8 +367,13 @@ export const auth = {
346
367
  notifySessionChanged();
347
368
  },
348
369
  clearTokens(): void {
349
- _storage.set(ACCESS_KEY, null);
350
- _storage.set(REFRESH_KEY, null);
370
+ // Clear every backend, not just the active one. Sign-out must leave nothing
371
+ // behind anywhere — including tokens an older build stranded in
372
+ // sessionStorage — or the next page load could resurrect a dead session.
373
+ for (const backend of [localStorageBackend, sessionStorageBackend, cookieBackend]) {
374
+ backend.set(ACCESS_KEY, null);
375
+ backend.set(REFRESH_KEY, null);
376
+ }
351
377
  notifySessionChanged();
352
378
  },
353
379
  /** Session-aware: token PRESENT and not past its `exp` (or a live refresh
@@ -183,7 +183,11 @@ export function AccountsProvider({ children }: AccountsProviderProps) {
183
183
 
184
184
  if (result.access && result.refresh) {
185
185
  // The ONE write path — atomic pair, notifies every session subscriber.
186
- auth.setStorageMode(result.persistent_session ? 'localStorage' : 'sessionStorage');
186
+ // Storage is always localStorage: "remember me" is a TOKEN LIFETIME
187
+ // decision the server already made (`remember_me` → REMEMBER_ME_LIFETIME),
188
+ // not a storage decision. Switching the client to sessionStorage here used
189
+ // to strand the tokens — the module reinitializes to localStorage on the
190
+ // next page load and would never look at sessionStorage again.
187
191
  auth.setSession({ access: result.access, refresh: result.refresh });
188
192
  try {
189
193
  await refreshProfile({ callerId: 'verifyOTP', force: true });
@@ -168,8 +168,9 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
168
168
 
169
169
  authLogger.info('GitHub OAuth successful, user:', response.user);
170
170
 
171
- auth.setStorageMode(response.persistent_session ? 'localStorage' : 'sessionStorage');
172
- // The ONE write path atomic pair + session notification.
171
+ // The ONE write path — atomic pair + session notification. Storage is
172
+ // always localStorage; session length is the server's call via
173
+ // `remember_me`. See AccountsContext.verifyOTP.
173
174
  auth.setSession({ access: response.access, refresh: response.refresh });
174
175
 
175
176
  // Track successful OAuth
@@ -71,8 +71,9 @@ export const useTwoFactorVerify = (
71
71
  remaining_backup_codes?: number;
72
72
  persistent_session: boolean;
73
73
  }) => {
74
- auth.setStorageMode(response.persistent_session ? 'localStorage' : 'sessionStorage');
75
74
  // Save tokens — the ONE write path (atomic pair + session notification).
75
+ // Storage is always localStorage; session length is the server's call via
76
+ // `remember_me`. See AccountsContext.verifyOTP.
76
77
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
77
78
 
78
79
  if (response.warning) {