@djangocfg/api 2.1.448 → 2.1.450

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.
Files changed (55) hide show
  1. package/dist/auth-server.cjs +1 -2272
  2. package/dist/auth-server.cjs.map +1 -1
  3. package/dist/auth-server.d.cts +1 -35
  4. package/dist/auth-server.d.ts +1 -35
  5. package/dist/auth-server.mjs +1 -2272
  6. package/dist/auth-server.mjs.map +1 -1
  7. package/dist/auth.cjs +285 -146
  8. package/dist/auth.cjs.map +1 -1
  9. package/dist/auth.d.cts +137 -2
  10. package/dist/auth.d.ts +137 -2
  11. package/dist/auth.mjs +294 -155
  12. package/dist/auth.mjs.map +1 -1
  13. package/dist/clients.cjs +35 -0
  14. package/dist/clients.cjs.map +1 -1
  15. package/dist/clients.d.cts +147 -2
  16. package/dist/clients.d.ts +147 -2
  17. package/dist/clients.mjs +35 -0
  18. package/dist/clients.mjs.map +1 -1
  19. package/dist/hooks.cjs +11 -0
  20. package/dist/hooks.cjs.map +1 -1
  21. package/dist/hooks.mjs +11 -0
  22. package/dist/hooks.mjs.map +1 -1
  23. package/dist/index.cjs +35 -0
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +169 -6
  26. package/dist/index.d.ts +169 -6
  27. package/dist/index.mjs +35 -0
  28. package/dist/index.mjs.map +1 -1
  29. package/package.json +7 -4
  30. package/src/_api/generated/_cfg_accounts/api.ts +8 -0
  31. package/src/_api/generated/_cfg_accounts/openapi.json +84 -5
  32. package/src/_api/generated/_cfg_accounts/schemas/OAuthTokenResponse.ts +2 -2
  33. package/src/_api/generated/_cfg_accounts/schemas/OTPRequestResponse.ts +2 -0
  34. package/src/_api/generated/_cfg_accounts/schemas/WebmailLink.ts +15 -0
  35. package/src/_api/generated/_cfg_accounts/schemas/WebmailLinkProviderEnum.ts +9 -0
  36. package/src/_api/generated/_cfg_accounts/schemas/index.ts +2 -0
  37. package/src/_api/generated/_cfg_centrifugo/api.ts +8 -0
  38. package/src/_api/generated/_cfg_totp/api.ts +8 -0
  39. package/src/_api/generated/helpers/auth.ts +12 -0
  40. package/src/_api/generated/openapi.json +84 -5
  41. package/src/_api/generated/types.gen.ts +131 -2
  42. package/src/auth/__tests__/jwt.test.ts +119 -0
  43. package/src/auth/__tests__/onUnauthorized.test.ts +206 -0
  44. package/src/auth/__tests__/refreshRotation.test.ts +119 -0
  45. package/src/auth/__tests__/sessionBootstrap.test.ts +76 -0
  46. package/src/auth/context/AuthContext.tsx +131 -14
  47. package/src/auth/hooks/useAuthForm.ts +5 -2
  48. package/src/auth/hooks/useAuthFormState.ts +4 -1
  49. package/src/auth/hooks/useTokenRefresh.ts +28 -62
  50. package/src/auth/middlewares/index.ts +7 -7
  51. package/src/auth/refreshHandler.ts +79 -0
  52. package/src/auth/types/form.ts +10 -0
  53. package/src/auth/types/index.ts +1 -0
  54. package/src/auth/utils/jwt.ts +66 -0
  55. package/src/auth/middlewares/tokenRefresh.ts +0 -161
package/dist/auth.mjs CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  useContext as useContext2,
28
28
  useEffect as useEffect9,
29
29
  useMemo as useMemo3,
30
- useRef as useRef7,
30
+ useRef as useRef6,
31
31
  useState as useState12
32
32
  } from "react";
33
33
  import { SWRConfig } from "swr";
@@ -161,6 +161,7 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
161
161
  const [acceptedTerms, setAcceptedTerms] = useState2(true);
162
162
  const [step, setStep] = useState2("identifier");
163
163
  const [error, setError] = useState2("");
164
+ const [webmail, setWebmail] = useState2(null);
164
165
  const [twoFactorSessionId, setTwoFactorSessionId] = useState2(null);
165
166
  const [shouldPrompt2FA, setShouldPrompt2FA] = useState2(false);
166
167
  const [twoFactorCode, setTwoFactorCode] = useState2("");
@@ -200,6 +201,7 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
200
201
  rateLimitSeconds,
201
202
  isRateLimited: rateLimitSeconds > 0,
202
203
  rateLimitLabel: formatCountdown(rateLimitSeconds),
204
+ webmail,
203
205
  // Handlers
204
206
  setIdentifier,
205
207
  setOtp,
@@ -212,7 +214,8 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
212
214
  setShouldPrompt2FA,
213
215
  setTwoFactorCode,
214
216
  setUseBackupCode,
215
- startRateLimitCountdown
217
+ startRateLimitCountdown,
218
+ setWebmail
216
219
  };
217
220
  }, "useAuthFormState");
218
221
 
