@djangocfg/api 2.1.580 → 2.1.582
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/ChallengePolicy-DDX7PIIY.mjs +20 -0
- package/dist/ChallengePolicy-DDX7PIIY.mjs.map +1 -0
- package/dist/OTPRequestResponse-TZSEVCID.mjs +32 -0
- package/dist/OTPRequestResponse-TZSEVCID.mjs.map +1 -0
- package/dist/auth.cjs +789 -821
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +32 -29
- package/dist/auth.d.ts +32 -29
- package/dist/auth.mjs +628 -662
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.d.cts +8 -0
- package/dist/clients.d.ts +8 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/package.json +2 -2
- package/src/_api/generated/_cfg_accounts/openapi.json +10 -0
- package/src/_api/generated/_cfg_accounts/schemas/OTPRequestResponse.ts +2 -0
- package/src/_api/generated/openapi.json +10 -0
- package/src/_api/generated/types.gen.ts +8 -0
- package/src/auth/__tests__/twoFactorChallenge.test.tsx +37 -0
- package/src/auth/constants.ts +2 -0
- package/src/auth/context/AuthContext.tsx +1 -0
- package/src/auth/hooks/index.ts +8 -0
- package/src/auth/hooks/useAuthForm.ts +28 -97
- package/src/auth/hooks/useAuthFormState.ts +37 -41
- package/src/auth/hooks/useTwoFactorChallenge.ts +35 -0
- package/src/auth/types/form.ts +10 -34
- package/src/auth/__tests__/useAuthForm.2fa.test.ts +0 -106
package/src/auth/types/form.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type { WebmailLink };
|
|
|
15
15
|
// Step Types
|
|
16
16
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
|
-
export type AuthStep = 'identifier' | 'otp' | '
|
|
18
|
+
export type AuthStep = 'identifier' | 'otp' | 'success';
|
|
19
19
|
|
|
20
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
21
|
// OTP Result Types
|
|
@@ -28,6 +28,8 @@ export interface OTPRequestResult {
|
|
|
28
28
|
retryAfter?: number;
|
|
29
29
|
/** Webmail deep-link for the recipient's provider, or null if unknown. */
|
|
30
30
|
webmail?: WebmailLink | null;
|
|
31
|
+
/** Seconds until a new code may be requested. */
|
|
32
|
+
resendIn?: number;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
@@ -103,20 +105,16 @@ export interface AuthFormState {
|
|
|
103
105
|
step: AuthStep;
|
|
104
106
|
/** Error message */
|
|
105
107
|
error: string;
|
|
106
|
-
/** 2FA session ID from OTP/OAuth verification */
|
|
107
|
-
twoFactorSessionId: string | null;
|
|
108
|
-
/** Whether user should be prompted to enable 2FA */
|
|
109
|
-
shouldPrompt2FA: boolean;
|
|
110
|
-
/** 2FA code input */
|
|
111
|
-
twoFactorCode: string;
|
|
112
|
-
/** Using backup code instead of TOTP */
|
|
113
|
-
useBackupCode: boolean;
|
|
114
108
|
/** Seconds remaining until rate limit lifts (0 = not rate limited) */
|
|
115
109
|
rateLimitSeconds: number;
|
|
116
110
|
/** True when rateLimitSeconds > 0 — submit should be disabled */
|
|
117
111
|
isRateLimited: boolean;
|
|
118
112
|
/** Formatted countdown label, e.g. "19:00" or "42s" */
|
|
119
113
|
rateLimitLabel: string;
|
|
114
|
+
/** Seconds until another code may be requested (0 = may resend now) */
|
|
115
|
+
resendSeconds: number;
|
|
116
|
+
/** Formatted resend countdown, e.g. "28s" */
|
|
117
|
+
resendLabel: string;
|
|
120
118
|
/** Webmail deep-link for the current identifier's provider (null = no button). */
|
|
121
119
|
webmail: WebmailLink | null;
|
|
122
120
|
}
|
|
@@ -136,12 +134,10 @@ export interface AuthFormStateHandlers {
|
|
|
136
134
|
clearError: () => void;
|
|
137
135
|
setStep: (step: AuthStep) => void;
|
|
138
136
|
setIsLoading: (loading: boolean) => void;
|
|
139
|
-
setTwoFactorSessionId: (sessionId: string | null) => void;
|
|
140
|
-
setShouldPrompt2FA: (prompt: boolean) => void;
|
|
141
|
-
setTwoFactorCode: (code: string) => void;
|
|
142
|
-
setUseBackupCode: (useBackup: boolean) => void;
|
|
143
137
|
/** Start a countdown timer that disables submit for `seconds` seconds */
|
|
144
138
|
startRateLimitCountdown: (seconds: number) => void;
|
|
139
|
+
/** Start the countdown before another code may be requested */
|
|
140
|
+
startResendCountdown: (seconds: number) => void;
|
|
145
141
|
/** Set (or clear) the webmail deep-link for the current identifier */
|
|
146
142
|
setWebmail: (webmail: WebmailLink | null) => void;
|
|
147
143
|
}
|
|
@@ -154,12 +150,6 @@ export interface AuthFormSubmitHandlers {
|
|
|
154
150
|
handleResendOTP: () => Promise<void>;
|
|
155
151
|
handleBackToIdentifier: () => void;
|
|
156
152
|
forceOTPStep: () => void;
|
|
157
|
-
/** Handle 2FA TOTP/backup code verification */
|
|
158
|
-
handle2FASubmit: (e: React.FormEvent) => Promise<void>;
|
|
159
|
-
/** Switch to backup code input */
|
|
160
|
-
handleUseBackupCode: () => void;
|
|
161
|
-
/** Switch back to TOTP input */
|
|
162
|
-
handleUseTOTP: () => void;
|
|
163
153
|
}
|
|
164
154
|
|
|
165
155
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -180,19 +170,6 @@ export interface AuthFormAutoSubmit {
|
|
|
180
170
|
isAutoSubmittingFromUrl: MutableRefObject<boolean>;
|
|
181
171
|
}
|
|
182
172
|
|
|
183
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
184
|
-
// 2FA State (from useTwoFactor hook)
|
|
185
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
186
|
-
|
|
187
|
-
export interface AuthForm2FAState {
|
|
188
|
-
/** Loading state for 2FA verification */
|
|
189
|
-
is2FALoading: boolean;
|
|
190
|
-
/** Warning message from 2FA (e.g., low backup codes) */
|
|
191
|
-
twoFactorWarning: string | null;
|
|
192
|
-
/** Remaining attempts before 2FA lockout (null = unknown) */
|
|
193
|
-
twoFactorAttemptsRemaining: number | null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
173
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
197
174
|
// Combined Form Interface
|
|
198
175
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -202,8 +179,7 @@ export interface AuthFormReturn extends
|
|
|
202
179
|
AuthFormStateHandlers,
|
|
203
180
|
AuthFormSubmitHandlers,
|
|
204
181
|
AuthFormValidation,
|
|
205
|
-
AuthFormAutoSubmit
|
|
206
|
-
AuthForm2FAState {}
|
|
182
|
+
AuthFormAutoSubmit {}
|
|
207
183
|
|
|
208
184
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
209
185
|
// Hook Options
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for useAuthForm 2FA behavior.
|
|
3
|
-
*
|
|
4
|
-
* The sign-in flow only *verifies* an existing 2FA challenge — it never
|
|
5
|
-
* prompts the user to *set up* 2FA (that lives in ProfileLayout). These tests
|
|
6
|
-
* cover the step machine and the shouldPrompt2FA state that the verification
|
|
7
|
-
* branch relies on.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// @vitest-environment jsdom
|
|
11
|
-
import { act, renderHook } from '@testing-library/react';
|
|
12
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
|
-
|
|
14
|
-
import { useAuthForm } from '../hooks/useAuthForm';
|
|
15
|
-
|
|
16
|
-
// useAuthForm pulls in useAutoAuth → useCfgRouter → next/navigation, which
|
|
17
|
-
// throws outside an app-router provider. Stub the navigation surface it uses.
|
|
18
|
-
vi.mock('next/navigation', () => ({
|
|
19
|
-
useRouter: () => ({ push: vi.fn(), replace: vi.fn(), prefetch: vi.fn(), back: vi.fn() }),
|
|
20
|
-
usePathname: () => '/auth',
|
|
21
|
-
useSearchParams: () => new URLSearchParams(),
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
// Mock dependencies (migrated from Jest → Vitest)
|
|
25
|
-
vi.mock('../context', () => ({
|
|
26
|
-
useAuth: () => ({
|
|
27
|
-
requestOTP: vi.fn().mockResolvedValue({ success: true }),
|
|
28
|
-
verifyOTP: vi.fn(),
|
|
29
|
-
getSavedEmail: vi.fn().mockReturnValue(null),
|
|
30
|
-
saveEmail: vi.fn(),
|
|
31
|
-
clearSavedEmail: vi.fn(),
|
|
32
|
-
getSavedPhone: vi.fn().mockReturnValue(null),
|
|
33
|
-
savePhone: vi.fn(),
|
|
34
|
-
clearSavedPhone: vi.fn(),
|
|
35
|
-
}),
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
vi.mock('../hooks/useTwoFactorVerify', () => ({
|
|
39
|
-
useTwoFactorVerify: () => ({
|
|
40
|
-
isLoading: false,
|
|
41
|
-
warning: null,
|
|
42
|
-
verifyTOTP: vi.fn(),
|
|
43
|
-
verifyBackupCode: vi.fn(),
|
|
44
|
-
}),
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
vi.mock('../utils/logger', () => ({
|
|
48
|
-
authLogger: {
|
|
49
|
-
info: vi.fn(),
|
|
50
|
-
warn: vi.fn(),
|
|
51
|
-
error: vi.fn(),
|
|
52
|
-
},
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
describe('useAuthForm - 2FA', () => {
|
|
56
|
-
const defaultOptions = {
|
|
57
|
-
sourceUrl: 'http://localhost',
|
|
58
|
-
redirectUrl: '/dashboard',
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
beforeEach(() => {
|
|
62
|
-
vi.clearAllMocks();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('step transitions', () => {
|
|
66
|
-
it('should start on the identifier step', () => {
|
|
67
|
-
const { result } = renderHook(() => useAuthForm(defaultOptions));
|
|
68
|
-
|
|
69
|
-
expect(result.current.step).toBe('identifier');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should expose a setStep handler', () => {
|
|
73
|
-
const { result } = renderHook(() => useAuthForm(defaultOptions));
|
|
74
|
-
|
|
75
|
-
expect(typeof result.current.setStep).toBe('function');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should be able to transition to the success step', () => {
|
|
79
|
-
const { result } = renderHook(() => useAuthForm(defaultOptions));
|
|
80
|
-
|
|
81
|
-
act(() => {
|
|
82
|
-
result.current.setStep('success');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
expect(result.current.step).toBe('success');
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('shouldPrompt2FA state', () => {
|
|
90
|
-
it('should initialize shouldPrompt2FA as false', () => {
|
|
91
|
-
const { result } = renderHook(() => useAuthForm(defaultOptions));
|
|
92
|
-
|
|
93
|
-
expect(result.current.shouldPrompt2FA).toBe(false);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should be able to set shouldPrompt2FA', () => {
|
|
97
|
-
const { result } = renderHook(() => useAuthForm(defaultOptions));
|
|
98
|
-
|
|
99
|
-
act(() => {
|
|
100
|
-
result.current.setShouldPrompt2FA(true);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
expect(result.current.shouldPrompt2FA).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
});
|