@djangocfg/api 2.1.582 → 2.1.584

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.
Files changed (69) hide show
  1. package/README.md +18 -0
  2. package/dist/OAuthTokenResponse-WEDM6IP5.mjs +19 -0
  3. package/dist/OAuthTokenResponse-WEDM6IP5.mjs.map +1 -0
  4. package/dist/OTPVerifyResponse-P5LDNM7H.mjs +20 -0
  5. package/dist/OTPVerifyResponse-P5LDNM7H.mjs.map +1 -0
  6. package/dist/PasswordLoginResponse-33B576NG.mjs +20 -0
  7. package/dist/PasswordLoginResponse-33B576NG.mjs.map +1 -0
  8. package/dist/auth.cjs +279 -103
  9. package/dist/auth.cjs.map +1 -1
  10. package/dist/auth.d.cts +14 -41
  11. package/dist/auth.d.ts +14 -41
  12. package/dist/auth.mjs +279 -101
  13. package/dist/auth.mjs.map +1 -1
  14. package/dist/clients.cjs +242 -54
  15. package/dist/clients.cjs.map +1 -1
  16. package/dist/clients.d.cts +13 -41
  17. package/dist/clients.d.ts +13 -41
  18. package/dist/clients.mjs +242 -54
  19. package/dist/clients.mjs.map +1 -1
  20. package/dist/hooks.cjs +242 -54
  21. package/dist/hooks.cjs.map +1 -1
  22. package/dist/hooks.mjs +242 -54
  23. package/dist/hooks.mjs.map +1 -1
  24. package/dist/index.cjs +247 -54
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +90 -63
  27. package/dist/index.d.ts +90 -63
  28. package/dist/index.mjs +247 -54
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/storage.cjs +268 -0
  31. package/dist/storage.cjs.map +1 -0
  32. package/dist/storage.d.cts +84 -0
  33. package/dist/storage.d.ts +84 -0
  34. package/dist/storage.mjs +247 -0
  35. package/dist/storage.mjs.map +1 -0
  36. package/package.json +8 -2
  37. package/src/__tests__/noStorageLiterals.test.ts +27 -0
  38. package/src/__tests__/storage.test.ts +134 -0
  39. package/src/__tests__/storageSsr.test.ts +14 -0
  40. package/src/_api/generated/_cfg_accounts/openapi.json +1 -28
  41. package/src/_api/generated/_cfg_accounts/schemas/OAuthCallbackRequestRequest.ts +0 -1
  42. package/src/_api/generated/_cfg_accounts/schemas/OAuthTokenResponse.ts +0 -1
  43. package/src/_api/generated/_cfg_accounts/schemas/OTPVerifyRequest.ts +0 -1
  44. package/src/_api/generated/_cfg_accounts/schemas/OTPVerifyResponse.ts +0 -1
  45. package/src/_api/generated/_cfg_accounts/schemas/PasswordLoginRequest.ts +0 -1
  46. package/src/_api/generated/_cfg_accounts/schemas/PasswordLoginResponse.ts +0 -1
  47. package/src/_api/generated/_cfg_totp/openapi.json +0 -5
  48. package/src/_api/generated/_cfg_totp/schemas/VerifyResponse.ts +0 -1
  49. package/src/_api/generated/helpers/auth.ts +28 -65
  50. package/src/_api/generated/helpers/index.ts +12 -0
  51. package/src/_api/generated/helpers/keys.ts +327 -0
  52. package/src/_api/generated/openapi.json +1 -33
  53. package/src/_api/generated/types.gen.ts +4 -59
  54. package/src/auth/__tests__/authRedirect.test.ts +1 -1
  55. package/src/auth/__tests__/passwordLogin.test.tsx +1 -3
  56. package/src/auth/__tests__/profileCache.test.ts +1 -1
  57. package/src/auth/context/AccountsContext.tsx +6 -8
  58. package/src/auth/context/AuthContext.tsx +3 -5
  59. package/src/auth/context/types.ts +1 -1
  60. package/src/auth/hooks/useAuthForm.ts +3 -5
  61. package/src/auth/hooks/useAuthFormState.ts +0 -4
  62. package/src/auth/hooks/useAuthRedirect.ts +3 -2
  63. package/src/auth/hooks/useGithubAuth.ts +14 -16
  64. package/src/auth/hooks/useLocalStorage.ts +7 -7
  65. package/src/auth/hooks/useProfileCache.ts +2 -1
  66. package/src/auth/hooks/useSessionStorage.ts +7 -7
  67. package/src/auth/hooks/useTwoFactorVerify.ts +2 -2
  68. package/src/auth/types/form.ts +0 -5
  69. package/src/storage.ts +21 -0
