@djangocfg/api 2.1.58 → 2.1.60
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 +41 -0
- package/dist/auth.cjs +84 -9
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +59 -1
- package/dist/auth.d.ts +59 -1
- package/dist/auth.mjs +83 -8
- package/dist/auth.mjs.map +1 -1
- package/package.json +3 -3
- package/src/auth/__tests__/useAuthForm.2fa.test.ts +170 -0
- package/src/auth/__tests__/useTwoFactorStatus.test.ts +227 -0
- package/src/auth/hooks/index.ts +5 -0
- package/src/auth/hooks/useAuthForm.ts +10 -3
- package/src/auth/hooks/useTwoFactorStatus.ts +140 -0
- package/src/auth/types/form.ts +15 -0
package/README.md
CHANGED
|
@@ -144,6 +144,7 @@ import {
|
|
|
144
144
|
useGithubAuth, // GitHub OAuth integration
|
|
145
145
|
useTwoFactor, // 2FA verification (verify TOTP code)
|
|
146
146
|
useTwoFactorSetup, // 2FA setup flow (generate QR, verify, enable)
|
|
147
|
+
useTwoFactorStatus, // 2FA status check and disable
|
|
147
148
|
useAutoAuth, // Auto-authentication on mount
|
|
148
149
|
useLocalStorage, // localStorage helper
|
|
149
150
|
useSessionStorage, // sessionStorage helper
|
|
@@ -416,6 +417,46 @@ export function TwoFactorSetup() {
|
|
|
416
417
|
}
|
|
417
418
|
```
|
|
418
419
|
|
|
420
|
+
### useTwoFactorStatus
|
|
421
|
+
|
|
422
|
+
Check 2FA status and disable 2FA for authenticated users:
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
'use client';
|
|
426
|
+
import { useTwoFactorStatus } from '@djangocfg/api/auth';
|
|
427
|
+
|
|
428
|
+
export function TwoFactorStatus() {
|
|
429
|
+
const {
|
|
430
|
+
// State
|
|
431
|
+
has2FAEnabled, // boolean | null - current 2FA status
|
|
432
|
+
devices, // TwoFactorDevice[] - list of TOTP devices
|
|
433
|
+
isLoading,
|
|
434
|
+
error,
|
|
435
|
+
|
|
436
|
+
// Actions
|
|
437
|
+
fetchStatus, // () => Promise<void> - refresh status
|
|
438
|
+
disable2FA, // (code: string) => Promise<boolean> - disable with TOTP code
|
|
439
|
+
clearError, // () => void
|
|
440
|
+
} = useTwoFactorStatus();
|
|
441
|
+
|
|
442
|
+
useEffect(() => {
|
|
443
|
+
fetchStatus();
|
|
444
|
+
}, [fetchStatus]);
|
|
445
|
+
|
|
446
|
+
if (has2FAEnabled) {
|
|
447
|
+
return (
|
|
448
|
+
<div>
|
|
449
|
+
<p>2FA is enabled</p>
|
|
450
|
+
<p>Devices: {devices.length}</p>
|
|
451
|
+
<button onClick={() => disable2FA('123456')}>Disable 2FA</button>
|
|
452
|
+
</div>
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return <p>2FA is not enabled</p>;
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
419
460
|
## Server Components & API Routes
|
|
420
461
|
|
|
421
462
|
Use fetchers for server-side data fetching:
|
package/dist/auth.cjs
CHANGED
|
@@ -61,6 +61,7 @@ __export(auth_exports, {
|
|
|
61
61
|
useSessionStorage: () => useSessionStorage,
|
|
62
62
|
useTwoFactor: () => useTwoFactor,
|
|
63
63
|
useTwoFactorSetup: () => useTwoFactorSetup,
|
|
64
|
+
useTwoFactorStatus: () => useTwoFactorStatus,
|
|
64
65
|
validateEmail: () => validateEmail,
|
|
65
66
|
validateIdentifier: () => validateIdentifier
|
|
66
67
|
});
|
|
@@ -5886,7 +5887,8 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
|
|
|
5886
5887
|
sourceUrl,
|
|
5887
5888
|
redirectUrl,
|
|
5888
5889
|
requireTermsAcceptance = false,
|
|
5889
|
-
authPath = "/auth"
|
|
5890
|
+
authPath = "/auth",
|
|
5891
|
+
enable2FASetup = true
|
|
5890
5892
|
} = options;
|
|
5891
5893
|
const formState = useAuthFormState();
|
|
5892
5894
|
const validation = useAuthValidation();
|
|
@@ -6041,14 +6043,18 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
|
|
|
6041
6043
|
}
|
|
6042
6044
|
if (result.success) {
|
|
6043
6045
|
saveIdentifierToStorage(submitIdentifier, submitChannel);
|
|
6044
|
-
if (result.should_prompt_2fa) {
|
|
6046
|
+
if (result.should_prompt_2fa && enable2FASetup) {
|
|
6045
6047
|
authLogger.info("OTP verification successful, prompting 2FA setup");
|
|
6046
6048
|
setShouldPrompt2FA(true);
|
|
6047
6049
|
setStep("2fa-setup");
|
|
6048
6050
|
onOTPSuccess?.();
|
|
6049
6051
|
return true;
|
|
6050
6052
|
}
|
|
6051
|
-
|
|
6053
|
+
if (result.should_prompt_2fa && !enable2FASetup) {
|
|
6054
|
+
authLogger.info("OTP verification successful, skipping 2FA setup prompt (disabled)");
|
|
6055
|
+
} else {
|
|
6056
|
+
authLogger.info("OTP verification successful, showing success screen");
|
|
6057
|
+
}
|
|
6052
6058
|
setStep("success");
|
|
6053
6059
|
onOTPSuccess?.();
|
|
6054
6060
|
return true;
|
|
@@ -6065,7 +6071,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
|
|
|
6065
6071
|
} finally {
|
|
6066
6072
|
setIsLoading(false);
|
|
6067
6073
|
}
|
|
6068
|
-
}, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, setTwoFactorSessionId, setShouldPrompt2FA, setStep]);
|
|
6074
|
+
}, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, setTwoFactorSessionId, setShouldPrompt2FA, setStep, enable2FASetup]);
|
|
6069
6075
|
const handleOTPSubmit = (0, import_react8.useCallback)(async (e) => {
|
|
6070
6076
|
e.preventDefault();
|
|
6071
6077
|
await submitOTP(identifier, otp, channel);
|
|
@@ -6391,15 +6397,84 @@ var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
|
|
|
6391
6397
|
};
|
|
6392
6398
|
}, "useTwoFactorSetup");
|
|
6393
6399
|
|
|
6394
|
-
// src/auth/hooks/
|
|
6400
|
+
// src/auth/hooks/useTwoFactorStatus.ts
|
|
6395
6401
|
var import_react11 = require("react");
|
|
6402
|
+
var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
|
|
6403
|
+
const [isLoading, setIsLoading] = (0, import_react11.useState)(false);
|
|
6404
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
6405
|
+
const [has2FAEnabled, setHas2FAEnabled] = (0, import_react11.useState)(null);
|
|
6406
|
+
const [devices, setDevices] = (0, import_react11.useState)([]);
|
|
6407
|
+
const clearError = (0, import_react11.useCallback)(() => {
|
|
6408
|
+
setError(null);
|
|
6409
|
+
}, []);
|
|
6410
|
+
const fetchStatus = (0, import_react11.useCallback)(async () => {
|
|
6411
|
+
setIsLoading(true);
|
|
6412
|
+
setError(null);
|
|
6413
|
+
try {
|
|
6414
|
+
authLogger.info("Fetching 2FA status...");
|
|
6415
|
+
const response = await apiTotp.totp_management.totpDevicesList();
|
|
6416
|
+
const mappedDevices = (response.results || []).map((device) => ({
|
|
6417
|
+
id: device.id,
|
|
6418
|
+
name: device.name,
|
|
6419
|
+
createdAt: device.created_at,
|
|
6420
|
+
lastUsedAt: device.last_used_at ?? null,
|
|
6421
|
+
isPrimary: device.is_primary
|
|
6422
|
+
}));
|
|
6423
|
+
setDevices(mappedDevices);
|
|
6424
|
+
const enabled = mappedDevices.length > 0;
|
|
6425
|
+
setHas2FAEnabled(enabled);
|
|
6426
|
+
authLogger.info("2FA status:", enabled ? "enabled" : "disabled");
|
|
6427
|
+
} catch (err) {
|
|
6428
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch 2FA status";
|
|
6429
|
+
authLogger.error("Failed to fetch 2FA status:", err);
|
|
6430
|
+
setError(errorMessage);
|
|
6431
|
+
} finally {
|
|
6432
|
+
setIsLoading(false);
|
|
6433
|
+
}
|
|
6434
|
+
}, []);
|
|
6435
|
+
const disable2FA = (0, import_react11.useCallback)(async (code) => {
|
|
6436
|
+
if (!code || code.length !== 6) {
|
|
6437
|
+
setError("Please enter a 6-digit code");
|
|
6438
|
+
return false;
|
|
6439
|
+
}
|
|
6440
|
+
setIsLoading(true);
|
|
6441
|
+
setError(null);
|
|
6442
|
+
try {
|
|
6443
|
+
authLogger.info("Disabling 2FA...");
|
|
6444
|
+
await apiTotp.totp_management.totpDisableCreate({ code });
|
|
6445
|
+
setHas2FAEnabled(false);
|
|
6446
|
+
setDevices([]);
|
|
6447
|
+
authLogger.info("2FA disabled successfully");
|
|
6448
|
+
return true;
|
|
6449
|
+
} catch (err) {
|
|
6450
|
+
const errorMessage = err instanceof Error ? err.message : "Invalid verification code";
|
|
6451
|
+
authLogger.error("Failed to disable 2FA:", err);
|
|
6452
|
+
setError(errorMessage);
|
|
6453
|
+
return false;
|
|
6454
|
+
} finally {
|
|
6455
|
+
setIsLoading(false);
|
|
6456
|
+
}
|
|
6457
|
+
}, []);
|
|
6458
|
+
return {
|
|
6459
|
+
isLoading,
|
|
6460
|
+
error,
|
|
6461
|
+
has2FAEnabled,
|
|
6462
|
+
devices,
|
|
6463
|
+
fetchStatus,
|
|
6464
|
+
disable2FA,
|
|
6465
|
+
clearError
|
|
6466
|
+
};
|
|
6467
|
+
}, "useTwoFactorStatus");
|
|
6468
|
+
|
|
6469
|
+
// src/auth/hooks/useAuthGuard.ts
|
|
6470
|
+
var import_react12 = require("react");
|
|
6396
6471
|
var import_hooks6 = require("@djangocfg/ui-nextjs/hooks");
|
|
6397
6472
|
var useAuthGuard = /* @__PURE__ */ __name((options = {}) => {
|
|
6398
6473
|
const { redirectTo = "/auth", requireAuth = true, saveRedirectUrl: shouldSaveUrl = true } = options;
|
|
6399
6474
|
const { isAuthenticated, isLoading, saveRedirectUrl } = useAuth();
|
|
6400
6475
|
const router = (0, import_hooks6.useCfgRouter)();
|
|
6401
|
-
const [isRedirecting, setIsRedirecting] = (0,
|
|
6402
|
-
(0,
|
|
6476
|
+
const [isRedirecting, setIsRedirecting] = (0, import_react12.useState)(false);
|
|
6477
|
+
(0, import_react12.useEffect)(() => {
|
|
6403
6478
|
if (!isLoading && requireAuth && !isAuthenticated && !isRedirecting) {
|
|
6404
6479
|
if (shouldSaveUrl && typeof window !== "undefined") {
|
|
6405
6480
|
const currentUrl = window.location.pathname + window.location.search;
|
|
@@ -6413,9 +6488,9 @@ var useAuthGuard = /* @__PURE__ */ __name((options = {}) => {
|
|
|
6413
6488
|
}, "useAuthGuard");
|
|
6414
6489
|
|
|
6415
6490
|
// src/auth/hooks/useLocalStorage.ts
|
|
6416
|
-
var
|
|
6491
|
+
var import_react13 = require("react");
|
|
6417
6492
|
function useLocalStorage2(key, initialValue) {
|
|
6418
|
-
const [storedValue, setStoredValue] = (0,
|
|
6493
|
+
const [storedValue, setStoredValue] = (0, import_react13.useState)(() => {
|
|
6419
6494
|
if (typeof window === "undefined") {
|
|
6420
6495
|
return initialValue;
|
|
6421
6496
|
}
|