@@ -598,6 +601,17 @@ var auth = {
598
601
  */
599
602
  setRefreshHandler(fn) {
600
603
  _refreshHandler = fn;
604
+ },
605
+ /**
606
+ * Proactively run the registered refresh handler right now, reusing the
607
+ * SAME single-flight promise as the 401-recovery interceptor. Callers
608
+ * (e.g. an expiry timer / focus / reconnect) get token rotation,
609
+ * de-duplication and rotated-token persistence for free — they must NOT
610
+ * re-implement any of it. Returns the fresh access token, or null if
611
+ * there is no handler / no refresh token / the refresh failed.
612
+ */
613
+ refreshNow() {
614
+ return tryRefresh();
601
615
  }
602
616
  };
603
617
  async function tryRefresh() {
@@ -2321,6 +2335,14 @@ var API = class {
2321
2335
  setRefreshHandler(fn) {
2322
2336
  auth.setRefreshHandler(fn);
2323
2337
  }
2338
+ /**
2339
+ * Proactively refresh now via the registered handler, sharing the single
2340
+ * 401-recovery flight (rotation + dedup + rotated-token persistence).
2341
+ * See `auth.refreshNow`.
2342
+ */
2343
+ refreshNow() {
2344
+ return auth.refreshNow();
2345
+ }
2324
2346
  };
2325
2347
 
2326
2348
  // src/_api/generated/_cfg_centrifugo/api.ts
@@ -2385,6 +2407,14 @@ var API2 = class {
2385
2407
  setRefreshHandler(fn) {
2386
2408
  auth.setRefreshHandler(fn);
2387
2409
  }
2410
+ /**
2411
+ * Proactively refresh now via the registered handler, sharing the single
2412
+ * 401-recovery flight (rotation + dedup + rotated-token persistence).
2413
+ * See `auth.refreshNow`.
2414
+ */
2415
+ refreshNow() {
2416
+ return auth.refreshNow();
2417
+ }
2388
2418
  };
2389
2419
 
2390
2420
  // src/_api/generated/_cfg_totp/api.ts
@@ -2452,6 +2482,14 @@ var API3 = class {
2452
2482
  setRefreshHandler(fn) {
2453
2483
  auth.setRefreshHandler(fn);
2454
2484
  }
2485
+ /**
2486
+ * Proactively refresh now via the registered handler, sharing the single
2487
+ * 401-recovery flight (rotation + dedup + rotated-token persistence).
2488
+ * See `auth.refreshNow`.
2489
+ */
2490
+ refreshNow() {
2491
+ return auth.refreshNow();
2492
+ }
2455
2493
  };
2456
2494
 
2457
2495
  // src/_api/generated/index.ts
@@ -2672,7 +2710,8 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2672
2710
  setShouldPrompt2FA,
2673
2711
  setTwoFactorCode,
2674
2712
  setUseBackupCode,
2675
- startRateLimitCountdown
2713
+ startRateLimitCountdown,
2714
+ setWebmail
2676
2715
  } = formState;
