@djangocfg/api 2.1.57 → 2.1.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -9
- package/dist/auth.cjs +1865 -402
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +352 -76
- package/dist/auth.d.ts +352 -76
- package/dist/auth.mjs +1867 -404
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.cjs +1637 -137
- package/dist/clients.cjs.map +1 -1
- package/dist/clients.d.cts +1394 -282
- package/dist/clients.d.ts +1394 -282
- package/dist/clients.mjs +1637 -137
- package/dist/clients.mjs.map +1 -1
- package/dist/hooks.cjs +24 -11
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +88 -21
- package/dist/hooks.d.ts +88 -21
- package/dist/hooks.mjs +24 -11
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.cjs +38 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -21
- package/dist/index.d.ts +94 -21
- package/dist/index.mjs +38 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/auth/context/AccountsContext.tsx +8 -1
- package/src/auth/context/AuthContext.tsx +31 -8
- package/src/auth/context/types.ts +8 -1
- package/src/auth/hooks/index.ts +29 -5
- package/src/auth/hooks/useAuthForm.ts +292 -226
- package/src/auth/hooks/useAuthFormState.ts +60 -0
- package/src/auth/hooks/useAuthValidation.ts +77 -0
- package/src/auth/hooks/useGithubAuth.ts +26 -5
- package/src/auth/hooks/useTwoFactor.ts +239 -0
- package/src/auth/hooks/useTwoFactorSetup.ts +213 -0
- package/src/auth/index.ts +3 -0
- package/src/auth/types/form.ts +194 -0
- package/src/auth/types/index.ts +28 -0
- package/src/clients.ts +10 -0
- package/src/generated/cfg_accounts/_utils/schemas/OAuthTokenResponse.schema.ts +26 -3
- package/src/generated/cfg_accounts/_utils/schemas/OTPVerifyResponse.schema.ts +26 -3
- package/src/generated/cfg_accounts/accounts/client.ts +4 -1
- package/src/generated/cfg_accounts/accounts/models.ts +15 -6
- package/src/generated/cfg_accounts/accounts__oauth/models.ts +16 -7
- package/src/generated/cfg_accounts/client.ts +5 -2
- package/src/generated/cfg_accounts/http.ts +8 -2
- package/src/generated/cfg_accounts/schema.json +47 -19
- package/src/generated/cfg_centrifugo/client.ts +5 -2
- package/src/generated/cfg_centrifugo/http.ts +8 -2
- package/src/generated/cfg_totp/CLAUDE.md +12 -12
- package/src/generated/cfg_totp/_utils/fetchers/index.ts +3 -3
- package/src/generated/cfg_totp/_utils/fetchers/{totp__2fa_management.ts → totp__totp_management.ts} +3 -3
- package/src/generated/cfg_totp/_utils/fetchers/{totp__2fa_setup.ts → totp__totp_setup.ts} +3 -3
- package/src/generated/cfg_totp/_utils/fetchers/{totp__2fa_verification.ts → totp__totp_verification.ts} +3 -3
- package/src/generated/cfg_totp/_utils/hooks/index.ts +3 -3
- package/src/generated/cfg_totp/_utils/hooks/{totp__2fa_management.ts → totp__totp_management.ts} +2 -2
- package/src/generated/cfg_totp/_utils/hooks/{totp__2fa_setup.ts → totp__totp_setup.ts} +2 -2
- package/src/generated/cfg_totp/_utils/hooks/{totp__2fa_verification.ts → totp__totp_verification.ts} +2 -2
- package/src/generated/cfg_totp/_utils/schemas/DeviceList.schema.ts +1 -1
- package/src/generated/cfg_totp/client.ts +14 -11
- package/src/generated/cfg_totp/http.ts +8 -2
- package/src/generated/cfg_totp/index.ts +16 -16
- package/src/generated/cfg_totp/schema.json +8 -7
- package/src/generated/cfg_totp/{totp__2fa_management → totp__totp_management}/client.ts +2 -2
- package/src/generated/cfg_totp/{totp__2fa_management → totp__totp_management}/models.ts +1 -1
- package/src/generated/cfg_totp/{totp__2fa_setup → totp__totp_setup}/client.ts +4 -4
- package/src/generated/cfg_totp/{totp__2fa_verification → totp__totp_verification}/client.ts +2 -2
- package/src/generated/cfg_webpush/client.ts +5 -2
- package/src/generated/cfg_webpush/http.ts +8 -2
- /package/src/generated/cfg_totp/{totp__2fa_management → totp__totp_management}/index.ts +0 -0
- /package/src/generated/cfg_totp/{totp__2fa_setup → totp__totp_setup}/index.ts +0 -0
- /package/src/generated/cfg_totp/{totp__2fa_setup → totp__totp_setup}/models.ts +0 -0
- /package/src/generated/cfg_totp/{totp__2fa_verification → totp__totp_verification}/index.ts +0 -0
- /package/src/generated/cfg_totp/{totp__2fa_verification → totp__totp_verification}/models.ts +0 -0
package/dist/auth.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(auth_exports, {
|
|
|
39
39
|
authLogger: () => authLogger,
|
|
40
40
|
clearProfileCache: () => clearProfileCache,
|
|
41
41
|
decodeBase64: () => decodeBase64,
|
|
42
|
+
detectChannelFromIdentifier: () => detectChannelFromIdentifier,
|
|
42
43
|
encodeBase64: () => encodeBase64,
|
|
43
44
|
formatAuthError: () => formatAuthError,
|
|
44
45
|
getCacheMetadata: () => getCacheMetadata,
|
|
@@ -49,14 +50,19 @@ __export(auth_exports, {
|
|
|
49
50
|
useAccountsContext: () => useAccountsContext,
|
|
50
51
|
useAuth: () => useAuth,
|
|
51
52
|
useAuthForm: () => useAuthForm,
|
|
53
|
+
useAuthFormState: () => useAuthFormState,
|
|
52
54
|
useAuthGuard: () => useAuthGuard,
|
|
53
55
|
useAuthRedirectManager: () => useAuthRedirectManager,
|
|
56
|
+
useAuthValidation: () => useAuthValidation,
|
|
54
57
|
useAutoAuth: () => useAutoAuth,
|
|
55
58
|
useBase64: () => useBase64,
|
|
56
59
|
useGithubAuth: () => useGithubAuth,
|
|
57
60
|
useLocalStorage: () => useLocalStorage2,
|
|
58
61
|
useSessionStorage: () => useSessionStorage,
|
|
59
|
-
|
|
62
|
+
useTwoFactor: () => useTwoFactor,
|
|
63
|
+
useTwoFactorSetup: () => useTwoFactorSetup,
|
|
64
|
+
validateEmail: () => validateEmail,
|
|
65
|
+
validateIdentifier: () => validateIdentifier
|
|
60
66
|
});
|
|
61
67
|
module.exports = __toCommonJS(auth_exports);
|
|
62
68
|
|
|
@@ -222,7 +228,10 @@ var Accounts = class {
|
|
|
222
228
|
return response;
|
|
223
229
|
}
|
|
224
230
|
/**
|
|
225
|
-
* Verify OTP code and return JWT tokens.
|
|
231
|
+
* Verify OTP code and return JWT tokens or 2FA session. If user has 2FA
|
|
232
|
+
* enabled: - Returns requires_2fa=True with session_id - Client must
|
|
233
|
+
* complete 2FA verification at /cfg/totp/verify/ If user has no 2FA: -
|
|
234
|
+
* Returns JWT tokens and user data directly
|
|
226
235
|
*/
|
|
227
236
|
async otpVerifyCreate(data) {
|
|
228
237
|
const response = await this.client.request("POST", "/cfg/accounts/otp/verify/", { body: data });
|
|
@@ -236,7 +245,7 @@ var FetchAdapter = class {
|
|
|
236
245
|
__name(this, "FetchAdapter");
|
|
237
246
|
}
|
|
238
247
|
async request(request) {
|
|
239
|
-
const { method, url, headers, body, params, formData } = request;
|
|
248
|
+
const { method, url, headers, body, params, formData, binaryBody } = request;
|
|
240
249
|
let finalUrl = url;
|
|
241
250
|
if (params) {
|
|
242
251
|
const searchParams = new URLSearchParams();
|
|
@@ -254,6 +263,9 @@ var FetchAdapter = class {
|
|
|
254
263
|
let requestBody;
|
|
255
264
|
if (formData) {
|
|
256
265
|
requestBody = formData;
|
|
266
|
+
} else if (binaryBody) {
|
|
267
|
+
finalHeaders["Content-Type"] = "application/octet-stream";
|
|
268
|
+
requestBody = binaryBody;
|
|
257
269
|
} else if (body) {
|
|
258
270
|
finalHeaders["Content-Type"] = "application/json";
|
|
259
271
|
requestBody = JSON.stringify(body);
|
|
@@ -653,7 +665,7 @@ var APIClient = class {
|
|
|
653
665
|
const headers = {
|
|
654
666
|
...options?.headers || {}
|
|
655
667
|
};
|
|
656
|
-
if (!options?.formData && !headers["Content-Type"]) {
|
|
668
|
+
if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
|
|
657
669
|
headers["Content-Type"] = "application/json";
|
|
658
670
|
}
|
|
659
671
|
if (this.logger) {
|
|
@@ -672,7 +684,8 @@ var APIClient = class {
|
|
|
672
684
|
headers,
|
|
673
685
|
params: options?.params,
|
|
674
686
|
body: options?.body,
|
|
675
|
-
formData: options?.formData
|
|
687
|
+
formData: options?.formData,
|
|
688
|
+
binaryBody: options?.binaryBody
|
|
676
689
|
});
|
|
677
690
|
const duration = Date.now() - startTime;
|
|
678
691
|
if (response.status >= 400) {
|
|
@@ -926,11 +939,14 @@ var OAuthProvidersResponseSchema = import_zod8.z.object({
|
|
|
926
939
|
// src/generated/cfg_accounts/_utils/schemas/OAuthTokenResponse.schema.ts
|
|
927
940
|
var import_zod9 = require("zod");
|
|
928
941
|
var OAuthTokenResponseSchema = import_zod9.z.object({
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
942
|
+
requires_2fa: import_zod9.z.boolean().optional(),
|
|
943
|
+
session_id: import_zod9.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i).nullable().optional(),
|
|
944
|
+
access: import_zod9.z.string().nullable().optional(),
|
|
945
|
+
refresh: import_zod9.z.string().nullable().optional(),
|
|
946
|
+
user: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.any()).nullable().optional(),
|
|
932
947
|
is_new_user: import_zod9.z.boolean(),
|
|
933
|
-
is_new_connection: import_zod9.z.boolean()
|
|
948
|
+
is_new_connection: import_zod9.z.boolean(),
|
|
949
|
+
should_prompt_2fa: import_zod9.z.boolean().optional()
|
|
934
950
|
});
|
|
935
951
|
|
|
936
952
|
// src/generated/cfg_accounts/_utils/schemas/OTPErrorResponse.schema.ts
|
|
@@ -989,9 +1005,12 @@ var UserSchema = import_zod14.z.object({
|
|
|
989
1005
|
|
|
990
1006
|
// src/generated/cfg_accounts/_utils/schemas/OTPVerifyResponse.schema.ts
|
|
991
1007
|
var OTPVerifyResponseSchema = import_zod15.z.object({
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1008
|
+
requires_2fa: import_zod15.z.boolean().optional(),
|
|
1009
|
+
session_id: import_zod15.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i).nullable().optional(),
|
|
1010
|
+
refresh: import_zod15.z.string().nullable().optional(),
|
|
1011
|
+
access: import_zod15.z.string().nullable().optional(),
|
|
1012
|
+
user: UserSchema.nullable().optional(),
|
|
1013
|
+
should_prompt_2fa: import_zod15.z.boolean().optional()
|
|
995
1014
|
});
|
|
996
1015
|
|
|
997
1016
|
// src/generated/cfg_accounts/_utils/schemas/PatchedUserProfileUpdateRequest.schema.ts
|
|
@@ -1710,7 +1729,7 @@ var FetchAdapter2 = class {
|
|
|
1710
1729
|
__name(this, "FetchAdapter");
|
|
1711
1730
|
}
|
|
1712
1731
|
async request(request) {
|
|
1713
|
-
const { method, url, headers, body, params, formData } = request;
|
|
1732
|
+
const { method, url, headers, body, params, formData, binaryBody } = request;
|
|
1714
1733
|
let finalUrl = url;
|
|
1715
1734
|
if (params) {
|
|
1716
1735
|
const searchParams = new URLSearchParams();
|
|
@@ -1728,6 +1747,9 @@ var FetchAdapter2 = class {
|
|
|
1728
1747
|
let requestBody;
|
|
1729
1748
|
if (formData) {
|
|
1730
1749
|
requestBody = formData;
|
|
1750
|
+
} else if (binaryBody) {
|
|
1751
|
+
finalHeaders["Content-Type"] = "application/octet-stream";
|
|
1752
|
+
requestBody = binaryBody;
|
|
1731
1753
|
} else if (body) {
|
|
1732
1754
|
finalHeaders["Content-Type"] = "application/json";
|
|
1733
1755
|
requestBody = JSON.stringify(body);
|
|
@@ -2127,7 +2149,7 @@ var APIClient2 = class {
|
|
|
2127
2149
|
const headers = {
|
|
2128
2150
|
...options?.headers || {}
|
|
2129
2151
|
};
|
|
2130
|
-
if (!options?.formData && !headers["Content-Type"]) {
|
|
2152
|
+
if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
|
|
2131
2153
|
headers["Content-Type"] = "application/json";
|
|
2132
2154
|
}
|
|
2133
2155
|
if (this.logger) {
|
|
@@ -2146,7 +2168,8 @@ var APIClient2 = class {
|
|
|
2146
2168
|
headers,
|
|
2147
2169
|
params: options?.params,
|
|
2148
2170
|
body: options?.body,
|
|
2149
|
-
formData: options?.formData
|
|
2171
|
+
formData: options?.formData,
|
|
2172
|
+
binaryBody: options?.binaryBody
|
|
2150
2173
|
});
|
|
2151
2174
|
const duration = Date.now() - startTime;
|
|
2152
2175
|
if (response.status >= 400) {
|
|
@@ -2807,7 +2830,7 @@ var FetchAdapter3 = class {
|
|
|
2807
2830
|
__name(this, "FetchAdapter");
|
|
2808
2831
|
}
|
|
2809
2832
|
async request(request) {
|
|
2810
|
-
const { method, url, headers, body, params, formData } = request;
|
|
2833
|
+
const { method, url, headers, body, params, formData, binaryBody } = request;
|
|
2811
2834
|
let finalUrl = url;
|
|
2812
2835
|
if (params) {
|
|
2813
2836
|
const searchParams = new URLSearchParams();
|
|
@@ -2825,6 +2848,9 @@ var FetchAdapter3 = class {
|
|
|
2825
2848
|
let requestBody;
|
|
2826
2849
|
if (formData) {
|
|
2827
2850
|
requestBody = formData;
|
|
2851
|
+
} else if (binaryBody) {
|
|
2852
|
+
finalHeaders["Content-Type"] = "application/octet-stream";
|
|
2853
|
+
requestBody = binaryBody;
|
|
2828
2854
|
} else if (body) {
|
|
2829
2855
|
finalHeaders["Content-Type"] = "application/json";
|
|
2830
2856
|
requestBody = JSON.stringify(body);
|
|
@@ -3221,7 +3247,7 @@ var APIClient3 = class {
|
|
|
3221
3247
|
const headers = {
|
|
3222
3248
|
...options?.headers || {}
|
|
3223
3249
|
};
|
|
3224
|
-
if (!options?.formData && !headers["Content-Type"]) {
|
|
3250
|
+
if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
|
|
3225
3251
|
headers["Content-Type"] = "application/json";
|
|
3226
3252
|
}
|
|
3227
3253
|
if (this.logger) {
|
|
@@ -3240,7 +3266,8 @@ var APIClient3 = class {
|
|
|
3240
3266
|
headers,
|
|
3241
3267
|
params: options?.params,
|
|
3242
3268
|
body: options?.body,
|
|
3243
|
-
formData: options?.formData
|
|
3269
|
+
formData: options?.formData,
|
|
3270
|
+
binaryBody: options?.binaryBody
|
|
3244
3271
|
});
|
|
3245
3272
|
const duration = Date.now() - startTime;
|
|
3246
3273
|
if (response.status >= 400) {
|
|
@@ -4110,6 +4137,10 @@ function AccountsProvider({ children }) {
|
|
|
4110
4137
|
}, "requestOTP");
|
|
4111
4138
|
const verifyOTP = /* @__PURE__ */ __name(async (data) => {
|
|
4112
4139
|
const result = await otpVerifyMutation(data, api);
|
|
4140
|
+
if (result.requires_2fa && result.session_id) {
|
|
4141
|
+
authLogger.info("2FA required, session:", result.session_id);
|
|
4142
|
+
return result;
|
|
4143
|
+
}
|
|
4113
4144
|
if (result.access && result.refresh) {
|
|
4114
4145
|
api.setToken(result.access, result.refresh);
|
|
4115
4146
|
await refreshProfile("AccountsContext.verifyOTP");
|
|
@@ -4368,7 +4399,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4368
4399
|
[accounts]
|
|
4369
4400
|
);
|
|
4370
4401
|
const verifyOTP = (0, import_react3.useCallback)(
|
|
4371
|
-
async (identifier, otpCode, channel, sourceUrl, redirectUrl) => {
|
|
4402
|
+
async (identifier, otpCode, channel, sourceUrl, redirectUrl, skipRedirect) => {
|
|
4372
4403
|
try {
|
|
4373
4404
|
const channelValue = channel === "phone" ? enums_exports.OTPVerifyRequestChannel.PHONE : enums_exports.OTPVerifyRequestChannel.EMAIL;
|
|
4374
4405
|
const result = await accounts.verifyOTP({
|
|
@@ -4376,6 +4407,16 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4376
4407
|
otp: otpCode,
|
|
4377
4408
|
channel: channelValue
|
|
4378
4409
|
});
|
|
4410
|
+
if (result.requires_2fa && result.session_id) {
|
|
4411
|
+
authLogger.info("2FA required, session:", result.session_id);
|
|
4412
|
+
return {
|
|
4413
|
+
success: true,
|
|
4414
|
+
message: "OTP verified, 2FA required",
|
|
4415
|
+
requires_2fa: true,
|
|
4416
|
+
session_id: result.session_id,
|
|
4417
|
+
should_prompt_2fa: result.should_prompt_2fa
|
|
4418
|
+
};
|
|
4419
|
+
}
|
|
4379
4420
|
if (!result.access || !result.refresh) {
|
|
4380
4421
|
authLogger.error("Verify OTP returned invalid response:", result);
|
|
4381
4422
|
return {
|
|
@@ -4398,14 +4439,17 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4398
4439
|
if (result.user?.id) {
|
|
4399
4440
|
Analytics.setUser(String(result.user.id));
|
|
4400
4441
|
}
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4442
|
+
if (!skipRedirect) {
|
|
4443
|
+
const savedRedirect = redirectManager.useAndClearRedirect();
|
|
4444
|
+
const finalRedirectUrl = redirectUrl || savedRedirect || config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
|
|
4445
|
+
authLogger.info("Redirecting after auth to:", finalRedirectUrl);
|
|
4446
|
+
router.hardPush(finalRedirectUrl);
|
|
4447
|
+
}
|
|
4405
4448
|
return {
|
|
4406
4449
|
success: true,
|
|
4407
4450
|
message: "Login successful",
|
|
4408
|
-
user: result.user
|
|
4451
|
+
user: result.user,
|
|
4452
|
+
should_prompt_2fa: result.should_prompt_2fa
|
|
4409
4453
|
};
|
|
4410
4454
|
} catch (error) {
|
|
4411
4455
|
authLogger.error("Verify OTP error:", error);
|
|
@@ -4545,199 +4589,107 @@ var useAuth = /* @__PURE__ */ __name(() => {
|
|
|
4545
4589
|
return context;
|
|
4546
4590
|
}, "useAuth");
|
|
4547
4591
|
|
|
4548
|
-
// src/auth/hooks/
|
|
4592
|
+
// src/auth/hooks/useAuthFormState.ts
|
|
4549
4593
|
var import_react4 = require("react");
|
|
4550
|
-
var
|
|
4551
|
-
|
|
4552
|
-
const
|
|
4553
|
-
const
|
|
4554
|
-
const
|
|
4555
|
-
const [
|
|
4556
|
-
(0, import_react4.
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4594
|
+
var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "", initialChannel = "email") => {
|
|
4595
|
+
const [identifier, setIdentifier] = (0, import_react4.useState)(initialIdentifier);
|
|
4596
|
+
const [channel, setChannel] = (0, import_react4.useState)(initialChannel);
|
|
4597
|
+
const [otp, setOtp] = (0, import_react4.useState)("");
|
|
4598
|
+
const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
|
|
4599
|
+
const [acceptedTerms, setAcceptedTerms] = (0, import_react4.useState)(false);
|
|
4600
|
+
const [step, setStep] = (0, import_react4.useState)("identifier");
|
|
4601
|
+
const [error, setError] = (0, import_react4.useState)("");
|
|
4602
|
+
const [twoFactorSessionId, setTwoFactorSessionId] = (0, import_react4.useState)(null);
|
|
4603
|
+
const [shouldPrompt2FA, setShouldPrompt2FA] = (0, import_react4.useState)(false);
|
|
4604
|
+
const [twoFactorCode, setTwoFactorCode] = (0, import_react4.useState)("");
|
|
4605
|
+
const [useBackupCode, setUseBackupCode] = (0, import_react4.useState)(false);
|
|
4606
|
+
const clearError = (0, import_react4.useCallback)(() => setError(""), []);
|
|
4607
|
+
return {
|
|
4608
|
+
// State
|
|
4609
|
+
identifier,
|
|
4610
|
+
channel,
|
|
4611
|
+
otp,
|
|
4612
|
+
isLoading,
|
|
4613
|
+
acceptedTerms,
|
|
4614
|
+
step,
|
|
4615
|
+
error,
|
|
4616
|
+
twoFactorSessionId,
|
|
4617
|
+
shouldPrompt2FA,
|
|
4618
|
+
twoFactorCode,
|
|
4619
|
+
useBackupCode,
|
|
4620
|
+
// Handlers
|
|
4621
|
+
setIdentifier,
|
|
4622
|
+
setChannel,
|
|
4623
|
+
setOtp,
|
|
4624
|
+
setAcceptedTerms,
|
|
4625
|
+
setError,
|
|
4626
|
+
clearError,
|
|
4627
|
+
setStep,
|
|
4628
|
+
setIsLoading,
|
|
4629
|
+
setTwoFactorSessionId,
|
|
4630
|
+
setShouldPrompt2FA,
|
|
4631
|
+
setTwoFactorCode,
|
|
4632
|
+
setUseBackupCode
|
|
4633
|
+
};
|
|
4634
|
+
}, "useAuthFormState");
|
|
4568
4635
|
|
|
4569
|
-
// src/auth/hooks/
|
|
4636
|
+
// src/auth/hooks/useAuthValidation.ts
|
|
4570
4637
|
var import_react5 = require("react");
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
if (item === null) {
|
|
4579
|
-
return initialValue;
|
|
4580
|
-
}
|
|
4581
|
-
try {
|
|
4582
|
-
return JSON.parse(item);
|
|
4583
|
-
} catch {
|
|
4584
|
-
return item;
|
|
4585
|
-
}
|
|
4586
|
-
} catch (error) {
|
|
4587
|
-
authLogger.error(`Error reading localStorage key "${key}":`, error);
|
|
4588
|
-
return initialValue;
|
|
4589
|
-
}
|
|
4590
|
-
});
|
|
4591
|
-
const checkDataSize = /* @__PURE__ */ __name((data) => {
|
|
4592
|
-
try {
|
|
4593
|
-
const jsonString = JSON.stringify(data);
|
|
4594
|
-
const sizeInBytes = new Blob([jsonString]).size;
|
|
4595
|
-
const sizeInKB = sizeInBytes / 1024;
|
|
4596
|
-
if (sizeInKB > 1024) {
|
|
4597
|
-
authLogger.warn(`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`);
|
|
4598
|
-
return false;
|
|
4599
|
-
}
|
|
4600
|
-
return true;
|
|
4601
|
-
} catch (error) {
|
|
4602
|
-
authLogger.error(`Error checking data size for key "${key}":`, error);
|
|
4603
|
-
return false;
|
|
4604
|
-
}
|
|
4605
|
-
}, "checkDataSize");
|
|
4606
|
-
const clearOldData = /* @__PURE__ */ __name(() => {
|
|
4607
|
-
try {
|
|
4608
|
-
const keys = Object.keys(localStorage).filter((key2) => key2 && typeof key2 === "string");
|
|
4609
|
-
if (keys.length > 50) {
|
|
4610
|
-
const itemsToRemove = Math.ceil(keys.length * 0.2);
|
|
4611
|
-
for (let i = 0; i < itemsToRemove; i++) {
|
|
4612
|
-
try {
|
|
4613
|
-
const key2 = keys[i];
|
|
4614
|
-
if (key2) {
|
|
4615
|
-
localStorage.removeItem(key2);
|
|
4616
|
-
localStorage.removeItem(`${key2}_timestamp`);
|
|
4617
|
-
}
|
|
4618
|
-
} catch {
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
}
|
|
4622
|
-
} catch (error) {
|
|
4623
|
-
authLogger.error("Error clearing old localStorage data:", error);
|
|
4638
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
4639
|
+
var PHONE_REGEX = /^\+[1-9]\d{6,14}$/;
|
|
4640
|
+
var useAuthValidation = /* @__PURE__ */ __name(() => {
|
|
4641
|
+
const detectChannelFromIdentifier2 = (0, import_react5.useCallback)((id) => {
|
|
4642
|
+
if (!id) return null;
|
|
4643
|
+
if (id.includes("@")) {
|
|
4644
|
+
return "email";
|
|
4624
4645
|
}
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
try {
|
|
4628
|
-
const keys = Object.keys(localStorage);
|
|
4629
|
-
for (const key2 of keys) {
|
|
4630
|
-
try {
|
|
4631
|
-
localStorage.removeItem(key2);
|
|
4632
|
-
} catch {
|
|
4633
|
-
}
|
|
4634
|
-
}
|
|
4635
|
-
} catch (error) {
|
|
4636
|
-
authLogger.error("Error force clearing localStorage:", error);
|
|
4646
|
+
if (id.startsWith("+") && PHONE_REGEX.test(id)) {
|
|
4647
|
+
return "phone";
|
|
4637
4648
|
}
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
window.localStorage.removeItem(key);
|
|
4646
|
-
window.localStorage.removeItem(`${key}_timestamp`);
|
|
4647
|
-
} catch {
|
|
4648
|
-
}
|
|
4649
|
-
setStoredValue(valueToStore);
|
|
4650
|
-
return;
|
|
4651
|
-
}
|
|
4652
|
-
setStoredValue(valueToStore);
|
|
4653
|
-
if (typeof window !== "undefined") {
|
|
4654
|
-
try {
|
|
4655
|
-
if (typeof valueToStore === "string") {
|
|
4656
|
-
window.localStorage.setItem(key, valueToStore);
|
|
4657
|
-
} else {
|
|
4658
|
-
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
4659
|
-
}
|
|
4660
|
-
window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
4661
|
-
} catch (storageError) {
|
|
4662
|
-
if (storageError.name === "QuotaExceededError" || storageError.code === 22 || storageError.message?.includes("quota")) {
|
|
4663
|
-
authLogger.warn("localStorage quota exceeded, clearing old data...");
|
|
4664
|
-
clearOldData();
|
|
4665
|
-
try {
|
|
4666
|
-
if (typeof valueToStore === "string") {
|
|
4667
|
-
window.localStorage.setItem(key, valueToStore);
|
|
4668
|
-
} else {
|
|
4669
|
-
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
4670
|
-
}
|
|
4671
|
-
window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
4672
|
-
} catch (retryError) {
|
|
4673
|
-
authLogger.error(`Failed to set localStorage key "${key}" after clearing old data:`, retryError);
|
|
4674
|
-
try {
|
|
4675
|
-
forceClearAll();
|
|
4676
|
-
if (typeof valueToStore === "string") {
|
|
4677
|
-
window.localStorage.setItem(key, valueToStore);
|
|
4678
|
-
} else {
|
|
4679
|
-
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
4680
|
-
}
|
|
4681
|
-
window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
4682
|
-
} catch (finalError) {
|
|
4683
|
-
authLogger.error(`Failed to set localStorage key "${key}" after force clearing:`, finalError);
|
|
4684
|
-
setStoredValue(valueToStore);
|
|
4685
|
-
}
|
|
4686
|
-
}
|
|
4687
|
-
} else {
|
|
4688
|
-
throw storageError;
|
|
4689
|
-
}
|
|
4690
|
-
}
|
|
4691
|
-
}
|
|
4692
|
-
} catch (error) {
|
|
4693
|
-
authLogger.error(`Error setting localStorage key "${key}":`, error);
|
|
4694
|
-
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
4695
|
-
setStoredValue(valueToStore);
|
|
4649
|
+
return null;
|
|
4650
|
+
}, []);
|
|
4651
|
+
const validateIdentifier2 = (0, import_react5.useCallback)((id, channelType) => {
|
|
4652
|
+
if (!id) return false;
|
|
4653
|
+
const channel = channelType || detectChannelFromIdentifier2(id);
|
|
4654
|
+
if (channel === "email") {
|
|
4655
|
+
return EMAIL_REGEX.test(id);
|
|
4696
4656
|
}
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
try {
|
|
4700
|
-
setStoredValue(initialValue);
|
|
4701
|
-
if (typeof window !== "undefined") {
|
|
4702
|
-
try {
|
|
4703
|
-
window.localStorage.removeItem(key);
|
|
4704
|
-
window.localStorage.removeItem(`${key}_timestamp`);
|
|
4705
|
-
} catch (removeError) {
|
|
4706
|
-
if (removeError.name === "QuotaExceededError" || removeError.code === 22 || removeError.message?.includes("quota")) {
|
|
4707
|
-
authLogger.warn("localStorage quota exceeded during removal, clearing old data...");
|
|
4708
|
-
clearOldData();
|
|
4709
|
-
try {
|
|
4710
|
-
window.localStorage.removeItem(key);
|
|
4711
|
-
window.localStorage.removeItem(`${key}_timestamp`);
|
|
4712
|
-
} catch (retryError) {
|
|
4713
|
-
authLogger.error(`Failed to remove localStorage key "${key}" after clearing:`, retryError);
|
|
4714
|
-
forceClearAll();
|
|
4715
|
-
}
|
|
4716
|
-
} else {
|
|
4717
|
-
throw removeError;
|
|
4718
|
-
}
|
|
4719
|
-
}
|
|
4720
|
-
}
|
|
4721
|
-
} catch (error) {
|
|
4722
|
-
authLogger.error(`Error removing localStorage key "${key}":`, error);
|
|
4657
|
+
if (channel === "phone") {
|
|
4658
|
+
return PHONE_REGEX.test(id);
|
|
4723
4659
|
}
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4660
|
+
return false;
|
|
4661
|
+
}, [detectChannelFromIdentifier2]);
|
|
4662
|
+
return {
|
|
4663
|
+
detectChannelFromIdentifier: detectChannelFromIdentifier2,
|
|
4664
|
+
validateIdentifier: validateIdentifier2
|
|
4665
|
+
};
|
|
4666
|
+
}, "useAuthValidation");
|
|
4667
|
+
var detectChannelFromIdentifier = /* @__PURE__ */ __name((id) => {
|
|
4668
|
+
if (!id) return null;
|
|
4669
|
+
if (id.includes("@")) return "email";
|
|
4670
|
+
if (id.startsWith("+") && PHONE_REGEX.test(id)) return "phone";
|
|
4671
|
+
return null;
|
|
4672
|
+
}, "detectChannelFromIdentifier");
|
|
4673
|
+
var validateIdentifier = /* @__PURE__ */ __name((id, channelType) => {
|
|
4674
|
+
if (!id) return false;
|
|
4675
|
+
const channel = channelType || detectChannelFromIdentifier(id);
|
|
4676
|
+
if (channel === "email") return EMAIL_REGEX.test(id);
|
|
4677
|
+
if (channel === "phone") return PHONE_REGEX.test(id);
|
|
4678
|
+
return false;
|
|
4679
|
+
}, "validateIdentifier");
|
|
4728
4680
|
|
|
4729
4681
|
// src/auth/hooks/useAuthForm.ts
|
|
4730
|
-
var
|
|
4682
|
+
var import_react8 = require("react");
|
|
4731
4683
|
|
|
4732
4684
|
// src/auth/hooks/useAutoAuth.ts
|
|
4733
4685
|
var import_navigation2 = require("next/navigation");
|
|
4734
4686
|
var import_react6 = require("react");
|
|
4735
|
-
var
|
|
4687
|
+
var import_hooks3 = require("@djangocfg/ui-nextjs/hooks");
|
|
4736
4688
|
var useAutoAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
4737
4689
|
const { onOTPDetected, cleanupUrl = true, allowedPaths = ["/auth"] } = options;
|
|
4738
|
-
const queryParams = (0,
|
|
4690
|
+
const queryParams = (0, import_hooks3.useQueryParams)();
|
|
4739
4691
|
const pathname = (0, import_navigation2.usePathname)();
|
|
4740
|
-
const router = (0,
|
|
4692
|
+
const router = (0, import_hooks3.useCfgRouter)();
|
|
4741
4693
|
const isAllowedPath = allowedPaths.some((path) => pathname === path || pathname?.startsWith(path + "/"));
|
|
4742
4694
|
const hasOTP = !!queryParams.get("otp");
|
|
4743
4695
|
const isReady = !!pathname && hasOTP && isAllowedPath;
|
|
@@ -4762,81 +4714,1275 @@ var useAutoAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
|
4762
4714
|
};
|
|
4763
4715
|
}, "useAutoAuth");
|
|
4764
4716
|
|
|
4765
|
-
// src/auth/hooks/
|
|
4766
|
-
var
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
}
|
|
4799
|
-
(
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
}, [getSavedEmail, getSavedPhone, savedTermsAccepted]);
|
|
4813
|
-
(0, import_react7.useEffect)(() => {
|
|
4814
|
-
if (identifier) {
|
|
4815
|
-
const detectedChannel = detectChannelFromIdentifier(identifier);
|
|
4816
|
-
if (detectedChannel && detectedChannel !== channel) {
|
|
4817
|
-
setChannel(detectedChannel);
|
|
4818
|
-
}
|
|
4819
|
-
}
|
|
4820
|
-
}, [identifier, channel, detectChannelFromIdentifier]);
|
|
4821
|
-
const clearError = (0, import_react7.useCallback)(() => setError(""), []);
|
|
4822
|
-
const handleIdentifierSubmit = (0, import_react7.useCallback)(async (e) => {
|
|
4823
|
-
e.preventDefault();
|
|
4824
|
-
if (!identifier) {
|
|
4825
|
-
const message = channel === "phone" ? "Please enter your phone number" : "Please enter your email address";
|
|
4826
|
-
setError(message);
|
|
4827
|
-
onError?.(message);
|
|
4828
|
-
return;
|
|
4717
|
+
// src/auth/hooks/useTwoFactor.ts
|
|
4718
|
+
var import_react7 = require("react");
|
|
4719
|
+
var import_hooks4 = require("@djangocfg/ui-nextjs/hooks");
|
|
4720
|
+
|
|
4721
|
+
// src/generated/cfg_totp/totp__backup_codes/client.ts
|
|
4722
|
+
var BackupCodes = class {
|
|
4723
|
+
static {
|
|
4724
|
+
__name(this, "BackupCodes");
|
|
4725
|
+
}
|
|
4726
|
+
constructor(client) {
|
|
4727
|
+
this.client = client;
|
|
4728
|
+
}
|
|
4729
|
+
/**
|
|
4730
|
+
* Get backup codes status for user.
|
|
4731
|
+
*/
|
|
4732
|
+
async totpBackupCodesRetrieve() {
|
|
4733
|
+
const response = await this.client.request("GET", "/cfg/totp/backup-codes/");
|
|
4734
|
+
return response;
|
|
4735
|
+
}
|
|
4736
|
+
/**
|
|
4737
|
+
* Regenerate backup codes. Requires TOTP code for verification.
|
|
4738
|
+
* Invalidates all existing codes.
|
|
4739
|
+
*/
|
|
4740
|
+
async totpBackupCodesRegenerateCreate(data) {
|
|
4741
|
+
const response = await this.client.request("POST", "/cfg/totp/backup-codes/regenerate/", { body: data });
|
|
4742
|
+
return response;
|
|
4743
|
+
}
|
|
4744
|
+
};
|
|
4745
|
+
|
|
4746
|
+
// src/generated/cfg_totp/totp__totp_management/client.ts
|
|
4747
|
+
var TotpManagement = class {
|
|
4748
|
+
static {
|
|
4749
|
+
__name(this, "TotpManagement");
|
|
4750
|
+
}
|
|
4751
|
+
constructor(client) {
|
|
4752
|
+
this.client = client;
|
|
4753
|
+
}
|
|
4754
|
+
/**
|
|
4755
|
+
* List all TOTP devices for user.
|
|
4756
|
+
*/
|
|
4757
|
+
async totpDevicesList(...args) {
|
|
4758
|
+
const isParamsObject = args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0]);
|
|
4759
|
+
let params;
|
|
4760
|
+
if (isParamsObject) {
|
|
4761
|
+
params = args[0];
|
|
4762
|
+
} else {
|
|
4763
|
+
params = { page: args[0], page_size: args[1] };
|
|
4829
4764
|
}
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4765
|
+
const response = await this.client.request("GET", "/cfg/totp/devices/", { params });
|
|
4766
|
+
return response;
|
|
4767
|
+
}
|
|
4768
|
+
/**
|
|
4769
|
+
* Completely disable 2FA for account. Requires verification code.
|
|
4770
|
+
*/
|
|
4771
|
+
async totpDisableCreate(data) {
|
|
4772
|
+
const response = await this.client.request("POST", "/cfg/totp/disable/", { body: data });
|
|
4773
|
+
return response;
|
|
4774
|
+
}
|
|
4775
|
+
};
|
|
4776
|
+
|
|
4777
|
+
// src/generated/cfg_totp/totp__totp_setup/client.ts
|
|
4778
|
+
var TotpSetup = class {
|
|
4779
|
+
static {
|
|
4780
|
+
__name(this, "TotpSetup");
|
|
4781
|
+
}
|
|
4782
|
+
constructor(client) {
|
|
4783
|
+
this.client = client;
|
|
4784
|
+
}
|
|
4785
|
+
/**
|
|
4786
|
+
* Start 2FA setup process. Creates a new TOTP device and returns QR code
|
|
4787
|
+
* for scanning.
|
|
4788
|
+
*/
|
|
4789
|
+
async create(data) {
|
|
4790
|
+
const response = await this.client.request("POST", "/cfg/totp/setup/", { body: data });
|
|
4791
|
+
return response;
|
|
4792
|
+
}
|
|
4793
|
+
/**
|
|
4794
|
+
* Confirm 2FA setup with first valid code. Activates the device and
|
|
4795
|
+
* generates backup codes.
|
|
4796
|
+
*/
|
|
4797
|
+
async confirmCreate(data) {
|
|
4798
|
+
const response = await this.client.request("POST", "/cfg/totp/setup/confirm/", { body: data });
|
|
4799
|
+
return response;
|
|
4800
|
+
}
|
|
4801
|
+
};
|
|
4802
|
+
|
|
4803
|
+
// src/generated/cfg_totp/totp__totp_verification/client.ts
|
|
4804
|
+
var TotpVerification = class {
|
|
4805
|
+
static {
|
|
4806
|
+
__name(this, "TotpVerification");
|
|
4807
|
+
}
|
|
4808
|
+
constructor(client) {
|
|
4809
|
+
this.client = client;
|
|
4810
|
+
}
|
|
4811
|
+
/**
|
|
4812
|
+
* Verify TOTP code for 2FA session. Completes authentication and returns
|
|
4813
|
+
* JWT tokens on success.
|
|
4814
|
+
*/
|
|
4815
|
+
async totpVerifyCreate(data) {
|
|
4816
|
+
const response = await this.client.request("POST", "/cfg/totp/verify/", { body: data });
|
|
4817
|
+
return response;
|
|
4818
|
+
}
|
|
4819
|
+
/**
|
|
4820
|
+
* Verify backup recovery code for 2FA session. Alternative verification
|
|
4821
|
+
* method when TOTP device unavailable.
|
|
4822
|
+
*/
|
|
4823
|
+
async totpVerifyBackupCreate(data) {
|
|
4824
|
+
const response = await this.client.request("POST", "/cfg/totp/verify/backup/", { body: data });
|
|
4825
|
+
return response;
|
|
4826
|
+
}
|
|
4827
|
+
};
|
|
4828
|
+
|
|
4829
|
+
// src/generated/cfg_totp/totp/client.ts
|
|
4830
|
+
var Totp = class {
|
|
4831
|
+
static {
|
|
4832
|
+
__name(this, "Totp");
|
|
4833
|
+
}
|
|
4834
|
+
constructor(client) {
|
|
4835
|
+
this.client = client;
|
|
4836
|
+
}
|
|
4837
|
+
/**
|
|
4838
|
+
* Delete a TOTP device. Requires verification code if removing the
|
|
4839
|
+
* last/primary device.
|
|
4840
|
+
*/
|
|
4841
|
+
async devicesDestroy(id) {
|
|
4842
|
+
const response = await this.client.request("DELETE", `/cfg/totp/devices/${id}/`);
|
|
4843
|
+
return;
|
|
4844
|
+
}
|
|
4845
|
+
};
|
|
4846
|
+
|
|
4847
|
+
// src/generated/cfg_totp/http.ts
|
|
4848
|
+
var FetchAdapter4 = class {
|
|
4849
|
+
static {
|
|
4850
|
+
__name(this, "FetchAdapter");
|
|
4851
|
+
}
|
|
4852
|
+
async request(request) {
|
|
4853
|
+
const { method, url, headers, body, params, formData, binaryBody } = request;
|
|
4854
|
+
let finalUrl = url;
|
|
4855
|
+
if (params) {
|
|
4856
|
+
const searchParams = new URLSearchParams();
|
|
4857
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
4858
|
+
if (value !== null && value !== void 0) {
|
|
4859
|
+
searchParams.append(key, String(value));
|
|
4860
|
+
}
|
|
4861
|
+
});
|
|
4862
|
+
const queryString = searchParams.toString();
|
|
4863
|
+
if (queryString) {
|
|
4864
|
+
finalUrl = url.includes("?") ? `${url}&${queryString}` : `${url}?${queryString}`;
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
const finalHeaders = { ...headers };
|
|
4868
|
+
let requestBody;
|
|
4869
|
+
if (formData) {
|
|
4870
|
+
requestBody = formData;
|
|
4871
|
+
} else if (binaryBody) {
|
|
4872
|
+
finalHeaders["Content-Type"] = "application/octet-stream";
|
|
4873
|
+
requestBody = binaryBody;
|
|
4874
|
+
} else if (body) {
|
|
4875
|
+
finalHeaders["Content-Type"] = "application/json";
|
|
4876
|
+
requestBody = JSON.stringify(body);
|
|
4877
|
+
}
|
|
4878
|
+
const response = await fetch(finalUrl, {
|
|
4879
|
+
method,
|
|
4880
|
+
headers: finalHeaders,
|
|
4881
|
+
body: requestBody,
|
|
4882
|
+
credentials: "include"
|
|
4883
|
+
// Include Django session cookies
|
|
4884
|
+
});
|
|
4885
|
+
let data = null;
|
|
4886
|
+
const contentType = response.headers.get("content-type");
|
|
4887
|
+
if (response.status !== 204 && contentType?.includes("application/json")) {
|
|
4888
|
+
data = await response.json();
|
|
4889
|
+
} else if (response.status !== 204) {
|
|
4890
|
+
data = await response.text();
|
|
4891
|
+
}
|
|
4892
|
+
const responseHeaders = {};
|
|
4893
|
+
response.headers.forEach((value, key) => {
|
|
4894
|
+
responseHeaders[key] = value;
|
|
4895
|
+
});
|
|
4896
|
+
return {
|
|
4897
|
+
data,
|
|
4898
|
+
status: response.status,
|
|
4899
|
+
statusText: response.statusText,
|
|
4900
|
+
headers: responseHeaders
|
|
4901
|
+
};
|
|
4902
|
+
}
|
|
4903
|
+
};
|
|
4904
|
+
|
|
4905
|
+
// src/generated/cfg_totp/errors.ts
|
|
4906
|
+
var APIError4 = class extends Error {
|
|
4907
|
+
constructor(statusCode, statusText, response, url, message) {
|
|
4908
|
+
super(message || `HTTP ${statusCode}: ${statusText}`);
|
|
4909
|
+
this.statusCode = statusCode;
|
|
4910
|
+
this.statusText = statusText;
|
|
4911
|
+
this.response = response;
|
|
4912
|
+
this.url = url;
|
|
4913
|
+
this.name = "APIError";
|
|
4914
|
+
}
|
|
4915
|
+
static {
|
|
4916
|
+
__name(this, "APIError");
|
|
4917
|
+
}
|
|
4918
|
+
/**
|
|
4919
|
+
* Get error details from response.
|
|
4920
|
+
* DRF typically returns: { "detail": "Error message" } or { "field": ["error1", "error2"] }
|
|
4921
|
+
*/
|
|
4922
|
+
get details() {
|
|
4923
|
+
if (typeof this.response === "object" && this.response !== null) {
|
|
4924
|
+
return this.response;
|
|
4925
|
+
}
|
|
4926
|
+
return null;
|
|
4927
|
+
}
|
|
4928
|
+
/**
|
|
4929
|
+
* Get field-specific validation errors from DRF.
|
|
4930
|
+
* Returns: { "field_name": ["error1", "error2"], ... }
|
|
4931
|
+
*/
|
|
4932
|
+
get fieldErrors() {
|
|
4933
|
+
const details = this.details;
|
|
4934
|
+
if (!details) return null;
|
|
4935
|
+
const fieldErrors = {};
|
|
4936
|
+
for (const [key, value] of Object.entries(details)) {
|
|
4937
|
+
if (Array.isArray(value)) {
|
|
4938
|
+
fieldErrors[key] = value;
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
return Object.keys(fieldErrors).length > 0 ? fieldErrors : null;
|
|
4942
|
+
}
|
|
4943
|
+
/**
|
|
4944
|
+
* Get single error message from DRF.
|
|
4945
|
+
* Checks for "detail", "message", or first field error.
|
|
4946
|
+
*/
|
|
4947
|
+
get errorMessage() {
|
|
4948
|
+
const details = this.details;
|
|
4949
|
+
if (!details) return this.message;
|
|
4950
|
+
if (details.detail) {
|
|
4951
|
+
return Array.isArray(details.detail) ? details.detail.join(", ") : String(details.detail);
|
|
4952
|
+
}
|
|
4953
|
+
if (details.message) {
|
|
4954
|
+
return String(details.message);
|
|
4955
|
+
}
|
|
4956
|
+
const fieldErrors = this.fieldErrors;
|
|
4957
|
+
if (fieldErrors) {
|
|
4958
|
+
const firstField = Object.keys(fieldErrors)[0];
|
|
4959
|
+
if (firstField) {
|
|
4960
|
+
return `${firstField}: ${fieldErrors[firstField]?.join(", ")}`;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
return this.message;
|
|
4964
|
+
}
|
|
4965
|
+
// Helper methods for common HTTP status codes
|
|
4966
|
+
get isValidationError() {
|
|
4967
|
+
return this.statusCode === 400;
|
|
4968
|
+
}
|
|
4969
|
+
get isAuthError() {
|
|
4970
|
+
return this.statusCode === 401;
|
|
4971
|
+
}
|
|
4972
|
+
get isPermissionError() {
|
|
4973
|
+
return this.statusCode === 403;
|
|
4974
|
+
}
|
|
4975
|
+
get isNotFoundError() {
|
|
4976
|
+
return this.statusCode === 404;
|
|
4977
|
+
}
|
|
4978
|
+
get isServerError() {
|
|
4979
|
+
return this.statusCode >= 500 && this.statusCode < 600;
|
|
4980
|
+
}
|
|
4981
|
+
};
|
|
4982
|
+
var NetworkError4 = class extends Error {
|
|
4983
|
+
constructor(message, url, originalError) {
|
|
4984
|
+
super(message);
|
|
4985
|
+
this.url = url;
|
|
4986
|
+
this.originalError = originalError;
|
|
4987
|
+
this.name = "NetworkError";
|
|
4988
|
+
}
|
|
4989
|
+
static {
|
|
4990
|
+
__name(this, "NetworkError");
|
|
4991
|
+
}
|
|
4992
|
+
};
|
|
4993
|
+
|
|
4994
|
+
// src/generated/cfg_totp/logger.ts
|
|
4995
|
+
var import_consola14 = require("consola");
|
|
4996
|
+
var DEFAULT_CONFIG4 = {
|
|
4997
|
+
enabled: process.env.NODE_ENV !== "production",
|
|
4998
|
+
logRequests: true,
|
|
4999
|
+
logResponses: true,
|
|
5000
|
+
logErrors: true,
|
|
5001
|
+
logBodies: true,
|
|
5002
|
+
logHeaders: false
|
|
5003
|
+
};
|
|
5004
|
+
var SENSITIVE_HEADERS4 = [
|
|
5005
|
+
"authorization",
|
|
5006
|
+
"cookie",
|
|
5007
|
+
"set-cookie",
|
|
5008
|
+
"x-api-key",
|
|
5009
|
+
"x-csrf-token"
|
|
5010
|
+
];
|
|
5011
|
+
var APILogger4 = class {
|
|
5012
|
+
static {
|
|
5013
|
+
__name(this, "APILogger");
|
|
5014
|
+
}
|
|
5015
|
+
constructor(config = {}) {
|
|
5016
|
+
this.config = { ...DEFAULT_CONFIG4, ...config };
|
|
5017
|
+
this.consola = config.consola || (0, import_consola14.createConsola)({
|
|
5018
|
+
level: this.config.enabled ? 4 : 0
|
|
5019
|
+
});
|
|
5020
|
+
}
|
|
5021
|
+
/**
|
|
5022
|
+
* Enable logging
|
|
5023
|
+
*/
|
|
5024
|
+
enable() {
|
|
5025
|
+
this.config.enabled = true;
|
|
5026
|
+
}
|
|
5027
|
+
/**
|
|
5028
|
+
* Disable logging
|
|
5029
|
+
*/
|
|
5030
|
+
disable() {
|
|
5031
|
+
this.config.enabled = false;
|
|
5032
|
+
}
|
|
5033
|
+
/**
|
|
5034
|
+
* Update configuration
|
|
5035
|
+
*/
|
|
5036
|
+
setConfig(config) {
|
|
5037
|
+
this.config = { ...this.config, ...config };
|
|
5038
|
+
}
|
|
5039
|
+
/**
|
|
5040
|
+
* Filter sensitive headers
|
|
5041
|
+
*/
|
|
5042
|
+
filterHeaders(headers) {
|
|
5043
|
+
if (!headers) return {};
|
|
5044
|
+
const filtered = {};
|
|
5045
|
+
Object.keys(headers).forEach((key) => {
|
|
5046
|
+
const lowerKey = key.toLowerCase();
|
|
5047
|
+
if (SENSITIVE_HEADERS4.includes(lowerKey)) {
|
|
5048
|
+
filtered[key] = "***";
|
|
5049
|
+
} else {
|
|
5050
|
+
filtered[key] = headers[key] || "";
|
|
5051
|
+
}
|
|
5052
|
+
});
|
|
5053
|
+
return filtered;
|
|
5054
|
+
}
|
|
5055
|
+
/**
|
|
5056
|
+
* Log request
|
|
5057
|
+
*/
|
|
5058
|
+
logRequest(request) {
|
|
5059
|
+
if (!this.config.enabled || !this.config.logRequests) return;
|
|
5060
|
+
const { method, url, headers, body } = request;
|
|
5061
|
+
this.consola.start(`${method} ${url}`);
|
|
5062
|
+
if (this.config.logHeaders && headers) {
|
|
5063
|
+
this.consola.debug("Headers:", this.filterHeaders(headers));
|
|
5064
|
+
}
|
|
5065
|
+
if (this.config.logBodies && body) {
|
|
5066
|
+
this.consola.debug("Body:", body);
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
/**
|
|
5070
|
+
* Log response
|
|
5071
|
+
*/
|
|
5072
|
+
logResponse(request, response) {
|
|
5073
|
+
if (!this.config.enabled || !this.config.logResponses) return;
|
|
5074
|
+
const { method, url } = request;
|
|
5075
|
+
const { status, statusText, data, duration } = response;
|
|
5076
|
+
const statusColor = status >= 500 ? "red" : status >= 400 ? "yellow" : status >= 300 ? "cyan" : "green";
|
|
5077
|
+
this.consola.success(
|
|
5078
|
+
`${method} ${url} ${status} ${statusText} (${duration}ms)`
|
|
5079
|
+
);
|
|
5080
|
+
if (this.config.logBodies && data) {
|
|
5081
|
+
this.consola.debug("Response:", data);
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
/**
|
|
5085
|
+
* Log error
|
|
5086
|
+
*/
|
|
5087
|
+
logError(request, error) {
|
|
5088
|
+
if (!this.config.enabled || !this.config.logErrors) return;
|
|
5089
|
+
const { method, url } = request;
|
|
5090
|
+
const { message, statusCode, fieldErrors, duration } = error;
|
|
5091
|
+
this.consola.error(
|
|
5092
|
+
`${method} ${url} ${statusCode || "Network"} Error (${duration}ms)`
|
|
5093
|
+
);
|
|
5094
|
+
this.consola.error("Message:", message);
|
|
5095
|
+
if (fieldErrors && Object.keys(fieldErrors).length > 0) {
|
|
5096
|
+
this.consola.error("Field Errors:");
|
|
5097
|
+
Object.entries(fieldErrors).forEach(([field, errors]) => {
|
|
5098
|
+
errors.forEach((err) => {
|
|
5099
|
+
this.consola.error(` \u2022 ${field}: ${err}`);
|
|
5100
|
+
});
|
|
5101
|
+
});
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
/**
|
|
5105
|
+
* Log general info
|
|
5106
|
+
*/
|
|
5107
|
+
info(message, ...args) {
|
|
5108
|
+
if (!this.config.enabled) return;
|
|
5109
|
+
this.consola.info(message, ...args);
|
|
5110
|
+
}
|
|
5111
|
+
/**
|
|
5112
|
+
* Log warning
|
|
5113
|
+
*/
|
|
5114
|
+
warn(message, ...args) {
|
|
5115
|
+
if (!this.config.enabled) return;
|
|
5116
|
+
this.consola.warn(message, ...args);
|
|
5117
|
+
}
|
|
5118
|
+
/**
|
|
5119
|
+
* Log error
|
|
5120
|
+
*/
|
|
5121
|
+
error(message, ...args) {
|
|
5122
|
+
if (!this.config.enabled) return;
|
|
5123
|
+
this.consola.error(message, ...args);
|
|
5124
|
+
}
|
|
5125
|
+
/**
|
|
5126
|
+
* Log debug
|
|
5127
|
+
*/
|
|
5128
|
+
debug(message, ...args) {
|
|
5129
|
+
if (!this.config.enabled) return;
|
|
5130
|
+
this.consola.debug(message, ...args);
|
|
5131
|
+
}
|
|
5132
|
+
/**
|
|
5133
|
+
* Log success
|
|
5134
|
+
*/
|
|
5135
|
+
success(message, ...args) {
|
|
5136
|
+
if (!this.config.enabled) return;
|
|
5137
|
+
this.consola.success(message, ...args);
|
|
5138
|
+
}
|
|
5139
|
+
/**
|
|
5140
|
+
* Create a sub-logger with prefix
|
|
5141
|
+
*/
|
|
5142
|
+
withTag(tag) {
|
|
5143
|
+
return this.consola.withTag(tag);
|
|
5144
|
+
}
|
|
5145
|
+
};
|
|
5146
|
+
var defaultLogger4 = new APILogger4();
|
|
5147
|
+
|
|
5148
|
+
// src/generated/cfg_totp/retry.ts
|
|
5149
|
+
var import_p_retry4 = __toESM(require("p-retry"), 1);
|
|
5150
|
+
var DEFAULT_RETRY_CONFIG4 = {
|
|
5151
|
+
retries: 3,
|
|
5152
|
+
factor: 2,
|
|
5153
|
+
minTimeout: 1e3,
|
|
5154
|
+
maxTimeout: 6e4,
|
|
5155
|
+
randomize: true,
|
|
5156
|
+
onFailedAttempt: /* @__PURE__ */ __name(() => {
|
|
5157
|
+
}, "onFailedAttempt")
|
|
5158
|
+
};
|
|
5159
|
+
function shouldRetry4(error) {
|
|
5160
|
+
if (error instanceof NetworkError4) {
|
|
5161
|
+
return true;
|
|
5162
|
+
}
|
|
5163
|
+
if (error instanceof APIError4) {
|
|
5164
|
+
const status = error.statusCode;
|
|
5165
|
+
if (status >= 500 && status < 600) {
|
|
5166
|
+
return true;
|
|
5167
|
+
}
|
|
5168
|
+
if (status === 429) {
|
|
5169
|
+
return true;
|
|
5170
|
+
}
|
|
5171
|
+
return false;
|
|
5172
|
+
}
|
|
5173
|
+
return true;
|
|
5174
|
+
}
|
|
5175
|
+
__name(shouldRetry4, "shouldRetry");
|
|
5176
|
+
async function withRetry4(fn, config) {
|
|
5177
|
+
const finalConfig = { ...DEFAULT_RETRY_CONFIG4, ...config };
|
|
5178
|
+
return (0, import_p_retry4.default)(
|
|
5179
|
+
async () => {
|
|
5180
|
+
try {
|
|
5181
|
+
return await fn();
|
|
5182
|
+
} catch (error) {
|
|
5183
|
+
if (!shouldRetry4(error)) {
|
|
5184
|
+
throw new import_p_retry4.AbortError(error);
|
|
5185
|
+
}
|
|
5186
|
+
throw error;
|
|
5187
|
+
}
|
|
5188
|
+
},
|
|
5189
|
+
{
|
|
5190
|
+
retries: finalConfig.retries,
|
|
5191
|
+
factor: finalConfig.factor,
|
|
5192
|
+
minTimeout: finalConfig.minTimeout,
|
|
5193
|
+
maxTimeout: finalConfig.maxTimeout,
|
|
5194
|
+
randomize: finalConfig.randomize,
|
|
5195
|
+
onFailedAttempt: finalConfig.onFailedAttempt ? (error) => {
|
|
5196
|
+
const pRetryError = error;
|
|
5197
|
+
finalConfig.onFailedAttempt({
|
|
5198
|
+
error: pRetryError,
|
|
5199
|
+
attemptNumber: pRetryError.attemptNumber,
|
|
5200
|
+
retriesLeft: pRetryError.retriesLeft
|
|
5201
|
+
});
|
|
5202
|
+
} : void 0
|
|
5203
|
+
}
|
|
5204
|
+
);
|
|
5205
|
+
}
|
|
5206
|
+
__name(withRetry4, "withRetry");
|
|
5207
|
+
|
|
5208
|
+
// src/generated/cfg_totp/client.ts
|
|
5209
|
+
var APIClient4 = class {
|
|
5210
|
+
constructor(baseUrl, options) {
|
|
5211
|
+
this.logger = null;
|
|
5212
|
+
this.retryConfig = null;
|
|
5213
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
5214
|
+
this.httpClient = options?.httpClient || new FetchAdapter4();
|
|
5215
|
+
if (options?.loggerConfig !== void 0) {
|
|
5216
|
+
this.logger = new APILogger4(options.loggerConfig);
|
|
5217
|
+
}
|
|
5218
|
+
if (options?.retryConfig !== void 0) {
|
|
5219
|
+
this.retryConfig = options.retryConfig;
|
|
5220
|
+
}
|
|
5221
|
+
this.backup_codes = new BackupCodes(this);
|
|
5222
|
+
this.totp_management = new TotpManagement(this);
|
|
5223
|
+
this.totp_setup = new TotpSetup(this);
|
|
5224
|
+
this.totp_verification = new TotpVerification(this);
|
|
5225
|
+
this.totp = new Totp(this);
|
|
5226
|
+
}
|
|
5227
|
+
static {
|
|
5228
|
+
__name(this, "APIClient");
|
|
5229
|
+
}
|
|
5230
|
+
/**
|
|
5231
|
+
* Get CSRF token from cookies (for SessionAuthentication).
|
|
5232
|
+
*
|
|
5233
|
+
* Returns null if cookie doesn't exist (JWT-only auth).
|
|
5234
|
+
*/
|
|
5235
|
+
getCsrfToken() {
|
|
5236
|
+
const name = "csrftoken";
|
|
5237
|
+
const value = `; ${document.cookie}`;
|
|
5238
|
+
const parts = value.split(`; ${name}=`);
|
|
5239
|
+
if (parts.length === 2) {
|
|
5240
|
+
return parts.pop()?.split(";").shift() || null;
|
|
5241
|
+
}
|
|
5242
|
+
return null;
|
|
5243
|
+
}
|
|
5244
|
+
/**
|
|
5245
|
+
* Make HTTP request with Django CSRF and session handling.
|
|
5246
|
+
* Automatically retries on network errors and 5xx server errors.
|
|
5247
|
+
*/
|
|
5248
|
+
async request(method, path, options) {
|
|
5249
|
+
if (this.retryConfig) {
|
|
5250
|
+
return withRetry4(() => this._makeRequest(method, path, options), {
|
|
5251
|
+
...this.retryConfig,
|
|
5252
|
+
onFailedAttempt: /* @__PURE__ */ __name((info) => {
|
|
5253
|
+
if (this.logger) {
|
|
5254
|
+
this.logger.warn(
|
|
5255
|
+
`Retry attempt ${info.attemptNumber}/${info.retriesLeft + info.attemptNumber} for ${method} ${path}: ${info.error.message}`
|
|
5256
|
+
);
|
|
5257
|
+
}
|
|
5258
|
+
this.retryConfig?.onFailedAttempt?.(info);
|
|
5259
|
+
}, "onFailedAttempt")
|
|
5260
|
+
});
|
|
5261
|
+
}
|
|
5262
|
+
return this._makeRequest(method, path, options);
|
|
5263
|
+
}
|
|
5264
|
+
/**
|
|
5265
|
+
* Internal request method (without retry wrapper).
|
|
5266
|
+
* Used by request() method with optional retry logic.
|
|
5267
|
+
*/
|
|
5268
|
+
async _makeRequest(method, path, options) {
|
|
5269
|
+
const url = this.baseUrl ? `${this.baseUrl}${path}` : path;
|
|
5270
|
+
const startTime = Date.now();
|
|
5271
|
+
const headers = {
|
|
5272
|
+
...options?.headers || {}
|
|
5273
|
+
};
|
|
5274
|
+
if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
|
|
5275
|
+
headers["Content-Type"] = "application/json";
|
|
5276
|
+
}
|
|
5277
|
+
if (this.logger) {
|
|
5278
|
+
this.logger.logRequest({
|
|
5279
|
+
method,
|
|
5280
|
+
url,
|
|
5281
|
+
headers,
|
|
5282
|
+
body: options?.formData || options?.body,
|
|
5283
|
+
timestamp: startTime
|
|
5284
|
+
});
|
|
5285
|
+
}
|
|
5286
|
+
try {
|
|
5287
|
+
const response = await this.httpClient.request({
|
|
5288
|
+
method,
|
|
5289
|
+
url,
|
|
5290
|
+
headers,
|
|
5291
|
+
params: options?.params,
|
|
5292
|
+
body: options?.body,
|
|
5293
|
+
formData: options?.formData,
|
|
5294
|
+
binaryBody: options?.binaryBody
|
|
5295
|
+
});
|
|
5296
|
+
const duration = Date.now() - startTime;
|
|
5297
|
+
if (response.status >= 400) {
|
|
5298
|
+
const error = new APIError4(
|
|
5299
|
+
response.status,
|
|
5300
|
+
response.statusText,
|
|
5301
|
+
response.data,
|
|
5302
|
+
url
|
|
5303
|
+
);
|
|
5304
|
+
if (this.logger) {
|
|
5305
|
+
this.logger.logError(
|
|
5306
|
+
{
|
|
5307
|
+
method,
|
|
5308
|
+
url,
|
|
5309
|
+
headers,
|
|
5310
|
+
body: options?.formData || options?.body,
|
|
5311
|
+
timestamp: startTime
|
|
5312
|
+
},
|
|
5313
|
+
{
|
|
5314
|
+
message: error.message,
|
|
5315
|
+
statusCode: response.status,
|
|
5316
|
+
duration,
|
|
5317
|
+
timestamp: Date.now()
|
|
5318
|
+
}
|
|
5319
|
+
);
|
|
5320
|
+
}
|
|
5321
|
+
throw error;
|
|
5322
|
+
}
|
|
5323
|
+
if (this.logger) {
|
|
5324
|
+
this.logger.logResponse(
|
|
5325
|
+
{
|
|
5326
|
+
method,
|
|
5327
|
+
url,
|
|
5328
|
+
headers,
|
|
5329
|
+
body: options?.formData || options?.body,
|
|
5330
|
+
timestamp: startTime
|
|
5331
|
+
},
|
|
5332
|
+
{
|
|
5333
|
+
status: response.status,
|
|
5334
|
+
statusText: response.statusText,
|
|
5335
|
+
data: response.data,
|
|
5336
|
+
duration,
|
|
5337
|
+
timestamp: Date.now()
|
|
5338
|
+
}
|
|
5339
|
+
);
|
|
5340
|
+
}
|
|
5341
|
+
return response.data;
|
|
5342
|
+
} catch (error) {
|
|
5343
|
+
const duration = Date.now() - startTime;
|
|
5344
|
+
if (error instanceof APIError4) {
|
|
5345
|
+
throw error;
|
|
5346
|
+
}
|
|
5347
|
+
const isCORSError = error instanceof TypeError && (error.message.toLowerCase().includes("cors") || error.message.toLowerCase().includes("failed to fetch") || error.message.toLowerCase().includes("network request failed"));
|
|
5348
|
+
if (this.logger) {
|
|
5349
|
+
if (isCORSError) {
|
|
5350
|
+
this.logger.error(`\u{1F6AB} CORS Error: ${method} ${url}`);
|
|
5351
|
+
this.logger.error(` \u2192 ${error instanceof Error ? error.message : String(error)}`);
|
|
5352
|
+
this.logger.error(` \u2192 Configure security_domains parameter on the server`);
|
|
5353
|
+
} else {
|
|
5354
|
+
this.logger.error(`\u26A0\uFE0F Network Error: ${method} ${url}`);
|
|
5355
|
+
this.logger.error(` \u2192 ${error instanceof Error ? error.message : String(error)}`);
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
if (typeof window !== "undefined") {
|
|
5359
|
+
try {
|
|
5360
|
+
if (isCORSError) {
|
|
5361
|
+
window.dispatchEvent(new CustomEvent("cors-error", {
|
|
5362
|
+
detail: {
|
|
5363
|
+
url,
|
|
5364
|
+
method,
|
|
5365
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5366
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
5367
|
+
},
|
|
5368
|
+
bubbles: true,
|
|
5369
|
+
cancelable: false
|
|
5370
|
+
}));
|
|
5371
|
+
} else {
|
|
5372
|
+
window.dispatchEvent(new CustomEvent("network-error", {
|
|
5373
|
+
detail: {
|
|
5374
|
+
url,
|
|
5375
|
+
method,
|
|
5376
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5377
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
5378
|
+
},
|
|
5379
|
+
bubbles: true,
|
|
5380
|
+
cancelable: false
|
|
5381
|
+
}));
|
|
5382
|
+
}
|
|
5383
|
+
} catch (eventError) {
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
const networkError = error instanceof Error ? new NetworkError4(error.message, url, error) : new NetworkError4("Unknown error", url);
|
|
5387
|
+
if (this.logger) {
|
|
5388
|
+
this.logger.logError(
|
|
5389
|
+
{
|
|
5390
|
+
method,
|
|
5391
|
+
url,
|
|
5392
|
+
headers,
|
|
5393
|
+
body: options?.formData || options?.body,
|
|
5394
|
+
timestamp: startTime
|
|
5395
|
+
},
|
|
5396
|
+
{
|
|
5397
|
+
message: networkError.message,
|
|
5398
|
+
duration,
|
|
5399
|
+
timestamp: Date.now()
|
|
5400
|
+
}
|
|
5401
|
+
);
|
|
5402
|
+
}
|
|
5403
|
+
throw networkError;
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
};
|
|
5407
|
+
|
|
5408
|
+
// src/generated/cfg_totp/storage.ts
|
|
5409
|
+
var LocalStorageAdapter4 = class {
|
|
5410
|
+
static {
|
|
5411
|
+
__name(this, "LocalStorageAdapter");
|
|
5412
|
+
}
|
|
5413
|
+
constructor(logger2) {
|
|
5414
|
+
this.logger = logger2;
|
|
5415
|
+
}
|
|
5416
|
+
getItem(key) {
|
|
5417
|
+
try {
|
|
5418
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
5419
|
+
const value = localStorage.getItem(key);
|
|
5420
|
+
this.logger?.debug(`LocalStorage.getItem("${key}"): ${value ? "found" : "not found"}`);
|
|
5421
|
+
return value;
|
|
5422
|
+
}
|
|
5423
|
+
this.logger?.warn("LocalStorage not available: window.localStorage is undefined");
|
|
5424
|
+
} catch (error) {
|
|
5425
|
+
this.logger?.error("LocalStorage.getItem failed:", error);
|
|
5426
|
+
}
|
|
5427
|
+
return null;
|
|
5428
|
+
}
|
|
5429
|
+
setItem(key, value) {
|
|
5430
|
+
try {
|
|
5431
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
5432
|
+
localStorage.setItem(key, value);
|
|
5433
|
+
this.logger?.debug(`LocalStorage.setItem("${key}"): success`);
|
|
5434
|
+
} else {
|
|
5435
|
+
this.logger?.warn("LocalStorage not available: window.localStorage is undefined");
|
|
5436
|
+
}
|
|
5437
|
+
} catch (error) {
|
|
5438
|
+
this.logger?.error("LocalStorage.setItem failed:", error);
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
removeItem(key) {
|
|
5442
|
+
try {
|
|
5443
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
5444
|
+
localStorage.removeItem(key);
|
|
5445
|
+
this.logger?.debug(`LocalStorage.removeItem("${key}"): success`);
|
|
5446
|
+
} else {
|
|
5447
|
+
this.logger?.warn("LocalStorage not available: window.localStorage is undefined");
|
|
5448
|
+
}
|
|
5449
|
+
} catch (error) {
|
|
5450
|
+
this.logger?.error("LocalStorage.removeItem failed:", error);
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
};
|
|
5454
|
+
|
|
5455
|
+
// src/generated/cfg_totp/enums.ts
|
|
5456
|
+
var DeviceListStatus = /* @__PURE__ */ ((DeviceListStatus2) => {
|
|
5457
|
+
DeviceListStatus2["PENDING"] = "pending";
|
|
5458
|
+
DeviceListStatus2["ACTIVE"] = "active";
|
|
5459
|
+
DeviceListStatus2["DISABLED"] = "disabled";
|
|
5460
|
+
return DeviceListStatus2;
|
|
5461
|
+
})(DeviceListStatus || {});
|
|
5462
|
+
|
|
5463
|
+
// src/generated/cfg_totp/_utils/schemas/BackupCodesRegenerateRequest.schema.ts
|
|
5464
|
+
var import_zod60 = require("zod");
|
|
5465
|
+
var BackupCodesRegenerateRequestSchema = import_zod60.z.object({
|
|
5466
|
+
code: import_zod60.z.string().min(6).max(6)
|
|
5467
|
+
});
|
|
5468
|
+
|
|
5469
|
+
// src/generated/cfg_totp/_utils/schemas/BackupCodesRegenerateResponse.schema.ts
|
|
5470
|
+
var import_zod61 = require("zod");
|
|
5471
|
+
var BackupCodesRegenerateResponseSchema = import_zod61.z.object({
|
|
5472
|
+
backup_codes: import_zod61.z.array(import_zod61.z.string()),
|
|
5473
|
+
warning: import_zod61.z.string()
|
|
5474
|
+
});
|
|
5475
|
+
|
|
5476
|
+
// src/generated/cfg_totp/_utils/schemas/BackupCodesStatus.schema.ts
|
|
5477
|
+
var import_zod62 = require("zod");
|
|
5478
|
+
var BackupCodesStatusSchema = import_zod62.z.object({
|
|
5479
|
+
remaining_count: import_zod62.z.int(),
|
|
5480
|
+
total_generated: import_zod62.z.int(),
|
|
5481
|
+
warning: import_zod62.z.string().nullable().optional()
|
|
5482
|
+
});
|
|
5483
|
+
|
|
5484
|
+
// src/generated/cfg_totp/_utils/schemas/ConfirmSetupRequest.schema.ts
|
|
5485
|
+
var import_zod63 = require("zod");
|
|
5486
|
+
var ConfirmSetupRequestSchema = import_zod63.z.object({
|
|
5487
|
+
device_id: import_zod63.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
|
|
5488
|
+
code: import_zod63.z.string().min(6).max(6)
|
|
5489
|
+
});
|
|
5490
|
+
|
|
5491
|
+
// src/generated/cfg_totp/_utils/schemas/ConfirmSetupResponse.schema.ts
|
|
5492
|
+
var import_zod64 = require("zod");
|
|
5493
|
+
var ConfirmSetupResponseSchema = import_zod64.z.object({
|
|
5494
|
+
message: import_zod64.z.string(),
|
|
5495
|
+
backup_codes: import_zod64.z.array(import_zod64.z.string()),
|
|
5496
|
+
backup_codes_warning: import_zod64.z.string()
|
|
5497
|
+
});
|
|
5498
|
+
|
|
5499
|
+
// src/generated/cfg_totp/_utils/schemas/DeviceList.schema.ts
|
|
5500
|
+
var import_zod65 = require("zod");
|
|
5501
|
+
var DeviceListSchema = import_zod65.z.object({
|
|
5502
|
+
id: import_zod65.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
|
|
5503
|
+
name: import_zod65.z.string(),
|
|
5504
|
+
is_primary: import_zod65.z.boolean(),
|
|
5505
|
+
status: import_zod65.z.nativeEnum(DeviceListStatus),
|
|
5506
|
+
created_at: import_zod65.z.iso.datetime(),
|
|
5507
|
+
confirmed_at: import_zod65.z.iso.datetime().nullable(),
|
|
5508
|
+
last_used_at: import_zod65.z.iso.datetime().nullable()
|
|
5509
|
+
});
|
|
5510
|
+
|
|
5511
|
+
// src/generated/cfg_totp/_utils/schemas/DisableRequest.schema.ts
|
|
5512
|
+
var import_zod66 = require("zod");
|
|
5513
|
+
var DisableRequestSchema = import_zod66.z.object({
|
|
5514
|
+
code: import_zod66.z.string().min(6).max(6)
|
|
5515
|
+
});
|
|
5516
|
+
|
|
5517
|
+
// src/generated/cfg_totp/_utils/schemas/PaginatedDeviceListList.schema.ts
|
|
5518
|
+
var import_zod67 = require("zod");
|
|
5519
|
+
var PaginatedDeviceListListSchema = import_zod67.z.object({
|
|
5520
|
+
count: import_zod67.z.int(),
|
|
5521
|
+
page: import_zod67.z.int(),
|
|
5522
|
+
pages: import_zod67.z.int(),
|
|
5523
|
+
page_size: import_zod67.z.int(),
|
|
5524
|
+
has_next: import_zod67.z.boolean(),
|
|
5525
|
+
has_previous: import_zod67.z.boolean(),
|
|
5526
|
+
next_page: import_zod67.z.int().nullable().optional(),
|
|
5527
|
+
previous_page: import_zod67.z.int().nullable().optional(),
|
|
5528
|
+
results: import_zod67.z.array(DeviceListSchema)
|
|
5529
|
+
});
|
|
5530
|
+
|
|
5531
|
+
// src/generated/cfg_totp/_utils/schemas/SetupRequest.schema.ts
|
|
5532
|
+
var import_zod68 = require("zod");
|
|
5533
|
+
var SetupRequestSchema = import_zod68.z.object({
|
|
5534
|
+
device_name: import_zod68.z.string().min(1).max(100).optional()
|
|
5535
|
+
});
|
|
5536
|
+
|
|
5537
|
+
// src/generated/cfg_totp/_utils/schemas/SetupResponse.schema.ts
|
|
5538
|
+
var import_zod69 = require("zod");
|
|
5539
|
+
var SetupResponseSchema = import_zod69.z.object({
|
|
5540
|
+
device_id: import_zod69.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
|
|
5541
|
+
secret: import_zod69.z.string(),
|
|
5542
|
+
provisioning_uri: import_zod69.z.string(),
|
|
5543
|
+
qr_code_base64: import_zod69.z.string(),
|
|
5544
|
+
expires_in: import_zod69.z.int()
|
|
5545
|
+
});
|
|
5546
|
+
|
|
5547
|
+
// src/generated/cfg_totp/_utils/schemas/VerifyBackupRequest.schema.ts
|
|
5548
|
+
var import_zod70 = require("zod");
|
|
5549
|
+
var VerifyBackupRequestSchema = import_zod70.z.object({
|
|
5550
|
+
session_id: import_zod70.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
|
|
5551
|
+
backup_code: import_zod70.z.string().min(8).max(8)
|
|
5552
|
+
});
|
|
5553
|
+
|
|
5554
|
+
// src/generated/cfg_totp/_utils/schemas/VerifyRequest.schema.ts
|
|
5555
|
+
var import_zod71 = require("zod");
|
|
5556
|
+
var VerifyRequestSchema = import_zod71.z.object({
|
|
5557
|
+
session_id: import_zod71.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
|
|
5558
|
+
code: import_zod71.z.string().min(6).max(6)
|
|
5559
|
+
});
|
|
5560
|
+
|
|
5561
|
+
// src/generated/cfg_totp/_utils/schemas/VerifyResponse.schema.ts
|
|
5562
|
+
var import_zod72 = require("zod");
|
|
5563
|
+
var VerifyResponseSchema = import_zod72.z.object({
|
|
5564
|
+
message: import_zod72.z.string(),
|
|
5565
|
+
access_token: import_zod72.z.string(),
|
|
5566
|
+
refresh_token: import_zod72.z.string(),
|
|
5567
|
+
user: import_zod72.z.record(import_zod72.z.string(), import_zod72.z.any()),
|
|
5568
|
+
remaining_backup_codes: import_zod72.z.int().optional(),
|
|
5569
|
+
warning: import_zod72.z.string().optional()
|
|
5570
|
+
});
|
|
5571
|
+
|
|
5572
|
+
// src/generated/cfg_totp/_utils/fetchers/totp__backup_codes.ts
|
|
5573
|
+
var import_consola15 = require("consola");
|
|
5574
|
+
|
|
5575
|
+
// src/generated/cfg_totp/_utils/fetchers/totp__totp_management.ts
|
|
5576
|
+
var import_consola16 = require("consola");
|
|
5577
|
+
|
|
5578
|
+
// src/generated/cfg_totp/_utils/fetchers/totp__totp_setup.ts
|
|
5579
|
+
var import_consola17 = require("consola");
|
|
5580
|
+
|
|
5581
|
+
// src/generated/cfg_totp/_utils/fetchers/totp__totp_verification.ts
|
|
5582
|
+
var import_consola18 = require("consola");
|
|
5583
|
+
|
|
5584
|
+
// src/generated/cfg_totp/index.ts
|
|
5585
|
+
var TOKEN_KEY4 = "auth_token";
|
|
5586
|
+
var REFRESH_TOKEN_KEY4 = "refresh_token";
|
|
5587
|
+
var API4 = class {
|
|
5588
|
+
constructor(baseUrl, options) {
|
|
5589
|
+
this._token = null;
|
|
5590
|
+
this._refreshToken = null;
|
|
5591
|
+
this.baseUrl = baseUrl;
|
|
5592
|
+
this.options = options;
|
|
5593
|
+
const logger2 = options?.loggerConfig ? new APILogger4(options.loggerConfig) : void 0;
|
|
5594
|
+
this.storage = options?.storage || new LocalStorageAdapter4(logger2);
|
|
5595
|
+
this._loadTokensFromStorage();
|
|
5596
|
+
this._client = new APIClient4(this.baseUrl, {
|
|
5597
|
+
retryConfig: this.options?.retryConfig,
|
|
5598
|
+
loggerConfig: this.options?.loggerConfig
|
|
5599
|
+
});
|
|
5600
|
+
this._injectAuthHeader();
|
|
5601
|
+
this.backup_codes = this._client.backup_codes;
|
|
5602
|
+
this.totp_management = this._client.totp_management;
|
|
5603
|
+
this.totp_setup = this._client.totp_setup;
|
|
5604
|
+
this.totp_verification = this._client.totp_verification;
|
|
5605
|
+
this.totp = this._client.totp;
|
|
5606
|
+
}
|
|
5607
|
+
static {
|
|
5608
|
+
__name(this, "API");
|
|
5609
|
+
}
|
|
5610
|
+
_loadTokensFromStorage() {
|
|
5611
|
+
this._token = this.storage.getItem(TOKEN_KEY4);
|
|
5612
|
+
this._refreshToken = this.storage.getItem(REFRESH_TOKEN_KEY4);
|
|
5613
|
+
}
|
|
5614
|
+
_reinitClients() {
|
|
5615
|
+
this._client = new APIClient4(this.baseUrl, {
|
|
5616
|
+
retryConfig: this.options?.retryConfig,
|
|
5617
|
+
loggerConfig: this.options?.loggerConfig
|
|
5618
|
+
});
|
|
5619
|
+
this._injectAuthHeader();
|
|
5620
|
+
this.backup_codes = this._client.backup_codes;
|
|
5621
|
+
this.totp_management = this._client.totp_management;
|
|
5622
|
+
this.totp_setup = this._client.totp_setup;
|
|
5623
|
+
this.totp_verification = this._client.totp_verification;
|
|
5624
|
+
this.totp = this._client.totp;
|
|
5625
|
+
}
|
|
5626
|
+
_injectAuthHeader() {
|
|
5627
|
+
const originalRequest = this._client.request.bind(this._client);
|
|
5628
|
+
this._client.request = async (method, path, options) => {
|
|
5629
|
+
const token = this.getToken();
|
|
5630
|
+
const mergedOptions = {
|
|
5631
|
+
...options,
|
|
5632
|
+
headers: {
|
|
5633
|
+
...options?.headers || {},
|
|
5634
|
+
...token ? { "Authorization": `Bearer ${token}` } : {}
|
|
5635
|
+
}
|
|
5636
|
+
};
|
|
5637
|
+
return originalRequest(method, path, mergedOptions);
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
/**
|
|
5641
|
+
* Get current JWT token
|
|
5642
|
+
*/
|
|
5643
|
+
getToken() {
|
|
5644
|
+
return this.storage.getItem(TOKEN_KEY4);
|
|
5645
|
+
}
|
|
5646
|
+
/**
|
|
5647
|
+
* Get current refresh token
|
|
5648
|
+
*/
|
|
5649
|
+
getRefreshToken() {
|
|
5650
|
+
return this.storage.getItem(REFRESH_TOKEN_KEY4);
|
|
5651
|
+
}
|
|
5652
|
+
/**
|
|
5653
|
+
* Set JWT token and refresh token
|
|
5654
|
+
* @param token - JWT access token
|
|
5655
|
+
* @param refreshToken - JWT refresh token (optional)
|
|
5656
|
+
*/
|
|
5657
|
+
setToken(token, refreshToken) {
|
|
5658
|
+
this._token = token;
|
|
5659
|
+
this.storage.setItem(TOKEN_KEY4, token);
|
|
5660
|
+
if (refreshToken) {
|
|
5661
|
+
this._refreshToken = refreshToken;
|
|
5662
|
+
this.storage.setItem(REFRESH_TOKEN_KEY4, refreshToken);
|
|
5663
|
+
}
|
|
5664
|
+
this._reinitClients();
|
|
5665
|
+
}
|
|
5666
|
+
/**
|
|
5667
|
+
* Clear all tokens
|
|
5668
|
+
*/
|
|
5669
|
+
clearTokens() {
|
|
5670
|
+
this._token = null;
|
|
5671
|
+
this._refreshToken = null;
|
|
5672
|
+
this.storage.removeItem(TOKEN_KEY4);
|
|
5673
|
+
this.storage.removeItem(REFRESH_TOKEN_KEY4);
|
|
5674
|
+
this._reinitClients();
|
|
5675
|
+
}
|
|
5676
|
+
/**
|
|
5677
|
+
* Check if user is authenticated
|
|
5678
|
+
*/
|
|
5679
|
+
isAuthenticated() {
|
|
5680
|
+
return !!this.getToken();
|
|
5681
|
+
}
|
|
5682
|
+
/**
|
|
5683
|
+
* Update base URL and reinitialize clients
|
|
5684
|
+
* @param url - New base URL
|
|
5685
|
+
*/
|
|
5686
|
+
setBaseUrl(url) {
|
|
5687
|
+
this.baseUrl = url;
|
|
5688
|
+
this._reinitClients();
|
|
5689
|
+
}
|
|
5690
|
+
/**
|
|
5691
|
+
* Get current base URL
|
|
5692
|
+
*/
|
|
5693
|
+
getBaseUrl() {
|
|
5694
|
+
return this.baseUrl;
|
|
5695
|
+
}
|
|
5696
|
+
/**
|
|
5697
|
+
* Get OpenAPI schema path
|
|
5698
|
+
* @returns Path to the OpenAPI schema JSON file
|
|
5699
|
+
*
|
|
5700
|
+
* Note: The OpenAPI schema is available in the schema.json file.
|
|
5701
|
+
* You can load it dynamically using:
|
|
5702
|
+
* ```typescript
|
|
5703
|
+
* const schema = await fetch('./schema.json').then(r => r.json());
|
|
5704
|
+
* // or using fs in Node.js:
|
|
5705
|
+
* // const schema = JSON.parse(fs.readFileSync('./schema.json', 'utf-8'));
|
|
5706
|
+
* ```
|
|
5707
|
+
*/
|
|
5708
|
+
getSchemaPath() {
|
|
5709
|
+
return "./schema.json";
|
|
5710
|
+
}
|
|
5711
|
+
};
|
|
5712
|
+
|
|
5713
|
+
// src/generated/cfg_webpush/_utils/hooks/webpush__web_push.ts
|
|
5714
|
+
var import_swr7 = __toESM(require("swr"), 1);
|
|
5715
|
+
var import_swr8 = require("swr");
|
|
5716
|
+
|
|
5717
|
+
// src/generated/cfg_centrifugo/_utils/hooks/centrifugo__centrifugo_admin_api.ts
|
|
5718
|
+
var import_swr9 = require("swr");
|
|
5719
|
+
|
|
5720
|
+
// src/generated/cfg_centrifugo/_utils/hooks/centrifugo__centrifugo_auth.ts
|
|
5721
|
+
var import_swr10 = __toESM(require("swr"), 1);
|
|
5722
|
+
|
|
5723
|
+
// src/generated/cfg_centrifugo/_utils/hooks/centrifugo__centrifugo_monitoring.ts
|
|
5724
|
+
var import_swr11 = __toESM(require("swr"), 1);
|
|
5725
|
+
|
|
5726
|
+
// src/generated/cfg_centrifugo/_utils/hooks/centrifugo__centrifugo_testing.ts
|
|
5727
|
+
var import_swr12 = require("swr");
|
|
5728
|
+
|
|
5729
|
+
// src/generated/cfg_totp/_utils/hooks/totp__backup_codes.ts
|
|
5730
|
+
var import_swr13 = __toESM(require("swr"), 1);
|
|
5731
|
+
var import_swr14 = require("swr");
|
|
5732
|
+
|
|
5733
|
+
// src/generated/cfg_totp/_utils/hooks/totp__totp_management.ts
|
|
5734
|
+
var import_swr15 = __toESM(require("swr"), 1);
|
|
5735
|
+
var import_swr16 = require("swr");
|
|
5736
|
+
|
|
5737
|
+
// src/generated/cfg_totp/_utils/hooks/totp__totp_setup.ts
|
|
5738
|
+
var import_swr17 = require("swr");
|
|
5739
|
+
|
|
5740
|
+
// src/generated/cfg_totp/_utils/hooks/totp__totp_verification.ts
|
|
5741
|
+
var import_swr18 = require("swr");
|
|
5742
|
+
|
|
5743
|
+
// src/generated/cfg_totp/_utils/hooks/totp.ts
|
|
5744
|
+
var import_swr19 = require("swr");
|
|
5745
|
+
|
|
5746
|
+
// src/clients.ts
|
|
5747
|
+
var isStaticBuild3 = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
5748
|
+
var apiUrl2 = isStaticBuild3 ? "" : process.env.NEXT_PUBLIC_API_URL || "";
|
|
5749
|
+
var storage = new LocalStorageAdapter();
|
|
5750
|
+
var apiAccounts = new API(apiUrl2, { storage });
|
|
5751
|
+
var apiTotp = new API4(apiUrl2, { storage });
|
|
5752
|
+
var apiWebPush = new API3(apiUrl2, { storage });
|
|
5753
|
+
var apiCentrifugo = new API2(apiUrl2, { storage });
|
|
5754
|
+
|
|
5755
|
+
// src/auth/hooks/useTwoFactor.ts
|
|
5756
|
+
var useTwoFactor = /* @__PURE__ */ __name((options = {}) => {
|
|
5757
|
+
const { onSuccess, onError, redirectUrl, skipRedirect = false } = options;
|
|
5758
|
+
const router = (0, import_hooks4.useCfgRouter)();
|
|
5759
|
+
const [isLoading, setIsLoading] = (0, import_react7.useState)(false);
|
|
5760
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
5761
|
+
const [warning, setWarning] = (0, import_react7.useState)(null);
|
|
5762
|
+
const [remainingBackupCodes, setRemainingBackupCodes] = (0, import_react7.useState)(null);
|
|
5763
|
+
const clearError = (0, import_react7.useCallback)(() => {
|
|
5764
|
+
setError(null);
|
|
5765
|
+
}, []);
|
|
5766
|
+
const handleSuccess = (0, import_react7.useCallback)((response) => {
|
|
5767
|
+
apiAccounts.setToken(response.access_token, response.refresh_token);
|
|
5768
|
+
if (response.warning) {
|
|
5769
|
+
setWarning(response.warning);
|
|
5770
|
+
}
|
|
5771
|
+
if (response.remaining_backup_codes !== void 0) {
|
|
5772
|
+
setRemainingBackupCodes(response.remaining_backup_codes);
|
|
5773
|
+
}
|
|
5774
|
+
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
5775
|
+
category: "auth" /* AUTH */,
|
|
5776
|
+
label: "2fa"
|
|
5777
|
+
});
|
|
5778
|
+
if (response.user?.id) {
|
|
5779
|
+
Analytics.setUser(String(response.user.id));
|
|
5780
|
+
}
|
|
5781
|
+
onSuccess?.(response.user);
|
|
5782
|
+
if (!skipRedirect) {
|
|
5783
|
+
const finalRedirectUrl = redirectUrl || "/dashboard";
|
|
5784
|
+
authLogger.info("2FA successful, redirecting to:", finalRedirectUrl);
|
|
5785
|
+
router.hardPush(finalRedirectUrl);
|
|
5786
|
+
}
|
|
5787
|
+
}, [onSuccess, redirectUrl, router, skipRedirect]);
|
|
5788
|
+
const verifyTOTP = (0, import_react7.useCallback)(async (sessionId, code) => {
|
|
5789
|
+
if (!sessionId) {
|
|
5790
|
+
const msg = "Missing 2FA session ID";
|
|
5791
|
+
setError(msg);
|
|
5792
|
+
onError?.(msg);
|
|
5793
|
+
return false;
|
|
5794
|
+
}
|
|
5795
|
+
if (!code || code.length !== 6) {
|
|
5796
|
+
const msg = "Please enter a 6-digit code";
|
|
5797
|
+
setError(msg);
|
|
5798
|
+
onError?.(msg);
|
|
5799
|
+
return false;
|
|
5800
|
+
}
|
|
5801
|
+
setIsLoading(true);
|
|
5802
|
+
setError(null);
|
|
5803
|
+
try {
|
|
5804
|
+
authLogger.info("Verifying TOTP code...");
|
|
5805
|
+
const response = await apiTotp.totp_verification.totpVerifyCreate({
|
|
5806
|
+
session_id: sessionId,
|
|
5807
|
+
code
|
|
5808
|
+
});
|
|
5809
|
+
if (!response.access_token || !response.refresh_token) {
|
|
5810
|
+
throw new Error("Invalid response from 2FA verification");
|
|
5811
|
+
}
|
|
5812
|
+
handleSuccess(response);
|
|
5813
|
+
return true;
|
|
5814
|
+
} catch (err) {
|
|
5815
|
+
const errorMessage = err instanceof Error ? err.message : "Invalid verification code";
|
|
5816
|
+
authLogger.error("2FA TOTP verification error:", err);
|
|
5817
|
+
setError(errorMessage);
|
|
5818
|
+
onError?.(errorMessage);
|
|
5819
|
+
Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
|
|
5820
|
+
category: "auth" /* AUTH */,
|
|
5821
|
+
label: "2fa-totp"
|
|
5822
|
+
});
|
|
5823
|
+
return false;
|
|
5824
|
+
} finally {
|
|
5825
|
+
setIsLoading(false);
|
|
5826
|
+
}
|
|
5827
|
+
}, [handleSuccess, onError]);
|
|
5828
|
+
const verifyBackupCode = (0, import_react7.useCallback)(async (sessionId, backupCode) => {
|
|
5829
|
+
if (!sessionId) {
|
|
5830
|
+
const msg = "Missing 2FA session ID";
|
|
5831
|
+
setError(msg);
|
|
5832
|
+
onError?.(msg);
|
|
5833
|
+
return false;
|
|
5834
|
+
}
|
|
5835
|
+
if (!backupCode || backupCode.length < 8) {
|
|
5836
|
+
const msg = "Please enter your backup code";
|
|
5837
|
+
setError(msg);
|
|
5838
|
+
onError?.(msg);
|
|
5839
|
+
return false;
|
|
5840
|
+
}
|
|
5841
|
+
setIsLoading(true);
|
|
5842
|
+
setError(null);
|
|
5843
|
+
try {
|
|
5844
|
+
authLogger.info("Verifying backup code...");
|
|
5845
|
+
const response = await apiTotp.totp_verification.totpVerifyBackupCreate({
|
|
5846
|
+
session_id: sessionId,
|
|
5847
|
+
backup_code: backupCode.replace(/\s+/g, "")
|
|
5848
|
+
// Remove spaces
|
|
5849
|
+
});
|
|
5850
|
+
if (!response.access_token || !response.refresh_token) {
|
|
5851
|
+
throw new Error("Invalid response from backup code verification");
|
|
5852
|
+
}
|
|
5853
|
+
handleSuccess(response);
|
|
5854
|
+
return true;
|
|
5855
|
+
} catch (err) {
|
|
5856
|
+
const errorMessage = err instanceof Error ? err.message : "Invalid backup code";
|
|
5857
|
+
authLogger.error("2FA backup code verification error:", err);
|
|
5858
|
+
setError(errorMessage);
|
|
5859
|
+
onError?.(errorMessage);
|
|
5860
|
+
Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
|
|
5861
|
+
category: "auth" /* AUTH */,
|
|
5862
|
+
label: "2fa-backup"
|
|
5863
|
+
});
|
|
5864
|
+
return false;
|
|
5865
|
+
} finally {
|
|
5866
|
+
setIsLoading(false);
|
|
5867
|
+
}
|
|
5868
|
+
}, [handleSuccess, onError]);
|
|
5869
|
+
return {
|
|
5870
|
+
isLoading,
|
|
5871
|
+
error,
|
|
5872
|
+
warning,
|
|
5873
|
+
remainingBackupCodes,
|
|
5874
|
+
verifyTOTP,
|
|
5875
|
+
verifyBackupCode,
|
|
5876
|
+
clearError
|
|
5877
|
+
};
|
|
5878
|
+
}, "useTwoFactor");
|
|
5879
|
+
|
|
5880
|
+
// src/auth/hooks/useAuthForm.ts
|
|
5881
|
+
var useAuthForm = /* @__PURE__ */ __name((options) => {
|
|
5882
|
+
const {
|
|
5883
|
+
onIdentifierSuccess,
|
|
5884
|
+
onOTPSuccess,
|
|
5885
|
+
onError,
|
|
5886
|
+
sourceUrl,
|
|
5887
|
+
redirectUrl,
|
|
5888
|
+
requireTermsAcceptance = false,
|
|
5889
|
+
authPath = "/auth"
|
|
5890
|
+
} = options;
|
|
5891
|
+
const formState = useAuthFormState();
|
|
5892
|
+
const validation = useAuthValidation();
|
|
5893
|
+
const isAutoSubmitFromUrlRef = (0, import_react8.useRef)(false);
|
|
5894
|
+
const {
|
|
5895
|
+
requestOTP,
|
|
5896
|
+
verifyOTP,
|
|
5897
|
+
getSavedEmail,
|
|
5898
|
+
saveEmail,
|
|
5899
|
+
clearSavedEmail,
|
|
5900
|
+
getSavedPhone,
|
|
5901
|
+
savePhone,
|
|
5902
|
+
clearSavedPhone
|
|
5903
|
+
} = useAuth();
|
|
5904
|
+
const {
|
|
5905
|
+
identifier,
|
|
5906
|
+
channel,
|
|
5907
|
+
otp,
|
|
5908
|
+
isLoading,
|
|
5909
|
+
acceptedTerms,
|
|
5910
|
+
twoFactorSessionId,
|
|
5911
|
+
twoFactorCode,
|
|
5912
|
+
useBackupCode,
|
|
5913
|
+
setIdentifier,
|
|
5914
|
+
setChannel,
|
|
5915
|
+
setOtp,
|
|
5916
|
+
setStep,
|
|
5917
|
+
setError,
|
|
5918
|
+
setIsLoading,
|
|
5919
|
+
clearError,
|
|
5920
|
+
setTwoFactorSessionId,
|
|
5921
|
+
setShouldPrompt2FA,
|
|
5922
|
+
setTwoFactorCode,
|
|
5923
|
+
setUseBackupCode
|
|
5924
|
+
} = formState;
|
|
5925
|
+
const twoFactor = useTwoFactor({
|
|
5926
|
+
onSuccess: /* @__PURE__ */ __name(() => {
|
|
5927
|
+
authLogger.info("2FA verification successful, showing success screen");
|
|
5928
|
+
setStep("success");
|
|
5929
|
+
onOTPSuccess?.();
|
|
5930
|
+
}, "onSuccess"),
|
|
5931
|
+
onError: /* @__PURE__ */ __name((error) => {
|
|
5932
|
+
setError(error);
|
|
5933
|
+
onError?.(error);
|
|
5934
|
+
}, "onError"),
|
|
5935
|
+
redirectUrl,
|
|
5936
|
+
skipRedirect: true
|
|
5937
|
+
// We handle navigation via success step
|
|
5938
|
+
});
|
|
5939
|
+
const { detectChannelFromIdentifier: detectChannelFromIdentifier2, validateIdentifier: validateIdentifier2 } = validation;
|
|
5940
|
+
const saveIdentifierToStorage = (0, import_react8.useCallback)((id, ch) => {
|
|
5941
|
+
if (ch === "email") {
|
|
5942
|
+
saveEmail(id);
|
|
5943
|
+
clearSavedPhone();
|
|
5944
|
+
} else {
|
|
5945
|
+
savePhone(id);
|
|
5946
|
+
clearSavedEmail();
|
|
5947
|
+
}
|
|
5948
|
+
}, [saveEmail, savePhone, clearSavedEmail, clearSavedPhone]);
|
|
5949
|
+
(0, import_react8.useEffect)(() => {
|
|
5950
|
+
const savedPhone = getSavedPhone();
|
|
5951
|
+
const savedEmail = getSavedEmail();
|
|
5952
|
+
if (savedPhone) {
|
|
5953
|
+
setIdentifier(savedPhone);
|
|
5954
|
+
setChannel("phone");
|
|
5955
|
+
} else if (savedEmail) {
|
|
5956
|
+
setIdentifier(savedEmail);
|
|
5957
|
+
setChannel("email");
|
|
5958
|
+
}
|
|
5959
|
+
}, [getSavedEmail, getSavedPhone, setIdentifier, setChannel]);
|
|
5960
|
+
(0, import_react8.useEffect)(() => {
|
|
5961
|
+
if (identifier) {
|
|
5962
|
+
const detected = detectChannelFromIdentifier2(identifier);
|
|
5963
|
+
if (detected && detected !== channel) {
|
|
5964
|
+
setChannel(detected);
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
}, [identifier, channel, detectChannelFromIdentifier2, setChannel]);
|
|
5968
|
+
const handleIdentifierSubmit = (0, import_react8.useCallback)(async (e) => {
|
|
5969
|
+
e.preventDefault();
|
|
5970
|
+
if (!identifier) {
|
|
5971
|
+
const msg = channel === "phone" ? "Please enter your phone number" : "Please enter your email address";
|
|
5972
|
+
setError(msg);
|
|
5973
|
+
onError?.(msg);
|
|
5974
|
+
return;
|
|
5975
|
+
}
|
|
5976
|
+
if (!validateIdentifier2(identifier, channel)) {
|
|
5977
|
+
const msg = channel === "phone" ? "Please enter a valid phone number (e.g., +1234567890)" : "Please enter a valid email address";
|
|
5978
|
+
setError(msg);
|
|
5979
|
+
onError?.(msg);
|
|
5980
|
+
return;
|
|
4835
5981
|
}
|
|
4836
5982
|
if (requireTermsAcceptance && !acceptedTerms) {
|
|
4837
|
-
const
|
|
4838
|
-
setError(
|
|
4839
|
-
onError?.(
|
|
5983
|
+
const msg = "Please accept the Terms of Service and Privacy Policy";
|
|
5984
|
+
setError(msg);
|
|
5985
|
+
onError?.(msg);
|
|
4840
5986
|
return;
|
|
4841
5987
|
}
|
|
4842
5988
|
setIsLoading(true);
|
|
@@ -4844,185 +5990,206 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
|
|
|
4844
5990
|
try {
|
|
4845
5991
|
const result = await requestOTP(identifier, channel, sourceUrl);
|
|
4846
5992
|
if (result.success) {
|
|
4847
|
-
|
|
4848
|
-
saveEmail(identifier);
|
|
4849
|
-
setSavedPhone("");
|
|
4850
|
-
} else if (channel === "phone") {
|
|
4851
|
-
savePhone(identifier);
|
|
4852
|
-
setSavedEmail("");
|
|
4853
|
-
}
|
|
4854
|
-
setSavedTermsAccepted(true);
|
|
5993
|
+
saveIdentifierToStorage(identifier, channel);
|
|
4855
5994
|
setStep("otp");
|
|
4856
5995
|
onIdentifierSuccess?.(identifier, channel);
|
|
4857
5996
|
} else {
|
|
4858
5997
|
setError(result.message);
|
|
4859
5998
|
onError?.(result.message);
|
|
4860
5999
|
}
|
|
4861
|
-
} catch
|
|
4862
|
-
const
|
|
4863
|
-
setError(
|
|
4864
|
-
onError?.(
|
|
6000
|
+
} catch {
|
|
6001
|
+
const msg = "An unexpected error occurred";
|
|
6002
|
+
setError(msg);
|
|
6003
|
+
onError?.(msg);
|
|
4865
6004
|
} finally {
|
|
4866
6005
|
setIsLoading(false);
|
|
4867
6006
|
}
|
|
4868
|
-
}, [
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
6007
|
+
}, [
|
|
6008
|
+
identifier,
|
|
6009
|
+
channel,
|
|
6010
|
+
acceptedTerms,
|
|
6011
|
+
requireTermsAcceptance,
|
|
6012
|
+
validateIdentifier2,
|
|
6013
|
+
requestOTP,
|
|
6014
|
+
saveIdentifierToStorage,
|
|
6015
|
+
setError,
|
|
6016
|
+
setIsLoading,
|
|
6017
|
+
setStep,
|
|
6018
|
+
clearError,
|
|
6019
|
+
onIdentifierSuccess,
|
|
6020
|
+
onError,
|
|
6021
|
+
sourceUrl
|
|
6022
|
+
]);
|
|
6023
|
+
const submitOTP = (0, import_react8.useCallback)(async (submitIdentifier, submitOtp, submitChannel) => {
|
|
6024
|
+
if (!submitOtp || submitOtp.length < 6) {
|
|
6025
|
+
const msg = "Please enter the 6-digit verification code";
|
|
6026
|
+
setError(msg);
|
|
6027
|
+
onError?.(msg);
|
|
6028
|
+
return false;
|
|
4876
6029
|
}
|
|
4877
6030
|
setIsLoading(true);
|
|
4878
6031
|
clearError();
|
|
4879
6032
|
try {
|
|
4880
|
-
const result = await verifyOTP(
|
|
6033
|
+
const result = await verifyOTP(submitIdentifier, submitOtp, submitChannel, sourceUrl, redirectUrl, true);
|
|
6034
|
+
if (result.requires_2fa && result.session_id) {
|
|
6035
|
+
authLogger.info("2FA required after OTP verification");
|
|
6036
|
+
setTwoFactorSessionId(result.session_id);
|
|
6037
|
+
setShouldPrompt2FA(result.should_prompt_2fa || false);
|
|
6038
|
+
setStep("2fa");
|
|
6039
|
+
saveIdentifierToStorage(submitIdentifier, submitChannel);
|
|
6040
|
+
return true;
|
|
6041
|
+
}
|
|
4881
6042
|
if (result.success) {
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
6043
|
+
saveIdentifierToStorage(submitIdentifier, submitChannel);
|
|
6044
|
+
if (result.should_prompt_2fa) {
|
|
6045
|
+
authLogger.info("OTP verification successful, prompting 2FA setup");
|
|
6046
|
+
setShouldPrompt2FA(true);
|
|
6047
|
+
setStep("2fa-setup");
|
|
6048
|
+
onOTPSuccess?.();
|
|
6049
|
+
return true;
|
|
4888
6050
|
}
|
|
6051
|
+
authLogger.info("OTP verification successful, showing success screen");
|
|
6052
|
+
setStep("success");
|
|
4889
6053
|
onOTPSuccess?.();
|
|
6054
|
+
return true;
|
|
4890
6055
|
} else {
|
|
4891
6056
|
setError(result.message);
|
|
4892
6057
|
onError?.(result.message);
|
|
6058
|
+
return false;
|
|
4893
6059
|
}
|
|
4894
|
-
} catch
|
|
4895
|
-
const
|
|
4896
|
-
setError(
|
|
4897
|
-
onError?.(
|
|
6060
|
+
} catch {
|
|
6061
|
+
const msg = "An unexpected error occurred";
|
|
6062
|
+
setError(msg);
|
|
6063
|
+
onError?.(msg);
|
|
6064
|
+
return false;
|
|
4898
6065
|
} finally {
|
|
4899
6066
|
setIsLoading(false);
|
|
4900
6067
|
}
|
|
4901
|
-
}, [
|
|
4902
|
-
const
|
|
6068
|
+
}, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, setTwoFactorSessionId, setShouldPrompt2FA, setStep]);
|
|
6069
|
+
const handleOTPSubmit = (0, import_react8.useCallback)(async (e) => {
|
|
6070
|
+
e.preventDefault();
|
|
6071
|
+
await submitOTP(identifier, otp, channel);
|
|
6072
|
+
}, [identifier, otp, channel, submitOTP]);
|
|
6073
|
+
const handleResendOTP = (0, import_react8.useCallback)(async () => {
|
|
4903
6074
|
setIsLoading(true);
|
|
4904
6075
|
clearError();
|
|
4905
6076
|
try {
|
|
4906
6077
|
const result = await requestOTP(identifier, channel, sourceUrl);
|
|
4907
6078
|
if (result.success) {
|
|
4908
|
-
|
|
4909
|
-
saveEmail(identifier);
|
|
4910
|
-
setSavedPhone("");
|
|
4911
|
-
} else if (channel === "phone") {
|
|
4912
|
-
savePhone(identifier);
|
|
4913
|
-
setSavedEmail("");
|
|
4914
|
-
}
|
|
6079
|
+
saveIdentifierToStorage(identifier, channel);
|
|
4915
6080
|
setOtp("");
|
|
4916
6081
|
} else {
|
|
4917
6082
|
setError(result.message);
|
|
4918
6083
|
onError?.(result.message);
|
|
4919
6084
|
}
|
|
4920
|
-
} catch
|
|
4921
|
-
const
|
|
4922
|
-
setError(
|
|
4923
|
-
onError?.(
|
|
6085
|
+
} catch {
|
|
6086
|
+
const msg = "Failed to resend verification code";
|
|
6087
|
+
setError(msg);
|
|
6088
|
+
onError?.(msg);
|
|
4924
6089
|
} finally {
|
|
4925
6090
|
setIsLoading(false);
|
|
4926
6091
|
}
|
|
4927
|
-
}, [identifier, channel, requestOTP,
|
|
4928
|
-
const handleBackToIdentifier = (0,
|
|
6092
|
+
}, [identifier, channel, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, onError, sourceUrl]);
|
|
6093
|
+
const handleBackToIdentifier = (0, import_react8.useCallback)(() => {
|
|
4929
6094
|
setStep("identifier");
|
|
4930
6095
|
clearError();
|
|
4931
|
-
}, [clearError]);
|
|
4932
|
-
const forceOTPStep = (0,
|
|
6096
|
+
}, [setStep, clearError]);
|
|
6097
|
+
const forceOTPStep = (0, import_react8.useCallback)(() => {
|
|
4933
6098
|
setStep("otp");
|
|
4934
6099
|
clearError();
|
|
4935
|
-
}, [clearError]);
|
|
4936
|
-
const
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
6100
|
+
}, [setStep, clearError]);
|
|
6101
|
+
const handle2FASubmit = (0, import_react8.useCallback)(async (e) => {
|
|
6102
|
+
e.preventDefault();
|
|
6103
|
+
if (!twoFactorSessionId) {
|
|
6104
|
+
const msg = "Missing 2FA session";
|
|
6105
|
+
setError(msg);
|
|
6106
|
+
onError?.(msg);
|
|
6107
|
+
return;
|
|
6108
|
+
}
|
|
6109
|
+
if (useBackupCode) {
|
|
6110
|
+
await twoFactor.verifyBackupCode(twoFactorSessionId, twoFactorCode);
|
|
6111
|
+
} else {
|
|
6112
|
+
await twoFactor.verifyTOTP(twoFactorSessionId, twoFactorCode);
|
|
6113
|
+
}
|
|
6114
|
+
}, [twoFactorSessionId, twoFactorCode, useBackupCode, twoFactor, setError, onError]);
|
|
6115
|
+
const handleUseBackupCode = (0, import_react8.useCallback)(() => {
|
|
6116
|
+
setUseBackupCode(true);
|
|
6117
|
+
setTwoFactorCode("");
|
|
6118
|
+
clearError();
|
|
6119
|
+
}, [setUseBackupCode, setTwoFactorCode, clearError]);
|
|
6120
|
+
const handleUseTOTP = (0, import_react8.useCallback)(() => {
|
|
6121
|
+
setUseBackupCode(false);
|
|
6122
|
+
setTwoFactorCode("");
|
|
6123
|
+
clearError();
|
|
6124
|
+
}, [setUseBackupCode, setTwoFactorCode, clearError]);
|
|
4940
6125
|
useAutoAuth({
|
|
4941
|
-
allowedPaths: [authPath],
|
|
4942
|
-
onOTPDetected: /* @__PURE__ */ __name((
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
//
|
|
6126
|
+
allowedPaths: [authPath],
|
|
6127
|
+
onOTPDetected: /* @__PURE__ */ __name((detectedOtp) => {
|
|
6128
|
+
if (isAutoSubmitFromUrlRef.current || isLoading) return;
|
|
6129
|
+
isAutoSubmitFromUrlRef.current = true;
|
|
6130
|
+
authLogger.info("OTP detected from URL, auto-submitting");
|
|
6131
|
+
const savedPhone = getSavedPhone();
|
|
6132
|
+
const savedEmail = getSavedEmail();
|
|
6133
|
+
let autoIdentifier = "";
|
|
6134
|
+
let autoChannel = "email";
|
|
6135
|
+
if (savedPhone) {
|
|
6136
|
+
autoIdentifier = savedPhone;
|
|
6137
|
+
autoChannel = "phone";
|
|
6138
|
+
} else if (savedEmail) {
|
|
6139
|
+
autoIdentifier = savedEmail;
|
|
6140
|
+
autoChannel = "email";
|
|
6141
|
+
}
|
|
6142
|
+
if (!autoIdentifier) {
|
|
6143
|
+
authLogger.warn("No saved identifier found for auto-submit");
|
|
6144
|
+
isAutoSubmitFromUrlRef.current = false;
|
|
6145
|
+
return;
|
|
6146
|
+
}
|
|
6147
|
+
setIdentifier(autoIdentifier);
|
|
6148
|
+
setChannel(autoChannel);
|
|
6149
|
+
setOtp(detectedOtp);
|
|
6150
|
+
setStep("otp");
|
|
6151
|
+
setTimeout(async () => {
|
|
6152
|
+
try {
|
|
6153
|
+
await submitOTP(autoIdentifier, detectedOtp, autoChannel);
|
|
6154
|
+
} finally {
|
|
6155
|
+
isAutoSubmitFromUrlRef.current = false;
|
|
6156
|
+
}
|
|
6157
|
+
}, 100);
|
|
6158
|
+
}, "onOTPDetected"),
|
|
6159
|
+
cleanupUrl: true
|
|
6160
|
+
});
|
|
6161
|
+
return {
|
|
6162
|
+
// State
|
|
6163
|
+
...formState,
|
|
6164
|
+
// Submit handlers
|
|
4980
6165
|
handleIdentifierSubmit,
|
|
4981
6166
|
handleOTPSubmit,
|
|
4982
6167
|
handleResendOTP,
|
|
4983
6168
|
handleBackToIdentifier,
|
|
4984
6169
|
forceOTPStep,
|
|
4985
|
-
//
|
|
4986
|
-
|
|
4987
|
-
|
|
6170
|
+
// 2FA handlers
|
|
6171
|
+
handle2FASubmit,
|
|
6172
|
+
handleUseBackupCode,
|
|
6173
|
+
handleUseTOTP,
|
|
6174
|
+
// Validation
|
|
6175
|
+
...validation,
|
|
6176
|
+
// Auto-submit ref
|
|
6177
|
+
isAutoSubmittingFromUrl: isAutoSubmitFromUrlRef,
|
|
6178
|
+
// 2FA state from hook (for loading indicator)
|
|
6179
|
+
is2FALoading: twoFactor.isLoading,
|
|
6180
|
+
twoFactorWarning: twoFactor.warning
|
|
4988
6181
|
};
|
|
4989
6182
|
}, "useAuthForm");
|
|
4990
6183
|
|
|
4991
6184
|
// src/auth/hooks/useGithubAuth.ts
|
|
4992
|
-
var
|
|
6185
|
+
var import_react9 = require("react");
|
|
4993
6186
|
var import_hooks5 = require("@djangocfg/ui-nextjs/hooks");
|
|
4994
|
-
|
|
4995
|
-
// src/generated/cfg_webpush/_utils/hooks/webpush__web_push.ts
|
|
4996
|
-
var import_swr7 = __toESM(require("swr"), 1);
|
|
4997
|
-
var import_swr8 = require("swr");
|
|
4998
|
-
|
|
4999
|
-
// src/generated/cfg_centrifugo/_utils/hooks/centrifugo__centrifugo_admin_api.ts
|
|
5000
|
-
var import_swr9 = require("swr");
|
|
5001
|
-
|
|
5002
|
-
// src/generated/cfg_centrifugo/_utils/hooks/centrifugo__centrifugo_auth.ts
|
|
5003
|
-
var import_swr10 = __toESM(require("swr"), 1);
|
|
5004
|
-
|
|
5005
|
-
// src/generated/cfg_centrifugo/_utils/hooks/centrifugo__centrifugo_monitoring.ts
|
|
5006
|
-
var import_swr11 = __toESM(require("swr"), 1);
|
|
5007
|
-
|
|
5008
|
-
// src/generated/cfg_centrifugo/_utils/hooks/centrifugo__centrifugo_testing.ts
|
|
5009
|
-
var import_swr12 = require("swr");
|
|
5010
|
-
|
|
5011
|
-
// src/clients.ts
|
|
5012
|
-
var isStaticBuild3 = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
5013
|
-
var apiUrl2 = isStaticBuild3 ? "" : process.env.NEXT_PUBLIC_API_URL || "";
|
|
5014
|
-
var storage = new LocalStorageAdapter();
|
|
5015
|
-
var apiAccounts = new API(apiUrl2, { storage });
|
|
5016
|
-
var apiWebPush = new API3(apiUrl2, { storage });
|
|
5017
|
-
var apiCentrifugo = new API2(apiUrl2, { storage });
|
|
5018
|
-
|
|
5019
|
-
// src/auth/hooks/useGithubAuth.ts
|
|
5020
6187
|
var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
5021
|
-
const { sourceUrl, onSuccess, onError, redirectUrl } = options;
|
|
6188
|
+
const { sourceUrl, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
|
|
5022
6189
|
const router = (0, import_hooks5.useCfgRouter)();
|
|
5023
|
-
const [isLoading, setIsLoading] = (0,
|
|
5024
|
-
const [error, setError] = (0,
|
|
5025
|
-
const startGithubAuth = (0,
|
|
6190
|
+
const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
|
|
6191
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
6192
|
+
const startGithubAuth = (0, import_react9.useCallback)(async () => {
|
|
5026
6193
|
setIsLoading(true);
|
|
5027
6194
|
setError(null);
|
|
5028
6195
|
try {
|
|
@@ -5056,7 +6223,7 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
|
5056
6223
|
setIsLoading(false);
|
|
5057
6224
|
}
|
|
5058
6225
|
}, [sourceUrl, onError]);
|
|
5059
|
-
const handleGithubCallback = (0,
|
|
6226
|
+
const handleGithubCallback = (0, import_react9.useCallback)(async (code, state) => {
|
|
5060
6227
|
setIsLoading(true);
|
|
5061
6228
|
setError(null);
|
|
5062
6229
|
try {
|
|
@@ -5073,6 +6240,15 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
|
5073
6240
|
code,
|
|
5074
6241
|
state
|
|
5075
6242
|
});
|
|
6243
|
+
if (response.requires_2fa && response.session_id) {
|
|
6244
|
+
authLogger.info("GitHub OAuth requires 2FA, session:", response.session_id);
|
|
6245
|
+
Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
|
|
6246
|
+
category: "auth" /* AUTH */,
|
|
6247
|
+
label: "github-2fa-required"
|
|
6248
|
+
});
|
|
6249
|
+
onRequires2FA?.(response.session_id, response.should_prompt_2fa || false);
|
|
6250
|
+
return;
|
|
6251
|
+
}
|
|
5076
6252
|
if (!response.access || !response.refresh) {
|
|
5077
6253
|
throw new Error("Invalid response from OAuth callback");
|
|
5078
6254
|
}
|
|
@@ -5086,8 +6262,10 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
|
5086
6262
|
Analytics.setUser(String(response.user.id));
|
|
5087
6263
|
}
|
|
5088
6264
|
onSuccess?.(response.user, response.is_new_user || false);
|
|
5089
|
-
|
|
5090
|
-
|
|
6265
|
+
if (!skipRedirect) {
|
|
6266
|
+
const finalRedirectUrl = redirectUrl || "/dashboard";
|
|
6267
|
+
router.hardPush(finalRedirectUrl);
|
|
6268
|
+
}
|
|
5091
6269
|
} catch (err) {
|
|
5092
6270
|
const errorMessage = err instanceof Error ? err.message : "GitHub authentication failed";
|
|
5093
6271
|
authLogger.error("GitHub OAuth callback error:", err);
|
|
@@ -5109,6 +6287,291 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
|
5109
6287
|
};
|
|
5110
6288
|
}, "useGithubAuth");
|
|
5111
6289
|
|
|
6290
|
+
// src/auth/hooks/useTwoFactorSetup.ts
|
|
6291
|
+
var import_react10 = require("react");
|
|
6292
|
+
var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
|
|
6293
|
+
const { onComplete, onError } = options;
|
|
6294
|
+
const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
|
|
6295
|
+
const [error, setError] = (0, import_react10.useState)(null);
|
|
6296
|
+
const [setupData, setSetupData] = (0, import_react10.useState)(null);
|
|
6297
|
+
const [backupCodes, setBackupCodes] = (0, import_react10.useState)(null);
|
|
6298
|
+
const [backupCodesWarning, setBackupCodesWarning] = (0, import_react10.useState)(null);
|
|
6299
|
+
const [setupStep, setSetupStep] = (0, import_react10.useState)("idle");
|
|
6300
|
+
const clearError = (0, import_react10.useCallback)(() => {
|
|
6301
|
+
setError(null);
|
|
6302
|
+
}, []);
|
|
6303
|
+
const resetSetup = (0, import_react10.useCallback)(() => {
|
|
6304
|
+
setSetupData(null);
|
|
6305
|
+
setBackupCodes(null);
|
|
6306
|
+
setBackupCodesWarning(null);
|
|
6307
|
+
setSetupStep("idle");
|
|
6308
|
+
setError(null);
|
|
6309
|
+
}, []);
|
|
6310
|
+
const startSetup = (0, import_react10.useCallback)(async (deviceName) => {
|
|
6311
|
+
setIsLoading(true);
|
|
6312
|
+
setError(null);
|
|
6313
|
+
setSetupStep("scanning");
|
|
6314
|
+
try {
|
|
6315
|
+
authLogger.info("Starting 2FA setup...");
|
|
6316
|
+
const response = await apiTotp.totp_setup.create({
|
|
6317
|
+
device_name: deviceName
|
|
6318
|
+
});
|
|
6319
|
+
const data = {
|
|
6320
|
+
deviceId: response.device_id,
|
|
6321
|
+
secret: response.secret,
|
|
6322
|
+
provisioningUri: response.provisioning_uri,
|
|
6323
|
+
qrCodeBase64: response.qr_code_base64,
|
|
6324
|
+
expiresIn: response.expires_in
|
|
6325
|
+
};
|
|
6326
|
+
setSetupData(data);
|
|
6327
|
+
authLogger.info("2FA setup initiated, expires in:", data.expiresIn, "seconds");
|
|
6328
|
+
return data;
|
|
6329
|
+
} catch (err) {
|
|
6330
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to start 2FA setup";
|
|
6331
|
+
authLogger.error("2FA setup error:", err);
|
|
6332
|
+
setError(errorMessage);
|
|
6333
|
+
setSetupStep("idle");
|
|
6334
|
+
onError?.(errorMessage);
|
|
6335
|
+
return null;
|
|
6336
|
+
} finally {
|
|
6337
|
+
setIsLoading(false);
|
|
6338
|
+
}
|
|
6339
|
+
}, [onError]);
|
|
6340
|
+
const confirmSetup = (0, import_react10.useCallback)(async (code) => {
|
|
6341
|
+
if (!setupData) {
|
|
6342
|
+
const msg = "Setup not started. Call startSetup() first.";
|
|
6343
|
+
setError(msg);
|
|
6344
|
+
onError?.(msg);
|
|
6345
|
+
return null;
|
|
6346
|
+
}
|
|
6347
|
+
if (!code || code.length !== 6) {
|
|
6348
|
+
const msg = "Please enter a 6-digit code";
|
|
6349
|
+
setError(msg);
|
|
6350
|
+
onError?.(msg);
|
|
6351
|
+
return null;
|
|
6352
|
+
}
|
|
6353
|
+
setIsLoading(true);
|
|
6354
|
+
setError(null);
|
|
6355
|
+
setSetupStep("confirming");
|
|
6356
|
+
try {
|
|
6357
|
+
authLogger.info("Confirming 2FA setup...");
|
|
6358
|
+
const response = await apiTotp.totp_setup.confirmCreate({
|
|
6359
|
+
device_id: setupData.deviceId,
|
|
6360
|
+
code
|
|
6361
|
+
});
|
|
6362
|
+
const codes = response.backup_codes;
|
|
6363
|
+
setBackupCodes(codes);
|
|
6364
|
+
setBackupCodesWarning(response.backup_codes_warning);
|
|
6365
|
+
setSetupStep("complete");
|
|
6366
|
+
authLogger.info("2FA setup confirmed, backup codes generated:", codes.length);
|
|
6367
|
+
onComplete?.(codes);
|
|
6368
|
+
return codes;
|
|
6369
|
+
} catch (err) {
|
|
6370
|
+
const errorMessage = err instanceof Error ? err.message : "Invalid code. Please try again.";
|
|
6371
|
+
authLogger.error("2FA setup confirmation error:", err);
|
|
6372
|
+
setError(errorMessage);
|
|
6373
|
+
setSetupStep("scanning");
|
|
6374
|
+
onError?.(errorMessage);
|
|
6375
|
+
return null;
|
|
6376
|
+
} finally {
|
|
6377
|
+
setIsLoading(false);
|
|
6378
|
+
}
|
|
6379
|
+
}, [setupData, onComplete, onError]);
|
|
6380
|
+
return {
|
|
6381
|
+
isLoading,
|
|
6382
|
+
error,
|
|
6383
|
+
setupData,
|
|
6384
|
+
backupCodes,
|
|
6385
|
+
backupCodesWarning,
|
|
6386
|
+
setupStep,
|
|
6387
|
+
startSetup,
|
|
6388
|
+
confirmSetup,
|
|
6389
|
+
resetSetup,
|
|
6390
|
+
clearError
|
|
6391
|
+
};
|
|
6392
|
+
}, "useTwoFactorSetup");
|
|
6393
|
+
|
|
6394
|
+
// src/auth/hooks/useAuthGuard.ts
|
|
6395
|
+
var import_react11 = require("react");
|
|
6396
|
+
var import_hooks6 = require("@djangocfg/ui-nextjs/hooks");
|
|
6397
|
+
var useAuthGuard = /* @__PURE__ */ __name((options = {}) => {
|
|
6398
|
+
const { redirectTo = "/auth", requireAuth = true, saveRedirectUrl: shouldSaveUrl = true } = options;
|
|
6399
|
+
const { isAuthenticated, isLoading, saveRedirectUrl } = useAuth();
|
|
6400
|
+
const router = (0, import_hooks6.useCfgRouter)();
|
|
6401
|
+
const [isRedirecting, setIsRedirecting] = (0, import_react11.useState)(false);
|
|
6402
|
+
(0, import_react11.useEffect)(() => {
|
|
6403
|
+
if (!isLoading && requireAuth && !isAuthenticated && !isRedirecting) {
|
|
6404
|
+
if (shouldSaveUrl && typeof window !== "undefined") {
|
|
6405
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
6406
|
+
saveRedirectUrl(currentUrl);
|
|
6407
|
+
}
|
|
6408
|
+
setIsRedirecting(true);
|
|
6409
|
+
router.push(redirectTo);
|
|
6410
|
+
}
|
|
6411
|
+
}, [isAuthenticated, isLoading, router, redirectTo, requireAuth, isRedirecting, shouldSaveUrl, saveRedirectUrl]);
|
|
6412
|
+
return { isAuthenticated, isLoading, isRedirecting };
|
|
6413
|
+
}, "useAuthGuard");
|
|
6414
|
+
|
|
6415
|
+
// src/auth/hooks/useLocalStorage.ts
|
|
6416
|
+
var import_react12 = require("react");
|
|
6417
|
+
function useLocalStorage2(key, initialValue) {
|
|
6418
|
+
const [storedValue, setStoredValue] = (0, import_react12.useState)(() => {
|
|
6419
|
+
if (typeof window === "undefined") {
|
|
6420
|
+
return initialValue;
|
|
6421
|
+
}
|
|
6422
|
+
try {
|
|
6423
|
+
const item = window.localStorage.getItem(key);
|
|
6424
|
+
if (item === null) {
|
|
6425
|
+
return initialValue;
|
|
6426
|
+
}
|
|
6427
|
+
try {
|
|
6428
|
+
return JSON.parse(item);
|
|
6429
|
+
} catch {
|
|
6430
|
+
return item;
|
|
6431
|
+
}
|
|
6432
|
+
} catch (error) {
|
|
6433
|
+
authLogger.error(`Error reading localStorage key "${key}":`, error);
|
|
6434
|
+
return initialValue;
|
|
6435
|
+
}
|
|
6436
|
+
});
|
|
6437
|
+
const checkDataSize = /* @__PURE__ */ __name((data) => {
|
|
6438
|
+
try {
|
|
6439
|
+
const jsonString = JSON.stringify(data);
|
|
6440
|
+
const sizeInBytes = new Blob([jsonString]).size;
|
|
6441
|
+
const sizeInKB = sizeInBytes / 1024;
|
|
6442
|
+
if (sizeInKB > 1024) {
|
|
6443
|
+
authLogger.warn(`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`);
|
|
6444
|
+
return false;
|
|
6445
|
+
}
|
|
6446
|
+
return true;
|
|
6447
|
+
} catch (error) {
|
|
6448
|
+
authLogger.error(`Error checking data size for key "${key}":`, error);
|
|
6449
|
+
return false;
|
|
6450
|
+
}
|
|
6451
|
+
}, "checkDataSize");
|
|
6452
|
+
const clearOldData = /* @__PURE__ */ __name(() => {
|
|
6453
|
+
try {
|
|
6454
|
+
const keys = Object.keys(localStorage).filter((key2) => key2 && typeof key2 === "string");
|
|
6455
|
+
if (keys.length > 50) {
|
|
6456
|
+
const itemsToRemove = Math.ceil(keys.length * 0.2);
|
|
6457
|
+
for (let i = 0; i < itemsToRemove; i++) {
|
|
6458
|
+
try {
|
|
6459
|
+
const key2 = keys[i];
|
|
6460
|
+
if (key2) {
|
|
6461
|
+
localStorage.removeItem(key2);
|
|
6462
|
+
localStorage.removeItem(`${key2}_timestamp`);
|
|
6463
|
+
}
|
|
6464
|
+
} catch {
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
} catch (error) {
|
|
6469
|
+
authLogger.error("Error clearing old localStorage data:", error);
|
|
6470
|
+
}
|
|
6471
|
+
}, "clearOldData");
|
|
6472
|
+
const forceClearAll = /* @__PURE__ */ __name(() => {
|
|
6473
|
+
try {
|
|
6474
|
+
const keys = Object.keys(localStorage);
|
|
6475
|
+
for (const key2 of keys) {
|
|
6476
|
+
try {
|
|
6477
|
+
localStorage.removeItem(key2);
|
|
6478
|
+
} catch {
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
} catch (error) {
|
|
6482
|
+
authLogger.error("Error force clearing localStorage:", error);
|
|
6483
|
+
}
|
|
6484
|
+
}, "forceClearAll");
|
|
6485
|
+
const setValue = /* @__PURE__ */ __name((value) => {
|
|
6486
|
+
try {
|
|
6487
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
6488
|
+
if (!checkDataSize(valueToStore)) {
|
|
6489
|
+
authLogger.warn(`Data size too large for key "${key}", removing key`);
|
|
6490
|
+
try {
|
|
6491
|
+
window.localStorage.removeItem(key);
|
|
6492
|
+
window.localStorage.removeItem(`${key}_timestamp`);
|
|
6493
|
+
} catch {
|
|
6494
|
+
}
|
|
6495
|
+
setStoredValue(valueToStore);
|
|
6496
|
+
return;
|
|
6497
|
+
}
|
|
6498
|
+
setStoredValue(valueToStore);
|
|
6499
|
+
if (typeof window !== "undefined") {
|
|
6500
|
+
try {
|
|
6501
|
+
if (typeof valueToStore === "string") {
|
|
6502
|
+
window.localStorage.setItem(key, valueToStore);
|
|
6503
|
+
} else {
|
|
6504
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
6505
|
+
}
|
|
6506
|
+
window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
6507
|
+
} catch (storageError) {
|
|
6508
|
+
if (storageError.name === "QuotaExceededError" || storageError.code === 22 || storageError.message?.includes("quota")) {
|
|
6509
|
+
authLogger.warn("localStorage quota exceeded, clearing old data...");
|
|
6510
|
+
clearOldData();
|
|
6511
|
+
try {
|
|
6512
|
+
if (typeof valueToStore === "string") {
|
|
6513
|
+
window.localStorage.setItem(key, valueToStore);
|
|
6514
|
+
} else {
|
|
6515
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
6516
|
+
}
|
|
6517
|
+
window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
6518
|
+
} catch (retryError) {
|
|
6519
|
+
authLogger.error(`Failed to set localStorage key "${key}" after clearing old data:`, retryError);
|
|
6520
|
+
try {
|
|
6521
|
+
forceClearAll();
|
|
6522
|
+
if (typeof valueToStore === "string") {
|
|
6523
|
+
window.localStorage.setItem(key, valueToStore);
|
|
6524
|
+
} else {
|
|
6525
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
6526
|
+
}
|
|
6527
|
+
window.localStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
6528
|
+
} catch (finalError) {
|
|
6529
|
+
authLogger.error(`Failed to set localStorage key "${key}" after force clearing:`, finalError);
|
|
6530
|
+
setStoredValue(valueToStore);
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
} else {
|
|
6534
|
+
throw storageError;
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6538
|
+
} catch (error) {
|
|
6539
|
+
authLogger.error(`Error setting localStorage key "${key}":`, error);
|
|
6540
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
6541
|
+
setStoredValue(valueToStore);
|
|
6542
|
+
}
|
|
6543
|
+
}, "setValue");
|
|
6544
|
+
const removeValue = /* @__PURE__ */ __name(() => {
|
|
6545
|
+
try {
|
|
6546
|
+
setStoredValue(initialValue);
|
|
6547
|
+
if (typeof window !== "undefined") {
|
|
6548
|
+
try {
|
|
6549
|
+
window.localStorage.removeItem(key);
|
|
6550
|
+
window.localStorage.removeItem(`${key}_timestamp`);
|
|
6551
|
+
} catch (removeError) {
|
|
6552
|
+
if (removeError.name === "QuotaExceededError" || removeError.code === 22 || removeError.message?.includes("quota")) {
|
|
6553
|
+
authLogger.warn("localStorage quota exceeded during removal, clearing old data...");
|
|
6554
|
+
clearOldData();
|
|
6555
|
+
try {
|
|
6556
|
+
window.localStorage.removeItem(key);
|
|
6557
|
+
window.localStorage.removeItem(`${key}_timestamp`);
|
|
6558
|
+
} catch (retryError) {
|
|
6559
|
+
authLogger.error(`Failed to remove localStorage key "${key}" after clearing:`, retryError);
|
|
6560
|
+
forceClearAll();
|
|
6561
|
+
}
|
|
6562
|
+
} else {
|
|
6563
|
+
throw removeError;
|
|
6564
|
+
}
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
} catch (error) {
|
|
6568
|
+
authLogger.error(`Error removing localStorage key "${key}":`, error);
|
|
6569
|
+
}
|
|
6570
|
+
}, "removeValue");
|
|
6571
|
+
return [storedValue, setValue, removeValue];
|
|
6572
|
+
}
|
|
6573
|
+
__name(useLocalStorage2, "useLocalStorage");
|
|
6574
|
+
|
|
5112
6575
|
// src/auth/utils/validation.ts
|
|
5113
6576
|
var validateEmail = /* @__PURE__ */ __name((email) => {
|
|
5114
6577
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|