@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.mjs CHANGED
@@ -10,7 +10,9 @@ var AUTH_CONSTANTS = {
10
10
  /** TOTP code length (RFC 6238 — authenticator apps). */
11
11
  TOTP_LENGTH: 6,
12
12
  /** Backup code max length. */
13
- BACKUP_CODE_MAX_LENGTH: 12
13
+ BACKUP_CODE_MAX_LENGTH: 12,
14
+ /** Resend cooldown (s) when the server does not state one. Mirrors the backend per-email cooldown. */
15
+ RESEND_COOLDOWN_SECONDS: 30
14
16
  };
15
17
 
16
18
  // src/auth/context/AuthContext.tsx
@@ -978,6 +980,28 @@ var formatCountdown = /* @__PURE__ */ __name((s) => {
978
980
  const m = Math.floor(s / 60);
979
981
  return m > 0 ? `${m}:${String(s % 60).padStart(2, "0")}` : `${s}s`;
980
982
  }, "formatCountdown");
983
+ var useCountdown = /* @__PURE__ */ __name(() => {
984
+ const [seconds, setSeconds] = useState2(0);
985
+ const timerRef = useRef2(null);
986
+ const start = useCallback2((from) => {
987
+ if (timerRef.current) clearInterval(timerRef.current);
988
+ setSeconds(from);
989
+ timerRef.current = setInterval(() => {
990
+ setSeconds((prev) => {
991
+ if (prev <= 1) {
992
+ clearInterval(timerRef.current);
993
+ timerRef.current = null;
994
+ return 0;
995
+ }
996
+ return prev - 1;
997
+ });
998
+ }, 1e3);
999
+ }, []);
1000
+ useEffect2(() => () => {
1001
+ if (timerRef.current) clearInterval(timerRef.current);
1002
+ }, []);
1003
+ return { seconds, start };
1004
+ }, "useCountdown");
981
1005
  var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
982
1006
  const [identifier, setIdentifier] = useState2(initialIdentifier);
983
1007
  const [otp, setOtp] = useState2("");
@@ -985,33 +1009,12 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
985
1009
  const [isLoading, setIsLoading] = useState2(false);
986
1010
  const [acceptedTerms, setAcceptedTerms] = useState2(true);
987
1011
  const [marketingConsent, setMarketingConsent] = useState2(null);
988
- const [rememberMe, setRememberMe] = useState2(false);
1012
+ const [rememberMe, setRememberMe] = useState2(true);
989
1013
  const [step, setStep] = useState2("identifier");
990
1014
  const [error, setError] = useState2("");
991
1015
  const [webmail, setWebmail] = useState2(null);
992
- const [twoFactorSessionId, setTwoFactorSessionId] = useState2(null);
993
- const [shouldPrompt2FA, setShouldPrompt2FA] = useState2(false);
994
- const [twoFactorCode, setTwoFactorCode] = useState2("");
995
- const [useBackupCode, setUseBackupCode] = useState2(false);
996
- const [rateLimitSeconds, setRateLimitSeconds] = useState2(0);
997
- const rateLimitTimerRef = useRef2(null);
998
- const startRateLimitCountdown = useCallback2((seconds) => {
999
- if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
1000
- setRateLimitSeconds(seconds);
1001
- rateLimitTimerRef.current = setInterval(() => {
1002
- setRateLimitSeconds((prev) => {
1003
- if (prev <= 1) {
1004
- clearInterval(rateLimitTimerRef.current);
1005
- rateLimitTimerRef.current = null;
1006
- return 0;
1007
- }
1008
- return prev - 1;
1009
- });
1010
- }, 1e3);
1011
- }, []);
1012
- useEffect2(() => () => {
1013
- if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
1014
- }, []);
1016
+ const rateLimit = useCountdown();
1017
+ const resend = useCountdown();
1015
1018
  const clearError = useCallback2(() => setError(""), []);
1016
1019
  return {
1017
1020
  // State
@@ -1024,13 +1027,11 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
1024
1027
  rememberMe,
1025
1028
  step,
1026
1029
  error,
1027
- twoFactorSessionId,
1028
- shouldPrompt2FA,
1029
- twoFactorCode,
1030
- useBackupCode,
1031
- rateLimitSeconds,
1032
- isRateLimited: rateLimitSeconds > 0,
1033
- rateLimitLabel: formatCountdown(rateLimitSeconds),
1030
+ rateLimitSeconds: rateLimit.seconds,
1031
+ isRateLimited: rateLimit.seconds > 0,
1032
+ rateLimitLabel: formatCountdown(rateLimit.seconds),
1033
+ resendSeconds: resend.seconds,
1034
+ resendLabel: formatCountdown(resend.seconds),
1034
1035
  webmail,
1035
1036
  // Handlers
1036
1037
  setIdentifier,
@@ -1043,11 +1044,8 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
1043
1044
  clearError,
1044
1045
  setStep,
1045
1046
  setIsLoading,
1046
- setTwoFactorSessionId,
1047
- setShouldPrompt2FA,
1048
- setTwoFactorCode,
1049
- setUseBackupCode,
1050
- startRateLimitCountdown,
1047
+ startRateLimitCountdown: rateLimit.start,
1048
+ startResendCountdown: resend.start,
1051
1049
  setWebmail
1052
1050
  };
1053
1051
  }, "useAuthFormState");
@@ -1064,7 +1062,7 @@ var useAuthValidation = /* @__PURE__ */ __name(() => {
1064
1062
  var validateIdentifier = /* @__PURE__ */ __name((id) => EMAIL_REGEX.test(id), "validateIdentifier");
1065
1063
 
1066
1064
  // src/auth/hooks/useAuthForm.ts
1067
- import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4 } from "react";
1065
+ import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
1068
1066
 
1069
1067
  // src/auth/utils/logger.ts
1070
1068
  import { createConsola as createConsola2 } from "consola";
@@ -1129,88 +1127,412 @@ var useAutoAuth = /* @__PURE__ */ __name((options = {}) => {
1129
1127
  };
1130
1128
  }, "useAutoAuth");
1131
1129
 
1132
- // src/auth/hooks/useTwoFactorVerify.ts
1133
- import { useCallback as useCallback4, useState as useState4 } from "react";
1130
+ // src/auth/hooks/useTwoFactorChallenge.ts
1131
+ import { useSyncExternalStore as useSyncExternalStore2 } from "react";
1132
+ var current = null;
1133
+ var listeners = /* @__PURE__ */ new Set();
1134
+ var set = /* @__PURE__ */ __name((next) => {
1135
+ current = next;
1136
+ for (const listener of [...listeners]) listener();
1137
+ }, "set");
1138
+ var subscribe = /* @__PURE__ */ __name((listener) => {
1139
+ listeners.add(listener);
1140
+ return () => {
1141
+ listeners.delete(listener);
1142
+ };
1143
+ }, "subscribe");
1144
+ var beginTwoFactorChallenge = /* @__PURE__ */ __name((challenge) => set(challenge), "beginTwoFactorChallenge");
1145
+ var endTwoFactorChallenge = /* @__PURE__ */ __name(() => set(null), "endTwoFactorChallenge");
1146
+ var useTwoFactorChallenge = /* @__PURE__ */ __name(() => useSyncExternalStore2(subscribe, () => current, () => null), "useTwoFactorChallenge");
1134
1147
 
