@djangocfg/api 2.1.580 → 2.1.581

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/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 import_react16 = require("react");
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)(false);
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 [twoFactorSessionId, setTwoFactorSessionId] = (0, import_react4.useState)(null);
1193
- const [shouldPrompt2FA, setShouldPrompt2FA] = (0, import_react4.useState)(false);
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
- twoFactorSessionId,
1228
- shouldPrompt2FA,
1229
- twoFactorCode,
1230
- useBackupCode,
1231
- rateLimitSeconds,
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
- setTwoFactorSessionId,
1247
- setShouldPrompt2FA,
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 import_react9 = require("react");
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/useTwoFactorVerify.ts
1333
- var import_react8 = require("react");
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/_api/generated/core/bodySerializer.gen.ts
1336
- var serializeFormDataPair = /* @__PURE__ */ __name((data, key, value) => {
1337
- if (typeof value === "string" || value instanceof Blob) {
1338
- data.append(key, value);
1339
- } else if (value instanceof Date) {
1340
- data.append(key, value.toISOString());
1341
- } else {
1342
- data.append(key, JSON.stringify(value));
1343
- }
1344
- }, "serializeFormDataPair");
1345
- var formDataBodySerializer = {
1346
- bodySerializer: /* @__PURE__ */ __name((body) => {
1347
- const data = new FormData();
1348
- Object.entries(body).forEach(([key, value]) => {
1349
- if (value === void 0 || value === null) {
1350
- return;
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
- if (Array.isArray(value)) {
1353
- value.forEach((v) => serializeFormDataPair(data, key, v));
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
- serializeFormDataPair(data, key, value);
1493
+ setError(result.message);
1494
+ onError?.(result.message);
1495
+ return false;
1356
1496
  }
1357
- });
1358
- return data;
1359
- }, "bodySerializer")
1360
- };
1361
- var jsonBodySerializer = {
1362
- bodySerializer: /* @__PURE__ */ __name((body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value), "bodySerializer")
1363
- };
1364
-
1365
- // src/_api/generated/core/params.gen.ts
1366
- var extraPrefixesMap = {
1367
- $body_: "body",
1368
- $headers_: "headers",
1369
- $path_: "path",
1370
- $query_: "query"
1371
- };
1372
- var extraPrefixes = Object.entries(extraPrefixesMap);
1373
-
1374
- // src/_api/generated/core/serverSentEvents.gen.ts
1375
- function createSseClient({
1376
- onRequest,
1377
- onSseError,
1378
- onSseEvent,
1379
- responseTransformer,
1380
- responseValidator,
1381
- sseDefaultRetryDelay,
1382
- sseMaxRetryAttempts,
1383
- sseMaxRetryDelay,
1384
- sseSleepFn,
1385
- url,
1386
- ...options
1387
- }) {
1388
- let lastEventId;
1389
- const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1390
- const createStream = /* @__PURE__ */ __name(async function* () {
1391
- let retryDelay = sseDefaultRetryDelay ?? 3e3;
1392
- let attempt = 0;
1393
- const signal = options.signal ?? new AbortController().signal;
1394
- while (true) {
1395
- if (signal.aborted) break;
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
- try {
1402
- const requestInit = {
1403
- redirect: "follow",
1404
- ...options,
1405
- body: options.serializedBody,
1406
- headers,
1407
- signal
1408
- };
1409
- let request = new Request(url, requestInit);
1410
- if (onRequest) {
1411
- request = await onRequest(url, requestInit);
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/useSessionStorage.ts
2762
- var import_react7 = require("react");
2763
-
2764
- // src/auth/utils/storage.ts
2765
- var MAX_ITEM_KB = 1024;
2766
- function isQuotaExceededError(error) {
2767
- if (typeof error !== "object" || error === null) return false;
2768
- const { name, code, message } = error;
2769
- return name === "QuotaExceededError" || code === 22 || typeof message === "string" && message.includes("quota");
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
- const keys = Object.keys(sessionStorage).filter((key2) => key2 && typeof key2 === "string");
2806
- if (keys.length > 50) {
2807
- const itemsToRemove = Math.ceil(keys.length * 0.2);
2808
- for (let i = 0; i < itemsToRemove; i++) {
2809
- try {
2810
- const key2 = keys[i];
2811
- if (key2) {
2812
- sessionStorage.removeItem(key2);
2813
- sessionStorage.removeItem(`${key2}_timestamp`);
2814
- }
2815
- } catch {
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, import_react8.useState)(false);
2999
- const [error, setError] = (0, import_react8.useState)(null);
3000
- const [warning, setWarning] = (0, import_react8.useState)(null);
3001
- const [remainingBackupCodes, setRemainingBackupCodes] = (0, import_react8.useState)(null);
3002
- const [attemptsRemaining, setAttemptsRemaining] = (0, import_react8.useState)(null);
3003
- const clearError = (0, import_react8.useCallback)(() => {
3004
- setError(null);
3005
- }, []);
3006
- const handleSuccess = (0, import_react8.useCallback)((response) => {
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, import_react8.useCallback)(async (sessionId, code) => {
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
- } finally {
3403
- setIsLoading(false);
3480
+ return false;
3404
3481
  }
3405
- }, [identifier, marketingConsent, marketingConsentOption, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, setWebmail, onError, sourceUrl]);
3406
- const handleBackToIdentifier = (0, import_react9.useCallback)(() => {
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("Starting GitHub OAuth flow...");
3504
- Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
3505
- category: "auth" /* AUTH */,
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.authorization_url) {
3516
- throw new Error("Failed to get authorization URL");
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
- window.location.href = response.authorization_url;
3500
+ handleSuccess(response);
3501
+ return true;
3525
3502
  } catch (err) {
3526
- const errorMessage = formatAuthError(err, "Failed to start GitHub authentication");
3527
- authLogger.error("GitHub OAuth start error:", err);
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("auth_oauth_fail" /* AUTH_OAUTH_FAIL */, {
3509
+ Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
3531
3510
  category: "auth" /* AUTH */,
3532
- label: "github"
3511
+ label: "2fa-totp"
3533
3512
  });
3513
+ return false;
3534
3514
  } finally {
3535
3515
  setIsLoading(false);
3536
3516
  }
3537
- }, [sourceUrl, rememberMe, onError]);
3538
- const handleGithubCallback = (0, import_react10.useCallback)(async (code, state) => {
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("Processing GitHub OAuth callback...");
3543
- let rememberMeForSession = false;
3544
- if (typeof window !== "undefined") {
3545
- const storedState = sessionStorage.getItem("oauth_state");
3546
- if (storedState && storedState !== state) {
3547
- throw new Error("Invalid OAuth state - possible CSRF attack");
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.requires_2fa && response.session_id) {
3560
- authLogger.info("GitHub OAuth requires 2FA, session:", response.session_id);
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
- return;
3546
+ handleSuccess(response);
3547
+ return true;
3588
3548
  } catch (err) {
3589
- const errorMessage = formatAuthError(err, "GitHub authentication failed");
3590
- authLogger.error("GitHub OAuth callback error:", err);
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("auth_oauth_fail" /* AUTH_OAUTH_FAIL */, {
3555
+ Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
3594
3556
  category: "auth" /* AUTH */,
3595
- label: "github"
3557
+ label: "2fa-backup"
3596
3558
  });
3559
+ return false;
3597
3560
  } finally {
3598
3561
  setIsLoading(false);
3599
3562
  }
3600
- }, [onSuccess, onError, onRequires2FA, redirectUrl, router, skipRedirect]);
3563
+ }, [handleSuccess, onError]);
3601
3564
  return {
3602
3565
  isLoading,
3603
3566
  error,
3604
- startGithubAuth,
3605
- handleGithubCallback
3567
+ warning,
3568
+ remainingBackupCodes,
3569
+ attemptsRemaining,
3570
+ verifyTOTP,
3571
+ verifyBackupCode,
3572
+ clearError
3606
3573
  };
3607
- }, "useGithubAuth");
3574
+ }, "useTwoFactorVerify");
3608
3575
 
3609
3576
  // src/auth/hooks/useTwoFactorSetup.ts
3610
- var import_react11 = require("react");
3577
+ var import_react12 = require("react");
3611
3578
  var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
3612
3579
  const { onComplete, onError } = options;
3613
- const [isLoading, setIsLoading] = (0, import_react11.useState)(false);
3614
- const [error, setError] = (0, import_react11.useState)(null);
3615
- const [setupData, setSetupData] = (0, import_react11.useState)(null);
3616
- const [backupCodes, setBackupCodes] = (0, import_react11.useState)(null);
3617
- const [backupCodesWarning, setBackupCodesWarning] = (0, import_react11.useState)(null);
3618
- const [setupStep, setSetupStep] = (0, import_react11.useState)("idle");
3619
- const clearError = (0, import_react11.useCallback)(() => {
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, import_react11.useCallback)(() => {
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, import_react11.useCallback)(async (deviceName) => {
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, import_react11.useCallback)(async (code) => {
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 import_react12 = require("react");
3684
+ var import_react13 = require("react");
3718
3685
  var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
3719
- const [isLoading, setIsLoading] = (0, import_react12.useState)(false);
3720
- const [error, setError] = (0, import_react12.useState)(null);
3721
- const [has2FAEnabled, setHas2FAEnabled] = (0, import_react12.useState)(null);
3722
- const [devices, setDevices] = (0, import_react12.useState)([]);
3723
- const clearError = (0, import_react12.useCallback)(() => {
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, import_react12.useCallback)(async () => {
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, import_react12.useCallback)(async (code) => {
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 import_react13 = require("react");
3761
+ var import_react14 = require("react");
3795
3762
  function useLocalStorage(key, initialValue) {
3796
- const [storedValue, setStoredValue] = (0, import_react13.useState)(() => {
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 import_react14 = require("react");
4044
+ var import_react15 = require("react");
4078
4045
  var useDeleteAccount = /* @__PURE__ */ __name(() => {
4079
- const [isLoading, setIsLoading] = (0, import_react14.useState)(false);
4080
- const [error, setError] = (0, import_react14.useState)(null);
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, import_react14.useCallback)(() => {
4049
+ const clearError = (0, import_react15.useCallback)(() => {
4083
4050
  setError(null);
4084
4051
  }, []);
4085
- const deleteAccount = (0, import_react14.useCallback)(async () => {
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 import_react15 = require("react");
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, import_react15.createContext)(void 0);
4439
+ var AccountsContext = (0, import_react16.createContext)(void 0);
4473
4440
  function AccountsProvider({ children }) {
4474
- const [profile, setProfile] = (0, import_react15.useState)(() => {
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, import_react15.useState)(false);
4479
- const [profileError, setProfileError] = (0, import_react15.useState)(null);
4480
- const profileRef = (0, import_react15.useRef)(profile);
4481
- const isLoadingRef = (0, import_react15.useRef)(false);
4482
- const fetchSeqRef = (0, import_react15.useRef)(0);
4483
- (0, import_react15.useEffect)(() => {
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, import_react15.useEffect)(() => {
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, import_react15.useCallback)(async (options) => {
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, import_react15.useCallback)(async (data) => {
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, import_react15.useCallback)(async (data) => {
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, import_react15.useCallback)(async (avatar) => {
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, import_react15.useCallback)(async (data) => {
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, import_react15.useCallback)(async (result, callerId) => {
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, import_react15.useCallback)(async (data) => {
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, import_react15.useCallback)(async (data) => {
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, import_react15.useCallback)(() => {
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, import_react15.useMemo)(() => ({
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, import_react15.useContext)(AccountsContext);
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, import_react16.createContext)(void 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, import_react16.useMemo)(() => resolveAuthRoutes(config?.routes), [config?.routes]);
4647
- const routesRef = (0, import_react16.useRef)(routes);
4648
- (0, import_react16.useEffect)(() => {
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, import_react16.useRef)(config);
4656
- (0, import_react16.useEffect)(() => {
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, import_react16.useRef)(accounts);
4660
- (0, import_react16.useEffect)(() => {
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, import_react16.useRef)(false);
4664
- const [profileSettled, setProfileSettled] = (0, import_react16.useState)(false);
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, import_react16.useRef)(false);
4667
- (0, import_react16.useEffect)(() => {
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, import_react16.useRef)(false);
4696
- (0, import_react16.useEffect)(() => {
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, import_react16.useEffect)(() => {
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, import_react16.useCallback)(async (callerId) => {
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, import_react16.useCallback)(async () => {
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, import_react16.useCallback)(
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, import_react16.useCallback)(
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, import_react16.useCallback)(
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, import_react16.useCallback)(async () => {
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, import_react16.useCallback)(() => {
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, import_react16.useEffect)(() => {
4895
+ (0, import_react17.useEffect)(() => {
4928
4896
  applyRoleLogPolicy({ isAdmin: isAdminUser });
4929
4897
  }, [isAdminUser]);
4930
- const updateProfile = (0, import_react16.useCallback)(
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, import_react16.useCallback)(async (avatar) => {
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, import_react16.useMemo)(
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, import_react16.memo)(AuthProviderRaw);
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, import_react16.useContext)(AuthContext);
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");