2677
2716
  const twoFactor = useTwoFactor({
2678
2717
  onSuccess: /* @__PURE__ */ __name(() => {
@@ -2724,6 +2763,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2724
2763
  const result = await requestOTP(identifier, sourceUrl);
2725
2764
  if (result.success) {
2726
2765
  saveIdentifierToStorage(identifier);
2766
+ setWebmail(result.webmail ?? null);
2727
2767
  setStep("otp");
2728
2768
  onIdentifierSuccess?.(identifier);
2729
2769
  } else {
@@ -2753,6 +2793,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2753
2793
  setIsLoading,
2754
2794
  setStep,
2755
2795
  clearError,
2796
+ setWebmail,
2756
2797
  startRateLimitCountdown,
2757
2798
  onIdentifierSuccess,
2758
2799
  onError,
@@ -2808,6 +2849,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2808
2849
  const result = await requestOTP(identifier, sourceUrl);
2809
2850
  if (result.success) {
2810
2851
  saveIdentifierToStorage(identifier);
2852
+ setWebmail(result.webmail ?? null);
2811
2853
  setOtp("");
2812
2854
  } else {
2813
2855
  if (result.retryAfter) {
@@ -2825,7 +2867,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2825
2867
  } finally {
2826
2868
  setIsLoading(false);
2827
2869
  }
2828
- }, [identifier, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, onError, sourceUrl]);
2870
+ }, [identifier, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, setWebmail, onError, sourceUrl]);
2829
2871
  const handleBackToIdentifier = useCallback5(() => {
2830
2872
  setStep("identifier");
2831
2873
  clearError();
@@ -3727,51 +3769,91 @@ function getCacheMetadata() {
3727
3769
  __name(getCacheMetadata, "getCacheMetadata");
3728
3770
 
3729
3771
  // src/auth/hooks/useTokenRefresh.ts
3730
- import { useCallback as useCallback9, useEffect as useEffect7, useRef as useRef5 } from "react";
3731
- var TOKEN_REFRESH_THRESHOLD_MS = 10 * 60 * 1e3;
3732
- var CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3733
- function getTokenExpiry(token) {
3772
+ import { useCallback as useCallback9, useEffect as useEffect7 } from "react";
3773
+
3774
+ // src/auth/refreshHandler.ts
3775
+ var _registered = false;
3776
+ function ensureRefreshHandler() {
3777
+ if (_registered) return;
3778
+ _registered = true;
3779
+ CfgAccountsApi.setRefreshHandler(async (refresh) => {
3780
+ const result = await CfgAccountsAuth.cfgAccountsTokenRefreshCreate({
3781
+ body: { refresh },
3782
+ throwOnError: true
3783
+ });
3784
+ const access = result.data?.access;
3785
+ if (!access) {
3786
+ authLogger.error("Refresh response missing access token");
3787
+ return null;
3788
+ }
3789
+ return { access, refresh: result.data?.refresh ?? refresh };
3790
+ });
3791
+ authLogger.debug("Canonical refresh handler registered");
3792
+ }
3793
+ __name(ensureRefreshHandler, "ensureRefreshHandler");
3794
+ function triggerRefresh() {
3795
+ ensureRefreshHandler();
3796
+ const maybe = CfgAccountsApi;
3797
+ if (typeof maybe.refreshNow === "function") {
3798
+ return maybe.refreshNow();
3799
+ }
3800
+ authLogger.warn("api.refreshNow() unavailable \u2014 run `make generate` to regenerate the client");
3801
+ return Promise.resolve(null);
3802
+ }
3803
+ __name(triggerRefresh, "triggerRefresh");
3804
+
3805
+ // src/auth/utils/jwt.ts
3806
+ function decodeSegment(segment) {
3734
3807
  try {
3735
- const payload = JSON.parse(atob(token.split(".")[1]));
3736
- return payload.exp * 1e3;
3808
+ const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
3809
+ if (typeof atob !== "function") return null;
3810
+ const json = atob(base64);
3811
+ return JSON.parse(json);
3737
3812
  } catch {
3738
3813
  return null;
3739
3814
  }
3740
3815
  }
3816
+ __name(decodeSegment, "decodeSegment");
3817
+ function getTokenExpiry(token) {
3818
+ if (!token || typeof token !== "string") return null;
3819
+ const parts = token.split(".");
3820
+ if (parts.length !== 3) return null;
3821
+ const payload = decodeSegment(parts[1]);
3822
+ if (!payload || typeof payload !== "object") return null;
3823
+ const exp = payload.exp;
3824
+ if (typeof exp !== "number" || !Number.isFinite(exp)) return null;
3825
+ return exp * 1e3;
3826
+ }
3741
3827
  __name(getTokenExpiry, "getTokenExpiry");
3742
- function isTokenExpiringSoon(token, thresholdMs) {
3828
+ function isTokenExpired(token, skewMs = 0, now = Date.now()) {
3829
+ const expiry = getTokenExpiry(token);
3830
+ if (expiry === null) return true;
3831
+ return expiry - skewMs <= now;
3832
+ }
3833
+ __name(isTokenExpired, "isTokenExpired");
3834
+ function isTokenExpiringSoon(token, thresholdMs, now = Date.now()) {
3743
3835
  const expiry = getTokenExpiry(token);
3744
- if (!expiry) return false;
3745
- const timeUntilExpiry = expiry - Date.now();
3746
- return timeUntilExpiry < thresholdMs;
3836
+ if (expiry === null) return false;
3837
+ return expiry - now < thresholdMs;
3747
3838
  }
3748
3839
  __name(isTokenExpiringSoon, "isTokenExpiringSoon");
3840
+
3841
+ // src/auth/hooks/useTokenRefresh.ts
3842
+ var TOKEN_REFRESH_THRESHOLD_MS = 10 * 60 * 1e3;
3843
+ var CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3749
3844
  function useTokenRefresh(options = {}) {
3750
3845
  const { enabled = true, onRefresh, onRefreshError } = options;
3751
- const isRefreshingRef = useRef5(false);
3752
3846
  const refreshToken = useCallback9(async () => {
3753
- if (isRefreshingRef.current) {
3754
- authLogger.debug("Token refresh already in progress");
3755
- return false;
3756
- }
3757
- const refreshTokenValue = CfgAccountsApi.getRefreshToken();
3758
- if (!refreshTokenValue) {
3847
+ if (!CfgAccountsApi.getRefreshToken()) {
3759
3848
  authLogger.warn("No refresh token available");
3760
3849
  return false;
3761
3850
  }
3762
- isRefreshingRef.current = true;
3763
3851
  authLogger.info("Refreshing token...");
3764
3852
  try {
3765
- const result = await CfgAccountsAuth.cfgAccountsTokenRefreshCreate({
3766
- body: { refresh: refreshTokenValue },
3767
- throwOnError: true
3768
- });
3769
- const newAccessToken = result.data.access;
3853
+ const newAccessToken = await triggerRefresh();
3770
3854
  if (!newAccessToken) {
3771
- throw new Error("No access token in refresh response");
3855
+ throw new Error("Token refresh failed");
3772
3856
  }
3773
- CfgAccountsApi.setToken(newAccessToken);
3774
- CfgAccountsApi.setRefreshToken(refreshTokenValue);
3775
3857
  authLogger.info("Token refreshed successfully");
3776
3858
  onRefresh?.(newAccessToken);
3777
3859
  return true;
@@ -3779,8 +3861,6 @@ function useTokenRefresh(options = {}) {
3779
3861
  authLogger.error("Token refresh error:", error);
3780
3862
  onRefreshError?.(error instanceof Error ? error : new Error(String(error)));
3781
3863
  return false;
3782
- } finally {
3783
- isRefreshingRef.current = false;
3784
3864
  }
3785
3865
  }, [onRefresh, onRefreshError]);
3786
3866
  const checkAndRefresh = useCallback9(async () => {
@@ -3867,7 +3947,7 @@ import {
3867
3947
  useContext,
3868
3948
  useEffect as useEffect8,
3869
3949
  useMemo as useMemo2,
3870
- useRef as useRef6,
3950
+ useRef as useRef5,
3871
3951
  useState as useState11
3872
3952
  } from "react";
3873
3953
 
@@ -3971,8 +4051,8 @@ var OAuthTokenResponseSchema = z10.object({
3971
4051
  access: z10.string().nullable().optional(),
3972
4052
  refresh: z10.string().nullable().optional(),
3973
4053
  user: z10.object({}).passthrough().nullable().optional(),
3974
- is_new_user: z10.boolean(),
3975
- is_new_connection: z10.boolean(),
4054
+ is_new_user: z10.boolean().default(false).optional(),
4055
+ is_new_connection: z10.boolean().default(false).optional(),
3976
4056
  should_prompt_2fa: z10.boolean().optional()
3977
4057
  });
3978
4058
 
@@ -3989,9 +4069,27 @@ var OAuthProvidersResponseSchema = z11.object({
3989
4069
  import useSWRMutation7 from "swr/mutation";
3990
4070
 
3991
4071
  // src/_api/generated/_cfg_accounts/schemas/OTPRequestResponse.ts
4072
+ import { z as z14 } from "zod";
4073
+
4074
+ // src/_api/generated/_cfg_accounts/schemas/WebmailLink.ts
4075
+ import { z as z13 } from "zod";
4076
+
4077
+ // src/_api/generated/_cfg_accounts/schemas/WebmailLinkProviderEnum.ts
3992
4078
  import { z as z12 } from "zod";
3993
- var OTPRequestResponseSchema = z12.object({
3994
- message: z12.string()
4079
+ var WebmailLinkProviderEnumSchema = z12.enum(["gmail", "outlook", "yahoo", "icloud", "proton", "zoho", "aol", "fastmail", "gmx", "mailcom", "mail_ru", "yandex", "rambler", "qq", "netease", "sina", "aliyun", "naver", "daum", "web_de", "tonline", "seznam", "wp_pl", "o2_pl", "interia", "libero", "virgilio", "orange", "laposte", "free_fr", "sfr"]);
4080
+
4081
+ // src/_api/generated/_cfg_accounts/schemas/WebmailLink.ts
4082
+ var WebmailLinkSchema = z13.object({
4083
+ provider: WebmailLinkProviderEnumSchema,
4084
+ provider_name: z13.string(),
4085
+ url: z13.string(),
4086
+ is_search: z13.boolean()
4087
+ });
4088
+
4089
+ // src/_api/generated/_cfg_accounts/schemas/OTPRequestResponse.ts
4090
+ var OTPRequestResponseSchema = z14.object({
4091
+ message: z14.string(),
4092
+ webmail: WebmailLinkSchema.nullable().optional()
3995
4093
  });
3996
4094
 
3997
4095
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOtpRequestCreate.ts
@@ -4045,52 +4143,52 @@ __name(useCfgAccountsOtpRequestCreate, "useCfgAccountsOtpRequestCreate");
4045
4143
  import useSWRMutation8 from "swr/mutation";
4046
4144
 
4047
4145
  // src/_api/generated/_cfg_accounts/schemas/OTPVerifyResponse.ts
4048
- import { z as z15 } from "zod";
4146
+ import { z as z17 } from "zod";
4049
4147
 
4050
4148
  // src/_api/generated/_cfg_accounts/schemas/User.ts
4051
- import { z as z14 } from "zod";
4149
+ import { z as z16 } from "zod";
4052
4150
 
4053
4151
  // src/_api/generated/_cfg_accounts/schemas/CentrifugoToken.ts
4054
- import { z as z13 } from "zod";
4055
- var CentrifugoTokenSchema = z13.object({
4056
- token: z13.string(),
4057
- centrifugo_url: z13.string(),
4058
- expires_at: z13.string().datetime({ offset: true }),
4059
- channels: z13.array(z13.string())
4152
+ import { z as z15 } from "zod";
4153
+ var CentrifugoTokenSchema = z15.object({
4154
+ token: z15.string(),
4155
+ centrifugo_url: z15.string(),
4156
+ expires_at: z15.string().datetime({ offset: true }),
4157
+ channels: z15.array(z15.string())
4060
4158
  });
4061
4159
 
4062
4160
  // src/_api/generated/_cfg_accounts/schemas/User.ts
4063
- var UserSchema = z14.object({
4064
- id: z14.number().int(),
4065
- email: z14.email(),
4066
- first_name: z14.string().max(50).nullable().optional(),
4067
- last_name: z14.string().max(50).nullable().optional(),
4068
- full_name: z14.string(),
4069
- initials: z14.string(),
4070
- display_username: z14.string(),
4071
- company: z14.string().max(100).nullable().optional(),
4072
- phone: z14.string().max(20).nullable().optional(),
4073
- position: z14.string().max(100).nullable().optional(),
4074
- language: z14.string().max(10).nullable().optional(),
4075
- timezone: z14.string().max(64).nullable().optional(),
4076
- avatar: z14.string().nullable(),
4077
- is_staff: z14.boolean(),
4078
- is_superuser: z14.boolean(),
4079
- date_joined: z14.string().datetime({ offset: true }),
4080
- last_login: z14.string().datetime({ offset: true }).nullable(),
4081
- unanswered_messages_count: z14.number().int().default(0),
4161
+ var UserSchema = z16.object({
4162
+ id: z16.number().int(),
4163
+ email: z16.email(),
4164
+ first_name: z16.string().max(50).nullable().optional(),
4165
+ last_name: z16.string().max(50).nullable().optional(),
4166
+ full_name: z16.string(),
4167
+ initials: z16.string(),
4168
+ display_username: z16.string(),
4169
+ company: z16.string().max(100).nullable().optional(),
4170
+ phone: z16.string().max(20).nullable().optional(),
4171
+ position: z16.string().max(100).nullable().optional(),
4172
+ language: z16.string().max(10).nullable().optional(),
4173
+ timezone: z16.string().max(64).nullable().optional(),
4174
+ avatar: z16.string().nullable(),
4175
+ is_staff: z16.boolean(),
4176
+ is_superuser: z16.boolean(),
4177
+ date_joined: z16.string().datetime({ offset: true }),
4178
+ last_login: z16.string().datetime({ offset: true }).nullable(),
4179
+ unanswered_messages_count: z16.number().int().default(0),
4082
4180
  centrifugo: CentrifugoTokenSchema.nullable(),
4083
- api_key: z14.string().nullable()
4181
+ api_key: z16.string().nullable()
4084
4182
  });
4085
4183
 
4086
4184
  // src/_api/generated/_cfg_accounts/schemas/OTPVerifyResponse.ts
4087
- var OTPVerifyResponseSchema = z15.object({
4088
- requires_2fa: z15.boolean().default(false).optional(),
4089
- session_id: z15.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(),
4090
- refresh: z15.string().nullable().optional(),
4091
- access: z15.string().nullable().optional(),
4185
+ var OTPVerifyResponseSchema = z17.object({
4186
+ requires_2fa: z17.boolean().default(false).optional(),
4187
+ session_id: z17.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(),
4188
+ refresh: z17.string().nullable().optional(),
4189
+ access: z17.string().nullable().optional(),
4092
4190
  user: UserSchema.nullable().optional(),
4093
- should_prompt_2fa: z15.boolean().optional()
4191
+ should_prompt_2fa: z17.boolean().optional()
4094
4192
  });
4095
4193
 
4096
4194
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOtpVerifyCreate.ts
@@ -4192,10 +4290,10 @@ __name(useCfgAccountsProfileAvatarCreate, "useCfgAccountsProfileAvatarCreate");
4192
4290
  import useSWRMutation10 from "swr/mutation";
4193
4291
 
4194
4292
  // src/_api/generated/_cfg_accounts/schemas/AccountDeleteResponse.ts
4195
- import { z as z16 } from "zod";
4196
- var AccountDeleteResponseSchema = z16.object({
4197
- success: z16.boolean(),
4198
- message: z16.string()
4293
+ import { z as z18 } from "zod";
4294
+ var AccountDeleteResponseSchema = z18.object({
4295
+ success: z18.boolean(),
4296
+ message: z18.string()
4199
4297
  });
4200
4298
 
4201
4299
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsProfilePartialPartialUpdate.ts
@@ -4307,10 +4405,10 @@ __name(useCfgAccountsProfileUpdateUpdate, "useCfgAccountsProfileUpdateUpdate");
4307
4405
  import useSWRMutation15 from "swr/mutation";
4308
4406
 
4309
4407
  // src/_api/generated/_cfg_accounts/schemas/TokenRefresh.ts
4310
- import { z as z17 } from "zod";
4311
- var TokenRefreshSchema = z17.object({
4312
- access: z17.string(),
4313
- refresh: z17.string()
4408
+ import { z as z19 } from "zod";
4409
+ var TokenRefreshSchema = z19.object({
4410
+ access: z19.string(),
4411
+ refresh: z19.string()
4314
4412
  });
4315
4413
 
4316
4414
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
@@ -4361,98 +4459,98 @@ function useCfgAccountsTokenRefreshCreate(config) {
4361
4459
  __name(useCfgAccountsTokenRefreshCreate, "useCfgAccountsTokenRefreshCreate");
4362
4460
 
4363
4461
  // src/_api/generated/_cfg_accounts/schemas/APIKeyRequest.ts
4364
- import { z as z18 } from "zod";
4365
- var APIKeyRequestSchema = z18.object({
4366
- key: z18.string().min(1),
4367
- reissued_at: z18.string().datetime({ offset: true }).nullable(),
4368
- created_at: z18.string().datetime({ offset: true })
4462
+ import { z as z20 } from "zod";
4463
+ var APIKeyRequestSchema = z20.object({
4464
+ key: z20.string().min(1),
4465
+ reissued_at: z20.string().datetime({ offset: true }).nullable(),
4466
+ created_at: z20.string().datetime({ offset: true })
4369
4467
  });
4370
4468
 
4371
4469
  // src/_api/generated/_cfg_accounts/schemas/APIKeyTestRequest.ts
4372
- import { z as z19 } from "zod";
4373
- var APIKeyTestRequestSchema = z19.object({
4374
- key: z19.string().min(1)
4470
+ import { z as z21 } from "zod";
4471
+ var APIKeyTestRequestSchema = z21.object({
4472
+ key: z21.string().min(1)
4375
4473
  });
4376
4474
 
4377
4475
  // src/_api/generated/_cfg_accounts/schemas/CfgUserUpdateRequest.ts
4378
- import { z as z20 } from "zod";
4379
- var CfgUserUpdateRequestSchema = z20.object({
4380
- first_name: z20.string().max(50).optional(),
4381
- last_name: z20.string().max(50).optional(),
4382
- company: z20.string().max(100).optional(),
4383
- phone: z20.string().max(20).optional(),
4384
- position: z20.string().max(100).optional(),
4385
- language: z20.string().max(10).optional(),
4386
- timezone: z20.string().max(64).optional()
4476
+ import { z as z22 } from "zod";
4477
+ var CfgUserUpdateRequestSchema = z22.object({
4478
+ first_name: z22.string().max(50).optional(),
4479
+ last_name: z22.string().max(50).optional(),
4480
+ company: z22.string().max(100).optional(),
4481
+ phone: z22.string().max(20).optional(),
4482
+ position: z22.string().max(100).optional(),
4483
+ language: z22.string().max(10).optional(),
4484
+ timezone: z22.string().max(64).optional()
4387
4485
  });
4388
4486
 
4389
4487
  // src/_api/generated/_cfg_accounts/schemas/OAuthAuthorizeRequestRequest.ts
4390
- import { z as z21 } from "zod";
4391
- var OAuthAuthorizeRequestRequestSchema = z21.object({
4392
- redirect_uri: z21.string().optional(),
4393
- source_url: z21.string().optional()
4488
+ import { z as z23 } from "zod";
4489
+ var OAuthAuthorizeRequestRequestSchema = z23.object({
4490
+ redirect_uri: z23.string().optional(),
4491
+ source_url: z23.string().optional()
4394
4492
  });
4395
4493
 
4396
4494
  // src/_api/generated/_cfg_accounts/schemas/OAuthCallbackRequestRequest.ts
4397
- import { z as z22 } from "zod";
4398
- var OAuthCallbackRequestRequestSchema = z22.object({
4399
- code: z22.string().min(10).max(500),
4400
- state: z22.string().min(20).max(100),
4401
- redirect_uri: z22.string().optional()
4495
+ import { z as z24 } from "zod";
4496
+ var OAuthCallbackRequestRequestSchema = z24.object({
4497
+ code: z24.string().min(10).max(500),
4498
+ state: z24.string().min(20).max(100),
4499
+ redirect_uri: z24.string().optional()
4402
4500
  });
4403
4501
 
4404
4502
  // src/_api/generated/_cfg_accounts/schemas/OAuthDisconnectRequestRequest.ts
4405
- import { z as z23 } from "zod";
4406
- var OAuthDisconnectRequestRequestSchema = z23.object({
4503
+ import { z as z25 } from "zod";
4504
+ var OAuthDisconnectRequestRequestSchema = z25.object({
4407
4505
  provider: OAuthProviderEnumSchema
4408
4506
  });
4409
4507
 
4410
4508
  // src/_api/generated/_cfg_accounts/schemas/OAuthError.ts
4411
- import { z as z24 } from "zod";
4412
- var OAuthErrorSchema = z24.object({
4413
- error: z24.string(),
4414
- error_description: z24.string().optional()
4509
+ import { z as z26 } from "zod";
4510
+ var OAuthErrorSchema = z26.object({
4511
+ error: z26.string(),
4512
+ error_description: z26.string().optional()
4415
4513
  });
4416
4514
 
4417
4515
  // src/_api/generated/_cfg_accounts/schemas/OTPErrorResponse.ts
4418
- import { z as z25 } from "zod";
4419
- var OTPErrorResponseSchema = z25.object({
4420
- error: z25.string(),
4421
- error_code: z25.string().nullable().optional(),
4422
- retry_after: z25.number().int().nullable().optional()
4516
+ import { z as z27 } from "zod";
4517
+ var OTPErrorResponseSchema = z27.object({
4518
+ error: z27.string(),
4519
+ error_code: z27.string().nullable().optional(),
4520
+ retry_after: z27.number().int().nullable().optional()
4423
4521
  });
4424
4522
 
4425
4523
  // src/_api/generated/_cfg_accounts/schemas/OTPRequestRequest.ts
4426
- import { z as z26 } from "zod";
4427
- var OTPRequestRequestSchema = z26.object({
4428
- identifier: z26.string().min(1),
4429
- source_url: z26.string().optional()
4524
+ import { z as z28 } from "zod";
4525
+ var OTPRequestRequestSchema = z28.object({
4526
+ identifier: z28.string().min(1),
4527
+ source_url: z28.string().optional()
4430
4528
  });
4431
4529
 
4432
4530
  // src/_api/generated/_cfg_accounts/schemas/OTPVerifyRequest.ts
4433
- import { z as z27 } from "zod";
4434
- var OTPVerifyRequestSchema = z27.object({
4435
- identifier: z27.string().min(1),
4436
- otp: z27.string().min(4).max(4),
4437
- source_url: z27.string().optional()
4531
+ import { z as z29 } from "zod";
4532
+ var OTPVerifyRequestSchema = z29.object({
4533
+ identifier: z29.string().min(1),
4534
+ otp: z29.string().min(4).max(4),
4535
+ source_url: z29.string().optional()
4438
4536
  });
4439
4537
 
4440
4538
  // src/_api/generated/_cfg_accounts/schemas/PatchedCfgUserUpdateRequest.ts
4441
- import { z as z28 } from "zod";
4442
- var PatchedCfgUserUpdateRequestSchema = z28.object({
4443
- first_name: z28.string().max(50).optional(),
4444
- last_name: z28.string().max(50).optional(),
4445
- company: z28.string().max(100).optional(),
4446
- phone: z28.string().max(20).optional(),
4447
- position: z28.string().max(100).optional(),
4448
- language: z28.string().max(10).optional(),
4449
- timezone: z28.string().max(64).optional()
4539
+ import { z as z30 } from "zod";
4540
+ var PatchedCfgUserUpdateRequestSchema = z30.object({
4541
+ first_name: z30.string().max(50).optional(),
4542
+ last_name: z30.string().max(50).optional(),
4543
+ company: z30.string().max(100).optional(),
4544
+ phone: z30.string().max(20).optional(),
4545
+ position: z30.string().max(100).optional(),
4546
+ language: z30.string().max(10).optional(),
4547
+ timezone: z30.string().max(64).optional()
4450
4548
  });
4451
4549
 
4452
4550
  // src/_api/generated/_cfg_accounts/schemas/TokenRefreshRequest.ts
4453
- import { z as z29 } from "zod";
4454
- var TokenRefreshRequestSchema = z29.object({
4455
- refresh: z29.string().min(1)
4551
+ import { z as z31 } from "zod";
4552
+ var TokenRefreshRequestSchema = z31.object({
4553
+ refresh: z31.string().min(1)
4456
4554
  });
4457
4555
 
4458
4556
  // src/auth/context/AccountsContext.tsx
@@ -4465,8 +4563,8 @@ function AccountsProvider({ children }) {
4465
4563
  });
4466
4564
  const [isLoadingProfile, setIsLoadingProfile] = useState11(false);
4467
4565
  const [profileError, setProfileError] = useState11(null);
4468
- const profileRef = useRef6(profile);
4469
- const isLoadingRef = useRef6(false);
4566
+ const profileRef = useRef5(profile);
4567
+ const isLoadingRef = useRef5(false);
4470
4568
  useEffect8(() => {
4471
4569
  profileRef.current = profile;
4472
4570
  }, [profile]);
@@ -4606,8 +4704,15 @@ var hasValidTokens = /* @__PURE__ */ __name(() => {
4606
4704
  if (typeof window === "undefined") return false;
4607
4705
  return CfgAccountsApi.isAuthenticated();
4608
4706
  }, "hasValidTokens");
4707
+ var isSessionDeadOnBootstrap = /* @__PURE__ */ __name((accessToken, refreshToken, now = Date.now()) => {
4708
+ const accessDead = isTokenExpired(accessToken, 0, now);
4709
+ if (!accessDead) return false;
4710
+ const refreshDead = isTokenExpired(refreshToken, 0, now);
4711
+ return refreshDead;
4712
+ }, "isSessionDeadOnBootstrap");
4609
4713
  var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4610
4714
  const accounts = useAccountsContext();
4715
+ ensureRefreshHandler();
4611
4716
  const redirectManager = useAuthRedirectManager({
4612
4717
  fallbackUrl: config?.routes?.defaultCallback || defaultRoutes.defaultCallback,
4613
4718
  clearOnUse: true
@@ -4620,23 +4725,17 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4620
4725
  return true;
4621
4726
  });
4622
4727
  const [initialized, setInitialized] = useState12(false);
4728
+ const [authTick, setAuthTick] = useState12(0);
4729
+ const bumpAuthTick = useCallback12(() => setAuthTick((t) => t + 1), []);
4730
+ const onUnauthorizedRef = useRef6(false);
4623
4731
  const router = useCfgRouter();
4624
4732
  const pathname = usePathname3();
4625
4733
  const queryParams = useQueryParams();
4626
4734
  const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage(EMAIL_STORAGE_KEY, null);
4627
- useTokenRefresh({
4628
- enabled: true,
4629
- onRefresh: /* @__PURE__ */ __name((newToken) => {
4630
- authLogger.info("Token auto-refreshed successfully");
4631
- }, "onRefresh"),
4632
- onRefreshError: /* @__PURE__ */ __name((error) => {
4633
- authLogger.warn("Token auto-refresh failed:", error.message);
4634
- }, "onRefreshError")
4635
- });
4636
4735
  const user = accounts.profile;
4637
- const userRef = useRef7(user);
4638
- const configRef = useRef7(config);
4639
- const isLoadingProfileRef = useRef7(false);
4736
+ const userRef = useRef6(user);
4737
+ const configRef = useRef6(config);
4738
+ const isLoadingProfileRef = useRef6(false);
4640
4739
  useEffect9(() => {
4641
4740
  userRef.current = user;
4642
4741
  }, [user]);
@@ -4649,7 +4748,23 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4649
4748
  clearProfileCache();
4650
4749
  setInitialized(true);
4651
4750
  setIsLoading(false);
4652
- }, []);
4751
+ bumpAuthTick();
4752
+ }, [bumpAuthTick]);
4753
+ const handleProactiveRefreshError = useCallback12((error) => {
4754
+ authLogger.warn("Proactive token refresh failed \u2014 session is dead, redirecting to login:", error.message);
4755
+ if (onUnauthorizedRef.current) return;
4756
+ onUnauthorizedRef.current = true;
4757
+ clearAuthState("proactiveRefresh:failed");
4758
+ const authCallbackUrl = configRef.current?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
4759
+ router.hardReplace(authCallbackUrl);
4760
+ }, [clearAuthState, router]);
4761
+ useTokenRefresh({
4762
+ enabled: true,
4763
+ onRefresh: /* @__PURE__ */ __name(() => {
4764
+ authLogger.info("Token auto-refreshed successfully");
4765
+ }, "onRefresh"),
4766
+ onRefreshError: handleProactiveRefreshError
4767
+ });
4653
4768
  const handleGlobalAuthError = useCallback12((error, context = "API Request") => {
4654
4769
  const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
4655
4770
  if (isAuthError) {
@@ -4662,7 +4777,21 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4662
4777
  }
4663
4778
  return false;
4664
4779
  }, [clearAuthState]);
4665
- const isAutoLoggingOutRef = useRef7(false);
4780
+ useEffect9(() => {
4781
+ const handler = /* @__PURE__ */ __name(() => {
4782
+ if (onUnauthorizedRef.current) return;
4783
+ onUnauthorizedRef.current = true;
4784
+ authLogger.warn("Unrecoverable 401 (refresh failed) \u2014 clearing session and redirecting to login");
4785
+ clearAuthState("onUnauthorized:401");
4786
+ const authCallbackUrl = configRef.current?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
4787
+ router.hardReplace(authCallbackUrl);
4788
+ }, "handler");
4789
+ CfgAccountsApi.onUnauthorized(handler);
4790
+ return () => {
4791
+ CfgAccountsApi.onUnauthorized(null);
4792
+ };
4793
+ }, [clearAuthState, router]);
4794
+ const isAutoLoggingOutRef = useRef6(false);
4666
4795
  const swrOnError = useCallback12((error) => {
4667
4796
  const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
4668
4797
  if (isAuthError && !isAutoLoggingOutRef.current) {
@@ -4726,6 +4855,11 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4726
4855
  authLogger.info("Token from API:", token ? `${token.substring(0, 20)}...` : "null");
4727
4856
  authLogger.info("Refresh token from API:", refreshToken2 ? `${refreshToken2.substring(0, 20)}...` : "null");
4728
4857
  authLogger.info("localStorage keys:", Object.keys(localStorage).filter((k) => k.includes("token") || k.includes("auth")));
4858
+ if (!isInIframe && isSessionDeadOnBootstrap(token, refreshToken2)) {
4859
+ authLogger.warn("Bootstrap: dead session in storage (access + refresh both unusable) \u2014 clearing and showing login");
4860
+ clearAuthState("initializeAuth:deadSession");
4861
+ return;
4862
+ }
4729
4863
  const hasTokens = hasValidTokens();
4730
4864
  authLogger.info("Has tokens:", hasTokens);
4731
4865
  if (userRef.current) {
@@ -4826,7 +4960,8 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4826
4960
  });
4827
4961
  return {
4828
4962
  success: true,
4829
- message: result.message || `OTP code sent to your email address`
4963
+ message: result.message || `OTP code sent to your email address`,
4964
+ webmail: result.webmail ?? null
4830
4965
  };
4831
4966
  } catch (error) {
4832
4967
  authLogger.error("Request OTP error:", error);
@@ -4981,7 +5116,9 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4981
5116
  () => ({
4982
5117
  user,
4983
5118
  isLoading,
4984
- // Consider authenticated if we have valid tokens, even without user profile
5119
+ // Consider authenticated if we have valid tokens, even without user profile.
5120
+ // `authTick` (in deps) forces recompute after tokens are cleared/set, since
5121
+ // token state lives in storage, not React state.
4985
5122
  isAuthenticated: CfgAccountsApi.isAuthenticated(),
4986
5123
  isAdminUser,
4987
5124
  loadCurrentProfile,
@@ -5007,6 +5144,8 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5007
5144
  [
5008
5145
  user,
5009
5146
  isLoading,
5147
+ authTick,
5148
+ // recompute isAuthenticated when tokens are cleared/set
5010
5149
  isAdminUser,
5011
5150
  loadCurrentProfile,
5012
5151
  checkAuthAndRedirect,