1135
- // src/_api/generated/core/bodySerializer.gen.ts
1136
- var serializeFormDataPair = /* @__PURE__ */ __name((data, key, value) => {
1137
- if (typeof value === "string" || value instanceof Blob) {
1138
- data.append(key, value);
1139
- } else if (value instanceof Date) {
1140
- data.append(key, value.toISOString());
1141
- } else {
1142
- data.append(key, JSON.stringify(value));
1143
- }
1144
- }, "serializeFormDataPair");
1145
- var formDataBodySerializer = {
1146
- bodySerializer: /* @__PURE__ */ __name((body) => {
1147
- const data = new FormData();
1148
- Object.entries(body).forEach(([key, value]) => {
1149
- if (value === void 0 || value === null) {
1150
- return;
1148
+ // src/auth/hooks/useAuthForm.ts
1149
+ var useAuthForm = /* @__PURE__ */ __name((options) => {
1150
+ const {
1151
+ onIdentifierSuccess,
1152
+ onOTPSuccess,
1153
+ onError,
1154
+ sourceUrl,
1155
+ redirectUrl,
1156
+ requireTermsAcceptance = false,
1157
+ marketingConsent: marketingConsentOption,
1158
+ authPath = "/auth"
1159
+ } = options;
1160
+ const formState = useAuthFormState();
1161
+ const validation = useAuthValidation();
1162
+ const isAutoSubmitFromUrlRef = useRef4(false);
1163
+ const processedOtpKeyRef = useRef4(null);
1164
+ const {
1165
+ requestOTP,
1166
+ verifyOTP,
1167
+ login,
1168
+ getSavedEmail,
1169
+ saveEmail
1170
+ } = useAuth();
1171
+ const {
1172
+ identifier,
1173
+ otp,
1174
+ password,
1175
+ acceptedTerms,
1176
+ marketingConsent,
1177
+ rememberMe,
1178
+ setIdentifier,
1179
+ setOtp,
1180
+ setStep,
1181
+ setError,
1182
+ setIsLoading,
1183
+ clearError,
1184
+ startRateLimitCountdown,
1185
+ startResendCountdown,
1186
+ setWebmail
1187
+ } = formState;
1188
+ const { validateIdentifier: validateIdentifier2 } = validation;
1189
+ const saveIdentifierToStorage = useCallback4((id) => {
1190
+ saveEmail(id);
1191
+ }, [saveEmail]);
1192
+ useEffect4(() => {
1193
+ const savedEmail = getSavedEmail();
1194
+ if (savedEmail) {
1195
+ setIdentifier(savedEmail);
1196
+ }
1197
+ }, []);
1198
+ const handleIdentifierSubmit = useCallback4(async (e) => {
1199
+ e.preventDefault();
1200
+ if (!identifier) {
1201
+ const msg = "Please enter your email address";
1202
+ setError(msg);
1203
+ onError?.(msg);
1204
+ return;
1205
+ }
1206
+ if (!validateIdentifier2(identifier)) {
1207
+ const msg = "Please enter a valid email address";
1208
+ setError(msg);
1209
+ onError?.(msg);
1210
+ return;
1211
+ }
1212
+ if (requireTermsAcceptance && !acceptedTerms) {
1213
+ const msg = "Please accept the Terms of Service and Privacy Policy";
1214
+ setError(msg);
1215
+ onError?.(msg);
1216
+ return;
1217
+ }
1218
+ clearError();
1219
+ setOtp("");
1220
+ setWebmail(null);
1221
+ setStep("otp");
1222
+ const rollBack = /* @__PURE__ */ __name((msg) => {
1223
+ setStep("identifier");
1224
+ setError(msg);
1225
+ onError?.(msg);
1226
+ }, "rollBack");
1227
+ try {
1228
+ const consent = marketingConsentOption ? { marketingConsent: marketingConsent === true, disclosureVersion: marketingConsentOption.disclosureVersion } : void 0;
1229
+ const result = await requestOTP(identifier, sourceUrl, consent);
1230
+ if (result.success) {
1231
+ saveIdentifierToStorage(identifier);
1232
+ setWebmail(result.webmail ?? null);
1233
+ startResendCountdown(result.resendIn ?? AUTH_CONSTANTS.RESEND_COOLDOWN_SECONDS);
1234
+ onIdentifierSuccess?.(identifier);
1235
+ } else if (result.retryAfter) {
1236
+ setStep("identifier");
1237
+ startRateLimitCountdown(result.retryAfter);
1238
+ } else {
1239
+ rollBack(result.message);
1151
1240
  }
1152
- if (Array.isArray(value)) {
1153
- value.forEach((v) => serializeFormDataPair(data, key, v));
1241
+ } catch {
1242
+ rollBack("An unexpected error occurred");
1243
+ }
1244
+ }, [
1245
+ identifier,
1246
+ acceptedTerms,
1247
+ requireTermsAcceptance,
1248
+ marketingConsent,
1249
+ marketingConsentOption,
1250
+ validateIdentifier2,
1251
+ requestOTP,
1252
+ saveIdentifierToStorage,
1253
+ setError,
1254
+ setOtp,
1255
+ setStep,
1256
+ clearError,
1257
+ setWebmail,
1258
+ startRateLimitCountdown,
1259
+ startResendCountdown,
1260
+ onIdentifierSuccess,
1261
+ onError,
1262
+ sourceUrl
1263
+ ]);
1264
+ const submitOTP = useCallback4(async (submitIdentifier, submitOtp) => {
1265
+ if (!submitOtp || submitOtp.length < AUTH_CONSTANTS.EMAIL_OTP_LENGTH) {
1266
+ const msg = `Please enter the ${AUTH_CONSTANTS.EMAIL_OTP_LENGTH}-digit verification code`;
1267
+ setError(msg);
1268
+ onError?.(msg);
1269
+ return false;
1270
+ }
1271
+ setIsLoading(true);
1272
+ clearError();
1273
+ try {
1274
+ const result = await verifyOTP(submitIdentifier, submitOtp, sourceUrl, redirectUrl, true, rememberMe);
1275
+ if (result.requires_2fa && result.session_id) {
1276
+ authLogger.info("2FA required after OTP verification");
1277
+ beginTwoFactorChallenge({ sessionId: result.session_id });
1278
+ saveIdentifierToStorage(submitIdentifier);
1279
+ return true;
1280
+ }
1281
+ if (result.success) {
1282
+ saveIdentifierToStorage(submitIdentifier);
1283
+ authLogger.info("OTP verification successful, showing success screen");
1284
+ setStep("success");
1285
+ onOTPSuccess?.();
1286
+ return true;
1154
1287
  } else {
1155
- serializeFormDataPair(data, key, value);
1288
+ setError(result.message);
1289
+ onError?.(result.message);
1290
+ return false;
1156
1291
  }
1157
- });
1158
- return data;
1159
- }, "bodySerializer")
1160
- };
1161
- var jsonBodySerializer = {
1162
- bodySerializer: /* @__PURE__ */ __name((body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value), "bodySerializer")
1163
- };
1164
-
1165
- // src/_api/generated/core/params.gen.ts
1166
- var extraPrefixesMap = {
1167
- $body_: "body",
1168
- $headers_: "headers",
1169
- $path_: "path",
1170
- $query_: "query"
1171
- };
1172
- var extraPrefixes = Object.entries(extraPrefixesMap);
1173
-
1174
- // src/_api/generated/core/serverSentEvents.gen.ts
1175
- function createSseClient({
1176
- onRequest,
1177
- onSseError,
1178
- onSseEvent,
1179
- responseTransformer,
1180
- responseValidator,
1181
- sseDefaultRetryDelay,
1182
- sseMaxRetryAttempts,
1183
- sseMaxRetryDelay,
1184
- sseSleepFn,
1185
- url,
1186
- ...options
1187
- }) {
1188
- let lastEventId;
1189
- const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1190
- const createStream = /* @__PURE__ */ __name(async function* () {
1191
- let retryDelay = sseDefaultRetryDelay ?? 3e3;
1192
- let attempt = 0;
1193
- const signal = options.signal ?? new AbortController().signal;
1194
- while (true) {
1195
- if (signal.aborted) break;
1196
- attempt++;
1197
- const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
1198
- if (lastEventId !== void 0) {
1199
- headers.set("Last-Event-ID", lastEventId);
1292
+ } catch {
1293
+ const msg = "An unexpected error occurred";
1294
+ setError(msg);
1295
+ onError?.(msg);
1296
+ return false;
1297
+ } finally {
1298
+ setIsLoading(false);
1299
+ }
1300
+ }, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, rememberMe, setStep]);
1301
+ const handleOTPSubmit = useCallback4(async (e) => {
1302
+ e.preventDefault();
1303
+ await submitOTP(identifier, otp);
1304
+ }, [identifier, otp, submitOTP]);
1305
+ const submitPassword = useCallback4(async (submitIdentifier, submitPassword_) => {
1306
+ if (!submitIdentifier || !validateIdentifier2(submitIdentifier)) {
1307
+ const msg = "Please enter a valid email address";
1308
+ setError(msg);
1309
+ onError?.(msg);
1310
+ return false;
1311
+ }
1312
+ if (!submitPassword_) {
1313
+ const msg = "Please enter your password";
1314
+ setError(msg);
1315
+ onError?.(msg);
1316
+ return false;
1317
+ }
1318
+ setIsLoading(true);
1319
+ clearError();
1320
+ try {
1321
+ const result = await login(submitIdentifier, submitPassword_, {
1322
+ redirectUrl,
1323
+ skipRedirect: true,
1324
+ rememberMe
1325
+ });
1326
+ if (result.requires_2fa && result.session_id) {
1327
+ authLogger.info("2FA required after password login");
1328
+ beginTwoFactorChallenge({ sessionId: result.session_id });
1329
+ saveIdentifierToStorage(submitIdentifier);
1330
+ return true;
1200
1331
  }
1201
- try {
1202
- const requestInit = {
1203
- redirect: "follow",
1204
- ...options,
1205
- body: options.serializedBody,
1206
- headers,
1207
- signal
1208
- };
1209
- let request = new Request(url, requestInit);
1210
- if (onRequest) {
1211
- request = await onRequest(url, requestInit);
1212
- }
1213
- const _fetch = options.fetch ?? globalThis.fetch;
1332
+ if (result.success) {
1333
+ saveIdentifierToStorage(submitIdentifier);
1334
+ authLogger.info("Password login successful, showing success screen");
1335
+ setStep("success");
1336
+ onOTPSuccess?.();
1337
+ return true;
1338
+ }
1339
+ if (result.retryAfter) {
1340
+ startRateLimitCountdown(result.retryAfter);
1341
+ clearError();
1342
+ } else {
1343
+ setError(result.message);
1344
+ onError?.(result.message);
1345
+ }
1346
+ return false;
1347
+ } catch {
1348
+ const msg = "An unexpected error occurred";
1349
+ setError(msg);
1350
+ onError?.(msg);
1351
+ return false;
1352
+ } finally {
1353
+ setIsLoading(false);
1354
+ }
1355
+ }, [
1356
+ login,
1357
+ validateIdentifier2,
1358
+ saveIdentifierToStorage,
1359
+ setError,
1360
+ setIsLoading,
1361
+ clearError,
1362
+ startRateLimitCountdown,
1363
+ onOTPSuccess,
1364
+ onError,
1365
+ redirectUrl,
1366
+ rememberMe,
1367
+ setStep
1368
+ ]);
1369
+ const handlePasswordSubmit = useCallback4(async (e) => {
1370
+ e.preventDefault();
1371
+ await submitPassword(identifier, password);
1372
+ }, [identifier, password, submitPassword]);
1373
+ const handleResendOTP = useCallback4(async () => {
1374
+ setIsLoading(true);
1375
+ clearError();
1376
+ try {
1377
+ const consent = marketingConsentOption ? { marketingConsent: marketingConsent === true, disclosureVersion: marketingConsentOption.disclosureVersion } : void 0;
1378
+ const result = await requestOTP(identifier, sourceUrl, consent);
1379
+ if (result.success) {
1380
+ saveIdentifierToStorage(identifier);
1381
+ setWebmail(result.webmail ?? null);
1382
+ startResendCountdown(result.resendIn ?? AUTH_CONSTANTS.RESEND_COOLDOWN_SECONDS);
1383
+ setOtp("");
1384
+ } else {
1385
+ if (result.retryAfter) {
1386
+ startRateLimitCountdown(result.retryAfter);
1387
+ clearError();
1388
+ } else {
1389
+ setError(result.message);
1390
+ onError?.(result.message);
1391
+ }
1392
+ }
1393
+ } catch {
1394
+ const msg = "Failed to resend verification code";
1395
+ setError(msg);
1396
+ onError?.(msg);
1397
+ } finally {
1398
+ setIsLoading(false);
1399
+ }
1400
+ }, [identifier, marketingConsent, marketingConsentOption, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, startResendCountdown, setWebmail, onError, sourceUrl]);
1401
+ const handleBackToIdentifier = useCallback4(() => {
1402
+ setStep("identifier");
1403
+ clearError();
1404
+ }, [setStep, clearError]);
1405
+ const forceOTPStep = useCallback4(() => {
1406
+ setStep("otp");
1407
+ clearError();
1408
+ }, [setStep, clearError]);
1409
+ const handleOTPFromUrl = useCallback4((detectedOtp, detectedEmail) => {
1410
+ const autoIdentifier = detectedEmail || getSavedEmail() || "";
1411
+ if (!autoIdentifier) {
1412
+ authLogger.warn("No identifier found for auto-submit (no email in URL or storage)");
1413
+ return;
1414
+ }
1415
+ const otpKey = `${autoIdentifier}:${detectedOtp}`;
1416
+ if (processedOtpKeyRef.current === otpKey) return;
1417
+ if (isAutoSubmitFromUrlRef.current) return;
1418
+ processedOtpKeyRef.current = otpKey;
1419
+ isAutoSubmitFromUrlRef.current = true;
1420
+ authLogger.info("OTP detected from URL, auto-submitting");
1421
+ setIdentifier(autoIdentifier);
1422
+ setOtp(detectedOtp);
1423
+ setStep("otp");
1424
+ setTimeout(async () => {
1425
+ try {
1426
+ await submitOTP(autoIdentifier, detectedOtp);
1427
+ } finally {
1428
+ isAutoSubmitFromUrlRef.current = false;
1429
+ }
1430
+ }, 100);
1431
+ }, [getSavedEmail, setIdentifier, setOtp, setStep, submitOTP]);
1432
+ useAutoAuth({
1433
+ allowedPaths: [authPath],
1434
+ onOTPDetected: handleOTPFromUrl,
1435
+ cleanupUrl: true
1436
+ });
1437
+ return {
1438
+ // State
1439
+ ...formState,
1440
+ // Submit handlers
1441
+ handleIdentifierSubmit,
1442
+ handleOTPSubmit,
1443
+ handlePasswordSubmit,
1444
+ handleResendOTP,
1445
+ handleBackToIdentifier,
1446
+ forceOTPStep,
1447
+ // Validation
1448
+ ...validation,
1449
+ // Auto-submit ref
1450
+ isAutoSubmittingFromUrl: isAutoSubmitFromUrlRef
1451
+ };
1452
+ }, "useAuthForm");
1453
+
1454
+ // src/auth/hooks/useGithubAuth.ts
1455
+ import { useCallback as useCallback5, useState as useState3 } from "react";
1456
+
1457
+ // src/_api/generated/core/bodySerializer.gen.ts
1458
+ var serializeFormDataPair = /* @__PURE__ */ __name((data, key, value) => {
1459
+ if (typeof value === "string" || value instanceof Blob) {
1460
+ data.append(key, value);
1461
+ } else if (value instanceof Date) {
1462
+ data.append(key, value.toISOString());
1463
+ } else {
1464
+ data.append(key, JSON.stringify(value));
1465
+ }
1466
+ }, "serializeFormDataPair");
1467
+ var formDataBodySerializer = {
1468
+ bodySerializer: /* @__PURE__ */ __name((body) => {
1469
+ const data = new FormData();
1470
+ Object.entries(body).forEach(([key, value]) => {
1471
+ if (value === void 0 || value === null) {
1472
+ return;
1473
+ }
1474
+ if (Array.isArray(value)) {
1475
+ value.forEach((v) => serializeFormDataPair(data, key, v));
1476
+ } else {
1477
+ serializeFormDataPair(data, key, value);
1478
+ }
1479
+ });
1480
+ return data;
1481
+ }, "bodySerializer")
1482
+ };
1483
+ var jsonBodySerializer = {
1484
+ bodySerializer: /* @__PURE__ */ __name((body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value), "bodySerializer")
1485
+ };
1486
+
1487
+ // src/_api/generated/core/params.gen.ts
1488
+ var extraPrefixesMap = {
1489
+ $body_: "body",
1490
+ $headers_: "headers",
1491
+ $path_: "path",
1492
+ $query_: "query"
1493
+ };
1494
+ var extraPrefixes = Object.entries(extraPrefixesMap);
1495
+
1496
+ // src/_api/generated/core/serverSentEvents.gen.ts
1497
+ function createSseClient({
1498
+ onRequest,
1499
+ onSseError,
1500
+ onSseEvent,
1501
+ responseTransformer,
1502
+ responseValidator,
1503
+ sseDefaultRetryDelay,
1504
+ sseMaxRetryAttempts,
1505
+ sseMaxRetryDelay,
1506
+ sseSleepFn,
1507
+ url,
1508
+ ...options
1509
+ }) {
1510
+ let lastEventId;
1511
+ const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1512
+ const createStream = /* @__PURE__ */ __name(async function* () {
1513
+ let retryDelay = sseDefaultRetryDelay ?? 3e3;
1514
+ let attempt = 0;
1515
+ const signal = options.signal ?? new AbortController().signal;
1516
+ while (true) {
1517
+ if (signal.aborted) break;
1518
+ attempt++;
1519
+ const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
1520
+ if (lastEventId !== void 0) {
1521
+ headers.set("Last-Event-ID", lastEventId);
1522
+ }
1523
+ try {
1524
+ const requestInit = {
1525
+ redirect: "follow",
1526
+ ...options,
1527
+ body: options.serializedBody,
1528
+ headers,
1529
+ signal
1530
+ };
1531
+ let request = new Request(url, requestInit);
1532
+ if (onRequest) {
1533
+ request = await onRequest(url, requestInit);
1534
+ }
1535
+ const _fetch = options.fetch ?? globalThis.fetch;
1214
1536
  const response = await _fetch(request);
1215
1537
  if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
1216
1538
  if (!response.body) throw new Error("No body in SSE response");
@@ -2558,63 +2880,183 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
2558
2880
  log.error(message, error);
2559
2881
  }, "logAuthFailure");
2560
2882
 
2561
- // src/auth/hooks/useSessionStorage.ts
2562
- import { useState as useState3 } from "react";
2563
-
2564
- // src/auth/utils/storage.ts
2565
- var MAX_ITEM_KB = 1024;
2566
- function isQuotaExceededError(error) {
2567
- if (typeof error !== "object" || error === null) return false;
2568
- const { name, code, message } = error;
2569
- return name === "QuotaExceededError" || code === 22 || typeof message === "string" && message.includes("quota");
2570
- }
2571
- __name(isQuotaExceededError, "isQuotaExceededError");
2572
- function isWithinSizeLimit(data, key) {
2573
- try {
2574
- const sizeInKB = new Blob([JSON.stringify(data)]).size / 1024;
2575
- if (sizeInKB > MAX_ITEM_KB) {
2576
- authLogger.warn(
2577
- `Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`
2578
- );
2579
- return false;
2580
- }
2581
- return true;
2582
- } catch (error) {
2583
- authLogger.error(`Error checking data size for key "${key}":`, error);
2584
- return false;
2585
- }
2586
- }
2587
- __name(isWithinSizeLimit, "isWithinSizeLimit");
2588
-
2589
- // src/auth/hooks/useSessionStorage.ts
2590
- function useSessionStorage(key, initialValue) {
2591
- const [storedValue, setStoredValue] = useState3(() => {
2592
- if (typeof window === "undefined") {
2593
- return initialValue;
2594
- }
2595
- try {
2596
- const item = window.sessionStorage.getItem(key);
2597
- return item ? JSON.parse(item) : initialValue;
2598
- } catch (error) {
2599
- authLogger.error(`Error reading sessionStorage key "${key}":`, error);
2600
- return initialValue;
2601
- }
2602
- });
2603
- const clearOldData = /* @__PURE__ */ __name(() => {
2883
+ // src/auth/hooks/useGithubAuth.ts
2884
+ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
2885
+ const { sourceUrl, rememberMe = false, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
2886
+ const router = useCfgRouter();
2887
+ const [isLoading, setIsLoading] = useState3(false);
2888
+ const [error, setError] = useState3(null);
2889
+ const startGithubAuth = useCallback5(async () => {
2890
+ setIsLoading(true);
2891
+ setError(null);
2892
+ auth.clearSession();
2604
2893
  try {
2605
- const keys = Object.keys(sessionStorage).filter((key2) => key2 && typeof key2 === "string");
2606
- if (keys.length > 50) {
2607
- const itemsToRemove = Math.ceil(keys.length * 0.2);
2608
- for (let i = 0; i < itemsToRemove; i++) {
2609
- try {
2610
- const key2 = keys[i];
2611
- if (key2) {
2612
- sessionStorage.removeItem(key2);
2613
- sessionStorage.removeItem(`${key2}_timestamp`);
2614
- }
2615
- } catch {
2616
- }
2617
- }
2894
+ authLogger.info("Starting GitHub OAuth flow...");
2895
+ Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
2896
+ category: "auth" /* AUTH */,
2897
+ label: "github"
2898
+ });
2899
+ const result = await CfgAccountsOauth.cfgAccountsOauthGithubAuthorizeCreate({
2900
+ body: {
2901
+ source_url: sourceUrl || (typeof window !== "undefined" ? window.location.href : "")
2902
+ },
2903
+ throwOnError: true
2904
+ });
2905
+ const response = result.data;
2906
+ if (!response.authorization_url) {
2907
+ throw new Error("Failed to get authorization URL");
2908
+ }
2909
+ authLogger.info("Redirecting to GitHub...", response.authorization_url);
2910
+ if (typeof window !== "undefined") {
2911
+ sessionStorage.setItem("oauth_state", response.state);
2912
+ sessionStorage.setItem("oauth_provider", "github");
2913
+ sessionStorage.setItem("cfg.oauth.remember_me", String(rememberMe));
2914
+ }
2915
+ window.location.href = response.authorization_url;
2916
+ } catch (err) {
2917
+ const errorMessage = formatAuthError(err, "Failed to start GitHub authentication");
2918
+ authLogger.error("GitHub OAuth start error:", err);
2919
+ setError(errorMessage);
2920
+ onError?.(errorMessage);
2921
+ Analytics.event("auth_oauth_fail" /* AUTH_OAUTH_FAIL */, {
2922
+ category: "auth" /* AUTH */,
2923
+ label: "github"
2924
+ });
2925
+ } finally {
2926
+ setIsLoading(false);
2927
+ }
2928
+ }, [sourceUrl, rememberMe, onError]);
2929
+ const handleGithubCallback = useCallback5(async (code, state) => {
2930
+ setIsLoading(true);
2931
+ setError(null);
2932
+ try {
2933
+ authLogger.info("Processing GitHub OAuth callback...");
2934
+ let rememberMeForSession = false;
2935
+ if (typeof window !== "undefined") {
2936
+ const storedState = sessionStorage.getItem("oauth_state");
2937
+ if (storedState && storedState !== state) {
2938
+ throw new Error("Invalid OAuth state - possible CSRF attack");
2939
+ }
2940
+ rememberMeForSession = sessionStorage.getItem("cfg.oauth.remember_me") === "true";
2941
+ sessionStorage.removeItem("oauth_state");
2942
+ sessionStorage.removeItem("oauth_provider");
2943
+ sessionStorage.removeItem("cfg.oauth.remember_me");
2944
+ }
2945
+ const result = await CfgAccountsOauth.cfgAccountsOauthGithubCallbackCreate({
2946
+ body: { code, state, remember_me: rememberMeForSession },
2947
+ throwOnError: true
2948
+ });
2949
+ const response = result.data;
2950
+ if (response.requires_2fa && response.session_id) {
2951
+ authLogger.info("GitHub OAuth requires 2FA, session:", response.session_id);
2952
+ Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
2953
+ category: "auth" /* AUTH */,
2954
+ label: "github-2fa-required"
2955
+ });
2956
+ onRequires2FA?.(response.session_id, response.should_prompt_2fa || false);
2957
+ return;
2958
+ }
2959
+ if (!response.access || !response.refresh) {
2960
+ throw new Error("Invalid response from OAuth callback");
2961
+ }
2962
+ authLogger.info("GitHub OAuth successful, user:", response.user);
2963
+ auth.setSession({ access: response.access, refresh: response.refresh });
2964
+ Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
2965
+ category: "auth" /* AUTH */,
2966
+ label: "github"
2967
+ });
2968
+ if (response.user?.id) {
2969
+ Analytics.setUser(String(response.user.id));
2970
+ }
2971
+ if (response.user) {
2972
+ onSuccess?.(response.user, response.is_new_user || false);
2973
+ }
2974
+ if (!skipRedirect) {
2975
+ const finalRedirectUrl = redirectUrl || "/private";
2976
+ router.hardPush(finalRedirectUrl);
2977
+ }
2978
+ return;
2979
+ } catch (err) {
2980
+ const errorMessage = formatAuthError(err, "GitHub authentication failed");
2981
+ authLogger.error("GitHub OAuth callback error:", err);
2982
+ setError(errorMessage);
2983
+ onError?.(errorMessage);
2984
+ Analytics.event("auth_oauth_fail" /* AUTH_OAUTH_FAIL */, {
2985
+ category: "auth" /* AUTH */,
2986
+ label: "github"
2987
+ });
2988
+ } finally {
2989
+ setIsLoading(false);
2990
+ }
2991
+ }, [onSuccess, onError, onRequires2FA, redirectUrl, router, skipRedirect]);
2992
+ return {
2993
+ isLoading,
2994
+ error,
2995
+ startGithubAuth,
2996
+ handleGithubCallback
2997
+ };
2998
+ }, "useGithubAuth");
2999
+
3000
+ // src/auth/hooks/useTwoFactorVerify.ts
3001
+ import { useCallback as useCallback6, useState as useState5 } from "react";
3002
+
3003
+ // src/auth/hooks/useSessionStorage.ts
3004
+ import { useState as useState4 } from "react";
3005
+
3006
+ // src/auth/utils/storage.ts
3007
+ var MAX_ITEM_KB = 1024;
3008
+ function isQuotaExceededError(error) {
3009
+ if (typeof error !== "object" || error === null) return false;
3010
+ const { name, code, message } = error;
3011
+ return name === "QuotaExceededError" || code === 22 || typeof message === "string" && message.includes("quota");
3012
+ }
3013
+ __name(isQuotaExceededError, "isQuotaExceededError");
3014
+ function isWithinSizeLimit(data, key) {
3015
+ try {
3016
+ const sizeInKB = new Blob([JSON.stringify(data)]).size / 1024;
3017
+ if (sizeInKB > MAX_ITEM_KB) {
3018
+ authLogger.warn(
3019
+ `Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`
3020
+ );
3021
+ return false;
3022
+ }
3023
+ return true;
3024
+ } catch (error) {
3025
+ authLogger.error(`Error checking data size for key "${key}":`, error);
3026
+ return false;
3027
+ }
3028
+ }
3029
+ __name(isWithinSizeLimit, "isWithinSizeLimit");
3030
+
3031
+ // src/auth/hooks/useSessionStorage.ts
3032
+ function useSessionStorage(key, initialValue) {
3033
+ const [storedValue, setStoredValue] = useState4(() => {
3034
+ if (typeof window === "undefined") {
3035
+ return initialValue;
3036
+ }
3037
+ try {
3038
+ const item = window.sessionStorage.getItem(key);
3039
+ return item ? JSON.parse(item) : initialValue;
3040
+ } catch (error) {
3041
+ authLogger.error(`Error reading sessionStorage key "${key}":`, error);
3042
+ return initialValue;
3043
+ }
3044
+ });
3045
+ const clearOldData = /* @__PURE__ */ __name(() => {
3046
+ try {
3047
+ const keys = Object.keys(sessionStorage).filter((key2) => key2 && typeof key2 === "string");
3048
+ if (keys.length > 50) {
3049
+ const itemsToRemove = Math.ceil(keys.length * 0.2);
3050
+ for (let i = 0; i < itemsToRemove; i++) {
3051
+ try {
3052
+ const key2 = keys[i];
3053
+ if (key2) {
3054
+ sessionStorage.removeItem(key2);
3055
+ sessionStorage.removeItem(`${key2}_timestamp`);
3056
+ }
3057
+ } catch {
3058
+ }
3059
+ }
2618
3060
  }
2619
3061
  } catch (error) {
2620
3062
  authLogger.error("Error clearing old sessionStorage data:", error);
@@ -2795,15 +3237,15 @@ __name(isValidTotpCode, "isValidTotpCode");
2795
3237
  var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
2796
3238
  const { onSuccess, onError, redirectUrl, skipRedirect = false } = options;
2797
3239
  const router = useCfgRouter();
2798
- const [isLoading, setIsLoading] = useState4(false);
2799
- const [error, setError] = useState4(null);
2800
- const [warning, setWarning] = useState4(null);
2801
- const [remainingBackupCodes, setRemainingBackupCodes] = useState4(null);
2802
- const [attemptsRemaining, setAttemptsRemaining] = useState4(null);
2803
- const clearError = useCallback4(() => {
3240
+ const [isLoading, setIsLoading] = useState5(false);
3241
+ const [error, setError] = useState5(null);
3242
+ const [warning, setWarning] = useState5(null);
3243
+ const [remainingBackupCodes, setRemainingBackupCodes] = useState5(null);
3244
+ const [attemptsRemaining, setAttemptsRemaining] = useState5(null);
3245
+ const clearError = useCallback6(() => {
2804
3246
  setError(null);
2805
3247
  }, []);
2806
- const handleSuccess = useCallback4((response) => {
3248
+ const handleSuccess = useCallback6((response) => {
2807
3249
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
2808
3250
  if (response.warning) {
2809
3251
  setWarning(response.warning);
@@ -2825,7 +3267,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
2825
3267
  router.hardPush(finalRedirectUrl);
2826
3268
  }
2827
3269
  }, [onSuccess, redirectUrl, router, skipRedirect]);
2828
- const verifyTOTP = useCallback4(async (sessionId, code) => {
3270
+ const verifyTOTP = useCallback6(async (sessionId, code) => {
2829
3271
  if (!sessionId) {
2830
3272
  const msg = "Missing 2FA session ID";
2831
3273
  setError(msg);
@@ -2868,7 +3310,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
2868
3310
  setIsLoading(false);
2869
3311
  }
2870
3312
  }, [handleSuccess, onError]);
2871
- const verifyBackupCode = useCallback4(async (sessionId, backupCode) => {
3313
+ const verifyBackupCode = useCallback6(async (sessionId, backupCode) => {
2872
3314
  if (!sessionId) {
2873
3315
  const msg = "Missing 2FA session ID";
2874
3316
  setError(msg);
@@ -2926,486 +3368,6 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
2926
3368
  };
2927
3369
  }, "useTwoFactorVerify");
2928
3370
 
2929
- // src/auth/hooks/useAuthForm.ts
2930
- var useAuthForm = /* @__PURE__ */ __name((options) => {
2931
- const {
2932
- onIdentifierSuccess,
2933
- onOTPSuccess,
2934
- onError,
2935
- sourceUrl,
2936
- redirectUrl,
2937
- requireTermsAcceptance = false,
2938
- marketingConsent: marketingConsentOption,
2939
- authPath = "/auth"
2940
- } = options;
2941
- const formState = useAuthFormState();
2942
- const validation = useAuthValidation();
2943
- const isAutoSubmitFromUrlRef = useRef4(false);
2944
- const processedOtpKeyRef = useRef4(null);
2945
- const {
2946
- requestOTP,
2947
- verifyOTP,
2948
- login,
2949
- getSavedEmail,
2950
- saveEmail
2951
- } = useAuth();
2952
- const {
2953
- identifier,
2954
- otp,
2955
- password,
2956
- acceptedTerms,
2957
- marketingConsent,
2958
- rememberMe,
2959
- twoFactorSessionId,
2960
- twoFactorCode,
2961
- useBackupCode,
2962
- setIdentifier,
2963
- setOtp,
2964
- setStep,
2965
- setError,
2966
- setIsLoading,
2967
- clearError,
2968
- setTwoFactorSessionId,
2969
- setShouldPrompt2FA,
2970
- setTwoFactorCode,
2971
- setUseBackupCode,
2972
- startRateLimitCountdown,
2973
- setWebmail
2974
- } = formState;
2975
- const twoFactor = useTwoFactorVerify({
2976
- onSuccess: /* @__PURE__ */ __name(() => {
2977
- authLogger.info("2FA verification successful, showing success screen");
2978
- setStep("success");
2979
- onOTPSuccess?.();
2980
- }, "onSuccess"),
2981
- onError: /* @__PURE__ */ __name((error) => {
2982
- setError(error);
2983
- onError?.(error);
2984
- }, "onError"),
2985
- redirectUrl,
2986
- skipRedirect: true
2987
- // We handle navigation via success step
2988
- });
2989
- const { validateIdentifier: validateIdentifier2 } = validation;
2990
- const saveIdentifierToStorage = useCallback5((id) => {
2991
- saveEmail(id);
2992
- }, [saveEmail]);
2993
- useEffect4(() => {
2994
- const savedEmail = getSavedEmail();
2995
- if (savedEmail) {
2996
- setIdentifier(savedEmail);
2997
- }
2998
- }, []);
2999
- const handleIdentifierSubmit = useCallback5(async (e) => {
3000
- e.preventDefault();
3001
- if (!identifier) {
3002
- const msg = "Please enter your email address";
3003
- setError(msg);
3004
- onError?.(msg);
3005
- return;
3006
- }
3007
- if (!validateIdentifier2(identifier)) {
3008
- const msg = "Please enter a valid email address";
3009
- setError(msg);
3010
- onError?.(msg);
3011
- return;
3012
- }
3013
- if (requireTermsAcceptance && !acceptedTerms) {
3014
- const msg = "Please accept the Terms of Service and Privacy Policy";
3015
- setError(msg);
3016
- onError?.(msg);
3017
- return;
3018
- }
3019
- setIsLoading(true);
3020
- clearError();
3021
- try {
3022
- const consent = marketingConsentOption ? { marketingConsent: marketingConsent === true, disclosureVersion: marketingConsentOption.disclosureVersion } : void 0;
3023
- const result = await requestOTP(identifier, sourceUrl, consent);
3024
- if (result.success) {
3025
- saveIdentifierToStorage(identifier);
3026
- setWebmail(result.webmail ?? null);
3027
- setStep("otp");
3028
- onIdentifierSuccess?.(identifier);
3029
- } else {
3030
- if (result.retryAfter) {
3031
- startRateLimitCountdown(result.retryAfter);
3032
- clearError();
3033
- } else {
3034
- setError(result.message);
3035
- onError?.(result.message);
3036
- }
3037
- }
3038
- } catch {
3039
- const msg = "An unexpected error occurred";
3040
- setError(msg);
3041
- onError?.(msg);
3042
- } finally {
3043
- setIsLoading(false);
3044
- }
3045
- }, [
3046
- identifier,
3047
- acceptedTerms,
3048
- requireTermsAcceptance,
3049
- marketingConsent,
3050
- marketingConsentOption,
3051
- validateIdentifier2,
3052
- requestOTP,
3053
- saveIdentifierToStorage,
3054
- setError,
3055
- setIsLoading,
3056
- setStep,
3057
- clearError,
3058
- setWebmail,
3059
- startRateLimitCountdown,
3060
- onIdentifierSuccess,
3061
- onError,
3062
- sourceUrl
3063
- ]);
3064
- const submitOTP = useCallback5(async (submitIdentifier, submitOtp) => {
3065
- if (!submitOtp || submitOtp.length < AUTH_CONSTANTS.EMAIL_OTP_LENGTH) {
3066
- const msg = `Please enter the ${AUTH_CONSTANTS.EMAIL_OTP_LENGTH}-digit verification code`;
3067
- setError(msg);
3068
- onError?.(msg);
3069
- return false;
3070
- }
3071
- setIsLoading(true);
3072
- clearError();
3073
- try {
3074
- const result = await verifyOTP(submitIdentifier, submitOtp, sourceUrl, redirectUrl, true, rememberMe);
3075
- if (result.requires_2fa && result.session_id) {
3076
- authLogger.info("2FA required after OTP verification");
3077
- setTwoFactorSessionId(result.session_id);
3078
- setShouldPrompt2FA(result.should_prompt_2fa || false);
3079
- setStep("2fa");
3080
- saveIdentifierToStorage(submitIdentifier);
3081
- return true;
3082
- }
3083
- if (result.success) {
3084
- saveIdentifierToStorage(submitIdentifier);
3085
- authLogger.info("OTP verification successful, showing success screen");
3086
- setStep("success");
3087
- onOTPSuccess?.();
3088
- return true;
3089
- } else {
3090
- setError(result.message);
3091
- onError?.(result.message);
3092
- return false;
3093
- }
3094
- } catch {
3095
- const msg = "An unexpected error occurred";
3096
- setError(msg);
3097
- onError?.(msg);
3098
- return false;
3099
- } finally {
3100
- setIsLoading(false);
3101
- }
3102
- }, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, rememberMe, setTwoFactorSessionId, setShouldPrompt2FA, setStep]);
3103
- const handleOTPSubmit = useCallback5(async (e) => {
3104
- e.preventDefault();
3105
- await submitOTP(identifier, otp);
3106
- }, [identifier, otp, submitOTP]);
3107
- const submitPassword = useCallback5(async (submitIdentifier, submitPassword_) => {
3108
- if (!submitIdentifier || !validateIdentifier2(submitIdentifier)) {
3109
- const msg = "Please enter a valid email address";
3110
- setError(msg);
3111
- onError?.(msg);
3112
- return false;
3113
- }
3114
- if (!submitPassword_) {
3115
- const msg = "Please enter your password";
3116
- setError(msg);
3117
- onError?.(msg);
3118
- return false;
3119
- }
3120
- setIsLoading(true);
3121
- clearError();
3122
- try {
3123
- const result = await login(submitIdentifier, submitPassword_, {
3124
- redirectUrl,
3125
- skipRedirect: true,
3126
- rememberMe
3127
- });
3128
- if (result.requires_2fa && result.session_id) {
3129
- authLogger.info("2FA required after password login");
3130
- setTwoFactorSessionId(result.session_id);
3131
- setShouldPrompt2FA(result.should_prompt_2fa || false);
3132
- setStep("2fa");
3133
- saveIdentifierToStorage(submitIdentifier);
3134
- return true;
3135
- }
3136
- if (result.success) {
3137
- saveIdentifierToStorage(submitIdentifier);
3138
- authLogger.info("Password login successful, showing success screen");
3139
- setStep("success");
3140
- onOTPSuccess?.();
3141
- return true;
3142
- }
3143
- if (result.retryAfter) {
3144
- startRateLimitCountdown(result.retryAfter);
3145
- clearError();
3146
- } else {
3147
- setError(result.message);
3148
- onError?.(result.message);
3149
- }
3150
- return false;
3151
- } catch {
3152
- const msg = "An unexpected error occurred";
3153
- setError(msg);
3154
- onError?.(msg);
3155
- return false;
3156
- } finally {
3157
- setIsLoading(false);
3158
- }
3159
- }, [
3160
- login,
3161
- validateIdentifier2,
3162
- saveIdentifierToStorage,
3163
- setError,
3164
- setIsLoading,
3165
- clearError,
3166
- startRateLimitCountdown,
3167
- onOTPSuccess,
3168
- onError,
3169
- redirectUrl,
3170
- rememberMe,
3171
- setTwoFactorSessionId,
3172
- setShouldPrompt2FA,
3173
- setStep
3174
- ]);
3175
- const handlePasswordSubmit = useCallback5(async (e) => {
3176
- e.preventDefault();
3177
- await submitPassword(identifier, password);
3178
- }, [identifier, password, submitPassword]);
3179
- const handleResendOTP = useCallback5(async () => {
3180
- setIsLoading(true);
3181
- clearError();
3182
- try {
3183
- const consent = marketingConsentOption ? { marketingConsent: marketingConsent === true, disclosureVersion: marketingConsentOption.disclosureVersion } : void 0;
3184
- const result = await requestOTP(identifier, sourceUrl, consent);
3185
- if (result.success) {
3186
- saveIdentifierToStorage(identifier);
3187
- setWebmail(result.webmail ?? null);
3188
- setOtp("");
3189
- } else {
3190
- if (result.retryAfter) {
3191
- startRateLimitCountdown(result.retryAfter);
3192
- clearError();
3193
- } else {
3194
- setError(result.message);
3195
- onError?.(result.message);
3196
- }
3197
- }
3198
- } catch {
3199
- const msg = "Failed to resend verification code";
3200
- setError(msg);
3201
- onError?.(msg);
3202
- } finally {
3203
- setIsLoading(false);
3204
- }
3205
- }, [identifier, marketingConsent, marketingConsentOption, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, setWebmail, onError, sourceUrl]);
3206
- const handleBackToIdentifier = useCallback5(() => {
3207
- setStep("identifier");
3208
- clearError();
3209
- }, [setStep, clearError]);
3210
- const forceOTPStep = useCallback5(() => {
3211
- setStep("otp");
3212
- clearError();
3213
- }, [setStep, clearError]);
3214
- const handle2FASubmit = useCallback5(async (e) => {
3215
- e.preventDefault();
3216
- if (!twoFactorSessionId) {
3217
- const msg = "Missing 2FA session";
3218
- setError(msg);
3219
- onError?.(msg);
3220
- return;
3221
- }
3222
- if (useBackupCode) {
3223
- await twoFactor.verifyBackupCode(twoFactorSessionId, twoFactorCode);
3224
- } else {
3225
- await twoFactor.verifyTOTP(twoFactorSessionId, twoFactorCode);
3226
- }
3227
- }, [twoFactorSessionId, twoFactorCode, useBackupCode, twoFactor, setError, onError]);
3228
- const handleUseBackupCode = useCallback5(() => {
3229
- setUseBackupCode(true);
3230
- setTwoFactorCode("");
3231
- clearError();
3232
- }, [setUseBackupCode, setTwoFactorCode, clearError]);
3233
- const handleUseTOTP = useCallback5(() => {
3234
- setUseBackupCode(false);
3235
- setTwoFactorCode("");
3236
- clearError();
3237
- }, [setUseBackupCode, setTwoFactorCode, clearError]);
3238
- const handleOTPFromUrl = useCallback5((detectedOtp, detectedEmail) => {
3239
- const autoIdentifier = detectedEmail || getSavedEmail() || "";
3240
- if (!autoIdentifier) {
3241
- authLogger.warn("No identifier found for auto-submit (no email in URL or storage)");
3242
- return;
3243
- }
3244
- const otpKey = `${autoIdentifier}:${detectedOtp}`;
3245
- if (processedOtpKeyRef.current === otpKey) return;
3246
- if (isAutoSubmitFromUrlRef.current) return;
3247
- processedOtpKeyRef.current = otpKey;
3248
- isAutoSubmitFromUrlRef.current = true;
3249
- authLogger.info("OTP detected from URL, auto-submitting");
3250
- setIdentifier(autoIdentifier);
3251
- setOtp(detectedOtp);
3252
- setStep("otp");
3253
- setTimeout(async () => {
3254
- try {
3255
- await submitOTP(autoIdentifier, detectedOtp);
3256
- } finally {
3257
- isAutoSubmitFromUrlRef.current = false;
3258
- }
3259
- }, 100);
3260
- }, [getSavedEmail, setIdentifier, setOtp, setStep, submitOTP]);
3261
- useAutoAuth({
3262
- allowedPaths: [authPath],
3263
- onOTPDetected: handleOTPFromUrl,
3264
- cleanupUrl: true
3265
- });
3266
- return {
3267
- // State
3268
- ...formState,
3269
- // Submit handlers
3270
- handleIdentifierSubmit,
3271
- handleOTPSubmit,
3272
- handlePasswordSubmit,
3273
- handleResendOTP,
3274
- handleBackToIdentifier,
3275
- forceOTPStep,
3276
- // 2FA handlers
3277
- handle2FASubmit,
3278
- handleUseBackupCode,
3279
- handleUseTOTP,
3280
- // Validation
3281
- ...validation,
3282
- // Auto-submit ref
3283
- isAutoSubmittingFromUrl: isAutoSubmitFromUrlRef,
3284
- // 2FA state from hook (for loading indicator)
3285
- is2FALoading: twoFactor.isLoading,
3286
- twoFactorWarning: twoFactor.warning,
3287
- twoFactorAttemptsRemaining: twoFactor.attemptsRemaining
3288
- };
3289
- }, "useAuthForm");
3290
-
3291
- // src/auth/hooks/useGithubAuth.ts
3292
- import { useCallback as useCallback6, useState as useState5 } from "react";
3293
- var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
3294
- const { sourceUrl, rememberMe = false, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
3295
- const router = useCfgRouter();
3296
- const [isLoading, setIsLoading] = useState5(false);
3297
- const [error, setError] = useState5(null);
3298
- const startGithubAuth = useCallback6(async () => {
3299
- setIsLoading(true);
3300
- setError(null);
3301
- auth.clearSession();
3302
- try {
3303
- authLogger.info("Starting GitHub OAuth flow...");
3304
- Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
3305
- category: "auth" /* AUTH */,
3306
- label: "github"
3307
- });
3308
- const result = await CfgAccountsOauth.cfgAccountsOauthGithubAuthorizeCreate({
3309
- body: {
3310
- source_url: sourceUrl || (typeof window !== "undefined" ? window.location.href : "")
3311
- },
3312
- throwOnError: true
3313
- });
3314
- const response = result.data;
3315
- if (!response.authorization_url) {
3316
- throw new Error("Failed to get authorization URL");
3317
- }
3318
- authLogger.info("Redirecting to GitHub...", response.authorization_url);
3319
- if (typeof window !== "undefined") {
3320
- sessionStorage.setItem("oauth_state", response.state);
3321
- sessionStorage.setItem("oauth_provider", "github");
3322
- sessionStorage.setItem("cfg.oauth.remember_me", String(rememberMe));
3323
- }
3324
- window.location.href = response.authorization_url;
3325
- } catch (err) {
3326
- const errorMessage = formatAuthError(err, "Failed to start GitHub authentication");
3327
- authLogger.error("GitHub OAuth start error:", err);
3328
- setError(errorMessage);
3329
- onError?.(errorMessage);
3330
- Analytics.event("auth_oauth_fail" /* AUTH_OAUTH_FAIL */, {
3331
- category: "auth" /* AUTH */,
3332
- label: "github"
3333
- });
3334
- } finally {
3335
- setIsLoading(false);
3336
- }
3337
- }, [sourceUrl, rememberMe, onError]);
3338
- const handleGithubCallback = useCallback6(async (code, state) => {
3339
- setIsLoading(true);
3340
- setError(null);
3341
- try {
3342
- authLogger.info("Processing GitHub OAuth callback...");
3343
- let rememberMeForSession = false;
3344
- if (typeof window !== "undefined") {
3345
- const storedState = sessionStorage.getItem("oauth_state");
3346
- if (storedState && storedState !== state) {
3347
- throw new Error("Invalid OAuth state - possible CSRF attack");
3348
- }
3349
- rememberMeForSession = sessionStorage.getItem("cfg.oauth.remember_me") === "true";
3350
- sessionStorage.removeItem("oauth_state");
3351
- sessionStorage.removeItem("oauth_provider");
3352
- sessionStorage.removeItem("cfg.oauth.remember_me");
3353
- }
3354
- const result = await CfgAccountsOauth.cfgAccountsOauthGithubCallbackCreate({
3355
- body: { code, state, remember_me: rememberMeForSession },
3356
- throwOnError: true
3357
- });
3358
- const response = result.data;
3359
- if (response.requires_2fa && response.session_id) {
3360
- authLogger.info("GitHub OAuth requires 2FA, session:", response.session_id);
3361
- Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
3362
- category: "auth" /* AUTH */,
3363
- label: "github-2fa-required"
3364
- });
3365
- onRequires2FA?.(response.session_id, response.should_prompt_2fa || false);
3366
- return;
3367
- }
3368
- if (!response.access || !response.refresh) {
3369
- throw new Error("Invalid response from OAuth callback");
3370
- }
3371
- authLogger.info("GitHub OAuth successful, user:", response.user);
3372
- auth.setSession({ access: response.access, refresh: response.refresh });
3373
- Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
3374
- category: "auth" /* AUTH */,
3375
- label: "github"
3376
- });
3377
- if (response.user?.id) {
3378
- Analytics.setUser(String(response.user.id));
3379
- }
3380
- if (response.user) {
3381
- onSuccess?.(response.user, response.is_new_user || false);
3382
- }
3383
- if (!skipRedirect) {
3384
- const finalRedirectUrl = redirectUrl || "/private";
3385
- router.hardPush(finalRedirectUrl);
3386
- }
3387
- return;
3388
- } catch (err) {
3389
- const errorMessage = formatAuthError(err, "GitHub authentication failed");
3390
- authLogger.error("GitHub OAuth callback error:", err);
3391
- setError(errorMessage);
3392
- onError?.(errorMessage);
3393
- Analytics.event("auth_oauth_fail" /* AUTH_OAUTH_FAIL */, {
3394
- category: "auth" /* AUTH */,
3395
- label: "github"
3396
- });
3397
- } finally {
3398
- setIsLoading(false);
3399
- }
3400
- }, [onSuccess, onError, onRequires2FA, redirectUrl, router, skipRedirect]);
3401
- return {
3402
- isLoading,
3403
- error,
3404
- startGithubAuth,
3405
- handleGithubCallback
3406
- };
3407
- }, "useGithubAuth");
3408
-
3409
3371
  // src/auth/hooks/useTwoFactorSetup.ts
3410
3372
  import { useCallback as useCallback7, useState as useState6 } from "react";
3411
3373
  var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
@@ -3960,7 +3922,7 @@ function useCfgAccountsOtpRequestCreate(config) {
3960
3922
  async (_key, { arg }) => {
3961
3923
  const res = await CfgAccountsOtp.cfgAccountsOtpRequestCreate({ ...arg, throwOnError: true });
3962
3924
  const data = res.data;
3963
- void import("./OTPRequestResponse-ZU5KRNSW.mjs").then(({ OTPRequestResponseSchema }) => {
3925
+ void import("./OTPRequestResponse-TZSEVCID.mjs").then(({ OTPRequestResponseSchema }) => {
3964
3926
  const parsed = OTPRequestResponseSchema.safeParse(data);
3965
3927
  if (parsed.success) return;
3966
3928
  console.warn(
@@ -4564,7 +4526,8 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4564
4526
  return {
4565
4527
  success: true,
4566
4528
  message: result.message || "OTP code sent to your email address",
4567
- webmail: result.webmail ?? null
4529
+ webmail: result.webmail ?? null,
4530
+ resendIn: result.resend_in
4568
4531
  };
4569
4532
  } catch (error) {
4570
4533
  logAuthFailure(authLogger, "Request OTP failed:", error);
@@ -4907,10 +4870,12 @@ export {
4907
4870
  AuthContext_default as AuthContext,
4908
4871
  AuthProvider,
4909
4872
  authLogger,
4873
+ beginTwoFactorChallenge,
4910
4874
  clearProfileCache,
4911
4875
  consumeSavedRedirect,
4912
4876
  decodeBase64,
4913
4877
  encodeBase64,
4878
+ endTwoFactorChallenge,
4914
4879
  formatAuthError,
4915
4880
  getCacheMetadata,
4916
4881
  getCachedProfile,
@@ -4943,6 +4908,7 @@ export {
4943
4908
  useSession,
4944
4909
  useSessionStorage,
4945
4910
  useTwoFactor,
4911
+ useTwoFactorChallenge,
4946
4912
  useTwoFactorSetup,
4947
4913
  useTwoFactorStatus,
4948
4914
  useTwoFactorVerify,