@@ -321,10 +321,6 @@ export type OAuthCallbackRequestRequest = {
321
321
  * Same redirect_uri used in authorize request. If not provided, uses config's site_url + callback_path
322
322
  */
323
323
  redirect_uri?: string;
324
- /**
325
- * Keep this browser signed in for 30 days
326
- */
327
- remember_me?: boolean;
328
324
  };
329
325
 
330
326
  /**
@@ -454,10 +450,6 @@ export type OAuthTokenResponse = {
454
450
  * True if user should be prompted to enable 2FA
455
451
  */
456
452
  should_prompt_2fa?: boolean;
457
- /**
458
- * Whether this login should persist across browser restarts
459
- */
460
- persistent_session?: boolean;
461
453
  };
462
454
 
463
455
  /**
@@ -545,10 +537,6 @@ export type OtpVerifyRequest = {
545
537
  * Source URL for tracking login (e.g., https://my.djangocfg.com)
546
538
  */
547
539
  source_url?: string;
548
- /**
549
- * Keep this browser signed in for 30 days
550
- */
551
- remember_me?: boolean;
552
540
  };
553
541
 
554
542
  /**
@@ -589,10 +577,6 @@ export type OtpVerifyResponse = {
589
577
  * Whether user should be prompted to enable 2FA
590
578
  */
591
579
  should_prompt_2fa?: boolean;
592
- /**
593
- * Whether this login should persist across browser restarts
594
- */
595
- persistent_session?: boolean;
596
580
  };
597
581
 
598
582
  /**
@@ -623,14 +607,12 @@ export type PasswordLoginError = {
623
607
  *
624
608
  * Subclasses SimpleJWT so the field/validation contract stays theirs, but
625
609
  * replaces ``validate()`` — the token pair itself is minted by the view via
626
- * ``mint_tokens_for_request`` so the DPoP binding and remembered-session
610
+ * ``mint_tokens_for_request`` so the DPoP binding and token
627
611
  * lifetime match OTP, OAuth and TOTP exactly.
628
612
  */
629
613
  export type PasswordLoginRequest = {
630
- /**
631
- * Keep this browser signed in for 30 days
632
- */
633
- remember_me?: boolean;
614
+ email: string;
615
+ password: string;
634
616
  };
635
617
 
636
618
  /**
@@ -664,10 +646,6 @@ export type PasswordLoginResponse = {
664
646
  * Whether user should be prompted to enable 2FA
665
647
  */
666
648
  should_prompt_2fa?: boolean;
667
- /**
668
- * Whether this login should persist across browser restarts
669
- */
670
- persistent_session?: boolean;
671
649
  };
672
650
 
673
651
  /**
@@ -868,10 +846,6 @@ export type VerifyResponse = {
868
846
  * User profile data
869
847
  */
870
848
  user: TotpVerifyUser;
871
- /**
872
- * Whether this login should persist across browser restarts
873
- */
874
- persistent_session: boolean;
875
849
  /**
876
850
  * Number of remaining backup codes (if backup code was used)
877
851
  */
@@ -1064,27 +1038,6 @@ export type OtpVerifyResponseWritable = {
1064
1038
  * Whether user should be prompted to enable 2FA
1065
1039
  */
1066
1040
  should_prompt_2fa?: boolean;
1067
- /**
1068
- * Whether this login should persist across browser restarts
1069
- */
1070
- persistent_session?: boolean;
1071
- };
1072
-
1073
- /**
1074
- * Validate email+password against live accounts only.
1075
- *
1076
- * Subclasses SimpleJWT so the field/validation contract stays theirs, but
1077
- * replaces ``validate()`` — the token pair itself is minted by the view via
1078
- * ``mint_tokens_for_request`` so the DPoP binding and remembered-session
1079
- * lifetime match OTP, OAuth and TOTP exactly.
1080
- */
1081
- export type PasswordLoginRequestWritable = {
1082
- /**
1083
- * Keep this browser signed in for 30 days
1084
- */
1085
- remember_me?: boolean;
1086
- email: string;
1087
- password: string;
1088
1041
  };
1089
1042
 
1090
1043
  /**
@@ -1118,10 +1071,6 @@ export type PasswordLoginResponseWritable = {
1118
1071
  * Whether user should be prompted to enable 2FA
1119
1072
  */
