@djangocfg/api 2.1.546 → 2.1.547

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 (40) hide show
  1. package/dist/auth.cjs +331 -73
  2. package/dist/auth.cjs.map +1 -1
  3. package/dist/auth.d.cts +90 -1
  4. package/dist/auth.d.ts +90 -1
  5. package/dist/auth.mjs +331 -73
  6. package/dist/auth.mjs.map +1 -1
  7. package/dist/clients.cjs +18 -0
  8. package/dist/clients.cjs.map +1 -1
  9. package/dist/clients.d.cts +96 -0
  10. package/dist/clients.d.ts +96 -0
  11. package/dist/clients.mjs +18 -0
  12. package/dist/clients.mjs.map +1 -1
  13. package/dist/hooks.cjs.map +1 -1
  14. package/dist/hooks.mjs.map +1 -1
  15. package/dist/index.cjs +18 -0
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +171 -3
  18. package/dist/index.d.ts +171 -3
  19. package/dist/index.mjs +18 -0
  20. package/dist/index.mjs.map +1 -1
  21. package/package.json +2 -2
  22. package/src/_api/generated/_cfg_accounts/hooks/index.ts +1 -0
  23. package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenCreate.ts +64 -0
  24. package/src/_api/generated/_cfg_accounts/openapi.json +157 -0
  25. package/src/_api/generated/_cfg_accounts/schemas/PasswordLoginError.ts +13 -0
  26. package/src/_api/generated/_cfg_accounts/schemas/PasswordLoginRequest.ts +13 -0
  27. package/src/_api/generated/_cfg_accounts/schemas/PasswordLoginResponse.ts +18 -0
  28. package/src/_api/generated/_cfg_accounts/schemas/index.ts +3 -0
  29. package/src/_api/generated/openapi.json +157 -0
  30. package/src/_api/generated/sdk.gen.ts +20 -1
  31. package/src/_api/generated/types.gen.ts +149 -0
  32. package/src/auth/__tests__/passwordLogin.test.tsx +239 -0
  33. package/src/auth/context/AccountsContext.tsx +32 -7
  34. package/src/auth/context/AuthContext.tsx +104 -1
  35. package/src/auth/context/types.ts +10 -1
  36. package/src/auth/hooks/useAuthForm.ts +84 -0
  37. package/src/auth/hooks/useAuthFormState.ts +3 -0
  38. package/src/auth/types/form.ts +41 -1
  39. package/src/auth/types/index.ts +2 -0
  40. package/src/auth/utils/analytics.ts +1 -0
@@ -43,6 +43,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
43
43
  const {
44
44
  requestOTP,
45
45
  verifyOTP,
46
+ login,
46
47
  getSavedEmail,
47
48
  saveEmail,
48
49
  } = useAuth();
@@ -51,6 +52,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
51
52
  const {
52
53
  identifier,
53
54
  otp,
55
+ password,
54
56
  acceptedTerms,
55
57
  marketingConsent,
56
58
  rememberMe,
@@ -235,6 +237,87 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
235
237
  await submitOTP(identifier, otp);
236
238
  }, [identifier, otp, submitOTP]);
237
239
 
