@djangocfg/api 2.1.481 → 2.1.483
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 +1 -1
- package/dist/auth.cjs +64 -25
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +14 -1
- package/dist/auth.d.ts +14 -1
- package/dist/auth.mjs +64 -25
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.cjs +29 -8
- package/dist/clients.cjs.map +1 -1
- package/dist/clients.d.cts +22 -2
- package/dist/clients.d.ts +22 -2
- package/dist/clients.mjs +29 -8
- package/dist/clients.mjs.map +1 -1
- package/dist/hooks.cjs +29 -8
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.mjs +29 -8
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.cjs +31 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -4
- package/dist/index.d.ts +32 -4
- package/dist/index.mjs +31 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_api/generated/_cfg_accounts/api.ts +3 -3
- package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOtpConsentPolicyRetrieve.ts +2 -2
- package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOtpRequestCreate.ts +2 -2
- package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOtpVerifyCreate.ts +2 -2
- package/src/_api/generated/_cfg_accounts/index.ts +1 -1
- package/src/_api/generated/_cfg_accounts/openapi.json +21 -43
- package/src/_api/generated/_cfg_accounts/schemas/OAuthCallbackRequestRequest.ts +1 -0
- package/src/_api/generated/_cfg_accounts/schemas/OAuthTokenResponse.ts +1 -0
- package/src/_api/generated/_cfg_accounts/schemas/OTPVerifyRequest.ts +1 -0
- package/src/_api/generated/_cfg_accounts/schemas/OTPVerifyResponse.ts +1 -0
- package/src/_api/generated/_cfg_totp/openapi.json +5 -0
- package/src/_api/generated/_cfg_totp/schemas/VerifyResponse.ts +1 -0
- package/src/_api/generated/helpers/auth.ts +27 -6
- package/src/_api/generated/index.ts +1 -1
- package/src/_api/generated/openapi.json +26 -43
- package/src/_api/generated/sdk.gen.ts +1 -1
- package/src/_api/generated/types.gen.ts +28 -0
- package/src/auth/context/AccountsContext.tsx +1 -0
- package/src/auth/context/AuthContext.tsx +3 -2
- package/src/auth/context/types.ts +1 -1
- package/src/auth/hooks/useAuthForm.ts +3 -2
- package/src/auth/hooks/useAuthFormState.ts +3 -0
- package/src/auth/hooks/useAuthRedirect.ts +2 -2
- package/src/auth/hooks/useGithubAuth.ts +34 -21
- package/src/auth/hooks/useTwoFactorVerify.ts +5 -3
- package/src/auth/types/form.ts +3 -1
- package/src/index.ts +5 -1
|
@@ -49,7 +49,7 @@ import type { OtpRequestRequest } from '../../_api/generated/_cfg_accounts/types
|
|
|
49
49
|
// everything downstream reads the RESOLVED values from `useAuth().routes`.
|
|
50
50
|
const defaultRoutes = {
|
|
51
51
|
auth: '/auth',
|
|
52
|
-
defaultCallback: '/',
|
|
52
|
+
defaultCallback: '/private',
|
|
53
53
|
defaultAuthCallback: '/auth',
|
|
54
54
|
};
|
|
55
55
|
|
|
@@ -288,6 +288,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
288
288
|
sourceUrl?: string,
|
|
289
289
|
redirectUrl?: string,
|
|
290
290
|
skipRedirect?: boolean,
|
|
291
|
+
rememberMe = false,
|
|
291
292
|
): Promise<{
|
|
292
293
|
success: boolean;
|
|
293
294
|
message: string;
|
|
@@ -305,6 +306,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
305
306
|
identifier,
|
|
306
307
|
otp: otpCode,
|
|
307
308
|
source_url: sourceUrl,
|
|
309
|
+
remember_me: rememberMe,
|
|
308
310
|
});
|
|
309
311
|
|
|
310
312
|
if (result.requires_2fa && result.session_id) {
|
|
@@ -322,7 +324,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
322
324
|
authLogger.error('Verify OTP returned invalid response:', result);
|
|
323
325
|
return { success: false, message: 'Invalid OTP verification response' };
|
|
324
326
|
}
|
|
325
|
-
|
|
326
327
|
if (identifier.includes('@')) {
|
|
327
328
|
setStoredEmail(identifier);
|
|
328
329
|
}
|
|
@@ -50,7 +50,7 @@ export interface AuthContextType {
|
|
|
50
50
|
|
|
51
51
|
// OTP Methods - email only
|
|
52
52
|
requestOTP: (identifier: string, sourceUrl?: string, consent?: OTPRequestConsent) => Promise<OTPRequestResult>;
|
|
53
|
-
verifyOTP: (identifier: string, otpCode: string, sourceUrl?: string, redirectUrl?: string, skipRedirect?: boolean) => Promise<{
|
|
53
|
+
verifyOTP: (identifier: string, otpCode: string, sourceUrl?: string, redirectUrl?: string, skipRedirect?: boolean, rememberMe?: boolean) => Promise<{
|
|
54
54
|
success: boolean;
|
|
55
55
|
message: string;
|
|
56
56
|
user?: UserProfile;
|
|
@@ -54,6 +54,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
|
|
|
54
54
|
isLoading,
|
|
55
55
|
acceptedTerms,
|
|
56
56
|
marketingConsent,
|
|
57
|
+
rememberMe,
|
|
57
58
|
twoFactorSessionId,
|
|
58
59
|
twoFactorCode,
|
|
59
60
|
useBackupCode,
|
|
@@ -193,7 +194,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
|
|
|
193
194
|
|
|
194
195
|
try {
|
|
195
196
|
// Skip redirect - we'll show success screen first
|
|
196
|
-
const result = await verifyOTP(submitIdentifier, submitOtp, sourceUrl, redirectUrl, true);
|
|
197
|
+
const result = await verifyOTP(submitIdentifier, submitOtp, sourceUrl, redirectUrl, true, rememberMe);
|
|
197
198
|
|
|
198
199
|
// Check if 2FA is required (user has TOTP device)
|
|
199
200
|
if (result.requires_2fa && result.session_id) {
|
|
@@ -228,7 +229,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
|
|
|
228
229
|
} finally {
|
|
229
230
|
setIsLoading(false);
|
|
230
231
|
}
|
|
231
|
-
}, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, setTwoFactorSessionId, setShouldPrompt2FA, setStep]);
|
|
232
|
+
}, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, rememberMe, setTwoFactorSessionId, setShouldPrompt2FA, setStep]);
|
|
232
233
|
|
|
233
234
|
const handleOTPSubmit = useCallback(async (e: React.FormEvent) => {
|
|
234
235
|
e.preventDefault();
|
|
@@ -26,6 +26,7 @@ export const useAuthFormState = (
|
|
|
26
26
|
// null until the marketing-consent feature (if enabled) resolves its
|
|
27
27
|
// jurisdiction-aware default — the layout owns that decision.
|
|
28
28
|
const [marketingConsent, setMarketingConsent] = useState<boolean | null>(null);
|
|
29
|
+
const [rememberMe, setRememberMe] = useState(false);
|
|
29
30
|
const [step, setStep] = useState<AuthStep>('identifier');
|
|
30
31
|
const [error, setError] = useState('');
|
|
31
32
|
const [webmail, setWebmail] = useState<WebmailLink | null>(null);
|
|
@@ -68,6 +69,7 @@ export const useAuthFormState = (
|
|
|
68
69
|
isLoading,
|
|
69
70
|
acceptedTerms,
|
|
70
71
|
marketingConsent,
|
|
72
|
+
rememberMe,
|
|
71
73
|
step,
|
|
72
74
|
error,
|
|
73
75
|
twoFactorSessionId,
|
|
@@ -83,6 +85,7 @@ export const useAuthFormState = (
|
|
|
83
85
|
setOtp,
|
|
84
86
|
setAcceptedTerms,
|
|
85
87
|
setMarketingConsent,
|
|
88
|
+
setRememberMe,
|
|
86
89
|
setError,
|
|
87
90
|
clearError,
|
|
88
91
|
setStep,
|
|
@@ -52,7 +52,7 @@ export const consumeSavedRedirect = (): string | null => {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
export const useAuthRedirectManager = (options: AuthRedirectOptions = {}) => {
|
|
55
|
-
const { fallbackUrl = '/
|
|
55
|
+
const { fallbackUrl = '/private', clearOnUse = true } = options;
|
|
56
56
|
const [redirectUrl, setRedirectUrl, removeRedirectUrl] = useSessionStorage<string>(AUTH_REDIRECT_KEY, '');
|
|
57
57
|
|
|
58
58
|
const setRedirect = (url: string) => {
|
|
@@ -101,4 +101,4 @@ export const useAuthRedirectManager = (options: AuthRedirectOptions = {}) => {
|
|
|
101
101
|
getFinalRedirectUrl,
|
|
102
102
|
useAndClearRedirect
|
|
103
103
|
};
|
|
104
|
-
};
|
|
104
|
+
};
|
|
@@ -10,6 +10,8 @@ import { useCfgRouter } from './useCfgRouter';
|
|
|
10
10
|
|
|
11
11
|
export interface UseGithubAuthOptions {
|
|
12
12
|
sourceUrl?: string;
|
|
13
|
+
/** Keep the completed browser session for 30 days. */
|
|
14
|
+
rememberMe?: boolean;
|
|
13
15
|
onSuccess?: (user: any, isNewUser: boolean) => void;
|
|
14
16
|
onError?: (error: string) => void;
|
|
15
17
|
/** Callback when 2FA is required */
|
|
@@ -46,7 +48,7 @@ export interface UseGithubAuthReturn {
|
|
|
46
48
|
* ```
|
|
47
49
|
*/
|
|
48
50
|
export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuthReturn => {
|
|
49
|
-
const { sourceUrl, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
|
|
51
|
+
const { sourceUrl, rememberMe = false, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
|
|
50
52
|
const router = useCfgRouter();
|
|
51
53
|
|
|
52
54
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -58,6 +60,9 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
|
|
|
58
60
|
const startGithubAuth = useCallback(async () => {
|
|
59
61
|
setIsLoading(true);
|
|
60
62
|
setError(null);
|
|
63
|
+
// Match the OTP flow: a new login must not leave an older persistent
|
|
64
|
+
// session available after this browser is intentionally made session-only.
|
|
65
|
+
auth.clearSession();
|
|
61
66
|
|
|
62
67
|
try {
|
|
63
68
|
authLogger.info('Starting GitHub OAuth flow...');
|
|
@@ -87,6 +92,7 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
|
|
|
87
92
|
if (typeof window !== 'undefined') {
|
|
88
93
|
sessionStorage.setItem('oauth_state', response.state);
|
|
89
94
|
sessionStorage.setItem('oauth_provider', 'github');
|
|
95
|
+
sessionStorage.setItem('cfg.oauth.remember_me', String(rememberMe));
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
window.location.href = response.authorization_url;
|
|
@@ -105,7 +111,7 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
|
|
|
105
111
|
} finally {
|
|
106
112
|
setIsLoading(false);
|
|
107
113
|
}
|
|
108
|
-
}, [sourceUrl, onError]);
|
|
114
|
+
}, [sourceUrl, rememberMe, onError]);
|
|
109
115
|
|
|
110
116
|
/**
|
|
111
117
|
* Handle GitHub OAuth callback - exchanges code for JWT tokens.
|
|
@@ -121,34 +127,38 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
|
|
|
121
127
|
authLogger.info('Processing GitHub OAuth callback...');
|
|
122
128
|
|
|
123
129
|
// Verify state matches what we stored
|
|
130
|
+
let rememberMeForSession = false;
|
|
124
131
|
if (typeof window !== 'undefined') {
|
|
125
132
|
const storedState = sessionStorage.getItem('oauth_state');
|
|
126
133
|
if (storedState && storedState !== state) {
|
|
127
134
|
throw new Error('Invalid OAuth state - possible CSRF attack');
|
|
128
135
|
}
|
|
136
|
+
rememberMeForSession = sessionStorage.getItem('cfg.oauth.remember_me') === 'true';
|
|
129
137
|
// Clear stored state
|
|
130
138
|
sessionStorage.removeItem('oauth_state');
|
|
131
139
|
sessionStorage.removeItem('oauth_provider');
|
|
140
|
+
sessionStorage.removeItem('cfg.oauth.remember_me');
|
|
141
|
+
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
const result = await CfgAccountsOauth.cfgAccountsOauthGithubCallbackCreate({
|
|
135
|
-
body: { code, state },
|
|
145
|
+
body: { code, state, remember_me: rememberMeForSession },
|
|
136
146
|
throwOnError: true,
|
|
137
147
|
});
|
|
138
148
|
const response = result.data;
|
|
139
149
|
|
|
140
150
|
// Check if 2FA is required
|
|
141
151
|
if (response.requires_2fa && response.session_id) {
|
|
142
|
-
|
|
152
|
+
authLogger.info('GitHub OAuth requires 2FA, session:', response.session_id);
|
|
143
153
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
// Track 2FA requirement
|
|
155
|
+
Analytics.event(AnalyticsEvent.AUTH_OAUTH_START, {
|
|
156
|
+
category: AnalyticsCategory.AUTH,
|
|
157
|
+
label: 'github-2fa-required',
|
|
158
|
+
});
|
|
149
159
|
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
// Call 2FA callback
|
|
161
|
+
onRequires2FA?.(response.session_id, response.should_prompt_2fa || false);
|
|
152
162
|
return;
|
|
153
163
|
}
|
|
154
164
|
|
|
@@ -158,30 +168,33 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
|
|
|
158
168
|
|
|
159
169
|
authLogger.info('GitHub OAuth successful, user:', response.user);
|
|
160
170
|
|
|
161
|
-
|
|
171
|
+
auth.setStorageMode(response.persistent_session ? 'localStorage' : 'sessionStorage');
|
|
172
|
+
// The ONE write path — atomic pair + session notification.
|
|
162
173
|
auth.setSession({ access: response.access, refresh: response.refresh });
|
|
163
174
|
|
|
164
|
-
|
|
175
|
+
// Track successful OAuth
|
|
165
176
|
Analytics.event(AnalyticsEvent.AUTH_LOGIN_SUCCESS, {
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
category: AnalyticsCategory.AUTH,
|
|
178
|
+
label: 'github',
|
|
168
179
|
});
|
|
169
180
|
|
|
170
|
-
|
|
181
|
+
// Set user ID for future tracking
|
|
171
182
|
if (response.user?.id) {
|
|
172
183
|
Analytics.setUser(String(response.user.id));
|
|
173
184
|
}
|
|
174
185
|
|
|
175
|
-
|
|
186
|
+
// Call success callback
|
|
176
187
|
onSuccess?.(response.user, response.is_new_user || false);
|
|
177
188
|
|
|
178
|
-
|
|
189
|
+
// Redirect (unless skipRedirect is true)
|
|
179
190
|
if (!skipRedirect) {
|
|
180
|
-
|
|
181
|
-
const finalRedirectUrl = redirectUrl || '/
|
|
191
|
+
// Use hardPush for full page reload - ensures all React contexts reinitialize
|
|
192
|
+
const finalRedirectUrl = redirectUrl || '/private';
|
|
182
193
|
router.hardPush(finalRedirectUrl);
|
|
183
194
|
}
|
|
184
195
|
|
|
196
|
+
return;
|
|
197
|
+
|
|
185
198
|
} catch (err) {
|
|
186
199
|
const errorMessage = err instanceof Error ? err.message : 'GitHub authentication failed';
|
|
187
200
|
authLogger.error('GitHub OAuth callback error:', err);
|
|
@@ -196,7 +209,7 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
|
|
|
196
209
|
} finally {
|
|
197
210
|
setIsLoading(false);
|
|
198
211
|
}
|
|
199
|
-
}, [onSuccess, onError, redirectUrl, router]);
|
|
212
|
+
}, [onSuccess, onError, onRequires2FA, redirectUrl, router, skipRedirect]);
|
|
200
213
|
|
|
201
214
|
return {
|
|
202
215
|
isLoading,
|
|
@@ -67,9 +67,11 @@ export const useTwoFactorVerify = (
|
|
|
67
67
|
access_token: string;
|
|
68
68
|
refresh_token: string;
|
|
69
69
|
user: any;
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
warning?: string;
|
|
71
|
+
remaining_backup_codes?: number;
|
|
72
|
+
persistent_session: boolean;
|
|
72
73
|
}) => {
|
|
74
|
+
auth.setStorageMode(response.persistent_session ? 'localStorage' : 'sessionStorage');
|
|
73
75
|
// Save tokens — the ONE write path (atomic pair + session notification).
|
|
74
76
|
auth.setSession({ access: response.access_token, refresh: response.refresh_token });
|
|
75
77
|
|
|
@@ -92,7 +94,7 @@ export const useTwoFactorVerify = (
|
|
|
92
94
|
|
|
93
95
|
// Redirect (unless skipRedirect). Priority: saved back-url > option > default.
|
|
94
96
|
if (!skipRedirect) {
|
|
95
|
-
const finalRedirectUrl = consumeSavedRedirect() || redirectUrl || '/';
|
|
97
|
+
const finalRedirectUrl = consumeSavedRedirect() || redirectUrl || '/private';
|
|
96
98
|
authLogger.info('2FA successful, redirecting to:', finalRedirectUrl);
|
|
97
99
|
router.hardPush(finalRedirectUrl);
|
|
98
100
|
}
|
package/src/auth/types/form.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface AuthFormState {
|
|
|
60
60
|
* jurisdiction-aware default on mount.
|
|
61
61
|
*/
|
|
62
62
|
marketingConsent: boolean | null;
|
|
63
|
+
/** Keep this browser signed in for 30 days after successful verification. */
|
|
64
|
+
rememberMe: boolean;
|
|
63
65
|
/** Current form step */
|
|
64
66
|
step: AuthStep;
|
|
65
67
|
/** Error message */
|
|
@@ -91,6 +93,7 @@ export interface AuthFormStateHandlers {
|
|
|
91
93
|
setOtp: (otp: string) => void;
|
|
92
94
|
setAcceptedTerms: (accepted: boolean) => void;
|
|
93
95
|
setMarketingConsent: (consent: boolean | null) => void;
|
|
96
|
+
setRememberMe: (rememberMe: boolean) => void;
|
|
94
97
|
setError: (error: string) => void;
|
|
95
98
|
clearError: () => void;
|
|
96
99
|
setStep: (step: AuthStep) => void;
|
|
@@ -191,4 +194,3 @@ export interface UseAuthFormOptions {
|
|
|
191
194
|
/** Path to auth page for auto-OTP detection. Default: '/auth' */
|
|
192
195
|
authPath?: string;
|
|
193
196
|
}
|
|
194
|
-
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,11 @@ export * from './_api/generated';
|
|
|
14
14
|
export {
|
|
15
15
|
CfgAccountsApiKey,
|
|
16
16
|
CfgAccountsOauth,
|
|
17
|
-
|
|
17
|
+
// Compatibility alias: the OTP operations were split out of the legacy
|
|
18
|
+
// `CfgAccounts` generated class, but this package's public API keeps that
|
|
19
|
+
// import stable for existing applications.
|
|
20
|
+
CfgAccountsOtp as CfgAccounts,
|
|
21
|
+
CfgAccountsOtp,
|
|
18
22
|
CfgAccountsProfile,
|
|
19
23
|
CfgAccountsAuth,
|
|
20
24
|
CfgCentrifugo,
|