1120
1073
  should_prompt_2fa?: boolean;
1121
- /**
1122
- * Whether this login should persist across browser restarts
1123
- */
1124
- persistent_session?: boolean;
1125
1074
  };
1126
1075
 
1127
1076
  export type TokenRefreshWritable = {
@@ -1171,10 +1120,6 @@ export type VerifyResponseWritable = {
1171
1120
  * User profile data
1172
1121
  */
1173
1122
  user: TotpVerifyUserWritable;
1174
- /**
1175
- * Whether this login should persist across browser restarts
1176
- */
1177
- persistent_session: boolean;
1178
1123
  /**
1179
1124
  * Number of remaining backup codes (if backup code was used)
1180
1125
  */
@@ -1566,7 +1511,7 @@ export type CfgAccountsProfileUpdateUpdateResponses = {
1566
1511
  export type CfgAccountsProfileUpdateUpdateResponse = CfgAccountsProfileUpdateUpdateResponses[keyof CfgAccountsProfileUpdateUpdateResponses];
1567
1512
 
1568
1513
  export type CfgAccountsTokenCreateData = {
1569
- body: PasswordLoginRequestWritable;
1514
+ body: PasswordLoginRequest;
1570
1515
  path?: never;
1571
1516
  query?: never;
1572
1517
  url: '/cfg/accounts/token/';
@@ -37,7 +37,7 @@ function installSessionStorage() {
37
37
  return map;
38
38
  }
39
39
 
40
- const KEY = 'auth_redirect_url';
40
+ const KEY = 'cfg.auth.redirect_url';
41
41
 
42
42
  describe('useAuthRedirectManager (storage-backed paths)', () => {
43
43
  beforeEach(() => {
@@ -136,21 +136,19 @@ describe('AuthContext.login (password grant)', () => {
136
136
  expect(tokens).toEqual({ access: 'access-1', refresh: 'refresh-1' });
137
137
  });
138
138
 
139
- it('sends email, password and remember_me to the password endpoint', async () => {
139
+ it('sends email and password to the password endpoint', async () => {
140
140
  respondWith({ requires_2fa: false, access: 'access-1', refresh: 'refresh-1', user: USER });
141
141
  const { result } = await renderAuth();
142
142
 
143
143
  await act(async () => {
144
144
  await result.current.login('jane@example.com', 'correct horse', {
145
145
  skipRedirect: true,
146
- rememberMe: true,
147
146
  });
148
147
  });
149
148
 
150
149
  expect(loginWithPassword).toHaveBeenCalledWith({
151
150
  email: 'jane@example.com',
152
151
  password: 'correct horse',
153
- remember_me: true,
154
152
  });
155
153
  });
156
154
 
@@ -30,7 +30,7 @@ function installBrowserStorage() {
30
30
  return map;
31
31
  }
32
32
 
33
- const CACHE_KEY = 'user_profile_cache';
33
+ const CACHE_KEY = 'cfg.profile.cache';
34
34
  const NOW = 1_800_000_000_000;
35
35
 
36
36
  // Minimal User-shaped object; the cache is generic over its shape.
@@ -57,7 +57,7 @@ import type {
57
57
  OtpVerifyRequest,
58
58
  OtpRequestResponse,
59
59
  OtpVerifyResponse,
60
- PasswordLoginRequestWritable,
60
+ PasswordLoginRequest,
61
61
  PasswordLoginResponse,
62
62
  } from '../../_api/generated/_cfg_accounts/types.gen';
63
63
 
@@ -79,7 +79,7 @@ export interface AccountsContextValue {
79
79
 
80
80
  requestOTP: (data: OtpRequestRequest) => Promise<OtpRequestResponse>;
81
81
  verifyOTP: (data: OtpVerifyRequest) => Promise<OtpVerifyResponse>;
82
- loginWithPassword: (data: PasswordLoginRequestWritable) => Promise<PasswordLoginResponse>;
82
+ loginWithPassword: (data: PasswordLoginRequest) => Promise<PasswordLoginResponse>;
83
83
  logout: () => void;
84
84
  }
85
85
 
@@ -207,11 +207,9 @@ export function AccountsProvider({ children }: AccountsProviderProps) {
207
207
 
208
208
  if (result.access && result.refresh) {
209
209
  // The ONE write path — atomic pair, notifies every session subscriber.
210
- // Storage is always localStorage: "remember me" is a TOKEN LIFETIME
211
- // decision the server already made (`remember_me` → REMEMBER_ME_LIFETIME),
212
- // not a storage decision. Switching the client to sessionStorage here used
213
- // to strand the tokens — the module reinitializes to localStorage on the
214
- // next page load and would never look at sessionStorage again.
210
+ // Storage is always localStorage: session length is the token lifetime
211
+ // the server chose. A sessionStorage switch would strand the tokens — the
212
+ // module reinitializes to localStorage on the next page load.
215
213
  auth.setSession({ access: result.access, refresh: result.refresh });
216
214
  try {
217
215
  await refreshProfile({ callerId, force: true });
@@ -228,7 +226,7 @@ export function AccountsProvider({ children }: AccountsProviderProps) {
228
226
  }, [triggerOtpVerify, settleLoginEnvelope]);
229
227
 
230
228
  const loginWithPassword = useCallback(async (
231
- data: PasswordLoginRequestWritable,
229
+ data: PasswordLoginRequest,
232
230
  ): Promise<PasswordLoginResponse> => {
233
231
  // Errors propagate: the SWR trigger throws APIError for 401/429 and the
234
232
  // caller maps them. Nothing is written to the session on a failure.
@@ -30,6 +30,7 @@ import React, {
30
30
 
31
31
  import { auth } from '../../_api/generated/helpers/auth';
32
32
  import { APIError } from '../../_api/generated/helpers';
33
+ import { CFG_KEYS } from '../../_api/generated/helpers/keys';
33
34
  import { applyRoleLogPolicy } from '../../log-control';
34
35
  import { useCfgRouter, useLocalStorage, useQueryParams, useSession } from '../hooks';
35
36
  import { clearProfileCache } from '../hooks/useProfileCache';
@@ -68,7 +69,7 @@ export const resolveAuthRoutes = (
68
69
  defaultAuthCallback: routes?.defaultAuthCallback || routes?.auth || defaultRoutes.defaultAuthCallback,
69
70
  });
70
71
 
71
- const EMAIL_STORAGE_KEY = 'auth_email';
72
+ const EMAIL_STORAGE_KEY = CFG_KEYS.email.key;
72
73
 
73
74
  const AuthContext = createContext<AuthContextType | undefined>(undefined);
74
75
 
@@ -287,7 +288,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
287
288
  sourceUrl?: string,
288
289
  redirectUrl?: string,
289
290
  skipRedirect?: boolean,
290
- rememberMe = false,
291
291
  ): Promise<{
292
292
  success: boolean;
293
293
  message: string;
@@ -305,7 +305,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
305
305
  identifier,
306
306
  otp: otpCode,
307
307
  source_url: sourceUrl,
308
- remember_me: rememberMe,
309
308
  });
310
309
 
311
310
  if (result.requires_2fa && result.session_id) {
@@ -378,7 +377,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
378
377
  password: string,
379
378
  options?: PasswordLoginOptions,
380
379
  ): Promise<PasswordLoginResult> => {
381
- const { redirectUrl, skipRedirect, rememberMe = false } = options || {};
380
+ const { redirectUrl, skipRedirect } = options || {};
382
381
 
383
382
  try {
384
383
  // The caller opting out of redirect commits to handling ALL navigation
@@ -388,7 +387,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
388
387
  const result = await accountsRef.current.loginWithPassword({
389
388
  email,
390
389
  password,
391
- remember_me: rememberMe,
392
390
  });
393
391
 
394
392
  if (result.requires_2fa && result.session_id) {
@@ -55,7 +55,7 @@ export interface AuthContextType {
55
55
 
56
56
  // OTP Methods - email only
57
57
  requestOTP: (identifier: string, sourceUrl?: string, consent?: OTPRequestConsent) => Promise<OTPRequestResult>;
58
- verifyOTP: (identifier: string, otpCode: string, sourceUrl?: string, redirectUrl?: string, skipRedirect?: boolean, rememberMe?: boolean) => Promise<{
58
+ verifyOTP: (identifier: string, otpCode: string, sourceUrl?: string, redirectUrl?: string, skipRedirect?: boolean) => Promise<{
59
59
  success: boolean;
60
60
  message: string;
61
61
  user?: UserProfile;
@@ -55,7 +55,6 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
55
55
  password,
56
56
  acceptedTerms,
57
57
  marketingConsent,
58
- rememberMe,
59
58
  setIdentifier,
60
59
  setOtp,
61
60
  setStep,
@@ -176,7 +175,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
176
175
 
177
176
  try {
178
177
  // Skip redirect - we'll show success screen first
179
- const result = await verifyOTP(submitIdentifier, submitOtp, sourceUrl, redirectUrl, true, rememberMe);
178
+ const result = await verifyOTP(submitIdentifier, submitOtp, sourceUrl, redirectUrl, true);
180
179
 
181
180
  if (result.requires_2fa && result.session_id) {
182
181
  authLogger.info('2FA required after OTP verification');
@@ -205,7 +204,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
205
204
  } finally {
206
205
  setIsLoading(false);
207
206
  }
208
- }, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, rememberMe, setStep]);
207
+ }, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, setStep]);
209
208
 
210
209
  const handleOTPSubmit = useCallback(async (e: React.FormEvent) => {
211
210
  e.preventDefault();
@@ -244,7 +243,6 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
244
243
  const result = await login(submitIdentifier, submitPassword_, {
245
244
  redirectUrl,
246
245
  skipRedirect: true,
247
- rememberMe,
248
246
  });
249
247
 
250
248
  if (result.requires_2fa && result.session_id) {
@@ -283,7 +281,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
283
281
  }, [
284
282
  login, validateIdentifier, saveIdentifierToStorage, setError, setIsLoading,
285
283
  clearError, startRateLimitCountdown, onOTPSuccess, onError, redirectUrl,
286
- rememberMe, setStep,
284
+ setStep,
287
285
  ]);
288
286
 
289
287
  const handlePasswordSubmit = useCallback(async (e: React.FormEvent) => {
@@ -53,8 +53,6 @@ export const useAuthFormState = (
53
53
  // null until the marketing-consent feature (if enabled) resolves its
54
54
  // jurisdiction-aware default — the layout owns that decision.
55
55
  const [marketingConsent, setMarketingConsent] = useState<boolean | null>(null);
56
- // Signed in for 30 days unless an app opts into showing the choice.
57
- const [rememberMe, setRememberMe] = useState(true);
58
56
  const [step, setStep] = useState<AuthStep>('identifier');
59
57
  const [error, setError] = useState('');
60
58
  const [webmail, setWebmail] = useState<WebmailLink | null>(null);
@@ -72,7 +70,6 @@ export const useAuthFormState = (
72
70
  isLoading,
73
71
  acceptedTerms,
74
72
  marketingConsent,
75
- rememberMe,
76
73
  step,
77
74
  error,
78
75
  rateLimitSeconds: rateLimit.seconds,
@@ -87,7 +84,6 @@ export const useAuthFormState = (
87
84
  setPassword,
88
85
  setAcceptedTerms,
89
86
  setMarketingConsent,
90
- setRememberMe,
91
87
  setError,
92
88
  clearError,
93
89
  setStep,
@@ -1,6 +1,7 @@
1
+ import { CFG_KEYS } from '../../_api/generated/helpers/keys';
1
2
  import { useSessionStorage } from './useSessionStorage';
2
3
 
3
- const AUTH_REDIRECT_KEY = 'auth_redirect_url';
4
+ const AUTH_REDIRECT_KEY = CFG_KEYS.redirectUrl.key;
4
5
 
5
6
  export interface AuthRedirectOptions {
6
7
  fallbackUrl?: string;
@@ -28,7 +29,7 @@ const clearRedirectFromStorage = (): void => {
28
29
  if (typeof window === 'undefined') return;
29
30
  try {
30
31
  window.sessionStorage.removeItem(AUTH_REDIRECT_KEY);
31
- window.sessionStorage.removeItem(`${AUTH_REDIRECT_KEY}_timestamp`);
32
+ window.sessionStorage.removeItem(`${AUTH_REDIRECT_KEY}_ts`);
32
33
  } catch {
33
34
  // Ignore errors
34
35
  }
@@ -3,6 +3,7 @@
3
3
  import { useCallback, useState } from 'react';
4
4
 
5
5
  import { auth } from '../../_api/generated/helpers/auth';
6
+ import { CFG_KEYS } from '../../_api/generated/helpers/keys';
6
7
  import { CfgAccountsOauth } from '../../_api/generated/sdk.gen';
7
8
  import type { OAuthTokenResponse } from '../../_api/generated/types.gen';
8
9
  import { Analytics, AnalyticsCategory, AnalyticsEvent } from '../utils/analytics';
@@ -10,6 +11,9 @@ import { formatAuthError } from '../utils/errors';
10
11
  import { authLogger } from '../utils/logger';
11
12
  import { useCfgRouter } from './useCfgRouter';
12
13
 
14
+ const OAUTH_STATE_KEY = CFG_KEYS.oauthState.key;
15
+ const OAUTH_PROVIDER_KEY = CFG_KEYS.oauthProvider.key;
16
+
13
17
  /**
14
18
  * The user payload as the OAuth endpoint declares it.
15
19
  *
@@ -22,8 +26,6 @@ export type OAuthUser = NonNullable<OAuthTokenResponse['user']>;
22
26
 
23
27
  export interface UseGithubAuthOptions {
24
28
  sourceUrl?: string;
25
- /** Keep the completed browser session for 30 days. */
26
- rememberMe?: boolean;
27
29
  /**
28
30
  * The OAuth endpoint's OpenAPI schema types `user` as a free-form object, so
29
31
  * this is what the server actually promises — not a `User`. Narrow it at the
@@ -65,7 +67,7 @@ export interface UseGithubAuthReturn {
65
67
  * ```
66
68
  */
67
69
  export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuthReturn => {
68
- const { sourceUrl, rememberMe = false, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
70
+ const { sourceUrl, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
69
71
  const router = useCfgRouter();
70
72
 
71
73
  const [isLoading, setIsLoading] = useState(false);
@@ -107,9 +109,8 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
107
109
  authLogger.info('Redirecting to GitHub...', response.authorization_url);
108
110
 
109
111
  if (typeof window !== 'undefined') {
110
- sessionStorage.setItem('oauth_state', response.state);
111
- sessionStorage.setItem('oauth_provider', 'github');
112
- sessionStorage.setItem('cfg.oauth.remember_me', String(rememberMe));
112
+ sessionStorage.setItem(OAUTH_STATE_KEY, response.state);
113
+ sessionStorage.setItem(OAUTH_PROVIDER_KEY, 'github');
113
114
  }
114
115
 
115
116
  window.location.href = response.authorization_url;
@@ -128,7 +129,7 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
128
129
  } finally {
129
130
  setIsLoading(false);
130
131
  }
131
- }, [sourceUrl, rememberMe, onError]);
132
+ }, [sourceUrl, onError]);
132
133
 
133
134
  /**
134
135
  * Handle GitHub OAuth callback - exchanges code for JWT tokens.
@@ -144,22 +145,19 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
144
145
  authLogger.info('Processing GitHub OAuth callback...');
145
146
 
146
147
  // Verify state matches what we stored
147
- let rememberMeForSession = false;
148
148
  if (typeof window !== 'undefined') {
149
- const storedState = sessionStorage.getItem('oauth_state');
149
+ const storedState = sessionStorage.getItem(OAUTH_STATE_KEY);
150
150
  if (storedState && storedState !== state) {
151
151
  throw new Error('Invalid OAuth state - possible CSRF attack');
152
152
  }
153
- rememberMeForSession = sessionStorage.getItem('cfg.oauth.remember_me') === 'true';
154
153
  // Clear stored state
155
- sessionStorage.removeItem('oauth_state');
156
- sessionStorage.removeItem('oauth_provider');
157
- sessionStorage.removeItem('cfg.oauth.remember_me');
154
+ sessionStorage.removeItem(OAUTH_STATE_KEY);
155
+ sessionStorage.removeItem(OAUTH_PROVIDER_KEY);
158
156
 
159
157
  }
160
158
 
161
159
  const result = await CfgAccountsOauth.cfgAccountsOauthGithubCallbackCreate({
162
- body: { code, state, remember_me: rememberMeForSession },
160
+ body: { code, state },
163
161
  throwOnError: true,
164
162
  });
165
163
  const response = result.data;
@@ -186,8 +184,8 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
186
184
  authLogger.info('GitHub OAuth successful, user:', response.user);
187
185
 
188
186
  // The ONE write path — atomic pair + session notification. Storage is
189
- // always localStorage; session length is the server's call via
190
- // `remember_me`. See AccountsContext.verifyOTP.
187
+ // always localStorage; session length is the server's call.
188
+ // See AccountsContext.verifyOTP.
191
189
  auth.setSession({ access: response.access, refresh: response.refresh });
192
190
 
193
191
  // Track successful OAuth
@@ -49,7 +49,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
49
49
  const key = keys[i];
50
50
  if (key) {
51
51
  localStorage.removeItem(key);
52
- localStorage.removeItem(`${key}_timestamp`);
52
+ localStorage.removeItem(`${key}_ts`);
53
53
  }
54
54
  } catch {
55
55
  // Ignore errors when removing items
@@ -88,7 +88,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
88
88
  // Remove the key if data is too large
89
89
  try {
90
90
  window.localStorage.removeItem(key);
91
- window.localStorage.removeItem(`${key}_timestamp`);
91
+ window.localStorage.removeItem(`${key}_ts`);
92
92
  } catch {
93
93
  // Ignore errors when removing
94
94
  }
@@ -109,7 +109,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
109
109
  window.localStorage.setItem(key, JSON.stringify(valueToStore));
110
110
  }
111
111
  // Add timestamp for cleanup
112
- window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
112
+ window.localStorage.setItem(`${key}_ts`, Date.now().toString());
113
113
  } catch (storageError: unknown) {
114
114
  // If quota exceeded, clear old data and try again
115
115
  if (isQuotaExceededError(storageError)) {
@@ -124,7 +124,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
124
124
  } else {
125
125
  window.localStorage.setItem(key, JSON.stringify(valueToStore));
126
126
  }
127
- window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
127
+ window.localStorage.setItem(`${key}_ts`, Date.now().toString());
128
128
  } catch (retryError) {
129
129
  authLogger.error(`Failed to set localStorage key "${key}" after clearing old data:`, retryError);
130
130
  // If still fails, force clear all and try one more time
@@ -136,7 +136,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
136
136
  } else {
137
137
  window.localStorage.setItem(key, JSON.stringify(valueToStore));
138
138
  }
139
- window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
139
+ window.localStorage.setItem(`${key}_ts`, Date.now().toString());
140
140
  } catch (finalError) {
141
141
  authLogger.error(`Failed to set localStorage key "${key}" after force clearing:`, finalError);
142
142
  // If still fails, just update the state without localStorage
@@ -163,7 +163,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
163
163
  if (typeof window !== 'undefined') {
164
164
  try {
165
165
  window.localStorage.removeItem(key);
166
- window.localStorage.removeItem(`${key}_timestamp`);
166
+ window.localStorage.removeItem(`${key}_ts`);
167
167
  } catch (removeError: unknown) {
168
168
  // If removal fails due to quota, try to clear some data first
169
169
  if (isQuotaExceededError(removeError)) {
@@ -172,7 +172,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
172
172
 
173
173
  try {
174
174
  window.localStorage.removeItem(key);
175
- window.localStorage.removeItem(`${key}_timestamp`);
175
+ window.localStorage.removeItem(`${key}_ts`);
176
176
  } catch (retryError) {
177
177
  authLogger.error(`Failed to remove localStorage key "${key}" after clearing:`, retryError);
178
178
  // If still fails, force clear all
@@ -11,10 +11,11 @@
11
11
 
12
12
  import type { User } from '../../';
13
13
  import { authLogger as profileLogger } from '../utils/logger';
14
+ import { CFG_KEYS } from '../../_api/generated/helpers/keys';
14
15
  import { decodeBase64, encodeBase64 } from './useBase64';
15
16
 
16
17
  // Cache configuration
17
- const CACHE_KEY = 'user_profile_cache';
18
+ const CACHE_KEY = CFG_KEYS.profileCache.key;
18
19
  const CACHE_VERSION = 1;
19
20
  const DEFAULT_TTL = 14400000; // 4 hours in milliseconds (reduced API calls, more resilient)
20
21
 
@@ -39,7 +39,7 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
39
39
  const key = keys[i];
40
40
  if (key) {
41
41
  sessionStorage.removeItem(key);
42
- sessionStorage.removeItem(`${key}_timestamp`);
42
+ sessionStorage.removeItem(`${key}_ts`);
43
43
  }
44
44
  } catch {
45
45
  // Ignore errors when removing items
@@ -78,7 +78,7 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
78
78
  // Remove the key if data is too large
79
79
  try {
80
80
  window.sessionStorage.removeItem(key);
81
- window.sessionStorage.removeItem(`${key}_timestamp`);
81
+ window.sessionStorage.removeItem(`${key}_ts`);
82
82
  } catch {
83
83
  // Ignore errors when removing
84
84
  }
@@ -94,7 +94,7 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
94
94
  try {
95
95
  window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
96
96
  // Add timestamp for cleanup
97
- window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
97
+ window.sessionStorage.setItem(`${key}_ts`, Date.now().toString());
98
98
  } catch (storageError: unknown) {
99
99
  // If quota exceeded, clear old data and try again
100
100
  if (isQuotaExceededError(storageError)) {
@@ -104,14 +104,14 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
104
104
  // Try again after clearing
105
105
  try {
106
106
  window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
107
- window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
107
+ window.sessionStorage.setItem(`${key}_ts`, Date.now().toString());
108
108
  } catch (retryError) {
109
109
  authLogger.error(`Failed to set sessionStorage key "${key}" after clearing old data:`, retryError);
110
110
  // If still fails, force clear all and try one more time
111
111
  try {
112
112
  forceClearAll();
113
113
  window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
114
- window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
114
+ window.sessionStorage.setItem(`${key}_ts`, Date.now().toString());
115
115
  } catch (finalError) {
116
116
  authLogger.error(`Failed to set sessionStorage key "${key}" after force clearing:`, finalError);
117
117
  // If still fails, just update the state without sessionStorage
@@ -138,7 +138,7 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
138
138
  if (typeof window !== 'undefined') {
139
139
  try {
140
140
  window.sessionStorage.removeItem(key);
141
- window.sessionStorage.removeItem(`${key}_timestamp`);
141
+ window.sessionStorage.removeItem(`${key}_ts`);
142
142
  } catch (removeError: unknown) {
143
143
  // If removal fails due to quota, try to clear some data first
144
144
  if (isQuotaExceededError(removeError)) {
@@ -147,7 +147,7 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
147
147
 
148
148
  try {
149
149
  window.sessionStorage.removeItem(key);
150
- window.sessionStorage.removeItem(`${key}_timestamp`);
150
+ window.sessionStorage.removeItem(`${key}_ts`);
151
151
  } catch (retryError) {
152
152
  authLogger.error(`Failed to remove sessionStorage key "${key}" after clearing:`, retryError);
153
153
  // If still fails, force clear all
@@ -69,8 +69,8 @@ export const useTwoFactorVerify = (
69
69
  // copy it replaces cannot drift from the server contract any more.
70
70
  const handleSuccess = useCallback((response: VerifyResponse) => {
71
71
  // Save tokens — the ONE write path (atomic pair + session notification).
72
- // Storage is always localStorage; session length is the server's call via
73
- // `remember_me`. See AccountsContext.verifyOTP.
72
+ // Storage is always localStorage; session length is the server's call.
73
+ // See AccountsContext.verifyOTP.
74
74
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
75
75
 
76
76
  if (response.warning) {
@@ -52,8 +52,6 @@ export interface PasswordLoginOptions {
52
52
  redirectUrl?: string;
53
53
  /** Caller owns ALL navigation for this login, including the 2FA continuation. */
54
54
  skipRedirect?: boolean;
55
- /** Ask the server for a 30-day session instead of the default lifetime. */
56
- rememberMe?: boolean;
57
55
  }
58
56
 
59
57
  /**
@@ -99,8 +97,6 @@ export interface AuthFormState {
99
97
  * jurisdiction-aware default on mount.
100
98
  */
101
99
  marketingConsent: boolean | null;
102
- /** Keep this browser signed in for 30 days after successful verification. */
103
- rememberMe: boolean;
104
100
  /** Current form step */
105
101
  step: AuthStep;
106
102
  /** Error message */
@@ -129,7 +125,6 @@ export interface AuthFormStateHandlers {
129
125
  setPassword: (password: string) => void;
130
126
  setAcceptedTerms: (accepted: boolean) => void;
131
127
  setMarketingConsent: (consent: boolean | null) => void;
132
- setRememberMe: (rememberMe: boolean) => void;
133
128
  setError: (error: string) => void;
134
129
  clearError: () => void;
135
130
  setStep: (step: AuthStep) => void;
package/src/storage.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@djangocfg/api/storage` — the browser-storage key registry on its own.
3
+ *
4
+ * Zero dependencies and no client: packages that only need a key, a guarded
5
+ * read/write or `createStorage('<owner>')` import this, not the generated SDK.
6
+ * The source is the generated `helpers/keys.ts`.
7
+ */
8
+ export {
9
+ CFG_FAMILIES,
10
+ CFG_KEYS,
11
+ cfgStorage,
12
+ createStorage,
13
+ normalizeSegment,
14
+ } from './_api/generated/helpers/keys';
15
+ export type {
16
+ FamilySpec,
17
+ GuardedStorage,
18
+ KeySpec,
19
+ KeyStore,
20
+ StorageOptions,
21
+ } from './_api/generated/helpers/keys';