@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/dist/auth.cjs
CHANGED
|
@@ -71,7 +71,9 @@ var init_OTPRequestResponse = __esm({
|
|
|
71
71
|
init_WebmailLink();
|
|
72
72
|
OTPRequestResponseSchema = import_zod3.z.object({
|
|
73
73
|
message: import_zod3.z.string(),
|
|
74
|
-
webmail: WebmailLinkSchema.nullable().optional()
|
|
74
|
+
webmail: WebmailLinkSchema.nullable().optional(),
|
|
75
|
+
resend_in: import_zod3.z.number().int().min(0).optional(),
|
|
76
|
+
expires_in: import_zod3.z.number().int().min(0).optional()
|
|
75
77
|
});
|
|
76
78
|
}
|
|
77
79
|
});
|
|
@@ -168,10 +170,12 @@ __export(auth_exports, {
|
|
|
168
170
|
AuthContext: () => AuthContext_default,
|
|
169
171
|
AuthProvider: () => AuthProvider,
|
|
170
172
|
authLogger: () => authLogger,
|
|
173
|
+
beginTwoFactorChallenge: () => beginTwoFactorChallenge,
|
|
171
174
|
clearProfileCache: () => clearProfileCache,
|
|
172
175
|
consumeSavedRedirect: () => consumeSavedRedirect,
|
|
173
176
|
decodeBase64: () => decodeBase64,
|
|
174
177
|
encodeBase64: () => encodeBase64,
|
|
178
|
+
endTwoFactorChallenge: () => endTwoFactorChallenge,
|
|
175
179
|
formatAuthError: () => formatAuthError,
|
|
176
180
|
getCacheMetadata: () => getCacheMetadata,
|
|
177
181
|
getCachedProfile: () => getCachedProfile,
|
|
@@ -204,6 +208,7 @@ __export(auth_exports, {
|
|
|
204
208
|
useSession: () => useSession,
|
|
205
209
|
useSessionStorage: () => useSessionStorage,
|
|
206
210
|
useTwoFactor: () => useTwoFactor,
|
|
211
|
+
useTwoFactorChallenge: () => useTwoFactorChallenge,
|
|
207
212
|
useTwoFactorSetup: () => useTwoFactorSetup,
|
|
208
213
|
useTwoFactorStatus: () => useTwoFactorStatus,
|
|
209
214
|
useTwoFactorVerify: () => useTwoFactorVerify,
|
|
@@ -219,12 +224,14 @@ var AUTH_CONSTANTS = {
|
|
|
219
224
|
/** TOTP code length (RFC 6238 — authenticator apps). */
|
|
220
225
|
TOTP_LENGTH: 6,
|
|
221
226
|
/** Backup code max length. */
|
|
222
|
-
BACKUP_CODE_MAX_LENGTH: 12
|
|
227
|
+
BACKUP_CODE_MAX_LENGTH: 12,
|
|
228
|
+
/** Resend cooldown (s) when the server does not state one. Mirrors the backend per-email cooldown. */
|
|
229
|
+
RESEND_COOLDOWN_SECONDS: 30
|
|
223
230
|
};
|
|
224
231
|
|
|
225
232
|
// src/auth/context/AuthContext.tsx
|
|
226
233
|
var import_navigation4 = require("next/navigation");
|
|
227
|
-
var
|
|
234
|
+
var import_react17 = require("react");
|
|
228
235
|
|
|
229
236
|
// src/_api/generated/helpers/errors.ts
|
|
230
237
|
var APIError = class extends Error {
|
|
@@ -1178,6 +1185,28 @@ var formatCountdown = /* @__PURE__ */ __name((s) => {
|
|
|
1178
1185
|
const m = Math.floor(s / 60);
|
|
1179
1186
|
return m > 0 ? `${m}:${String(s % 60).padStart(2, "0")}` : `${s}s`;
|
|
1180
1187
|
}, "formatCountdown");
|
|
1188
|
+
var useCountdown = /* @__PURE__ */ __name(() => {
|
|
1189
|
+
const [seconds, setSeconds] = (0, import_react4.useState)(0);
|
|
1190
|
+
const timerRef = (0, import_react4.useRef)(null);
|
|
1191
|
+
const start = (0, import_react4.useCallback)((from) => {
|
|
1192
|
+
if (timerRef.current) clearInterval(timerRef.current);
|
|
1193
|
+
setSeconds(from);
|
|
1194
|
+
timerRef.current = setInterval(() => {
|
|
1195
|
+
setSeconds((prev) => {
|
|
1196
|
+
if (prev <= 1) {
|
|
1197
|
+
clearInterval(timerRef.current);
|
|
1198
|
+
timerRef.current = null;
|
|
1199
|
+
return 0;
|
|
1200
|
+
}
|
|
1201
|
+
return prev - 1;
|
|
1202
|
+
});
|
|
1203
|
+
}, 1e3);
|
|
1204
|
+
}, []);
|
|
1205
|
+
(0, import_react4.useEffect)(() => () => {
|
|
1206
|
+
if (timerRef.current) clearInterval(timerRef.current);
|
|
1207
|
+
}, []);
|
|
1208
|
+
return { seconds, start };
|
|
1209
|
+
}, "useCountdown");
|
|
1181
1210
|
var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
|
|
1182
1211
|
const [identifier, setIdentifier] = (0, import_react4.useState)(initialIdentifier);
|
|
1183
1212
|
const [otp, setOtp] = (0, import_react4.useState)("");
|
|
@@ -1185,33 +1214,12 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
|
|
|
1185
1214
|
const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
|
|
1186
1215
|
const [acceptedTerms, setAcceptedTerms] = (0, import_react4.useState)(true);
|
|
1187
1216
|
const [marketingConsent, setMarketingConsent] = (0, import_react4.useState)(null);
|
|
1188
|
-
const [rememberMe, setRememberMe] = (0, import_react4.useState)(
|
|
1217
|
+
const [rememberMe, setRememberMe] = (0, import_react4.useState)(true);
|
|
1189
1218
|
const [step, setStep] = (0, import_react4.useState)("identifier");
|
|
1190
1219
|
const [error, setError] = (0, import_react4.useState)("");
|
|
1191
1220
|
const [webmail, setWebmail] = (0, import_react4.useState)(null);
|
|
1192
|
-
const
|
|
1193
|
-
const
|
|
1194
|
-
const [twoFactorCode, setTwoFactorCode] = (0, import_react4.useState)("");
|
|
1195
|
-
const [useBackupCode, setUseBackupCode] = (0, import_react4.useState)(false);
|
|
1196
|
-
const [rateLimitSeconds, setRateLimitSeconds] = (0, import_react4.useState)(0);
|
|
1197
|
-
const rateLimitTimerRef = (0, import_react4.useRef)(null);
|
|
1198
|
-
const startRateLimitCountdown = (0, import_react4.useCallback)((seconds) => {
|
|
1199
|
-
if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
|
|
1200
|
-
setRateLimitSeconds(seconds);
|
|
1201
|
-
rateLimitTimerRef.current = setInterval(() => {
|
|
1202
|
-
setRateLimitSeconds((prev) => {
|
|
1203
|
-
if (prev <= 1) {
|
|
1204
|
-
clearInterval(rateLimitTimerRef.current);
|
|
1205
|
-
rateLimitTimerRef.current = null;
|
|
1206
|
-
return 0;
|
|
1207
|
-
}
|
|
1208
|
-
return prev - 1;
|
|
1209
|
-
});
|
|
1210
|
-
}, 1e3);
|
|
1211
|
-
}, []);
|
|
1212
|
-
(0, import_react4.useEffect)(() => () => {
|
|
1213
|
-
if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
|
|
1214
|
-
}, []);
|
|
1221
|
+
const rateLimit = useCountdown();
|
|
1222
|
+
const resend = useCountdown();
|
|
1215
1223
|
const clearError = (0, import_react4.useCallback)(() => setError(""), []);
|
|
1216
1224
|
return {
|
|
1217
1225
|
// State
|
|
@@ -1224,13 +1232,11 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
|
|
|
1224
1232
|
rememberMe,
|
|
1225
1233
|
step,
|
|
1226
1234
|
error,
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
isRateLimited: rateLimitSeconds > 0,
|
|
1233
|
-
rateLimitLabel: formatCountdown(rateLimitSeconds),
|
|
1235
|
+
rateLimitSeconds: rateLimit.seconds,
|
|
1236
|
+
isRateLimited: rateLimit.seconds > 0,
|
|
1237
|
+
rateLimitLabel: formatCountdown(rateLimit.seconds),
|
|
1238
|
+
resendSeconds: resend.seconds,
|
|
1239
|
+
resendLabel: formatCountdown(resend.seconds),
|
|
1234
1240
|
webmail,
|
|
1235
1241
|
// Handlers
|
|
1236
1242
|
setIdentifier,
|
|
@@ -1243,11 +1249,8 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
|
|
|
1243
1249
|
clearError,
|
|
1244
1250
|
setStep,
|
|
1245
1251
|
setIsLoading,
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
setTwoFactorCode,
|
|
1249
|
-
setUseBackupCode,
|
|
1250
|
-
startRateLimitCountdown,
|
|
1252
|
+
startRateLimitCountdown: rateLimit.start,
|
|
1253
|
+
startResendCountdown: resend.start,
|
|
1251
1254
|
setWebmail
|
|
1252
1255
|
};
|
|
1253
1256
|
}, "useAuthFormState");
|
|
@@ -1264,7 +1267,7 @@ var useAuthValidation = /* @__PURE__ */ __name(() => {
|
|
|
1264
1267
|
var validateIdentifier = /* @__PURE__ */ __name((id) => EMAIL_REGEX.test(id), "validateIdentifier");
|
|
1265
1268
|
|
|
1266
1269
|
// src/auth/hooks/useAuthForm.ts
|
|
1267
|
-
var
|
|
1270
|
+
var import_react8 = require("react");
|
|
1268
1271
|
|
|
1269
1272
|
// src/auth/utils/logger.ts
|
|
1270
1273
|
var import_consola2 = require("consola");
|
|
@@ -1329,86 +1332,410 @@ var useAutoAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
|
1329
1332
|
};
|
|
1330
1333
|
}, "useAutoAuth");
|
|
1331
1334
|
|
|
1332
|
-
// src/auth/hooks/
|
|
1333
|
-
var
|
|
1335
|
+
// src/auth/hooks/useTwoFactorChallenge.ts
|
|
1336
|
+
var import_react7 = require("react");
|
|
1337
|
+
var current = null;
|
|
1338
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
1339
|
+
var set = /* @__PURE__ */ __name((next) => {
|
|
1340
|
+
current = next;
|
|
1341
|
+
for (const listener of [...listeners]) listener();
|
|
1342
|
+
}, "set");
|
|
1343
|
+
var subscribe = /* @__PURE__ */ __name((listener) => {
|
|
1344
|
+
listeners.add(listener);
|
|
1345
|
+
return () => {
|
|
1346
|
+
listeners.delete(listener);
|
|
1347
|
+
};
|
|
1348
|
+
}, "subscribe");
|
|
1349
|
+
var beginTwoFactorChallenge = /* @__PURE__ */ __name((challenge) => set(challenge), "beginTwoFactorChallenge");
|
|
1350
|
+
var endTwoFactorChallenge = /* @__PURE__ */ __name(() => set(null), "endTwoFactorChallenge");
|
|
1351
|
+
var useTwoFactorChallenge = /* @__PURE__ */ __name(() => (0, import_react7.useSyncExternalStore)(subscribe, () => current, () => null), "useTwoFactorChallenge");
|
|
1334
1352
|
|
|
1335
|
-
// src/
|
|
1336
|
-
var
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1353
|
+
// src/auth/hooks/useAuthForm.ts
|
|
1354
|
+
var useAuthForm = /* @__PURE__ */ __name((options) => {
|
|
1355
|
+
const {
|
|
1356
|
+
onIdentifierSuccess,
|
|
1357
|
+
onOTPSuccess,
|
|
1358
|
+
onError,
|
|
1359
|
+
sourceUrl,
|
|
1360
|
+
redirectUrl,
|
|
1361
|
+
requireTermsAcceptance = false,
|
|
1362
|
+
marketingConsent: marketingConsentOption,
|
|
1363
|
+
authPath = "/auth"
|
|
1364
|
+
} = options;
|
|
1365
|
+
const formState = useAuthFormState();
|
|
1366
|
+
const validation = useAuthValidation();
|
|
1367
|
+
const isAutoSubmitFromUrlRef = (0, import_react8.useRef)(false);
|
|
1368
|
+
const processedOtpKeyRef = (0, import_react8.useRef)(null);
|
|
1369
|
+
const {
|
|
1370
|
+
requestOTP,
|
|
1371
|
+
verifyOTP,
|
|
1372
|
+
login,
|
|
1373
|
+
getSavedEmail,
|
|
1374
|
+
saveEmail
|
|
1375
|
+
} = useAuth();
|
|
1376
|
+
const {
|
|
1377
|
+
identifier,
|
|
1378
|
+
otp,
|
|
1379
|
+
password,
|
|
1380
|
+
acceptedTerms,
|
|
1381
|
+
marketingConsent,
|
|
1382
|
+
rememberMe,
|
|
1383
|
+
setIdentifier,
|
|
1384
|
+
setOtp,
|
|
1385
|
+
setStep,
|
|
1386
|
+
setError,
|
|
1387
|
+
setIsLoading,
|
|
1388
|
+
clearError,
|
|
1389
|
+
startRateLimitCountdown,
|
|
1390
|
+
startResendCountdown,
|
|
1391
|
+
setWebmail
|
|
1392
|
+
} = formState;
|
|
1393
|
+
const { validateIdentifier: validateIdentifier2 } = validation;
|
|
1394
|
+
const saveIdentifierToStorage = (0, import_react8.useCallback)((id) => {
|
|
1395
|
+
saveEmail(id);
|
|
1396
|
+
}, [saveEmail]);
|
|
1397
|
+
(0, import_react8.useEffect)(() => {
|
|
1398
|
+
const savedEmail = getSavedEmail();
|
|
1399
|
+
if (savedEmail) {
|
|
1400
|
+
setIdentifier(savedEmail);
|
|
1401
|
+
}
|
|
1402
|
+
}, []);
|
|
1403
|
+
const handleIdentifierSubmit = (0, import_react8.useCallback)(async (e) => {
|
|
1404
|
+
e.preventDefault();
|
|
1405
|
+
if (!identifier) {
|
|
1406
|
+
const msg = "Please enter your email address";
|
|
1407
|
+
setError(msg);
|
|
1408
|
+
onError?.(msg);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
if (!validateIdentifier2(identifier)) {
|
|
1412
|
+
const msg = "Please enter a valid email address";
|
|
1413
|
+
setError(msg);
|
|
1414
|
+
onError?.(msg);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
if (requireTermsAcceptance && !acceptedTerms) {
|
|
1418
|
+
const msg = "Please accept the Terms of Service and Privacy Policy";
|
|
1419
|
+
setError(msg);
|
|
1420
|
+
onError?.(msg);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
clearError();
|
|
1424
|
+
setOtp("");
|
|
1425
|
+
setWebmail(null);
|
|
1426
|
+
setStep("otp");
|
|
1427
|
+
const rollBack = /* @__PURE__ */ __name((msg) => {
|
|
1428
|
+
setStep("identifier");
|
|
1429
|
+
setError(msg);
|
|
1430
|
+
onError?.(msg);
|
|
1431
|
+
}, "rollBack");
|
|
1432
|
+
try {
|
|
1433
|
+
const consent = marketingConsentOption ? { marketingConsent: marketingConsent === true, disclosureVersion: marketingConsentOption.disclosureVersion } : void 0;
|
|
1434
|
+
const result = await requestOTP(identifier, sourceUrl, consent);
|
|
1435
|
+
if (result.success) {
|
|
1436
|
+
saveIdentifierToStorage(identifier);
|
|
1437
|
+
setWebmail(result.webmail ?? null);
|
|
1438
|
+
startResendCountdown(result.resendIn ?? AUTH_CONSTANTS.RESEND_COOLDOWN_SECONDS);
|
|
1439
|
+
onIdentifierSuccess?.(identifier);
|
|
1440
|
+
} else if (result.retryAfter) {
|
|
1441
|
+
setStep("identifier");
|
|
1442
|
+
startRateLimitCountdown(result.retryAfter);
|
|
1443
|
+
} else {
|
|
1444
|
+
rollBack(result.message);
|
|
1351
1445
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1446
|
+
} catch {
|
|
1447
|
+
rollBack("An unexpected error occurred");
|
|
1448
|
+
}
|
|
1449
|
+
}, [
|
|
1450
|
+
identifier,
|
|
1451
|
+
acceptedTerms,
|
|
1452
|
+
requireTermsAcceptance,
|
|
1453
|
+
marketingConsent,
|
|
1454
|
+
marketingConsentOption,
|
|
1455
|
+
validateIdentifier2,
|
|
1456
|
+
requestOTP,
|
|
1457
|
+
saveIdentifierToStorage,
|
|
1458
|
+
setError,
|
|
1459
|
+
setOtp,
|
|
1460
|
+
setStep,
|
|
1461
|
+
clearError,
|
|
1462
|
+
setWebmail,
|
|
1463
|
+
startRateLimitCountdown,
|
|
1464
|
+
startResendCountdown,
|
|
1465
|
+
onIdentifierSuccess,
|
|
1466
|
+
onError,
|
|
1467
|
+
sourceUrl
|
|
1468
|
+
]);
|
|
1469
|
+
const submitOTP = (0, import_react8.useCallback)(async (submitIdentifier, submitOtp) => {
|
|
1470
|
+
if (!submitOtp || submitOtp.length < AUTH_CONSTANTS.EMAIL_OTP_LENGTH) {
|
|
1471
|
+
const msg = `Please enter the ${AUTH_CONSTANTS.EMAIL_OTP_LENGTH}-digit verification code`;
|
|
1472
|
+
setError(msg);
|
|
1473
|
+
onError?.(msg);
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
setIsLoading(true);
|
|
1477
|
+
clearError();
|
|
1478
|
+
try {
|
|
1479
|
+
const result = await verifyOTP(submitIdentifier, submitOtp, sourceUrl, redirectUrl, true, rememberMe);
|
|
1480
|
+
if (result.requires_2fa && result.session_id) {
|
|
1481
|
+
authLogger.info("2FA required after OTP verification");
|
|
1482
|
+
beginTwoFactorChallenge({ sessionId: result.session_id });
|
|
1483
|
+
saveIdentifierToStorage(submitIdentifier);
|
|
1484
|
+
return true;
|
|
1485
|
+
}
|
|
1486
|
+
if (result.success) {
|
|
1487
|
+
saveIdentifierToStorage(submitIdentifier);
|
|
1488
|
+
authLogger.info("OTP verification successful, showing success screen");
|
|
1489
|
+
setStep("success");
|
|
1490
|
+
onOTPSuccess?.();
|
|
1491
|
+
return true;
|
|
1354
1492
|
} else {
|
|
1355
|
-
|
|
1493
|
+
setError(result.message);
|
|
1494
|
+
onError?.(result.message);
|
|
1495
|
+
return false;
|
|
1356
1496
|
}
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
attempt++;
|
|
1397
|
-
const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
|
|
1398
|
-
if (lastEventId !== void 0) {
|
|
1399
|
-
headers.set("Last-Event-ID", lastEventId);
|
|
1497
|
+
} catch {
|
|
1498
|
+
const msg = "An unexpected error occurred";
|
|
1499
|
+
setError(msg);
|
|
1500
|
+
onError?.(msg);
|
|
1501
|
+
return false;
|
|
1502
|
+
} finally {
|
|
1503
|
+
setIsLoading(false);
|
|
1504
|
+
}
|
|
1505
|
+
}, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, rememberMe, setStep]);
|
|
1506
|
+
const handleOTPSubmit = (0, import_react8.useCallback)(async (e) => {
|
|
1507
|
+
e.preventDefault();
|
|
1508
|
+
await submitOTP(identifier, otp);
|
|
1509
|
+
}, [identifier, otp, submitOTP]);
|
|
1510
|
+
const submitPassword = (0, import_react8.useCallback)(async (submitIdentifier, submitPassword_) => {
|
|
1511
|
+
if (!submitIdentifier || !validateIdentifier2(submitIdentifier)) {
|
|
1512
|
+
const msg = "Please enter a valid email address";
|
|
1513
|
+
setError(msg);
|
|
1514
|
+
onError?.(msg);
|
|
1515
|
+
return false;
|
|
1516
|
+
}
|
|
1517
|
+
if (!submitPassword_) {
|
|
1518
|
+
const msg = "Please enter your password";
|
|
1519
|
+
setError(msg);
|
|
1520
|
+
onError?.(msg);
|
|
1521
|
+
return false;
|
|
1522
|
+
}
|
|
1523
|
+
setIsLoading(true);
|
|
1524
|
+
clearError();
|
|
1525
|
+
try {
|
|
1526
|
+
const result = await login(submitIdentifier, submitPassword_, {
|
|
1527
|
+
redirectUrl,
|
|
1528
|
+
skipRedirect: true,
|
|
1529
|
+
rememberMe
|
|
1530
|
+
});
|
|
1531
|
+
if (result.requires_2fa && result.session_id) {
|
|
1532
|
+
authLogger.info("2FA required after password login");
|
|
1533
|
+
beginTwoFactorChallenge({ sessionId: result.session_id });
|
|
1534
|
+
saveIdentifierToStorage(submitIdentifier);
|
|
1535
|
+
return true;
|
|
1400
1536
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1537
|
+
if (result.success) {
|
|
1538
|
+
saveIdentifierToStorage(submitIdentifier);
|
|
1539
|
+
authLogger.info("Password login successful, showing success screen");
|
|
1540
|
+
setStep("success");
|
|
1541
|
+
onOTPSuccess?.();
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
if (result.retryAfter) {
|
|
1545
|
+
startRateLimitCountdown(result.retryAfter);
|
|
1546
|
+
clearError();
|
|
1547
|
+
} else {
|
|
1548
|
+
setError(result.message);
|
|
1549
|
+
onError?.(result.message);
|
|
1550
|
+
}
|
|
1551
|
+
return false;
|
|
1552
|
+
} catch {
|
|
1553
|
+
const msg = "An unexpected error occurred";
|
|
1554
|
+
setError(msg);
|
|
1555
|
+
onError?.(msg);
|
|
1556
|
+
return false;
|
|
1557
|
+
} finally {
|
|
1558
|
+
setIsLoading(false);
|
|
1559
|
+
}
|
|
1560
|
+
}, [
|
|
1561
|
+
login,
|
|
1562
|
+
validateIdentifier2,
|
|
1563
|
+
saveIdentifierToStorage,
|
|
1564
|
+
setError,
|
|
1565
|
+
setIsLoading,
|
|
1566
|
+
clearError,
|
|
1567
|
+
startRateLimitCountdown,
|
|
1568
|
+
onOTPSuccess,
|
|
1569
|
+
onError,
|
|
1570
|
+
redirectUrl,
|
|
1571
|
+
rememberMe,
|
|
1572
|
+
setStep
|
|
1573
|
+
]);
|
|
1574
|
+
const handlePasswordSubmit = (0, import_react8.useCallback)(async (e) => {
|
|
1575
|
+
e.preventDefault();
|
|
1576
|
+
await submitPassword(identifier, password);
|
|
1577
|
+
}, [identifier, password, submitPassword]);
|
|
1578
|
+
const handleResendOTP = (0, import_react8.useCallback)(async () => {
|
|
1579
|
+
setIsLoading(true);
|
|
1580
|
+
clearError();
|
|
1581
|
+
try {
|
|
1582
|
+
const consent = marketingConsentOption ? { marketingConsent: marketingConsent === true, disclosureVersion: marketingConsentOption.disclosureVersion } : void 0;
|
|
1583
|
+
const result = await requestOTP(identifier, sourceUrl, consent);
|
|
1584
|
+
if (result.success) {
|
|
1585
|
+
saveIdentifierToStorage(identifier);
|
|
1586
|
+
setWebmail(result.webmail ?? null);
|
|
1587
|
+
startResendCountdown(result.resendIn ?? AUTH_CONSTANTS.RESEND_COOLDOWN_SECONDS);
|
|
1588
|
+
setOtp("");
|
|
1589
|
+
} else {
|
|
1590
|
+
if (result.retryAfter) {
|
|
1591
|
+
startRateLimitCountdown(result.retryAfter);
|
|
1592
|
+
clearError();
|
|
1593
|
+
} else {
|
|
1594
|
+
setError(result.message);
|
|
1595
|
+
onError?.(result.message);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
} catch {
|
|
1599
|
+
const msg = "Failed to resend verification code";
|
|
1600
|
+
setError(msg);
|
|
1601
|
+
onError?.(msg);
|
|
1602
|
+
} finally {
|
|
1603
|
+
setIsLoading(false);
|
|
1604
|
+
}
|
|
1605
|
+
}, [identifier, marketingConsent, marketingConsentOption, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, startResendCountdown, setWebmail, onError, sourceUrl]);
|
|
1606
|
+
const handleBackToIdentifier = (0, import_react8.useCallback)(() => {
|
|
1607
|
+
setStep("identifier");
|
|
1608
|
+
clearError();
|
|
1609
|
+
}, [setStep, clearError]);
|
|
1610
|
+
const forceOTPStep = (0, import_react8.useCallback)(() => {
|
|
1611
|
+
setStep("otp");
|
|
1612
|
+
clearError();
|
|
1613
|
+
}, [setStep, clearError]);
|
|
1614
|
+
const handleOTPFromUrl = (0, import_react8.useCallback)((detectedOtp, detectedEmail) => {
|
|
1615
|
+
const autoIdentifier = detectedEmail || getSavedEmail() || "";
|
|
1616
|
+
if (!autoIdentifier) {
|
|
1617
|
+
authLogger.warn("No identifier found for auto-submit (no email in URL or storage)");
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
const otpKey = `${autoIdentifier}:${detectedOtp}`;
|
|
1621
|
+
if (processedOtpKeyRef.current === otpKey) return;
|
|
1622
|
+
if (isAutoSubmitFromUrlRef.current) return;
|
|
1623
|
+
processedOtpKeyRef.current = otpKey;
|
|
1624
|
+
isAutoSubmitFromUrlRef.current = true;
|
|
1625
|
+
authLogger.info("OTP detected from URL, auto-submitting");
|
|
1626
|
+
setIdentifier(autoIdentifier);
|
|
1627
|
+
setOtp(detectedOtp);
|
|
1628
|
+
setStep("otp");
|
|
1629
|
+
setTimeout(async () => {
|
|
1630
|
+
try {
|
|
1631
|
+
await submitOTP(autoIdentifier, detectedOtp);
|
|
1632
|
+
} finally {
|
|
1633
|
+
isAutoSubmitFromUrlRef.current = false;
|
|
1634
|
+
}
|
|
1635
|
+
}, 100);
|
|
1636
|
+
}, [getSavedEmail, setIdentifier, setOtp, setStep, submitOTP]);
|
|
1637
|
+
useAutoAuth({
|
|
1638
|
+
allowedPaths: [authPath],
|
|
1639
|
+
onOTPDetected: handleOTPFromUrl,
|
|
1640
|
+
cleanupUrl: true
|
|
1641
|
+
});
|
|
1642
|
+
return {
|
|
1643
|
+
// State
|
|
1644
|
+
...formState,
|
|
1645
|
+
// Submit handlers
|
|
1646
|
+
handleIdentifierSubmit,
|
|
1647
|
+
handleOTPSubmit,
|
|
1648
|
+
handlePasswordSubmit,
|
|
1649
|
+
handleResendOTP,
|
|
1650
|
+
handleBackToIdentifier,
|
|
1651
|
+
forceOTPStep,
|
|
1652
|
+
// Validation
|
|
1653
|
+
...validation,
|
|
1654
|
+
// Auto-submit ref
|
|
1655
|
+
isAutoSubmittingFromUrl: isAutoSubmitFromUrlRef
|
|
1656
|
+
};
|
|
1657
|
+
}, "useAuthForm");
|
|
1658
|
+
|
|
1659
|
+
// src/auth/hooks/useGithubAuth.ts
|
|
1660
|
+
var import_react9 = require("react");
|
|
1661
|
+
|
|
1662
|
+
// src/_api/generated/core/bodySerializer.gen.ts
|
|
1663
|
+
var serializeFormDataPair = /* @__PURE__ */ __name((data, key, value) => {
|
|
1664
|
+
if (typeof value === "string" || value instanceof Blob) {
|
|
1665
|
+
data.append(key, value);
|
|
1666
|
+
} else if (value instanceof Date) {
|
|
1667
|
+
data.append(key, value.toISOString());
|
|
1668
|
+
} else {
|
|
1669
|
+
data.append(key, JSON.stringify(value));
|
|
1670
|
+
}
|
|
1671
|
+
}, "serializeFormDataPair");
|
|
1672
|
+
var formDataBodySerializer = {
|
|
1673
|
+
bodySerializer: /* @__PURE__ */ __name((body) => {
|
|
1674
|
+
const data = new FormData();
|
|
1675
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
1676
|
+
if (value === void 0 || value === null) {
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
if (Array.isArray(value)) {
|
|
1680
|
+
value.forEach((v) => serializeFormDataPair(data, key, v));
|
|
1681
|
+
} else {
|
|
1682
|
+
serializeFormDataPair(data, key, value);
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
return data;
|
|
1686
|
+
}, "bodySerializer")
|
|
1687
|
+
};
|
|
1688
|
+
var jsonBodySerializer = {
|
|
1689
|
+
bodySerializer: /* @__PURE__ */ __name((body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value), "bodySerializer")
|
|
1690
|
+
};
|
|
1691
|
+
|
|
1692
|
+
// src/_api/generated/core/params.gen.ts
|
|
1693
|
+
var extraPrefixesMap = {
|
|
1694
|
+
$body_: "body",
|
|
1695
|
+
$headers_: "headers",
|
|
1696
|
+
$path_: "path",
|
|
1697
|
+
$query_: "query"
|
|
1698
|
+
};
|
|
1699
|
+
var extraPrefixes = Object.entries(extraPrefixesMap);
|
|
1700
|
+
|
|
1701
|
+
// src/_api/generated/core/serverSentEvents.gen.ts
|
|
1702
|
+
function createSseClient({
|
|
1703
|
+
onRequest,
|
|
1704
|
+
onSseError,
|
|
1705
|
+
onSseEvent,
|
|
1706
|
+
responseTransformer,
|
|
1707
|
+
responseValidator,
|
|
1708
|
+
sseDefaultRetryDelay,
|
|
1709
|
+
sseMaxRetryAttempts,
|
|
1710
|
+
sseMaxRetryDelay,
|
|
1711
|
+
sseSleepFn,
|
|
1712
|
+
url,
|
|
1713
|
+
...options
|
|
1714
|
+
}) {
|
|
1715
|
+
let lastEventId;
|
|
1716
|
+
const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1717
|
+
const createStream = /* @__PURE__ */ __name(async function* () {
|
|
1718
|
+
let retryDelay = sseDefaultRetryDelay ?? 3e3;
|
|
1719
|
+
let attempt = 0;
|
|
1720
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
1721
|
+
while (true) {
|
|
1722
|
+
if (signal.aborted) break;
|
|
1723
|
+
attempt++;
|
|
1724
|
+
const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
|
|
1725
|
+
if (lastEventId !== void 0) {
|
|
1726
|
+
headers.set("Last-Event-ID", lastEventId);
|
|
1727
|
+
}
|
|
1728
|
+
try {
|
|
1729
|
+
const requestInit = {
|
|
1730
|
+
redirect: "follow",
|
|
1731
|
+
...options,
|
|
1732
|
+
body: options.serializedBody,
|
|
1733
|
+
headers,
|
|
1734
|
+
signal
|
|
1735
|
+
};
|
|
1736
|
+
let request = new Request(url, requestInit);
|
|
1737
|
+
if (onRequest) {
|
|
1738
|
+
request = await onRequest(url, requestInit);
|
|
1412
1739
|
}
|
|
1413
1740
|
const _fetch = options.fetch ?? globalThis.fetch;
|
|
1414
1741
|
const response = await _fetch(request);
|
|
@@ -2758,63 +3085,183 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
|
|
|
2758
3085
|
log.error(message, error);
|
|
2759
3086
|
}, "logAuthFailure");
|
|
2760
3087
|
|
|
2761
|
-
// src/auth/hooks/
|
|
2762
|
-
var
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
__name(isQuotaExceededError, "isQuotaExceededError");
|
|
2772
|
-
function isWithinSizeLimit(data, key) {
|
|
2773
|
-
try {
|
|
2774
|
-
const sizeInKB = new Blob([JSON.stringify(data)]).size / 1024;
|
|
2775
|
-
if (sizeInKB > MAX_ITEM_KB) {
|
|
2776
|
-
authLogger.warn(
|
|
2777
|
-
`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`
|
|
2778
|
-
);
|
|
2779
|
-
return false;
|
|
2780
|
-
}
|
|
2781
|
-
return true;
|
|
2782
|
-
} catch (error) {
|
|
2783
|
-
authLogger.error(`Error checking data size for key "${key}":`, error);
|
|
2784
|
-
return false;
|
|
2785
|
-
}
|
|
2786
|
-
}
|
|
2787
|
-
__name(isWithinSizeLimit, "isWithinSizeLimit");
|
|
2788
|
-
|
|
2789
|
-
// src/auth/hooks/useSessionStorage.ts
|
|
2790
|
-
function useSessionStorage(key, initialValue) {
|
|
2791
|
-
const [storedValue, setStoredValue] = (0, import_react7.useState)(() => {
|
|
2792
|
-
if (typeof window === "undefined") {
|
|
2793
|
-
return initialValue;
|
|
2794
|
-
}
|
|
2795
|
-
try {
|
|
2796
|
-
const item = window.sessionStorage.getItem(key);
|
|
2797
|
-
return item ? JSON.parse(item) : initialValue;
|
|
2798
|
-
} catch (error) {
|
|
2799
|
-
authLogger.error(`Error reading sessionStorage key "${key}":`, error);
|
|
2800
|
-
return initialValue;
|
|
2801
|
-
}
|
|
2802
|
-
});
|
|
2803
|
-
const clearOldData = /* @__PURE__ */ __name(() => {
|
|
3088
|
+
// src/auth/hooks/useGithubAuth.ts
|
|
3089
|
+
var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
3090
|
+
const { sourceUrl, rememberMe = false, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
|
|
3091
|
+
const router = useCfgRouter();
|
|
3092
|
+
const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
|
|
3093
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
3094
|
+
const startGithubAuth = (0, import_react9.useCallback)(async () => {
|
|
3095
|
+
setIsLoading(true);
|
|
3096
|
+
setError(null);
|
|
3097
|
+
auth.clearSession();
|
|
2804
3098
|
try {
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
3099
|
+
authLogger.info("Starting GitHub OAuth flow...");
|
|
3100
|
+
Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
|
|
3101
|
+
category: "auth" /* AUTH */,
|
|
3102
|
+
label: "github"
|
|
3103
|
+
});
|
|
3104
|
+
const result = await CfgAccountsOauth.cfgAccountsOauthGithubAuthorizeCreate({
|
|
3105
|
+
body: {
|
|
3106
|
+
source_url: sourceUrl || (typeof window !== "undefined" ? window.location.href : "")
|
|
3107
|
+
},
|
|
3108
|
+
throwOnError: true
|
|
3109
|
+
});
|
|
3110
|
+
const response = result.data;
|
|
3111
|
+
if (!response.authorization_url) {
|
|
3112
|
+
throw new Error("Failed to get authorization URL");
|
|
3113
|
+
}
|
|
3114
|
+
authLogger.info("Redirecting to GitHub...", response.authorization_url);
|
|
3115
|
+
if (typeof window !== "undefined") {
|
|
3116
|
+
sessionStorage.setItem("oauth_state", response.state);
|
|
3117
|
+
sessionStorage.setItem("oauth_provider", "github");
|
|
3118
|
+
sessionStorage.setItem("cfg.oauth.remember_me", String(rememberMe));
|
|
3119
|
+
}
|
|
3120
|
+
window.location.href = response.authorization_url;
|
|
3121
|
+
} catch (err) {
|
|
3122
|
+
const errorMessage = formatAuthError(err, "Failed to start GitHub authentication");
|
|
3123
|
+
authLogger.error("GitHub OAuth start error:", err);
|
|
3124
|
+
setError(errorMessage);
|
|
3125
|
+
onError?.(errorMessage);
|
|
3126
|
+
Analytics.event("auth_oauth_fail" /* AUTH_OAUTH_FAIL */, {
|
|
3127
|
+
category: "auth" /* AUTH */,
|
|
3128
|
+
label: "github"
|
|
3129
|
+
});
|
|
3130
|
+
} finally {
|
|
3131
|
+
setIsLoading(false);
|
|
3132
|
+
}
|
|
3133
|
+
}, [sourceUrl, rememberMe, onError]);
|
|
3134
|
+
const handleGithubCallback = (0, import_react9.useCallback)(async (code, state) => {
|
|
3135
|
+
setIsLoading(true);
|
|
3136
|
+
setError(null);
|
|
3137
|
+
try {
|
|
3138
|
+
authLogger.info("Processing GitHub OAuth callback...");
|
|
3139
|
+
let rememberMeForSession = false;
|
|
3140
|
+
if (typeof window !== "undefined") {
|
|
3141
|
+
const storedState = sessionStorage.getItem("oauth_state");
|
|
3142
|
+
if (storedState && storedState !== state) {
|
|
3143
|
+
throw new Error("Invalid OAuth state - possible CSRF attack");
|
|
3144
|
+
}
|
|
3145
|
+
rememberMeForSession = sessionStorage.getItem("cfg.oauth.remember_me") === "true";
|
|
3146
|
+
sessionStorage.removeItem("oauth_state");
|
|
3147
|
+
sessionStorage.removeItem("oauth_provider");
|
|
3148
|
+
sessionStorage.removeItem("cfg.oauth.remember_me");
|
|
3149
|
+
}
|
|
3150
|
+
const result = await CfgAccountsOauth.cfgAccountsOauthGithubCallbackCreate({
|
|
3151
|
+
body: { code, state, remember_me: rememberMeForSession },
|
|
3152
|
+
throwOnError: true
|
|
3153
|
+
});
|
|
3154
|
+
const response = result.data;
|
|
3155
|
+
if (response.requires_2fa && response.session_id) {
|
|
3156
|
+
authLogger.info("GitHub OAuth requires 2FA, session:", response.session_id);
|
|
3157
|
+
Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
|
|
3158
|
+
category: "auth" /* AUTH */,
|
|
3159
|
+
label: "github-2fa-required"
|
|
3160
|
+
});
|
|
3161
|
+
onRequires2FA?.(response.session_id, response.should_prompt_2fa || false);
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
if (!response.access || !response.refresh) {
|
|
3165
|
+
throw new Error("Invalid response from OAuth callback");
|
|
3166
|
+
}
|
|
3167
|
+
authLogger.info("GitHub OAuth successful, user:", response.user);
|
|
3168
|
+
auth.setSession({ access: response.access, refresh: response.refresh });
|
|
3169
|
+
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
3170
|
+
category: "auth" /* AUTH */,
|
|
3171
|
+
label: "github"
|
|
3172
|
+
});
|
|
3173
|
+
if (response.user?.id) {
|
|
3174
|
+
Analytics.setUser(String(response.user.id));
|
|
3175
|
+
}
|
|
3176
|
+
if (response.user) {
|
|
3177
|
+
onSuccess?.(response.user, response.is_new_user || false);
|
|
3178
|
+
}
|
|
3179
|
+
if (!skipRedirect) {
|
|
3180
|
+
const finalRedirectUrl = redirectUrl || "/private";
|
|
3181
|
+
router.hardPush(finalRedirectUrl);
|
|
3182
|
+
}
|
|
3183
|
+
return;
|
|
3184
|
+
} catch (err) {
|
|
3185
|
+
const errorMessage = formatAuthError(err, "GitHub authentication failed");
|
|
3186
|
+
authLogger.error("GitHub OAuth callback error:", err);
|
|
3187
|
+
setError(errorMessage);
|
|
3188
|
+
onError?.(errorMessage);
|
|
3189
|
+
Analytics.event("auth_oauth_fail" /* AUTH_OAUTH_FAIL */, {
|
|
3190
|
+
category: "auth" /* AUTH */,
|
|
3191
|
+
label: "github"
|
|
3192
|
+
});
|
|
3193
|
+
} finally {
|
|
3194
|
+
setIsLoading(false);
|
|
3195
|
+
}
|
|
3196
|
+
}, [onSuccess, onError, onRequires2FA, redirectUrl, router, skipRedirect]);
|
|
3197
|
+
return {
|
|
3198
|
+
isLoading,
|
|
3199
|
+
error,
|
|
3200
|
+
startGithubAuth,
|
|
3201
|
+
handleGithubCallback
|
|
3202
|
+
};
|
|
3203
|
+
}, "useGithubAuth");
|
|
3204
|
+
|
|
3205
|
+
// src/auth/hooks/useTwoFactorVerify.ts
|
|
3206
|
+
var import_react11 = require("react");
|
|
3207
|
+
|
|
3208
|
+
// src/auth/hooks/useSessionStorage.ts
|
|
3209
|
+
var import_react10 = require("react");
|
|
3210
|
+
|
|
3211
|
+
// src/auth/utils/storage.ts
|
|
3212
|
+
var MAX_ITEM_KB = 1024;
|
|
3213
|
+
function isQuotaExceededError(error) {
|
|
3214
|
+
if (typeof error !== "object" || error === null) return false;
|
|
3215
|
+
const { name, code, message } = error;
|
|
3216
|
+
return name === "QuotaExceededError" || code === 22 || typeof message === "string" && message.includes("quota");
|
|
3217
|
+
}
|
|
3218
|
+
__name(isQuotaExceededError, "isQuotaExceededError");
|
|
3219
|
+
function isWithinSizeLimit(data, key) {
|
|
3220
|
+
try {
|
|
3221
|
+
const sizeInKB = new Blob([JSON.stringify(data)]).size / 1024;
|
|
3222
|
+
if (sizeInKB > MAX_ITEM_KB) {
|
|
3223
|
+
authLogger.warn(
|
|
3224
|
+
`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`
|
|
3225
|
+
);
|
|
3226
|
+
return false;
|
|
3227
|
+
}
|
|
3228
|
+
return true;
|
|
3229
|
+
} catch (error) {
|
|
3230
|
+
authLogger.error(`Error checking data size for key "${key}":`, error);
|
|
3231
|
+
return false;
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
__name(isWithinSizeLimit, "isWithinSizeLimit");
|
|
3235
|
+
|
|
3236
|
+
// src/auth/hooks/useSessionStorage.ts
|
|
3237
|
+
function useSessionStorage(key, initialValue) {
|
|
3238
|
+
const [storedValue, setStoredValue] = (0, import_react10.useState)(() => {
|
|
3239
|
+
if (typeof window === "undefined") {
|
|
3240
|
+
return initialValue;
|
|
3241
|
+
}
|
|
3242
|
+
try {
|
|
3243
|
+
const item = window.sessionStorage.getItem(key);
|
|
3244
|
+
return item ? JSON.parse(item) : initialValue;
|
|
3245
|
+
} catch (error) {
|
|
3246
|
+
authLogger.error(`Error reading sessionStorage key "${key}":`, error);
|
|
3247
|
+
return initialValue;
|
|
3248
|
+
}
|
|
3249
|
+
});
|
|
3250
|
+
const clearOldData = /* @__PURE__ */ __name(() => {
|
|
3251
|
+
try {
|
|
3252
|
+
const keys = Object.keys(sessionStorage).filter((key2) => key2 && typeof key2 === "string");
|
|
3253
|
+
if (keys.length > 50) {
|
|
3254
|
+
const itemsToRemove = Math.ceil(keys.length * 0.2);
|
|
3255
|
+
for (let i = 0; i < itemsToRemove; i++) {
|
|
3256
|
+
try {
|
|
3257
|
+
const key2 = keys[i];
|
|
3258
|
+
if (key2) {
|
|
3259
|
+
sessionStorage.removeItem(key2);
|
|
3260
|
+
sessionStorage.removeItem(`${key2}_timestamp`);
|
|
3261
|
+
}
|
|
3262
|
+
} catch {
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
2818
3265
|
}
|
|
2819
3266
|
} catch (error) {
|
|
2820
3267
|
authLogger.error("Error clearing old sessionStorage data:", error);
|
|
@@ -2991,642 +3438,162 @@ function isValidTotpCode(code) {
|
|
|
2991
3438
|
}
|
|
2992
3439
|
__name(isValidTotpCode, "isValidTotpCode");
|
|
2993
3440
|
|
|
2994
|
-
// src/auth/hooks/useTwoFactorVerify.ts
|
|
2995
|
-
var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
|
|
2996
|
-
const { onSuccess, onError, redirectUrl, skipRedirect = false } = options;
|
|
2997
|
-
const router = useCfgRouter();
|
|
2998
|
-
const [isLoading, setIsLoading] = (0,
|
|
2999
|
-
const [error, setError] = (0,
|
|
3000
|
-
const [warning, setWarning] = (0,
|
|
3001
|
-
const [remainingBackupCodes, setRemainingBackupCodes] = (0,
|
|
3002
|
-
const [attemptsRemaining, setAttemptsRemaining] = (0,
|
|
3003
|
-
const clearError = (0,
|
|
3004
|
-
setError(null);
|
|
3005
|
-
}, []);
|
|
3006
|
-
const handleSuccess = (0,
|
|
3007
|
-
auth.setSession({ access: response.access_token, refresh: response.refresh_token });
|
|
3008
|
-
if (response.warning) {
|
|
3009
|
-
setWarning(response.warning);
|
|
3010
|
-
}
|
|
3011
|
-
if (response.remaining_backup_codes !== void 0) {
|
|
3012
|
-
setRemainingBackupCodes(response.remaining_backup_codes);
|
|
3013
|
-
}
|
|
3014
|
-
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
3015
|
-
category: "auth" /* AUTH */,
|
|
3016
|
-
label: "2fa"
|
|
3017
|
-
});
|
|
3018
|
-
if (response.user?.id) {
|
|
3019
|
-
Analytics.setUser(String(response.user.id));
|
|
3020
|
-
}
|
|
3021
|
-
onSuccess?.(response.user);
|
|
3022
|
-
if (!skipRedirect) {
|
|
3023
|
-
const finalRedirectUrl = consumeSavedRedirect() || redirectUrl || "/private";
|
|
3024
|
-
authLogger.info("2FA successful, redirecting to:", finalRedirectUrl);
|
|
3025
|
-
router.hardPush(finalRedirectUrl);
|
|
3026
|
-
}
|
|
3027
|
-
}, [onSuccess, redirectUrl, router, skipRedirect]);
|
|
3028
|
-
const verifyTOTP = (0,
|
|
3029
|
-
if (!sessionId) {
|
|
3030
|
-
const msg = "Missing 2FA session ID";
|
|
3031
|
-
setError(msg);
|
|
3032
|
-
onError?.(msg);
|
|
3033
|
-
return false;
|
|
3034
|
-
}
|
|
3035
|
-
if (!isValidTotpCode(code)) {
|
|
3036
|
-
const msg = "Please enter a 6-digit code";
|
|
3037
|
-
setError(msg);
|
|
3038
|
-
onError?.(msg);
|
|
3039
|
-
return false;
|
|
3040
|
-
}
|
|
3041
|
-
setIsLoading(true);
|
|
3042
|
-
setError(null);
|
|
3043
|
-
try {
|
|
3044
|
-
authLogger.info("Verifying TOTP code...");
|
|
3045
|
-
const result = await CfgTotpVerify.cfgTotpVerifyCreate({
|
|
3046
|
-
body: { session_id: sessionId, code },
|
|
3047
|
-
throwOnError: true
|
|
3048
|
-
});
|
|
3049
|
-
const response = result.data;
|
|
3050
|
-
if (!response.access_token || !response.refresh_token) {
|
|
3051
|
-
throw new Error("Invalid response from 2FA verification");
|
|
3052
|
-
}
|
|
3053
|
-
handleSuccess(response);
|
|
3054
|
-
return true;
|
|
3055
|
-
} catch (err) {
|
|
3056
|
-
logAuthFailure(authLogger, "2FA TOTP verification failed:", err);
|
|
3057
|
-
const errorMessage = formatAuthError(err, "Invalid verification code");
|
|
3058
|
-
const attempts = extractAttemptsRemaining(err);
|
|
3059
|
-
if (attempts !== null) setAttemptsRemaining(attempts);
|
|
3060
|
-
setError(errorMessage);
|
|
3061
|
-
onError?.(errorMessage);
|
|
3062
|
-
Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
|
|
3063
|
-
category: "auth" /* AUTH */,
|
|
3064
|
-
label: "2fa-totp"
|
|
3065
|
-
});
|
|
3066
|
-
return false;
|
|
3067
|
-
} finally {
|
|
3068
|
-
setIsLoading(false);
|
|
3069
|
-
}
|
|
3070
|
-
}, [handleSuccess, onError]);
|
|
3071
|
-
const verifyBackupCode = (0, import_react8.useCallback)(async (sessionId, backupCode) => {
|
|
3072
|
-
if (!sessionId) {
|
|
3073
|
-
const msg = "Missing 2FA session ID";
|
|
3074
|
-
setError(msg);
|
|
3075
|
-
onError?.(msg);
|
|
3076
|
-
return false;
|
|
3077
|
-
}
|
|
3078
|
-
if (!backupCode || backupCode.length < 8) {
|
|
3079
|
-
const msg = "Please enter your backup code";
|
|
3080
|
-
setError(msg);
|
|
3081
|
-
onError?.(msg);
|
|
3082
|
-
return false;
|
|
3083
|
-
}
|
|
3084
|
-
setIsLoading(true);
|
|
3085
|
-
setError(null);
|
|
3086
|
-
try {
|
|
3087
|
-
authLogger.info("Verifying backup code...");
|
|
3088
|
-
const result = await CfgTotpVerify.cfgTotpVerifyBackupCreate({
|
|
3089
|
-
body: {
|
|
3090
|
-
session_id: sessionId,
|
|
3091
|
-
backup_code: backupCode.replace(/\s+/g, "")
|
|
3092
|
-
},
|
|
3093
|
-
throwOnError: true
|
|
3094
|
-
});
|
|
3095
|
-
const response = result.data;
|
|
3096
|
-
if (!response.access_token || !response.refresh_token) {
|
|
3097
|
-
throw new Error("Invalid response from backup code verification");
|
|
3098
|
-
}
|
|
3099
|
-
handleSuccess(response);
|
|
3100
|
-
return true;
|
|
3101
|
-
} catch (err) {
|
|
3102
|
-
logAuthFailure(authLogger, "2FA backup code verification failed:", err);
|
|
3103
|
-
const errorMessage = formatAuthError(err, "Invalid backup code");
|
|
3104
|
-
const attempts = extractAttemptsRemaining(err);
|
|
3105
|
-
if (attempts !== null) setAttemptsRemaining(attempts);
|
|
3106
|
-
setError(errorMessage);
|
|
3107
|
-
onError?.(errorMessage);
|
|
3108
|
-
Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
|
|
3109
|
-
category: "auth" /* AUTH */,
|
|
3110
|
-
label: "2fa-backup"
|
|
3111
|
-
});
|
|
3112
|
-
return false;
|
|
3113
|
-
} finally {
|
|
3114
|
-
setIsLoading(false);
|
|
3115
|
-
}
|
|
3116
|
-
}, [handleSuccess, onError]);
|
|
3117
|
-
return {
|
|
3118
|
-
isLoading,
|
|
3119
|
-
error,
|
|
3120
|
-
warning,
|
|
3121
|
-
remainingBackupCodes,
|
|
3122
|
-
attemptsRemaining,
|
|
3123
|
-
verifyTOTP,
|
|
3124
|
-
verifyBackupCode,
|
|
3125
|
-
clearError
|
|
3126
|
-
};
|
|
3127
|
-
}, "useTwoFactorVerify");
|
|
3128
|
-
|
|
3129
|
-
// src/auth/hooks/useAuthForm.ts
|
|
3130
|
-
var useAuthForm = /* @__PURE__ */ __name((options) => {
|
|
3131
|
-
const {
|
|
3132
|
-
onIdentifierSuccess,
|
|
3133
|
-
onOTPSuccess,
|
|
3134
|
-
onError,
|
|
3135
|
-
sourceUrl,
|
|
3136
|
-
redirectUrl,
|
|
3137
|
-
requireTermsAcceptance = false,
|
|
3138
|
-
marketingConsent: marketingConsentOption,
|
|
3139
|
-
authPath = "/auth"
|
|
3140
|
-
} = options;
|
|
3141
|
-
const formState = useAuthFormState();
|
|
3142
|
-
const validation = useAuthValidation();
|
|
3143
|
-
const isAutoSubmitFromUrlRef = (0, import_react9.useRef)(false);
|
|
3144
|
-
const processedOtpKeyRef = (0, import_react9.useRef)(null);
|
|
3145
|
-
const {
|
|
3146
|
-
requestOTP,
|
|
3147
|
-
verifyOTP,
|
|
3148
|
-
login,
|
|
3149
|
-
getSavedEmail,
|
|
3150
|
-
saveEmail
|
|
3151
|
-
} = useAuth();
|
|
3152
|
-
const {
|
|
3153
|
-
identifier,
|
|
3154
|
-
otp,
|
|
3155
|
-
password,
|
|
3156
|
-
acceptedTerms,
|
|
3157
|
-
marketingConsent,
|
|
3158
|
-
rememberMe,
|
|
3159
|
-
twoFactorSessionId,
|
|
3160
|
-
twoFactorCode,
|
|
3161
|
-
useBackupCode,
|
|
3162
|
-
setIdentifier,
|
|
3163
|
-
setOtp,
|
|
3164
|
-
setStep,
|
|
3165
|
-
setError,
|
|
3166
|
-
setIsLoading,
|
|
3167
|
-
clearError,
|
|
3168
|
-
setTwoFactorSessionId,
|
|
3169
|
-
setShouldPrompt2FA,
|
|
3170
|
-
setTwoFactorCode,
|
|
3171
|
-
setUseBackupCode,
|
|
3172
|
-
startRateLimitCountdown,
|
|
3173
|
-
setWebmail
|
|
3174
|
-
} = formState;
|
|
3175
|
-
const twoFactor = useTwoFactorVerify({
|
|
3176
|
-
onSuccess: /* @__PURE__ */ __name(() => {
|
|
3177
|
-
authLogger.info("2FA verification successful, showing success screen");
|
|
3178
|
-
setStep("success");
|
|
3179
|
-
onOTPSuccess?.();
|
|
3180
|
-
}, "onSuccess"),
|
|
3181
|
-
onError: /* @__PURE__ */ __name((error) => {
|
|
3182
|
-
setError(error);
|
|
3183
|
-
onError?.(error);
|
|
3184
|
-
}, "onError"),
|
|
3185
|
-
redirectUrl,
|
|
3186
|
-
skipRedirect: true
|
|
3187
|
-
// We handle navigation via success step
|
|
3188
|
-
});
|
|
3189
|
-
const { validateIdentifier: validateIdentifier2 } = validation;
|
|
3190
|
-
const saveIdentifierToStorage = (0, import_react9.useCallback)((id) => {
|
|
3191
|
-
saveEmail(id);
|
|
3192
|
-
}, [saveEmail]);
|
|
3193
|
-
(0, import_react9.useEffect)(() => {
|
|
3194
|
-
const savedEmail = getSavedEmail();
|
|
3195
|
-
if (savedEmail) {
|
|
3196
|
-
setIdentifier(savedEmail);
|
|
3197
|
-
}
|
|
3198
|
-
}, []);
|
|
3199
|
-
const handleIdentifierSubmit = (0, import_react9.useCallback)(async (e) => {
|
|
3200
|
-
e.preventDefault();
|
|
3201
|
-
if (!identifier) {
|
|
3202
|
-
const msg = "Please enter your email address";
|
|
3203
|
-
setError(msg);
|
|
3204
|
-
onError?.(msg);
|
|
3205
|
-
return;
|
|
3206
|
-
}
|
|
3207
|
-
if (!validateIdentifier2(identifier)) {
|
|
3208
|
-
const msg = "Please enter a valid email address";
|
|
3209
|
-
setError(msg);
|
|
3210
|
-
onError?.(msg);
|
|
3211
|
-
return;
|
|
3212
|
-
}
|
|
3213
|
-
if (requireTermsAcceptance && !acceptedTerms) {
|
|
3214
|
-
const msg = "Please accept the Terms of Service and Privacy Policy";
|
|
3215
|
-
setError(msg);
|
|
3216
|
-
onError?.(msg);
|
|
3217
|
-
return;
|
|
3218
|
-
}
|
|
3219
|
-
setIsLoading(true);
|
|
3220
|
-
clearError();
|
|
3221
|
-
try {
|
|
3222
|
-
const consent = marketingConsentOption ? { marketingConsent: marketingConsent === true, disclosureVersion: marketingConsentOption.disclosureVersion } : void 0;
|
|
3223
|
-
const result = await requestOTP(identifier, sourceUrl, consent);
|
|
3224
|
-
if (result.success) {
|
|
3225
|
-
saveIdentifierToStorage(identifier);
|
|
3226
|
-
setWebmail(result.webmail ?? null);
|
|
3227
|
-
setStep("otp");
|
|
3228
|
-
onIdentifierSuccess?.(identifier);
|
|
3229
|
-
} else {
|
|
3230
|
-
if (result.retryAfter) {
|
|
3231
|
-
startRateLimitCountdown(result.retryAfter);
|
|
3232
|
-
clearError();
|
|
3233
|
-
} else {
|
|
3234
|
-
setError(result.message);
|
|
3235
|
-
onError?.(result.message);
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
} catch {
|
|
3239
|
-
const msg = "An unexpected error occurred";
|
|
3240
|
-
setError(msg);
|
|
3241
|
-
onError?.(msg);
|
|
3242
|
-
} finally {
|
|
3243
|
-
setIsLoading(false);
|
|
3244
|
-
}
|
|
3245
|
-
}, [
|
|
3246
|
-
identifier,
|
|
3247
|
-
acceptedTerms,
|
|
3248
|
-
requireTermsAcceptance,
|
|
3249
|
-
marketingConsent,
|
|
3250
|
-
marketingConsentOption,
|
|
3251
|
-
validateIdentifier2,
|
|
3252
|
-
requestOTP,
|
|
3253
|
-
saveIdentifierToStorage,
|
|
3254
|
-
setError,
|
|
3255
|
-
setIsLoading,
|
|
3256
|
-
setStep,
|
|
3257
|
-
clearError,
|
|
3258
|
-
setWebmail,
|
|
3259
|
-
startRateLimitCountdown,
|
|
3260
|
-
onIdentifierSuccess,
|
|
3261
|
-
onError,
|
|
3262
|
-
sourceUrl
|
|
3263
|
-
]);
|
|
3264
|
-
const submitOTP = (0, import_react9.useCallback)(async (submitIdentifier, submitOtp) => {
|
|
3265
|
-
if (!submitOtp || submitOtp.length < AUTH_CONSTANTS.EMAIL_OTP_LENGTH) {
|
|
3266
|
-
const msg = `Please enter the ${AUTH_CONSTANTS.EMAIL_OTP_LENGTH}-digit verification code`;
|
|
3267
|
-
setError(msg);
|
|
3268
|
-
onError?.(msg);
|
|
3269
|
-
return false;
|
|
3270
|
-
}
|
|
3271
|
-
setIsLoading(true);
|
|
3272
|
-
clearError();
|
|
3273
|
-
try {
|
|
3274
|
-
const result = await verifyOTP(submitIdentifier, submitOtp, sourceUrl, redirectUrl, true, rememberMe);
|
|
3275
|
-
if (result.requires_2fa && result.session_id) {
|
|
3276
|
-
authLogger.info("2FA required after OTP verification");
|
|
3277
|
-
setTwoFactorSessionId(result.session_id);
|
|
3278
|
-
setShouldPrompt2FA(result.should_prompt_2fa || false);
|
|
3279
|
-
setStep("2fa");
|
|
3280
|
-
saveIdentifierToStorage(submitIdentifier);
|
|
3281
|
-
return true;
|
|
3282
|
-
}
|
|
3283
|
-
if (result.success) {
|
|
3284
|
-
saveIdentifierToStorage(submitIdentifier);
|
|
3285
|
-
authLogger.info("OTP verification successful, showing success screen");
|
|
3286
|
-
setStep("success");
|
|
3287
|
-
onOTPSuccess?.();
|
|
3288
|
-
return true;
|
|
3289
|
-
} else {
|
|
3290
|
-
setError(result.message);
|
|
3291
|
-
onError?.(result.message);
|
|
3292
|
-
return false;
|
|
3293
|
-
}
|
|
3294
|
-
} catch {
|
|
3295
|
-
const msg = "An unexpected error occurred";
|
|
3296
|
-
setError(msg);
|
|
3297
|
-
onError?.(msg);
|
|
3298
|
-
return false;
|
|
3299
|
-
} finally {
|
|
3300
|
-
setIsLoading(false);
|
|
3301
|
-
}
|
|
3302
|
-
}, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, rememberMe, setTwoFactorSessionId, setShouldPrompt2FA, setStep]);
|
|
3303
|
-
const handleOTPSubmit = (0, import_react9.useCallback)(async (e) => {
|
|
3304
|
-
e.preventDefault();
|
|
3305
|
-
await submitOTP(identifier, otp);
|
|
3306
|
-
}, [identifier, otp, submitOTP]);
|
|
3307
|
-
const submitPassword = (0, import_react9.useCallback)(async (submitIdentifier, submitPassword_) => {
|
|
3308
|
-
if (!submitIdentifier || !validateIdentifier2(submitIdentifier)) {
|
|
3309
|
-
const msg = "Please enter a valid email address";
|
|
3310
|
-
setError(msg);
|
|
3311
|
-
onError?.(msg);
|
|
3312
|
-
return false;
|
|
3313
|
-
}
|
|
3314
|
-
if (!submitPassword_) {
|
|
3315
|
-
const msg = "Please enter your password";
|
|
3316
|
-
setError(msg);
|
|
3317
|
-
onError?.(msg);
|
|
3318
|
-
return false;
|
|
3319
|
-
}
|
|
3320
|
-
setIsLoading(true);
|
|
3321
|
-
clearError();
|
|
3322
|
-
try {
|
|
3323
|
-
const result = await login(submitIdentifier, submitPassword_, {
|
|
3324
|
-
redirectUrl,
|
|
3325
|
-
skipRedirect: true,
|
|
3326
|
-
rememberMe
|
|
3327
|
-
});
|
|
3328
|
-
if (result.requires_2fa && result.session_id) {
|
|
3329
|
-
authLogger.info("2FA required after password login");
|
|
3330
|
-
setTwoFactorSessionId(result.session_id);
|
|
3331
|
-
setShouldPrompt2FA(result.should_prompt_2fa || false);
|
|
3332
|
-
setStep("2fa");
|
|
3333
|
-
saveIdentifierToStorage(submitIdentifier);
|
|
3334
|
-
return true;
|
|
3335
|
-
}
|
|
3336
|
-
if (result.success) {
|
|
3337
|
-
saveIdentifierToStorage(submitIdentifier);
|
|
3338
|
-
authLogger.info("Password login successful, showing success screen");
|
|
3339
|
-
setStep("success");
|
|
3340
|
-
onOTPSuccess?.();
|
|
3341
|
-
return true;
|
|
3342
|
-
}
|
|
3343
|
-
if (result.retryAfter) {
|
|
3344
|
-
startRateLimitCountdown(result.retryAfter);
|
|
3345
|
-
clearError();
|
|
3346
|
-
} else {
|
|
3347
|
-
setError(result.message);
|
|
3348
|
-
onError?.(result.message);
|
|
3349
|
-
}
|
|
3350
|
-
return false;
|
|
3351
|
-
} catch {
|
|
3352
|
-
const msg = "An unexpected error occurred";
|
|
3353
|
-
setError(msg);
|
|
3354
|
-
onError?.(msg);
|
|
3355
|
-
return false;
|
|
3356
|
-
} finally {
|
|
3357
|
-
setIsLoading(false);
|
|
3358
|
-
}
|
|
3359
|
-
}, [
|
|
3360
|
-
login,
|
|
3361
|
-
validateIdentifier2,
|
|
3362
|
-
saveIdentifierToStorage,
|
|
3363
|
-
setError,
|
|
3364
|
-
setIsLoading,
|
|
3365
|
-
clearError,
|
|
3366
|
-
startRateLimitCountdown,
|
|
3367
|
-
onOTPSuccess,
|
|
3368
|
-
onError,
|
|
3369
|
-
redirectUrl,
|
|
3370
|
-
rememberMe,
|
|
3371
|
-
setTwoFactorSessionId,
|
|
3372
|
-
setShouldPrompt2FA,
|
|
3373
|
-
setStep
|
|
3374
|
-
]);
|
|
3375
|
-
const handlePasswordSubmit = (0, import_react9.useCallback)(async (e) => {
|
|
3376
|
-
e.preventDefault();
|
|
3377
|
-
await submitPassword(identifier, password);
|
|
3378
|
-
}, [identifier, password, submitPassword]);
|
|
3379
|
-
const handleResendOTP = (0, import_react9.useCallback)(async () => {
|
|
3380
|
-
setIsLoading(true);
|
|
3381
|
-
clearError();
|
|
3382
|
-
try {
|
|
3383
|
-
const consent = marketingConsentOption ? { marketingConsent: marketingConsent === true, disclosureVersion: marketingConsentOption.disclosureVersion } : void 0;
|
|
3384
|
-
const result = await requestOTP(identifier, sourceUrl, consent);
|
|
3385
|
-
if (result.success) {
|
|
3386
|
-
saveIdentifierToStorage(identifier);
|
|
3387
|
-
setWebmail(result.webmail ?? null);
|
|
3388
|
-
setOtp("");
|
|
3389
|
-
} else {
|
|
3390
|
-
if (result.retryAfter) {
|
|
3391
|
-
startRateLimitCountdown(result.retryAfter);
|
|
3392
|
-
clearError();
|
|
3393
|
-
} else {
|
|
3394
|
-
setError(result.message);
|
|
3395
|
-
onError?.(result.message);
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
} catch {
|
|
3399
|
-
const msg = "Failed to resend verification code";
|
|
3441
|
+
// src/auth/hooks/useTwoFactorVerify.ts
|
|
3442
|
+
var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
|
|
3443
|
+
const { onSuccess, onError, redirectUrl, skipRedirect = false } = options;
|
|
3444
|
+
const router = useCfgRouter();
|
|
3445
|
+
const [isLoading, setIsLoading] = (0, import_react11.useState)(false);
|
|
3446
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
3447
|
+
const [warning, setWarning] = (0, import_react11.useState)(null);
|
|
3448
|
+
const [remainingBackupCodes, setRemainingBackupCodes] = (0, import_react11.useState)(null);
|
|
3449
|
+
const [attemptsRemaining, setAttemptsRemaining] = (0, import_react11.useState)(null);
|
|
3450
|
+
const clearError = (0, import_react11.useCallback)(() => {
|
|
3451
|
+
setError(null);
|
|
3452
|
+
}, []);
|
|
3453
|
+
const handleSuccess = (0, import_react11.useCallback)((response) => {
|
|
3454
|
+
auth.setSession({ access: response.access_token, refresh: response.refresh_token });
|
|
3455
|
+
if (response.warning) {
|
|
3456
|
+
setWarning(response.warning);
|
|
3457
|
+
}
|
|
3458
|
+
if (response.remaining_backup_codes !== void 0) {
|
|
3459
|
+
setRemainingBackupCodes(response.remaining_backup_codes);
|
|
3460
|
+
}
|
|
3461
|
+
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
3462
|
+
category: "auth" /* AUTH */,
|
|
3463
|
+
label: "2fa"
|
|
3464
|
+
});
|
|
3465
|
+
if (response.user?.id) {
|
|
3466
|
+
Analytics.setUser(String(response.user.id));
|
|
3467
|
+
}
|
|
3468
|
+
onSuccess?.(response.user);
|
|
3469
|
+
if (!skipRedirect) {
|
|
3470
|
+
const finalRedirectUrl = consumeSavedRedirect() || redirectUrl || "/private";
|
|
3471
|
+
authLogger.info("2FA successful, redirecting to:", finalRedirectUrl);
|
|
3472
|
+
router.hardPush(finalRedirectUrl);
|
|
3473
|
+
}
|
|
3474
|
+
}, [onSuccess, redirectUrl, router, skipRedirect]);
|
|
3475
|
+
const verifyTOTP = (0, import_react11.useCallback)(async (sessionId, code) => {
|
|
3476
|
+
if (!sessionId) {
|
|
3477
|
+
const msg = "Missing 2FA session ID";
|
|
3400
3478
|
setError(msg);
|
|
3401
3479
|
onError?.(msg);
|
|
3402
|
-
|
|
3403
|
-
setIsLoading(false);
|
|
3480
|
+
return false;
|
|
3404
3481
|
}
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
setStep("identifier");
|
|
3408
|
-
clearError();
|
|
3409
|
-
}, [setStep, clearError]);
|
|
3410
|
-
const forceOTPStep = (0, import_react9.useCallback)(() => {
|
|
3411
|
-
setStep("otp");
|
|
3412
|
-
clearError();
|
|
3413
|
-
}, [setStep, clearError]);
|
|
3414
|
-
const handle2FASubmit = (0, import_react9.useCallback)(async (e) => {
|
|
3415
|
-
e.preventDefault();
|
|
3416
|
-
if (!twoFactorSessionId) {
|
|
3417
|
-
const msg = "Missing 2FA session";
|
|
3482
|
+
if (!isValidTotpCode(code)) {
|
|
3483
|
+
const msg = "Please enter a 6-digit code";
|
|
3418
3484
|
setError(msg);
|
|
3419
3485
|
onError?.(msg);
|
|
3420
|
-
return;
|
|
3421
|
-
}
|
|
3422
|
-
if (useBackupCode) {
|
|
3423
|
-
await twoFactor.verifyBackupCode(twoFactorSessionId, twoFactorCode);
|
|
3424
|
-
} else {
|
|
3425
|
-
await twoFactor.verifyTOTP(twoFactorSessionId, twoFactorCode);
|
|
3426
|
-
}
|
|
3427
|
-
}, [twoFactorSessionId, twoFactorCode, useBackupCode, twoFactor, setError, onError]);
|
|
3428
|
-
const handleUseBackupCode = (0, import_react9.useCallback)(() => {
|
|
3429
|
-
setUseBackupCode(true);
|
|
3430
|
-
setTwoFactorCode("");
|
|
3431
|
-
clearError();
|
|
3432
|
-
}, [setUseBackupCode, setTwoFactorCode, clearError]);
|
|
3433
|
-
const handleUseTOTP = (0, import_react9.useCallback)(() => {
|
|
3434
|
-
setUseBackupCode(false);
|
|
3435
|
-
setTwoFactorCode("");
|
|
3436
|
-
clearError();
|
|
3437
|
-
}, [setUseBackupCode, setTwoFactorCode, clearError]);
|
|
3438
|
-
const handleOTPFromUrl = (0, import_react9.useCallback)((detectedOtp, detectedEmail) => {
|
|
3439
|
-
const autoIdentifier = detectedEmail || getSavedEmail() || "";
|
|
3440
|
-
if (!autoIdentifier) {
|
|
3441
|
-
authLogger.warn("No identifier found for auto-submit (no email in URL or storage)");
|
|
3442
|
-
return;
|
|
3486
|
+
return false;
|
|
3443
3487
|
}
|
|
3444
|
-
const otpKey = `${autoIdentifier}:${detectedOtp}`;
|
|
3445
|
-
if (processedOtpKeyRef.current === otpKey) return;
|
|
3446
|
-
if (isAutoSubmitFromUrlRef.current) return;
|
|
3447
|
-
processedOtpKeyRef.current = otpKey;
|
|
3448
|
-
isAutoSubmitFromUrlRef.current = true;
|
|
3449
|
-
authLogger.info("OTP detected from URL, auto-submitting");
|
|
3450
|
-
setIdentifier(autoIdentifier);
|
|
3451
|
-
setOtp(detectedOtp);
|
|
3452
|
-
setStep("otp");
|
|
3453
|
-
setTimeout(async () => {
|
|
3454
|
-
try {
|
|
3455
|
-
await submitOTP(autoIdentifier, detectedOtp);
|
|
3456
|
-
} finally {
|
|
3457
|
-
isAutoSubmitFromUrlRef.current = false;
|
|
3458
|
-
}
|
|
3459
|
-
}, 100);
|
|
3460
|
-
}, [getSavedEmail, setIdentifier, setOtp, setStep, submitOTP]);
|
|
3461
|
-
useAutoAuth({
|
|
3462
|
-
allowedPaths: [authPath],
|
|
3463
|
-
onOTPDetected: handleOTPFromUrl,
|
|
3464
|
-
cleanupUrl: true
|
|
3465
|
-
});
|
|
3466
|
-
return {
|
|
3467
|
-
// State
|
|
3468
|
-
...formState,
|
|
3469
|
-
// Submit handlers
|
|
3470
|
-
handleIdentifierSubmit,
|
|
3471
|
-
handleOTPSubmit,
|
|
3472
|
-
handlePasswordSubmit,
|
|
3473
|
-
handleResendOTP,
|
|
3474
|
-
handleBackToIdentifier,
|
|
3475
|
-
forceOTPStep,
|
|
3476
|
-
// 2FA handlers
|
|
3477
|
-
handle2FASubmit,
|
|
3478
|
-
handleUseBackupCode,
|
|
3479
|
-
handleUseTOTP,
|
|
3480
|
-
// Validation
|
|
3481
|
-
...validation,
|
|
3482
|
-
// Auto-submit ref
|
|
3483
|
-
isAutoSubmittingFromUrl: isAutoSubmitFromUrlRef,
|
|
3484
|
-
// 2FA state from hook (for loading indicator)
|
|
3485
|
-
is2FALoading: twoFactor.isLoading,
|
|
3486
|
-
twoFactorWarning: twoFactor.warning,
|
|
3487
|
-
twoFactorAttemptsRemaining: twoFactor.attemptsRemaining
|
|
3488
|
-
};
|
|
3489
|
-
}, "useAuthForm");
|
|
3490
|
-
|
|
3491
|
-
// src/auth/hooks/useGithubAuth.ts
|
|
3492
|
-
var import_react10 = require("react");
|
|
3493
|
-
var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
3494
|
-
const { sourceUrl, rememberMe = false, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
|
|
3495
|
-
const router = useCfgRouter();
|
|
3496
|
-
const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
|
|
3497
|
-
const [error, setError] = (0, import_react10.useState)(null);
|
|
3498
|
-
const startGithubAuth = (0, import_react10.useCallback)(async () => {
|
|
3499
3488
|
setIsLoading(true);
|
|
3500
3489
|
setError(null);
|
|
3501
|
-
auth.clearSession();
|
|
3502
3490
|
try {
|
|
3503
|
-
authLogger.info("
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
label: "github"
|
|
3507
|
-
});
|
|
3508
|
-
const result = await CfgAccountsOauth.cfgAccountsOauthGithubAuthorizeCreate({
|
|
3509
|
-
body: {
|
|
3510
|
-
source_url: sourceUrl || (typeof window !== "undefined" ? window.location.href : "")
|
|
3511
|
-
},
|
|
3491
|
+
authLogger.info("Verifying TOTP code...");
|
|
3492
|
+
const result = await CfgTotpVerify.cfgTotpVerifyCreate({
|
|
3493
|
+
body: { session_id: sessionId, code },
|
|
3512
3494
|
throwOnError: true
|
|
3513
3495
|
});
|
|
3514
3496
|
const response = result.data;
|
|
3515
|
-
if (!response.
|
|
3516
|
-
throw new Error("
|
|
3517
|
-
}
|
|
3518
|
-
authLogger.info("Redirecting to GitHub...", response.authorization_url);
|
|
3519
|
-
if (typeof window !== "undefined") {
|
|
3520
|
-
sessionStorage.setItem("oauth_state", response.state);
|
|
3521
|
-
sessionStorage.setItem("oauth_provider", "github");
|
|
3522
|
-
sessionStorage.setItem("cfg.oauth.remember_me", String(rememberMe));
|
|
3497
|
+
if (!response.access_token || !response.refresh_token) {
|
|
3498
|
+
throw new Error("Invalid response from 2FA verification");
|
|
3523
3499
|
}
|
|
3524
|
-
|
|
3500
|
+
handleSuccess(response);
|
|
3501
|
+
return true;
|
|
3525
3502
|
} catch (err) {
|
|
3526
|
-
|
|
3527
|
-
|
|
3503
|
+
logAuthFailure(authLogger, "2FA TOTP verification failed:", err);
|
|
3504
|
+
const errorMessage = formatAuthError(err, "Invalid verification code");
|
|
3505
|
+
const attempts = extractAttemptsRemaining(err);
|
|
3506
|
+
if (attempts !== null) setAttemptsRemaining(attempts);
|
|
3528
3507
|
setError(errorMessage);
|
|
3529
3508
|
onError?.(errorMessage);
|
|
3530
|
-
Analytics.event("
|
|
3509
|
+
Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
|
|
3531
3510
|
category: "auth" /* AUTH */,
|
|
3532
|
-
label: "
|
|
3511
|
+
label: "2fa-totp"
|
|
3533
3512
|
});
|
|
3513
|
+
return false;
|
|
3534
3514
|
} finally {
|
|
3535
3515
|
setIsLoading(false);
|
|
3536
3516
|
}
|
|
3537
|
-
}, [
|
|
3538
|
-
const
|
|
3517
|
+
}, [handleSuccess, onError]);
|
|
3518
|
+
const verifyBackupCode = (0, import_react11.useCallback)(async (sessionId, backupCode) => {
|
|
3519
|
+
if (!sessionId) {
|
|
3520
|
+
const msg = "Missing 2FA session ID";
|
|
3521
|
+
setError(msg);
|
|
3522
|
+
onError?.(msg);
|
|
3523
|
+
return false;
|
|
3524
|
+
}
|
|
3525
|
+
if (!backupCode || backupCode.length < 8) {
|
|
3526
|
+
const msg = "Please enter your backup code";
|
|
3527
|
+
setError(msg);
|
|
3528
|
+
onError?.(msg);
|
|
3529
|
+
return false;
|
|
3530
|
+
}
|
|
3539
3531
|
setIsLoading(true);
|
|
3540
3532
|
setError(null);
|
|
3541
3533
|
try {
|
|
3542
|
-
authLogger.info("
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
}
|
|
3549
|
-
rememberMeForSession = sessionStorage.getItem("cfg.oauth.remember_me") === "true";
|
|
3550
|
-
sessionStorage.removeItem("oauth_state");
|
|
3551
|
-
sessionStorage.removeItem("oauth_provider");
|
|
3552
|
-
sessionStorage.removeItem("cfg.oauth.remember_me");
|
|
3553
|
-
}
|
|
3554
|
-
const result = await CfgAccountsOauth.cfgAccountsOauthGithubCallbackCreate({
|
|
3555
|
-
body: { code, state, remember_me: rememberMeForSession },
|
|
3534
|
+
authLogger.info("Verifying backup code...");
|
|
3535
|
+
const result = await CfgTotpVerify.cfgTotpVerifyBackupCreate({
|
|
3536
|
+
body: {
|
|
3537
|
+
session_id: sessionId,
|
|
3538
|
+
backup_code: backupCode.replace(/\s+/g, "")
|
|
3539
|
+
},
|
|
3556
3540
|
throwOnError: true
|
|
3557
3541
|
});
|
|
3558
3542
|
const response = result.data;
|
|
3559
|
-
if (response.
|
|
3560
|
-
|
|
3561
|
-
Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
|
|
3562
|
-
category: "auth" /* AUTH */,
|
|
3563
|
-
label: "github-2fa-required"
|
|
3564
|
-
});
|
|
3565
|
-
onRequires2FA?.(response.session_id, response.should_prompt_2fa || false);
|
|
3566
|
-
return;
|
|
3567
|
-
}
|
|
3568
|
-
if (!response.access || !response.refresh) {
|
|
3569
|
-
throw new Error("Invalid response from OAuth callback");
|
|
3570
|
-
}
|
|
3571
|
-
authLogger.info("GitHub OAuth successful, user:", response.user);
|
|
3572
|
-
auth.setSession({ access: response.access, refresh: response.refresh });
|
|
3573
|
-
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
3574
|
-
category: "auth" /* AUTH */,
|
|
3575
|
-
label: "github"
|
|
3576
|
-
});
|
|
3577
|
-
if (response.user?.id) {
|
|
3578
|
-
Analytics.setUser(String(response.user.id));
|
|
3579
|
-
}
|
|
3580
|
-
if (response.user) {
|
|
3581
|
-
onSuccess?.(response.user, response.is_new_user || false);
|
|
3582
|
-
}
|
|
3583
|
-
if (!skipRedirect) {
|
|
3584
|
-
const finalRedirectUrl = redirectUrl || "/private";
|
|
3585
|
-
router.hardPush(finalRedirectUrl);
|
|
3543
|
+
if (!response.access_token || !response.refresh_token) {
|
|
3544
|
+
throw new Error("Invalid response from backup code verification");
|
|
3586
3545
|
}
|
|
3587
|
-
|
|
3546
|
+
handleSuccess(response);
|
|
3547
|
+
return true;
|
|
3588
3548
|
} catch (err) {
|
|
3589
|
-
|
|
3590
|
-
|
|
3549
|
+
logAuthFailure(authLogger, "2FA backup code verification failed:", err);
|
|
3550
|
+
const errorMessage = formatAuthError(err, "Invalid backup code");
|
|
3551
|
+
const attempts = extractAttemptsRemaining(err);
|
|
3552
|
+
if (attempts !== null) setAttemptsRemaining(attempts);
|
|
3591
3553
|
setError(errorMessage);
|
|
3592
3554
|
onError?.(errorMessage);
|
|
3593
|
-
Analytics.event("
|
|
3555
|
+
Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
|
|
3594
3556
|
category: "auth" /* AUTH */,
|
|
3595
|
-
label: "
|
|
3557
|
+
label: "2fa-backup"
|
|
3596
3558
|
});
|
|
3559
|
+
return false;
|
|
3597
3560
|
} finally {
|
|
3598
3561
|
setIsLoading(false);
|
|
3599
3562
|
}
|
|
3600
|
-
}, [
|
|
3563
|
+
}, [handleSuccess, onError]);
|
|
3601
3564
|
return {
|
|
3602
3565
|
isLoading,
|
|
3603
3566
|
error,
|
|
3604
|
-
|
|
3605
|
-
|
|
3567
|
+
warning,
|
|
3568
|
+
remainingBackupCodes,
|
|
3569
|
+
attemptsRemaining,
|
|
3570
|
+
verifyTOTP,
|
|
3571
|
+
verifyBackupCode,
|
|
3572
|
+
clearError
|
|
3606
3573
|
};
|
|
3607
|
-
}, "
|
|
3574
|
+
}, "useTwoFactorVerify");
|
|
3608
3575
|
|
|
3609
3576
|
// src/auth/hooks/useTwoFactorSetup.ts
|
|
3610
|
-
var
|
|
3577
|
+
var import_react12 = require("react");
|
|
3611
3578
|
var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
|
|
3612
3579
|
const { onComplete, onError } = options;
|
|
3613
|
-
const [isLoading, setIsLoading] = (0,
|
|
3614
|
-
const [error, setError] = (0,
|
|
3615
|
-
const [setupData, setSetupData] = (0,
|
|
3616
|
-
const [backupCodes, setBackupCodes] = (0,
|
|
3617
|
-
const [backupCodesWarning, setBackupCodesWarning] = (0,
|
|
3618
|
-
const [setupStep, setSetupStep] = (0,
|
|
3619
|
-
const clearError = (0,
|
|
3580
|
+
const [isLoading, setIsLoading] = (0, import_react12.useState)(false);
|
|
3581
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
3582
|
+
const [setupData, setSetupData] = (0, import_react12.useState)(null);
|
|
3583
|
+
const [backupCodes, setBackupCodes] = (0, import_react12.useState)(null);
|
|
3584
|
+
const [backupCodesWarning, setBackupCodesWarning] = (0, import_react12.useState)(null);
|
|
3585
|
+
const [setupStep, setSetupStep] = (0, import_react12.useState)("idle");
|
|
3586
|
+
const clearError = (0, import_react12.useCallback)(() => {
|
|
3620
3587
|
setError(null);
|
|
3621
3588
|
}, []);
|
|
3622
|
-
const resetSetup = (0,
|
|
3589
|
+
const resetSetup = (0, import_react12.useCallback)(() => {
|
|
3623
3590
|
setSetupData(null);
|
|
3624
3591
|
setBackupCodes(null);
|
|
3625
3592
|
setBackupCodesWarning(null);
|
|
3626
3593
|
setSetupStep("idle");
|
|
3627
3594
|
setError(null);
|
|
3628
3595
|
}, []);
|
|
3629
|
-
const startSetup = (0,
|
|
3596
|
+
const startSetup = (0, import_react12.useCallback)(async (deviceName) => {
|
|
3630
3597
|
setIsLoading(true);
|
|
3631
3598
|
setError(null);
|
|
3632
3599
|
setSetupStep("scanning");
|
|
@@ -3658,7 +3625,7 @@ var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3658
3625
|
setIsLoading(false);
|
|
3659
3626
|
}
|
|
3660
3627
|
}, [onError]);
|
|
3661
|
-
const confirmSetup = (0,
|
|
3628
|
+
const confirmSetup = (0, import_react12.useCallback)(async (code) => {
|
|
3662
3629
|
if (!setupData) {
|
|
3663
3630
|
const msg = "Setup not started. Call startSetup() first.";
|
|
3664
3631
|
setError(msg);
|
|
@@ -3714,16 +3681,16 @@ var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3714
3681
|
}, "useTwoFactorSetup");
|
|
3715
3682
|
|
|
3716
3683
|
// src/auth/hooks/useTwoFactorStatus.ts
|
|
3717
|
-
var
|
|
3684
|
+
var import_react13 = require("react");
|
|
3718
3685
|
var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
|
|
3719
|
-
const [isLoading, setIsLoading] = (0,
|
|
3720
|
-
const [error, setError] = (0,
|
|
3721
|
-
const [has2FAEnabled, setHas2FAEnabled] = (0,
|
|
3722
|
-
const [devices, setDevices] = (0,
|
|
3723
|
-
const clearError = (0,
|
|
3686
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(false);
|
|
3687
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
3688
|
+
const [has2FAEnabled, setHas2FAEnabled] = (0, import_react13.useState)(null);
|
|
3689
|
+
const [devices, setDevices] = (0, import_react13.useState)([]);
|
|
3690
|
+
const clearError = (0, import_react13.useCallback)(() => {
|
|
3724
3691
|
setError(null);
|
|
3725
3692
|
}, []);
|
|
3726
|
-
const fetchStatus = (0,
|
|
3693
|
+
const fetchStatus = (0, import_react13.useCallback)(async () => {
|
|
3727
3694
|
setIsLoading(true);
|
|
3728
3695
|
setError(null);
|
|
3729
3696
|
try {
|
|
@@ -3748,7 +3715,7 @@ var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
|
|
|
3748
3715
|
setIsLoading(false);
|
|
3749
3716
|
}
|
|
3750
3717
|
}, []);
|
|
3751
|
-
const disable2FA = (0,
|
|
3718
|
+
const disable2FA = (0, import_react13.useCallback)(async (code) => {
|
|
3752
3719
|
if (!isValidTotpCode(code)) {
|
|
3753
3720
|
setError("Please enter a 6-digit code");
|
|
3754
3721
|
return false;
|
|
@@ -3791,9 +3758,9 @@ var useTwoFactor = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3791
3758
|
}, "useTwoFactor");
|
|
3792
3759
|
|
|
3793
3760
|
// src/auth/hooks/useLocalStorage.ts
|
|
3794
|
-
var
|
|
3761
|
+
var import_react14 = require("react");
|
|
3795
3762
|
function useLocalStorage(key, initialValue) {
|
|
3796
|
-
const [storedValue, setStoredValue] = (0,
|
|
3763
|
+
const [storedValue, setStoredValue] = (0, import_react14.useState)(() => {
|
|
3797
3764
|
if (typeof window === "undefined") {
|
|
3798
3765
|
return initialValue;
|
|
3799
3766
|
}
|
|
@@ -4074,15 +4041,15 @@ function getCacheMetadata() {
|
|
|
4074
4041
|
__name(getCacheMetadata, "getCacheMetadata");
|
|
4075
4042
|
|
|
4076
4043
|
// src/auth/hooks/useDeleteAccount.ts
|
|
4077
|
-
var
|
|
4044
|
+
var import_react15 = require("react");
|
|
4078
4045
|
var useDeleteAccount = /* @__PURE__ */ __name(() => {
|
|
4079
|
-
const [isLoading, setIsLoading] = (0,
|
|
4080
|
-
const [error, setError] = (0,
|
|
4046
|
+
const [isLoading, setIsLoading] = (0, import_react15.useState)(false);
|
|
4047
|
+
const [error, setError] = (0, import_react15.useState)(null);
|
|
4081
4048
|
const { logout } = useAuth();
|
|
4082
|
-
const clearError = (0,
|
|
4049
|
+
const clearError = (0, import_react15.useCallback)(() => {
|
|
4083
4050
|
setError(null);
|
|
4084
4051
|
}, []);
|
|
4085
|
-
const deleteAccount = (0,
|
|
4052
|
+
const deleteAccount = (0, import_react15.useCallback)(async () => {
|
|
4086
4053
|
setIsLoading(true);
|
|
4087
4054
|
setError(null);
|
|
4088
4055
|
try {
|
|
@@ -4112,7 +4079,7 @@ var useDeleteAccount = /* @__PURE__ */ __name(() => {
|
|
|
4112
4079
|
}, "useDeleteAccount");
|
|
4113
4080
|
|
|
4114
4081
|
// src/auth/context/AccountsContext.tsx
|
|
4115
|
-
var
|
|
4082
|
+
var import_react16 = require("react");
|
|
4116
4083
|
|
|
4117
4084
|
// src/_api/generated/_cfg_accounts/hooks/useCfgAccountsApiKeyRegenerateCreate.ts
|
|
4118
4085
|
var import_mutation = __toESM(require("swr/mutation"), 1);
|
|
@@ -4469,21 +4436,21 @@ function hasApiOrigin() {
|
|
|
4469
4436
|
}
|
|
4470
4437
|
}
|
|
4471
4438
|
__name(hasApiOrigin, "hasApiOrigin");
|
|
4472
|
-
var AccountsContext = (0,
|
|
4439
|
+
var AccountsContext = (0, import_react16.createContext)(void 0);
|
|
4473
4440
|
function AccountsProvider({ children }) {
|
|
4474
|
-
const [profile, setProfile] = (0,
|
|
4441
|
+
const [profile, setProfile] = (0, import_react16.useState)(() => {
|
|
4475
4442
|
const cached = getCachedProfile();
|
|
4476
4443
|
return cached || void 0;
|
|
4477
4444
|
});
|
|
4478
|
-
const [isLoadingProfile, setIsLoadingProfile] = (0,
|
|
4479
|
-
const [profileError, setProfileError] = (0,
|
|
4480
|
-
const profileRef = (0,
|
|
4481
|
-
const isLoadingRef = (0,
|
|
4482
|
-
const fetchSeqRef = (0,
|
|
4483
|
-
(0,
|
|
4445
|
+
const [isLoadingProfile, setIsLoadingProfile] = (0, import_react16.useState)(false);
|
|
4446
|
+
const [profileError, setProfileError] = (0, import_react16.useState)(null);
|
|
4447
|
+
const profileRef = (0, import_react16.useRef)(profile);
|
|
4448
|
+
const isLoadingRef = (0, import_react16.useRef)(false);
|
|
4449
|
+
const fetchSeqRef = (0, import_react16.useRef)(0);
|
|
4450
|
+
(0, import_react16.useEffect)(() => {
|
|
4484
4451
|
profileRef.current = profile;
|
|
4485
4452
|
}, [profile]);
|
|
4486
|
-
(0,
|
|
4453
|
+
(0, import_react16.useEffect)(() => {
|
|
4487
4454
|
isLoadingRef.current = isLoadingProfile;
|
|
4488
4455
|
}, [isLoadingProfile]);
|
|
4489
4456
|
const { trigger: triggerProfileUpdate } = useCfgAccountsProfileUpdateUpdate();
|
|
@@ -4492,7 +4459,7 @@ function AccountsProvider({ children }) {
|
|
|
4492
4459
|
const { trigger: triggerOtpRequest } = useCfgAccountsOtpRequestCreate();
|
|
4493
4460
|
const { trigger: triggerOtpVerify } = useCfgAccountsOtpVerifyCreate();
|
|
4494
4461
|
const { trigger: triggerPasswordLogin } = useCfgAccountsTokenCreate();
|
|
4495
|
-
const refreshProfile = (0,
|
|
4462
|
+
const refreshProfile = (0, import_react16.useCallback)(async (options) => {
|
|
4496
4463
|
const { callerId, force } = options || {};
|
|
4497
4464
|
if (!hasApiOrigin()) {
|
|
4498
4465
|
authLogger.debug(`No API origin configured, skipping profile fetch (caller: ${callerId})`);
|
|
@@ -4526,26 +4493,26 @@ function AccountsProvider({ children }) {
|
|
|
4526
4493
|
}
|
|
4527
4494
|
}
|
|
4528
4495
|
}, []);
|
|
4529
|
-
const updateProfile = (0,
|
|
4496
|
+
const updateProfile = (0, import_react16.useCallback)(async (data) => {
|
|
4530
4497
|
await triggerProfileUpdate({ body: data });
|
|
4531
4498
|
const user = await refreshProfile({ callerId: "updateProfile", force: true });
|
|
4532
4499
|
return user;
|
|
4533
4500
|
}, [triggerProfileUpdate, refreshProfile]);
|
|
4534
|
-
const partialUpdateProfile = (0,
|
|
4501
|
+
const partialUpdateProfile = (0, import_react16.useCallback)(async (data) => {
|
|
4535
4502
|
await triggerProfilePartialUpdate({ body: data });
|
|
4536
4503
|
const user = await refreshProfile({ callerId: "partialUpdateProfile", force: true });
|
|
4537
4504
|
return user;
|
|
4538
4505
|
}, [triggerProfilePartialUpdate, refreshProfile]);
|
|
4539
|
-
const uploadAvatar = (0,
|
|
4506
|
+
const uploadAvatar = (0, import_react16.useCallback)(async (avatar) => {
|
|
4540
4507
|
await triggerAvatar({ body: { avatar } });
|
|
4541
4508
|
const user = await refreshProfile({ callerId: "uploadAvatar", force: true });
|
|
4542
4509
|
return user;
|
|
4543
4510
|
}, [triggerAvatar, refreshProfile]);
|
|
4544
|
-
const requestOTP = (0,
|
|
4511
|
+
const requestOTP = (0, import_react16.useCallback)(async (data) => {
|
|
4545
4512
|
const result = await triggerOtpRequest({ body: data });
|
|
4546
4513
|
return result;
|
|
4547
4514
|
}, [triggerOtpRequest]);
|
|
4548
|
-
const settleLoginEnvelope = (0,
|
|
4515
|
+
const settleLoginEnvelope = (0, import_react16.useCallback)(async (result, callerId) => {
|
|
4549
4516
|
if (result.requires_2fa && result.session_id) {
|
|
4550
4517
|
authLogger.info("2FA required, session:", result.session_id);
|
|
4551
4518
|
return;
|
|
@@ -4559,17 +4526,17 @@ function AccountsProvider({ children }) {
|
|
|
4559
4526
|
}
|
|
4560
4527
|
}
|
|
4561
4528
|
}, [refreshProfile]);
|
|
4562
|
-
const verifyOTP = (0,
|
|
4529
|
+
const verifyOTP = (0, import_react16.useCallback)(async (data) => {
|
|
4563
4530
|
const result = await triggerOtpVerify({ body: data });
|
|
4564
4531
|
await settleLoginEnvelope(result, "verifyOTP");
|
|
4565
4532
|
return result;
|
|
4566
4533
|
}, [triggerOtpVerify, settleLoginEnvelope]);
|
|
4567
|
-
const loginWithPassword = (0,
|
|
4534
|
+
const loginWithPassword = (0, import_react16.useCallback)(async (data) => {
|
|
4568
4535
|
const result = await triggerPasswordLogin({ body: data });
|
|
4569
4536
|
await settleLoginEnvelope(result, "loginWithPassword");
|
|
4570
4537
|
return result;
|
|
4571
4538
|
}, [triggerPasswordLogin, settleLoginEnvelope]);
|
|
4572
|
-
const logout = (0,
|
|
4539
|
+
const logout = (0, import_react16.useCallback)(() => {
|
|
4573
4540
|
const refresh = auth.getRefreshToken();
|
|
4574
4541
|
if (refresh) {
|
|
4575
4542
|
CfgAccountsAuth.cfgAccountsTokenBlacklistCreate({ body: { refresh } }).catch(() => {
|
|
@@ -4581,7 +4548,7 @@ function AccountsProvider({ children }) {
|
|
|
4581
4548
|
setProfileError(null);
|
|
4582
4549
|
clearProfileCache();
|
|
4583
4550
|
}, []);
|
|
4584
|
-
const value = (0,
|
|
4551
|
+
const value = (0, import_react16.useMemo)(() => ({
|
|
4585
4552
|
profile,
|
|
4586
4553
|
isLoadingProfile,
|
|
4587
4554
|
profileError,
|
|
@@ -4610,7 +4577,7 @@ function AccountsProvider({ children }) {
|
|
|
4610
4577
|
}
|
|
4611
4578
|
__name(AccountsProvider, "AccountsProvider");
|
|
4612
4579
|
function useAccountsContext() {
|
|
4613
|
-
const context = (0,
|
|
4580
|
+
const context = (0, import_react16.useContext)(AccountsContext);
|
|
4614
4581
|
if (!context) {
|
|
4615
4582
|
throw new Error("useAccountsContext must be used within AccountsProvider");
|
|
4616
4583
|
}
|
|
@@ -4631,7 +4598,7 @@ var resolveAuthRoutes = /* @__PURE__ */ __name((routes) => ({
|
|
|
4631
4598
|
defaultAuthCallback: routes?.defaultAuthCallback || routes?.auth || defaultRoutes.defaultAuthCallback
|
|
4632
4599
|
}), "resolveAuthRoutes");
|
|
4633
4600
|
var EMAIL_STORAGE_KEY = "auth_email";
|
|
4634
|
-
var AuthContext = (0,
|
|
4601
|
+
var AuthContext = (0, import_react17.createContext)(void 0);
|
|
4635
4602
|
var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
4636
4603
|
const accounts = useAccountsContext();
|
|
4637
4604
|
const session = useSession();
|
|
@@ -4643,28 +4610,28 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4643
4610
|
EMAIL_STORAGE_KEY,
|
|
4644
4611
|
null
|
|
4645
4612
|
);
|
|
4646
|
-
const routes = (0,
|
|
4647
|
-
const routesRef = (0,
|
|
4648
|
-
(0,
|
|
4613
|
+
const routes = (0, import_react17.useMemo)(() => resolveAuthRoutes(config?.routes), [config?.routes]);
|
|
4614
|
+
const routesRef = (0, import_react17.useRef)(routes);
|
|
4615
|
+
(0, import_react17.useEffect)(() => {
|
|
4649
4616
|
routesRef.current = routes;
|
|
4650
4617
|
}, [routes]);
|
|
4651
4618
|
const redirectManager = useAuthRedirectManager({
|
|
4652
4619
|
fallbackUrl: routes.defaultCallback,
|
|
4653
4620
|
clearOnUse: true
|
|
4654
4621
|
});
|
|
4655
|
-
const configRef = (0,
|
|
4656
|
-
(0,
|
|
4622
|
+
const configRef = (0, import_react17.useRef)(config);
|
|
4623
|
+
(0, import_react17.useEffect)(() => {
|
|
4657
4624
|
configRef.current = config;
|
|
4658
4625
|
}, [config]);
|
|
4659
|
-
const accountsRef = (0,
|
|
4660
|
-
(0,
|
|
4626
|
+
const accountsRef = (0, import_react17.useRef)(accounts);
|
|
4627
|
+
(0, import_react17.useEffect)(() => {
|
|
4661
4628
|
accountsRef.current = accounts;
|
|
4662
4629
|
}, [accounts]);
|
|
4663
|
-
const pendingManualNavRef = (0,
|
|
4664
|
-
const [profileSettled, setProfileSettled] = (0,
|
|
4630
|
+
const pendingManualNavRef = (0, import_react17.useRef)(false);
|
|
4631
|
+
const [profileSettled, setProfileSettled] = (0, import_react17.useState)(false);
|
|
4665
4632
|
const hasProfile = Boolean(accounts.profile);
|
|
4666
|
-
const profileAttemptedRef = (0,
|
|
4667
|
-
(0,
|
|
4633
|
+
const profileAttemptedRef = (0, import_react17.useRef)(false);
|
|
4634
|
+
(0, import_react17.useEffect)(() => {
|
|
4668
4635
|
if (!isAuthenticated) {
|
|
4669
4636
|
profileAttemptedRef.current = false;
|
|
4670
4637
|
const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
|
|
@@ -4692,8 +4659,8 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4692
4659
|
};
|
|
4693
4660
|
}, [isAuthenticated, hasProfile]);
|
|
4694
4661
|
const isLoading = !profileSettled;
|
|
4695
|
-
const expiredHandledRef = (0,
|
|
4696
|
-
(0,
|
|
4662
|
+
const expiredHandledRef = (0, import_react17.useRef)(false);
|
|
4663
|
+
(0, import_react17.useEffect)(() => {
|
|
4697
4664
|
const unsubscribe = auth.onSessionExpired(() => {
|
|
4698
4665
|
if (expiredHandledRef.current) return;
|
|
4699
4666
|
expiredHandledRef.current = true;
|
|
@@ -4706,7 +4673,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4706
4673
|
});
|
|
4707
4674
|
return unsubscribe;
|
|
4708
4675
|
}, [router]);
|
|
4709
|
-
(0,
|
|
4676
|
+
(0, import_react17.useEffect)(() => {
|
|
4710
4677
|
if (!isAuthenticated || pendingManualNavRef.current) return;
|
|
4711
4678
|
if (pathname === routes.auth && !queryParams.get("flow")) {
|
|
4712
4679
|
const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
|
|
@@ -4714,7 +4681,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4714
4681
|
router.push(saved || routes.defaultCallback);
|
|
4715
4682
|
}
|
|
4716
4683
|
}, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
|
|
4717
|
-
const loadCurrentProfile = (0,
|
|
4684
|
+
const loadCurrentProfile = (0, import_react17.useCallback)(async (callerId) => {
|
|
4718
4685
|
if (!auth.isAuthenticated()) {
|
|
4719
4686
|
throw new Error("No valid authentication token");
|
|
4720
4687
|
}
|
|
@@ -4722,7 +4689,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4722
4689
|
callerId: callerId || "AuthContext.loadCurrentProfile"
|
|
4723
4690
|
});
|
|
4724
4691
|
}, []);
|
|
4725
|
-
const checkAuthAndRedirect = (0,
|
|
4692
|
+
const checkAuthAndRedirect = (0, import_react17.useCallback)(async () => {
|
|
4726
4693
|
if (auth.isAuthenticated()) {
|
|
4727
4694
|
try {
|
|
4728
4695
|
await loadCurrentProfile("checkAuthAndRedirect");
|
|
@@ -4734,7 +4701,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4734
4701
|
router.push(routesRef.current.defaultAuthCallback);
|
|
4735
4702
|
}
|
|
4736
4703
|
}, [loadCurrentProfile, router]);
|
|
4737
|
-
const requestOTP = (0,
|
|
4704
|
+
const requestOTP = (0, import_react17.useCallback)(
|
|
4738
4705
|
async (identifier, sourceUrl, consent) => {
|
|
4739
4706
|
auth.clearSession();
|
|
4740
4707
|
try {
|
|
@@ -4756,7 +4723,8 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4756
4723
|
return {
|
|
4757
4724
|
success: true,
|
|
4758
4725
|
message: result.message || "OTP code sent to your email address",
|
|
4759
|
-
webmail: result.webmail ?? null
|
|
4726
|
+
webmail: result.webmail ?? null,
|
|
4727
|
+
resendIn: result.resend_in
|
|
4760
4728
|
};
|
|
4761
4729
|
} catch (error) {
|
|
4762
4730
|
logAuthFailure(authLogger, "Request OTP failed:", error);
|
|
@@ -4775,7 +4743,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4775
4743
|
},
|
|
4776
4744
|
[]
|
|
4777
4745
|
);
|
|
4778
|
-
const verifyOTP = (0,
|
|
4746
|
+
const verifyOTP = (0, import_react17.useCallback)(
|
|
4779
4747
|
async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect, rememberMe = false) => {
|
|
4780
4748
|
try {
|
|
4781
4749
|
pendingManualNavRef.current = Boolean(skipRedirect);
|
|
@@ -4836,7 +4804,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4836
4804
|
},
|
|
4837
4805
|
[setStoredEmail, redirectManager, router]
|
|
4838
4806
|
);
|
|
4839
|
-
const login = (0,
|
|
4807
|
+
const login = (0, import_react17.useCallback)(
|
|
4840
4808
|
async (email, password, options) => {
|
|
4841
4809
|
const { redirectUrl, skipRedirect, rememberMe = false } = options || {};
|
|
4842
4810
|
try {
|
|
@@ -4902,7 +4870,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4902
4870
|
},
|
|
4903
4871
|
[setStoredEmail, redirectManager, router]
|
|
4904
4872
|
);
|
|
4905
|
-
const refreshToken = (0,
|
|
4873
|
+
const refreshToken = (0, import_react17.useCallback)(async () => {
|
|
4906
4874
|
const access = await auth.refreshNow();
|
|
4907
4875
|
if (access) {
|
|
4908
4876
|
Analytics.event("auth_token_refresh" /* AUTH_TOKEN_REFRESH */, {
|
|
@@ -4915,7 +4883,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4915
4883
|
});
|
|
4916
4884
|
return { success: false, message: "Error refreshing token" };
|
|
4917
4885
|
}, []);
|
|
4918
|
-
const logout = (0,
|
|
4886
|
+
const logout = (0, import_react17.useCallback)(() => {
|
|
4919
4887
|
Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
|
|
4920
4888
|
category: "auth" /* AUTH */
|
|
4921
4889
|
});
|
|
@@ -4924,21 +4892,21 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4924
4892
|
}, [router]);
|
|
4925
4893
|
const user = accounts.profile ?? null;
|
|
4926
4894
|
const isAdminUser = Boolean(user?.is_staff || user?.is_superuser);
|
|
4927
|
-
(0,
|
|
4895
|
+
(0, import_react17.useEffect)(() => {
|
|
4928
4896
|
applyRoleLogPolicy({ isAdmin: isAdminUser });
|
|
4929
4897
|
}, [isAdminUser]);
|
|
4930
|
-
const updateProfile = (0,
|
|
4898
|
+
const updateProfile = (0, import_react17.useCallback)(
|
|
4931
4899
|
async (data) => {
|
|
4932
4900
|
const result = await accountsRef.current.partialUpdateProfile(data);
|
|
4933
4901
|
return result;
|
|
4934
4902
|
},
|
|
4935
4903
|
[]
|
|
4936
4904
|
);
|
|
4937
|
-
const uploadAvatar = (0,
|
|
4905
|
+
const uploadAvatar = (0, import_react17.useCallback)(async (avatar) => {
|
|
4938
4906
|
const result = await accountsRef.current.uploadAvatar(avatar);
|
|
4939
4907
|
return result;
|
|
4940
4908
|
}, []);
|
|
4941
|
-
const value = (0,
|
|
4909
|
+
const value = (0, import_react17.useMemo)(
|
|
4942
4910
|
() => ({
|
|
4943
4911
|
user,
|
|
4944
4912
|
isLoading,
|
|
@@ -4994,7 +4962,7 @@ function AuthProviderRaw({ children, config, enabled = true }) {
|
|
|
4994
4962
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AccountsProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthProviderInternal, { config, children }) });
|
|
4995
4963
|
}
|
|
4996
4964
|
__name(AuthProviderRaw, "AuthProviderRaw");
|
|
4997
|
-
var AuthProvider = (0,
|
|
4965
|
+
var AuthProvider = (0, import_react17.memo)(AuthProviderRaw);
|
|
4998
4966
|
var defaultAuthState = {
|
|
4999
4967
|
user: null,
|
|
5000
4968
|
isLoading: false,
|
|
@@ -5050,7 +5018,7 @@ var defaultAuthState = {
|
|
|
5050
5018
|
}, "uploadAvatar")
|
|
5051
5019
|
};
|
|
5052
5020
|
var useAuth = /* @__PURE__ */ __name(() => {
|
|
5053
|
-
const context = (0,
|
|
5021
|
+
const context = (0, import_react17.useContext)(AuthContext);
|
|
5054
5022
|
if (context === void 0) {
|
|
5055
5023
|
if (isBrowser2 && isDev) {
|
|
5056
5024
|
authLogger.debug("useAuth called outside AuthProvider, returning default state");
|