240
+ // ─────────────────────────────────────────────────────────────────────────
241
+ // Password Submit
242
+ // ─────────────────────────────────────────────────────────────────────────
243
+
244
+ // The password credential lands on the SAME step machine as the OTP code:
245
+ // both end at '2fa' or 'success', so the layout renders one set of screens.
246
+ const submitPassword = useCallback(async (
247
+ submitIdentifier: string,
248
+ submitPassword_: string,
249
+ ): Promise<boolean> => {
250
+ if (!submitIdentifier || !validateIdentifier(submitIdentifier)) {
251
+ const msg = 'Please enter a valid email address';
252
+ setError(msg);
253
+ onError?.(msg);
254
+ return false;
255
+ }
256
+
257
+ if (!submitPassword_) {
258
+ const msg = 'Please enter your password';
259
+ setError(msg);
260
+ onError?.(msg);
261
+ return false;
262
+ }
263
+
264
+ setIsLoading(true);
265
+ clearError();
266
+
267
+ try {
268
+ // Skip redirect - we'll show success screen first
269
+ const result = await login(submitIdentifier, submitPassword_, {
270
+ redirectUrl,
271
+ skipRedirect: true,
272
+ rememberMe,
273
+ });
274
+
275
+ if (result.requires_2fa && result.session_id) {
276
+ authLogger.info('2FA required after password login');
277
+ setTwoFactorSessionId(result.session_id);
278
+ setShouldPrompt2FA(result.should_prompt_2fa || false);
279
+ setStep('2fa');
280
+ saveIdentifierToStorage(submitIdentifier);
281
+ return true; // Password was accepted, now need 2FA
282
+ }
283
+
284
+ if (result.success) {
285
+ saveIdentifierToStorage(submitIdentifier);
286
+ authLogger.info('Password login successful, showing success screen');
287
+ setStep('success');
288
+ onOTPSuccess?.();
289
+ return true;
290
+ }
291
+
292
+ // A lockout (429) carries a retry-after; show the countdown instead of a
293
+ // message the user can only re-trigger.
294
+ if (result.retryAfter) {
295
+ startRateLimitCountdown(result.retryAfter);
296
+ clearError();
297
+ } else {
298
+ setError(result.message);
299
+ onError?.(result.message);
300
+ }
301
+ return false;
302
+ } catch {
303
+ const msg = 'An unexpected error occurred';
304
+ setError(msg);
305
+ onError?.(msg);
306
+ return false;
307
+ } finally {
308
+ setIsLoading(false);
309
+ }
310
+ }, [
311
+ login, validateIdentifier, saveIdentifierToStorage, setError, setIsLoading,
312
+ clearError, startRateLimitCountdown, onOTPSuccess, onError, redirectUrl,
313
+ rememberMe, setTwoFactorSessionId, setShouldPrompt2FA, setStep,
314
+ ]);
315
+
316
+ const handlePasswordSubmit = useCallback(async (e: React.FormEvent) => {
317
+ e.preventDefault();
318
+ await submitPassword(identifier, password);
319
+ }, [identifier, password, submitPassword]);
320
+
238
321
  const handleResendOTP = useCallback(async () => {
239
322
  setIsLoading(true);
240
323
  clearError();
@@ -370,6 +453,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
370
453
  // Submit handlers
371
454
  handleIdentifierSubmit,
372
455
  handleOTPSubmit,
456
+ handlePasswordSubmit,
373
457
  handleResendOTP,
374
458
  handleBackToIdentifier,
375
459
  forceOTPStep,
@@ -21,6 +21,7 @@ export const useAuthFormState = (
21
21
  ): UseAuthFormStateReturn => {
22
22
  const [identifier, setIdentifier] = useState(initialIdentifier);
23
23
  const [otp, setOtp] = useState('');
24
+ const [password, setPassword] = useState('');
24
25
  const [isLoading, setIsLoading] = useState(false);
25
26
  const [acceptedTerms, setAcceptedTerms] = useState(true);
26
27
  // null until the marketing-consent feature (if enabled) resolves its
@@ -66,6 +67,7 @@ export const useAuthFormState = (
66
67
  // State
67
68
  identifier,
68
69
  otp,
70
+ password,
69
71
  isLoading,
70
72
  acceptedTerms,
71
73
  marketingConsent,
@@ -83,6 +85,7 @@ export const useAuthFormState = (
83
85
  // Handlers
84
86
  setIdentifier,
85
87
  setOtp,
88
+ setPassword,
86
89
  setAcceptedTerms,
87
90
  setMarketingConsent,
88
91
  setRememberMe,
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { MutableRefObject } from 'react';
9
9
 
10
- import type { WebmailLink } from '../../_api/generated/types.gen';
10
+ import type { User, WebmailLink } from '../../_api/generated/types.gen';
11
11
 
12
12
  export type { WebmailLink };
13
13
 
@@ -41,6 +41,41 @@ export interface OTPRequestConsent {
41
41
  disclosureVersion: string;
42
42
  }
43
43
 
44
+ // ─────────────────────────────────────────────────────────────────────────────
45
+ // Password Login Types
46
+ // ─────────────────────────────────────────────────────────────────────────────
47
+
48
+ export interface PasswordLoginOptions {
49
+ /** URL to redirect to after a successful login (ignored when skipRedirect). */
50
+ redirectUrl?: string;
51
+ /** Caller owns ALL navigation for this login, including the 2FA continuation. */
52
+ skipRedirect?: boolean;
53
+ /** Ask the server for a 30-day session instead of the default lifetime. */
54
+ rememberMe?: boolean;
55
+ }
56
+
57
+ /**
58
+ * Result of a password login.
59
+ *
60
+ * Deliberately the same shape as the verifyOTP result plus the failure codes
61
+ * the password endpoint adds: a sign-in form branches on `requires_2fa` the
62
+ * same way whichever credential it collected.
63
+ */
64
+ export interface PasswordLoginResult {
65
+ success: boolean;
66
+ message: string;
67
+ user?: User;
68
+ requires_2fa?: boolean;
69
+ session_id?: string | null;
70
+ should_prompt_2fa?: boolean;
71
+ /** HTTP status of a failure — 401 for bad credentials, 429 when throttled. */
72
+ statusCode?: number;
73
+ /** Machine-readable failure code, e.g. `invalid_credentials`, `locked_out`. */
74
+ errorCode?: string;
75
+ /** Seconds until a throttled caller may retry (429 only). */
76
+ retryAfter?: number;
77
+ }
78
+
44
79
  // ─────────────────────────────────────────────────────────────────────────────
45
80
  // Form State
46
81
  // ─────────────────────────────────────────────────────────────────────────────
@@ -50,6 +85,8 @@ export interface AuthFormState {
50
85
  identifier: string;
51
86
  /** OTP code input */
52
87
  otp: string;
88
+ /** Password input (password sign-in; empty for the OTP-only flow) */
89
+ password: string;
53
90
  /** Loading state */
54
91
  isLoading: boolean;
55
92
  /** Terms acceptance state */
@@ -91,6 +128,7 @@ export interface AuthFormState {
91
128
  export interface AuthFormStateHandlers {
92
129
  setIdentifier: (identifier: string) => void;
93
130
  setOtp: (otp: string) => void;
131
+ setPassword: (password: string) => void;
94
132
  setAcceptedTerms: (accepted: boolean) => void;
95
133
  setMarketingConsent: (consent: boolean | null) => void;
96
134
  setRememberMe: (rememberMe: boolean) => void;
@@ -111,6 +149,8 @@ export interface AuthFormStateHandlers {
111
149
  export interface AuthFormSubmitHandlers {
112
150
  handleIdentifierSubmit: (e: React.FormEvent) => Promise<void>;
113
151
  handleOTPSubmit: (e: React.FormEvent) => Promise<void>;
152
+ /** Submit email + password against the password grant */
153
+ handlePasswordSubmit: (e: React.FormEvent) => Promise<void>;
114
154
  handleResendOTP: () => Promise<void>;
115
155
  handleBackToIdentifier: () => void;
116
156
  forceOTPStep: () => void;
@@ -14,6 +14,8 @@ export type {
14
14
  UseAuthFormOptions,
15
15
  OTPRequestResult,
16
16
  OTPRequestConsent,
17
+ PasswordLoginOptions,
18
+ PasswordLoginResult,
17
19
  WebmailLink,
18
20
  } from './form';
19
21
 
@@ -19,6 +19,7 @@ export enum AnalyticsEvent {
19
19
  AUTH_OTP_REQUEST = 'auth_otp_request',
20
20
  AUTH_LOGIN_SUCCESS = 'auth_login_success',
21
21
  AUTH_OTP_VERIFY_FAIL = 'auth_otp_verify_fail',
22
+ AUTH_PASSWORD_LOGIN_FAIL = 'auth_password_login_fail',
22
23
  AUTH_SESSION_EXPIRED = 'auth_session_expired',
23
24
  AUTH_TOKEN_REFRESH = 'auth_token_refresh',
24
25
  AUTH_TOKEN_REFRESH_FAIL = 'auth_token_refresh_fail',