@djangocfg/api 2.1.580 → 2.1.582
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ChallengePolicy-DDX7PIIY.mjs +20 -0
- package/dist/ChallengePolicy-DDX7PIIY.mjs.map +1 -0
- package/dist/OTPRequestResponse-TZSEVCID.mjs +32 -0
- package/dist/OTPRequestResponse-TZSEVCID.mjs.map +1 -0
- package/dist/auth.cjs +789 -821
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +32 -29
- package/dist/auth.d.ts +32 -29
- package/dist/auth.mjs +628 -662
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.d.cts +8 -0
- package/dist/clients.d.ts +8 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/package.json +2 -2
- package/src/_api/generated/_cfg_accounts/openapi.json +10 -0
- package/src/_api/generated/_cfg_accounts/schemas/OTPRequestResponse.ts +2 -0
- package/src/_api/generated/openapi.json +10 -0
- package/src/_api/generated/types.gen.ts +8 -0
- package/src/auth/__tests__/twoFactorChallenge.test.tsx +37 -0
- package/src/auth/constants.ts +2 -0
- package/src/auth/context/AuthContext.tsx +1 -0
- package/src/auth/hooks/index.ts +8 -0
- package/src/auth/hooks/useAuthForm.ts +28 -97
- package/src/auth/hooks/useAuthFormState.ts +37 -41
- package/src/auth/hooks/useTwoFactorChallenge.ts +35 -0
- package/src/auth/types/form.ts +10 -34
- package/src/auth/__tests__/useAuthForm.2fa.test.ts +0 -106
package/dist/auth.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(
|
|
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
|
|
993
|
-
const
|
|
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
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
1047
|
-
|
|
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
|
|
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/
|
|
1133
|
-
import {
|
|
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/
|
|
1136
|
-
var
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
-
|
|
1153
|
-
|
|
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
|
-
|
|
1288
|
+
setError(result.message);
|
|
1289
|
+
onError?.(result.message);
|
|
1290
|
+
return false;
|
|
1156
1291
|
}
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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/
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
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
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
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] =
|
|
2799
|
-
const [error, setError] =
|
|
2800
|
-
const [warning, setWarning] =
|
|
2801
|
-
const [remainingBackupCodes, setRemainingBackupCodes] =
|
|
2802
|
-
const [attemptsRemaining, setAttemptsRemaining] =
|
|
2803
|
-
const clearError =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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-
|
|
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,
|