@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.cjs CHANGED
@@ -223,6 +223,7 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
223
223
  const [acceptedTerms, setAcceptedTerms] = (0, import_react3.useState)(true);
224
224
  const [step, setStep] = (0, import_react3.useState)("identifier");
225
225
  const [error, setError] = (0, import_react3.useState)("");
226
+ const [webmail, setWebmail] = (0, import_react3.useState)(null);
226
227
  const [twoFactorSessionId, setTwoFactorSessionId] = (0, import_react3.useState)(null);
227
228
  const [shouldPrompt2FA, setShouldPrompt2FA] = (0, import_react3.useState)(false);
228
229
  const [twoFactorCode, setTwoFactorCode] = (0, import_react3.useState)("");
@@ -262,6 +263,7 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
262
263
  rateLimitSeconds,
263
264
  isRateLimited: rateLimitSeconds > 0,
264
265
  rateLimitLabel: formatCountdown(rateLimitSeconds),
266
+ webmail,
265
267
  // Handlers
266
268
  setIdentifier,
267
269
  setOtp,
@@ -274,7 +276,8 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
274
276
  setShouldPrompt2FA,
275
277
  setTwoFactorCode,
276
278
  setUseBackupCode,
277
- startRateLimitCountdown
279
+ startRateLimitCountdown,
280
+ setWebmail
278
281
  };
279
282
  }, "useAuthFormState");
280
283
 
@@ -660,6 +663,17 @@ var auth = {
660
663
  */
661
664
  setRefreshHandler(fn) {
662
665
  _refreshHandler = fn;
666
+ },
667
+ /**
668
+ * Proactively run the registered refresh handler right now, reusing the
669
+ * SAME single-flight promise as the 401-recovery interceptor. Callers
670
+ * (e.g. an expiry timer / focus / reconnect) get token rotation,
671
+ * de-duplication and rotated-token persistence for free — they must NOT
672
+ * re-implement any of it. Returns the fresh access token, or null if
673
+ * there is no handler / no refresh token / the refresh failed.
674
+ */
675
+ refreshNow() {
676
+ return tryRefresh();
663
677
  }
664
678
  };
665
679
  async function tryRefresh() {
@@ -2383,6 +2397,14 @@ var API = class {
2383
2397
  setRefreshHandler(fn) {
2384
2398
  auth.setRefreshHandler(fn);
2385
2399
  }
2400
+ /**
2401
+ * Proactively refresh now via the registered handler, sharing the single
2402
+ * 401-recovery flight (rotation + dedup + rotated-token persistence).
2403
+ * See `auth.refreshNow`.
2404
+ */
2405
+ refreshNow() {
2406
+ return auth.refreshNow();
2407
+ }
2386
2408
  };
2387
2409
 
2388
2410
  // src/_api/generated/_cfg_centrifugo/api.ts
@@ -2447,6 +2469,14 @@ var API2 = class {
2447
2469
  setRefreshHandler(fn) {
2448
2470
  auth.setRefreshHandler(fn);
2449
2471
  }
2472
+ /**
2473
+ * Proactively refresh now via the registered handler, sharing the single
2474
+ * 401-recovery flight (rotation + dedup + rotated-token persistence).
2475
+ * See `auth.refreshNow`.
2476
+ */
2477
+ refreshNow() {
2478
+ return auth.refreshNow();
2479
+ }
2450
2480
  };
2451
2481
 
2452
2482
  // src/_api/generated/_cfg_totp/api.ts
@@ -2514,6 +2544,14 @@ var API3 = class {
2514
2544
  setRefreshHandler(fn) {
2515
2545
  auth.setRefreshHandler(fn);
2516
2546
  }
2547
+ /**
2548
+ * Proactively refresh now via the registered handler, sharing the single
2549
+ * 401-recovery flight (rotation + dedup + rotated-token persistence).
2550
+ * See `auth.refreshNow`.
2551
+ */
2552
+ refreshNow() {
2553
+ return auth.refreshNow();
2554
+ }
2517
2555
  };
2518
2556
 
2519
2557
  // src/_api/generated/index.ts
@@ -2734,7 +2772,8 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2734
2772
  setShouldPrompt2FA,
2735
2773
  setTwoFactorCode,
2736
2774
  setUseBackupCode,
2737
- startRateLimitCountdown
2775
+ startRateLimitCountdown,
2776
+ setWebmail
2738
2777
  } = formState;
2739
2778
  const twoFactor = useTwoFactor({
2740
2779
  onSuccess: /* @__PURE__ */ __name(() => {
@@ -2786,6 +2825,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2786
2825
  const result = await requestOTP(identifier, sourceUrl);
2787
2826
  if (result.success) {
2788
2827
  saveIdentifierToStorage(identifier);
2828
+ setWebmail(result.webmail ?? null);
2789
2829
  setStep("otp");
2790
2830
  onIdentifierSuccess?.(identifier);
2791
2831
  } else {
@@ -2815,6 +2855,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2815
2855
  setIsLoading,
2816
2856
  setStep,
2817
2857
  clearError,
2858
+ setWebmail,
2818
2859
  startRateLimitCountdown,
2819
2860
  onIdentifierSuccess,
2820
2861
  onError,
@@ -2870,6 +2911,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2870
2911
  const result = await requestOTP(identifier, sourceUrl);
2871
2912
  if (result.success) {
2872
2913
  saveIdentifierToStorage(identifier);
2914
+ setWebmail(result.webmail ?? null);
2873
2915
  setOtp("");
2874
2916
  } else {
2875
2917
  if (result.retryAfter) {
@@ -2887,7 +2929,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2887
2929
  } finally {
2888
2930
  setIsLoading(false);
2889
2931
  }
2890
- }, [identifier, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, onError, sourceUrl]);
2932
+ }, [identifier, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, setWebmail, onError, sourceUrl]);
2891
2933
  const handleBackToIdentifier = (0, import_react7.useCallback)(() => {
2892
2934
  setStep("identifier");
2893
2935
  clearError();
@@ -3790,50 +3832,90 @@ __name(getCacheMetadata, "getCacheMetadata");
3790
3832
 
3791
3833
  // src/auth/hooks/useTokenRefresh.ts
3792
3834
  var import_react14 = require("react");
3793
- var TOKEN_REFRESH_THRESHOLD_MS = 10 * 60 * 1e3;
3794
- var CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3795
- function getTokenExpiry(token) {
3835
+
3836
+ // src/auth/refreshHandler.ts
3837
+ var _registered = false;
3838
+ function ensureRefreshHandler() {
3839
+ if (_registered) return;
3840
+ _registered = true;
3841
+ CfgAccountsApi.setRefreshHandler(async (refresh) => {
3842
+ const result = await CfgAccountsAuth.cfgAccountsTokenRefreshCreate({
3843
+ body: { refresh },
3844
+ throwOnError: true
3845
+ });
3846
+ const access = result.data?.access;
3847
+ if (!access) {
3848
+ authLogger.error("Refresh response missing access token");
3849
+ return null;
3850
+ }
3851
+ return { access, refresh: result.data?.refresh ?? refresh };
3852
+ });
3853
+ authLogger.debug("Canonical refresh handler registered");
3854
+ }
3855
+ __name(ensureRefreshHandler, "ensureRefreshHandler");
3856
+ function triggerRefresh() {
3857
+ ensureRefreshHandler();
3858
+ const maybe = CfgAccountsApi;
3859
+ if (typeof maybe.refreshNow === "function") {
3860
+ return maybe.refreshNow();
3861
+ }
3862
+ authLogger.warn("api.refreshNow() unavailable \u2014 run `make generate` to regenerate the client");
3863
+ return Promise.resolve(null);
3864
+ }
3865
+ __name(triggerRefresh, "triggerRefresh");
3866
+
3867
+ // src/auth/utils/jwt.ts
3868
+ function decodeSegment(segment) {
3796
3869
  try {
3797
- const payload = JSON.parse(atob(token.split(".")[1]));
3798
- return payload.exp * 1e3;
3870
+ const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
3871
+ if (typeof atob !== "function") return null;
3872
+ const json = atob(base64);
3873
+ return JSON.parse(json);
3799
3874
  } catch {
3800
3875
  return null;
3801
3876
  }
3802
3877
  }
3878
+ __name(decodeSegment, "decodeSegment");
3879
+ function getTokenExpiry(token) {
3880
+ if (!token || typeof token !== "string") return null;
3881
+ const parts = token.split(".");
3882
+ if (parts.length !== 3) return null;
3883
+ const payload = decodeSegment(parts[1]);
3884
+ if (!payload || typeof payload !== "object") return null;
3885
+ const exp = payload.exp;
3886
+ if (typeof exp !== "number" || !Number.isFinite(exp)) return null;
3887
+ return exp * 1e3;
3888
+ }
3803
3889
  __name(getTokenExpiry, "getTokenExpiry");
3804
- function isTokenExpiringSoon(token, thresholdMs) {
3890
+ function isTokenExpired(token, skewMs = 0, now = Date.now()) {
3891
+ const expiry = getTokenExpiry(token);
3892
+ if (expiry === null) return true;
3893
+ return expiry - skewMs <= now;
3894
+ }
3895
+ __name(isTokenExpired, "isTokenExpired");
3896
+ function isTokenExpiringSoon(token, thresholdMs, now = Date.now()) {
3805
3897
  const expiry = getTokenExpiry(token);
3806
- if (!expiry) return false;
3807
- const timeUntilExpiry = expiry - Date.now();
3808
- return timeUntilExpiry < thresholdMs;
3898
+ if (expiry === null) return false;
3899
+ return expiry - now < thresholdMs;
3809
3900
  }
3810
3901
  __name(isTokenExpiringSoon, "isTokenExpiringSoon");
3902
+
3903
+ // src/auth/hooks/useTokenRefresh.ts
3904
+ var TOKEN_REFRESH_THRESHOLD_MS = 10 * 60 * 1e3;
3905
+ var CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3811
3906
  function useTokenRefresh(options = {}) {
3812
3907
  const { enabled = true, onRefresh, onRefreshError } = options;
3813
- const isRefreshingRef = (0, import_react14.useRef)(false);
3814
3908
  const refreshToken = (0, import_react14.useCallback)(async () => {
3815
- if (isRefreshingRef.current) {
3816
- authLogger.debug("Token refresh already in progress");
3817
- return false;
3818
- }
3819
- const refreshTokenValue = CfgAccountsApi.getRefreshToken();
3820
- if (!refreshTokenValue) {
3909
+ if (!CfgAccountsApi.getRefreshToken()) {
3821
3910
  authLogger.warn("No refresh token available");
3822
3911
  return false;
3823
3912
  }
3824
- isRefreshingRef.current = true;
3825
3913
  authLogger.info("Refreshing token...");
3826
3914
  try {
3827
- const result = await CfgAccountsAuth.cfgAccountsTokenRefreshCreate({
3828
- body: { refresh: refreshTokenValue },
3829
- throwOnError: true
3830
- });
3831
- const newAccessToken = result.data.access;
3915
+ const newAccessToken = await triggerRefresh();
3832
3916
  if (!newAccessToken) {
3833
- throw new Error("No access token in refresh response");
3917
+ throw new Error("Token refresh failed");
3834
3918
  }
3835
- CfgAccountsApi.setToken(newAccessToken);
3836
- CfgAccountsApi.setRefreshToken(refreshTokenValue);
3837
3919
  authLogger.info("Token refreshed successfully");
3838
3920
  onRefresh?.(newAccessToken);
3839
3921
  return true;
@@ -3841,8 +3923,6 @@ function useTokenRefresh(options = {}) {
3841
3923
  authLogger.error("Token refresh error:", error);
3842
3924
  onRefreshError?.(error instanceof Error ? error : new Error(String(error)));
3843
3925
  return false;
3844
- } finally {
3845
- isRefreshingRef.current = false;
3846
3926
  }
3847
3927
  }, [onRefresh, onRefreshError]);
3848
3928
  const checkAndRefresh = (0, import_react14.useCallback)(async () => {
@@ -4025,8 +4105,8 @@ var OAuthTokenResponseSchema = import_zod10.z.object({
4025
4105
  access: import_zod10.z.string().nullable().optional(),
4026
4106
  refresh: import_zod10.z.string().nullable().optional(),
4027
4107
  user: import_zod10.z.object({}).passthrough().nullable().optional(),
4028
- is_new_user: import_zod10.z.boolean(),
4029
- is_new_connection: import_zod10.z.boolean(),
4108
+ is_new_user: import_zod10.z.boolean().default(false).optional(),
4109
+ is_new_connection: import_zod10.z.boolean().default(false).optional(),
4030
4110
  should_prompt_2fa: import_zod10.z.boolean().optional()
4031
4111
  });
4032
4112
 
@@ -4043,9 +4123,27 @@ var OAuthProvidersResponseSchema = import_zod11.z.object({
4043
4123
  var import_mutation7 = __toESM(require("swr/mutation"), 1);
4044
4124
 
4045
4125
  // src/_api/generated/_cfg_accounts/schemas/OTPRequestResponse.ts
4126
+ var import_zod14 = require("zod");
4127
+
4128
+ // src/_api/generated/_cfg_accounts/schemas/WebmailLink.ts
4129
+ var import_zod13 = require("zod");
4130
+
4131
+ // src/_api/generated/_cfg_accounts/schemas/WebmailLinkProviderEnum.ts
4046
4132
  var import_zod12 = require("zod");
4047
- var OTPRequestResponseSchema = import_zod12.z.object({
4048
- message: import_zod12.z.string()
4133
+ var WebmailLinkProviderEnumSchema = import_zod12.z.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"]);
4134
+
4135
+ // src/_api/generated/_cfg_accounts/schemas/WebmailLink.ts
4136
+ var WebmailLinkSchema = import_zod13.z.object({
4137
+ provider: WebmailLinkProviderEnumSchema,
4138
+ provider_name: import_zod13.z.string(),
4139
+ url: import_zod13.z.string(),
4140
+ is_search: import_zod13.z.boolean()
4141
+ });
4142
+
4143
+ // src/_api/generated/_cfg_accounts/schemas/OTPRequestResponse.ts
4144
+ var OTPRequestResponseSchema = import_zod14.z.object({
4145
+ message: import_zod14.z.string(),
4146
+ webmail: WebmailLinkSchema.nullable().optional()
4049
4147
  });
4050
4148
 
4051
4149
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOtpRequestCreate.ts
@@ -4099,52 +4197,52 @@ __name(useCfgAccountsOtpRequestCreate, "useCfgAccountsOtpRequestCreate");
4099
4197
  var import_mutation8 = __toESM(require("swr/mutation"), 1);
4100
4198
 
4101
4199
  // src/_api/generated/_cfg_accounts/schemas/OTPVerifyResponse.ts
4102
- var import_zod15 = require("zod");
4200
+ var import_zod17 = require("zod");
4103
4201
 
4104
4202
  // src/_api/generated/_cfg_accounts/schemas/User.ts
4105
- var import_zod14 = require("zod");
4203
+ var import_zod16 = require("zod");
4106
4204
 
4107
4205
  // src/_api/generated/_cfg_accounts/schemas/CentrifugoToken.ts
4108
- var import_zod13 = require("zod");
4109
- var CentrifugoTokenSchema = import_zod13.z.object({
4110
- token: import_zod13.z.string(),
4111
- centrifugo_url: import_zod13.z.string(),
4112
- expires_at: import_zod13.z.string().datetime({ offset: true }),
4113
- channels: import_zod13.z.array(import_zod13.z.string())
4206
+ var import_zod15 = require("zod");
4207
+ var CentrifugoTokenSchema = import_zod15.z.object({
4208
+ token: import_zod15.z.string(),
4209
+ centrifugo_url: import_zod15.z.string(),
4210
+ expires_at: import_zod15.z.string().datetime({ offset: true }),
4211
+ channels: import_zod15.z.array(import_zod15.z.string())
4114
4212
  });
4115
4213
 
4116
4214
  // src/_api/generated/_cfg_accounts/schemas/User.ts
4117
- var UserSchema = import_zod14.z.object({
4118
- id: import_zod14.z.number().int(),
4119
- email: import_zod14.z.email(),
4120
- first_name: import_zod14.z.string().max(50).nullable().optional(),
4121
- last_name: import_zod14.z.string().max(50).nullable().optional(),
4122
- full_name: import_zod14.z.string(),
4123
- initials: import_zod14.z.string(),
4124
- display_username: import_zod14.z.string(),
4125
- company: import_zod14.z.string().max(100).nullable().optional(),
4126
- phone: import_zod14.z.string().max(20).nullable().optional(),
4127
- position: import_zod14.z.string().max(100).nullable().optional(),
4128
- language: import_zod14.z.string().max(10).nullable().optional(),
4129
- timezone: import_zod14.z.string().max(64).nullable().optional(),
4130
- avatar: import_zod14.z.string().nullable(),
4131
- is_staff: import_zod14.z.boolean(),
4132
- is_superuser: import_zod14.z.boolean(),
4133
- date_joined: import_zod14.z.string().datetime({ offset: true }),
4134
- last_login: import_zod14.z.string().datetime({ offset: true }).nullable(),
4135
- unanswered_messages_count: import_zod14.z.number().int().default(0),
4215
+ var UserSchema = import_zod16.z.object({
4216
+ id: import_zod16.z.number().int(),
4217
+ email: import_zod16.z.email(),
4218
+ first_name: import_zod16.z.string().max(50).nullable().optional(),
4219
+ last_name: import_zod16.z.string().max(50).nullable().optional(),
4220
+ full_name: import_zod16.z.string(),
4221
+ initials: import_zod16.z.string(),
4222
+ display_username: import_zod16.z.string(),
4223
+ company: import_zod16.z.string().max(100).nullable().optional(),
4224
+ phone: import_zod16.z.string().max(20).nullable().optional(),
4225
+ position: import_zod16.z.string().max(100).nullable().optional(),
4226
+ language: import_zod16.z.string().max(10).nullable().optional(),
4227
+ timezone: import_zod16.z.string().max(64).nullable().optional(),
4228
+ avatar: import_zod16.z.string().nullable(),
4229
+ is_staff: import_zod16.z.boolean(),
4230
+ is_superuser: import_zod16.z.boolean(),
4231
+ date_joined: import_zod16.z.string().datetime({ offset: true }),
4232
+ last_login: import_zod16.z.string().datetime({ offset: true }).nullable(),
4233
+ unanswered_messages_count: import_zod16.z.number().int().default(0),
4136
4234
  centrifugo: CentrifugoTokenSchema.nullable(),
4137
- api_key: import_zod14.z.string().nullable()
4235
+ api_key: import_zod16.z.string().nullable()
4138
4236
  });
4139
4237
 
4140
4238
  // src/_api/generated/_cfg_accounts/schemas/OTPVerifyResponse.ts
4141
- var OTPVerifyResponseSchema = import_zod15.z.object({
4142
- requires_2fa: import_zod15.z.boolean().default(false).optional(),
4143
- 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(),
4144
- refresh: import_zod15.z.string().nullable().optional(),
4145
- access: import_zod15.z.string().nullable().optional(),
4239
+ var OTPVerifyResponseSchema = import_zod17.z.object({
4240
+ requires_2fa: import_zod17.z.boolean().default(false).optional(),
4241
+ session_id: import_zod17.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(),
4242
+ refresh: import_zod17.z.string().nullable().optional(),
4243
+ access: import_zod17.z.string().nullable().optional(),
4146
4244
  user: UserSchema.nullable().optional(),
4147
- should_prompt_2fa: import_zod15.z.boolean().optional()
4245
+ should_prompt_2fa: import_zod17.z.boolean().optional()
4148
4246
  });
4149
4247
 
4150
4248
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOtpVerifyCreate.ts
@@ -4246,10 +4344,10 @@ __name(useCfgAccountsProfileAvatarCreate, "useCfgAccountsProfileAvatarCreate");
4246
4344
  var import_mutation10 = __toESM(require("swr/mutation"), 1);
4247
4345
 
4248
4346
  // src/_api/generated/_cfg_accounts/schemas/AccountDeleteResponse.ts
4249
- var import_zod16 = require("zod");
4250
- var AccountDeleteResponseSchema = import_zod16.z.object({
4251
- success: import_zod16.z.boolean(),
4252
- message: import_zod16.z.string()
4347
+ var import_zod18 = require("zod");
4348
+ var AccountDeleteResponseSchema = import_zod18.z.object({
4349
+ success: import_zod18.z.boolean(),
4350
+ message: import_zod18.z.string()
4253
4351
  });
4254
4352
 
4255
4353
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsProfilePartialPartialUpdate.ts
@@ -4361,10 +4459,10 @@ __name(useCfgAccountsProfileUpdateUpdate, "useCfgAccountsProfileUpdateUpdate");
4361
4459
  var import_mutation15 = __toESM(require("swr/mutation"), 1);
4362
4460
 
4363
4461
  // src/_api/generated/_cfg_accounts/schemas/TokenRefresh.ts
4364
- var import_zod17 = require("zod");
4365
- var TokenRefreshSchema = import_zod17.z.object({
4366
- access: import_zod17.z.string(),
4367
- refresh: import_zod17.z.string()
4462
+ var import_zod19 = require("zod");
4463
+ var TokenRefreshSchema = import_zod19.z.object({
4464
+ access: import_zod19.z.string(),
4465
+ refresh: import_zod19.z.string()
4368
4466
  });
4369
4467
 
4370
4468
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
@@ -4415,98 +4513,98 @@ function useCfgAccountsTokenRefreshCreate(config) {
4415
4513
  __name(useCfgAccountsTokenRefreshCreate, "useCfgAccountsTokenRefreshCreate");
4416
4514
 
4417
4515
  // src/_api/generated/_cfg_accounts/schemas/APIKeyRequest.ts
4418
- var import_zod18 = require("zod");
4419
- var APIKeyRequestSchema = import_zod18.z.object({
4420
- key: import_zod18.z.string().min(1),
4421
- reissued_at: import_zod18.z.string().datetime({ offset: true }).nullable(),
4422
- created_at: import_zod18.z.string().datetime({ offset: true })
4516
+ var import_zod20 = require("zod");
4517
+ var APIKeyRequestSchema = import_zod20.z.object({
4518
+ key: import_zod20.z.string().min(1),
4519
+ reissued_at: import_zod20.z.string().datetime({ offset: true }).nullable(),
4520
+ created_at: import_zod20.z.string().datetime({ offset: true })
4423
4521
  });
4424
4522
 
4425
4523
  // src/_api/generated/_cfg_accounts/schemas/APIKeyTestRequest.ts
4426
- var import_zod19 = require("zod");
4427
- var APIKeyTestRequestSchema = import_zod19.z.object({
4428
- key: import_zod19.z.string().min(1)
4524
+ var import_zod21 = require("zod");
4525
+ var APIKeyTestRequestSchema = import_zod21.z.object({
4526
+ key: import_zod21.z.string().min(1)
4429
4527
  });
4430
4528
 
4431
4529
  // src/_api/generated/_cfg_accounts/schemas/CfgUserUpdateRequest.ts
4432
- var import_zod20 = require("zod");
4433
- var CfgUserUpdateRequestSchema = import_zod20.z.object({
4434
- first_name: import_zod20.z.string().max(50).optional(),
4435
- last_name: import_zod20.z.string().max(50).optional(),
4436
- company: import_zod20.z.string().max(100).optional(),
4437
- phone: import_zod20.z.string().max(20).optional(),
4438
- position: import_zod20.z.string().max(100).optional(),
4439
- language: import_zod20.z.string().max(10).optional(),
4440
- timezone: import_zod20.z.string().max(64).optional()
4530
+ var import_zod22 = require("zod");
4531
+ var CfgUserUpdateRequestSchema = import_zod22.z.object({
4532
+ first_name: import_zod22.z.string().max(50).optional(),
4533
+ last_name: import_zod22.z.string().max(50).optional(),
4534
+ company: import_zod22.z.string().max(100).optional(),
4535
+ phone: import_zod22.z.string().max(20).optional(),
4536
+ position: import_zod22.z.string().max(100).optional(),
4537
+ language: import_zod22.z.string().max(10).optional(),
4538
+ timezone: import_zod22.z.string().max(64).optional()
4441
4539
  });
4442
4540
 
4443
4541
  // src/_api/generated/_cfg_accounts/schemas/OAuthAuthorizeRequestRequest.ts
4444
- var import_zod21 = require("zod");
4445
- var OAuthAuthorizeRequestRequestSchema = import_zod21.z.object({
4446
- redirect_uri: import_zod21.z.string().optional(),
4447
- source_url: import_zod21.z.string().optional()
4542
+ var import_zod23 = require("zod");
4543
+ var OAuthAuthorizeRequestRequestSchema = import_zod23.z.object({
4544
+ redirect_uri: import_zod23.z.string().optional(),
4545
+ source_url: import_zod23.z.string().optional()
4448
4546
  });
4449
4547
 
4450
4548
  // src/_api/generated/_cfg_accounts/schemas/OAuthCallbackRequestRequest.ts
4451
- var import_zod22 = require("zod");
4452
- var OAuthCallbackRequestRequestSchema = import_zod22.z.object({
4453
- code: import_zod22.z.string().min(10).max(500),
4454
- state: import_zod22.z.string().min(20).max(100),
4455
- redirect_uri: import_zod22.z.string().optional()
4549
+ var import_zod24 = require("zod");
4550
+ var OAuthCallbackRequestRequestSchema = import_zod24.z.object({
4551
+ code: import_zod24.z.string().min(10).max(500),
4552
+ state: import_zod24.z.string().min(20).max(100),
4553
+ redirect_uri: import_zod24.z.string().optional()
4456
4554
  });
4457
4555
 
4458
4556
  // src/_api/generated/_cfg_accounts/schemas/OAuthDisconnectRequestRequest.ts
4459
- var import_zod23 = require("zod");
4460
- var OAuthDisconnectRequestRequestSchema = import_zod23.z.object({
4557
+ var import_zod25 = require("zod");
4558
+ var OAuthDisconnectRequestRequestSchema = import_zod25.z.object({
4461
4559
  provider: OAuthProviderEnumSchema
4462
4560
  });
4463
4561
 
4464
4562
  // src/_api/generated/_cfg_accounts/schemas/OAuthError.ts
4465
- var import_zod24 = require("zod");
4466
- var OAuthErrorSchema = import_zod24.z.object({
4467
- error: import_zod24.z.string(),
4468
- error_description: import_zod24.z.string().optional()
4563
+ var import_zod26 = require("zod");
4564
+ var OAuthErrorSchema = import_zod26.z.object({
4565
+ error: import_zod26.z.string(),
4566
+ error_description: import_zod26.z.string().optional()
4469
4567
  });
4470
4568
 
4471
4569
  // src/_api/generated/_cfg_accounts/schemas/OTPErrorResponse.ts
4472
- var import_zod25 = require("zod");
4473
- var OTPErrorResponseSchema = import_zod25.z.object({
4474
- error: import_zod25.z.string(),
4475
- error_code: import_zod25.z.string().nullable().optional(),
4476
- retry_after: import_zod25.z.number().int().nullable().optional()
4570
+ var import_zod27 = require("zod");
4571
+ var OTPErrorResponseSchema = import_zod27.z.object({
4572
+ error: import_zod27.z.string(),
4573
+ error_code: import_zod27.z.string().nullable().optional(),
4574
+ retry_after: import_zod27.z.number().int().nullable().optional()
4477
4575
  });
4478
4576
 
4479
4577
  // src/_api/generated/_cfg_accounts/schemas/OTPRequestRequest.ts
4480
- var import_zod26 = require("zod");
4481
- var OTPRequestRequestSchema = import_zod26.z.object({
4482
- identifier: import_zod26.z.string().min(1),
4483
- source_url: import_zod26.z.string().optional()
4578
+ var import_zod28 = require("zod");
4579
+ var OTPRequestRequestSchema = import_zod28.z.object({
4580
+ identifier: import_zod28.z.string().min(1),
4581
+ source_url: import_zod28.z.string().optional()
4484
4582
  });
4485
4583
 
4486
4584
  // src/_api/generated/_cfg_accounts/schemas/OTPVerifyRequest.ts
4487
- var import_zod27 = require("zod");
4488
- var OTPVerifyRequestSchema = import_zod27.z.object({
4489
- identifier: import_zod27.z.string().min(1),
4490
- otp: import_zod27.z.string().min(4).max(4),
4491
- source_url: import_zod27.z.string().optional()
4585
+ var import_zod29 = require("zod");
4586
+ var OTPVerifyRequestSchema = import_zod29.z.object({
4587
+ identifier: import_zod29.z.string().min(1),
4588
+ otp: import_zod29.z.string().min(4).max(4),
4589
+ source_url: import_zod29.z.string().optional()
4492
4590
  });
4493
4591
 
4494
4592
  // src/_api/generated/_cfg_accounts/schemas/PatchedCfgUserUpdateRequest.ts
4495
- var import_zod28 = require("zod");
4496
- var PatchedCfgUserUpdateRequestSchema = import_zod28.z.object({
4497
- first_name: import_zod28.z.string().max(50).optional(),
4498
- last_name: import_zod28.z.string().max(50).optional(),
4499
- company: import_zod28.z.string().max(100).optional(),
4500
- phone: import_zod28.z.string().max(20).optional(),
4501
- position: import_zod28.z.string().max(100).optional(),
4502
- language: import_zod28.z.string().max(10).optional(),
4503
- timezone: import_zod28.z.string().max(64).optional()
4593
+ var import_zod30 = require("zod");
4594
+ var PatchedCfgUserUpdateRequestSchema = import_zod30.z.object({
4595
+ first_name: import_zod30.z.string().max(50).optional(),
4596
+ last_name: import_zod30.z.string().max(50).optional(),
4597
+ company: import_zod30.z.string().max(100).optional(),
4598
+ phone: import_zod30.z.string().max(20).optional(),
4599
+ position: import_zod30.z.string().max(100).optional(),
4600
+ language: import_zod30.z.string().max(10).optional(),
4601
+ timezone: import_zod30.z.string().max(64).optional()
4504
4602
  });
4505
4603
 
4506
4604
  // src/_api/generated/_cfg_accounts/schemas/TokenRefreshRequest.ts
4507
- var import_zod29 = require("zod");
4508
- var TokenRefreshRequestSchema = import_zod29.z.object({
4509
- refresh: import_zod29.z.string().min(1)
4605
+ var import_zod31 = require("zod");
4606
+ var TokenRefreshRequestSchema = import_zod31.z.object({
4607
+ refresh: import_zod31.z.string().min(1)
4510
4608
  });
4511
4609
 
4512
4610
  // src/auth/context/AccountsContext.tsx
@@ -4660,8 +4758,15 @@ var hasValidTokens = /* @__PURE__ */ __name(() => {
4660
4758
  if (typeof window === "undefined") return false;
4661
4759
  return CfgAccountsApi.isAuthenticated();
4662
4760
  }, "hasValidTokens");
4761
+ var isSessionDeadOnBootstrap = /* @__PURE__ */ __name((accessToken, refreshToken, now = Date.now()) => {
4762
+ const accessDead = isTokenExpired(accessToken, 0, now);
4763
+ if (!accessDead) return false;
4764
+ const refreshDead = isTokenExpired(refreshToken, 0, now);
4765
+ return refreshDead;
4766
+ }, "isSessionDeadOnBootstrap");
4663
4767
  var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4664
4768
  const accounts = useAccountsContext();
4769
+ ensureRefreshHandler();
4665
4770
  const redirectManager = useAuthRedirectManager({
4666
4771
  fallbackUrl: config?.routes?.defaultCallback || defaultRoutes.defaultCallback,
4667
4772
  clearOnUse: true
@@ -4674,19 +4779,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4674
4779
  return true;
4675
4780
  });
4676
4781
  const [initialized, setInitialized] = (0, import_react17.useState)(false);
4782
+ const [authTick, setAuthTick] = (0, import_react17.useState)(0);
4783
+ const bumpAuthTick = (0, import_react17.useCallback)(() => setAuthTick((t) => t + 1), []);
4784
+ const onUnauthorizedRef = (0, import_react17.useRef)(false);
4677
4785
  const router = useCfgRouter();
4678
4786
  const pathname = (0, import_navigation4.usePathname)();
4679
4787
  const queryParams = useQueryParams();
4680
4788
  const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage(EMAIL_STORAGE_KEY, null);
4681
- useTokenRefresh({
4682
- enabled: true,
4683
- onRefresh: /* @__PURE__ */ __name((newToken) => {
4684
- authLogger.info("Token auto-refreshed successfully");
4685
- }, "onRefresh"),
4686
- onRefreshError: /* @__PURE__ */ __name((error) => {
4687
- authLogger.warn("Token auto-refresh failed:", error.message);
4688
- }, "onRefreshError")
4689
- });
4690
4789
  const user = accounts.profile;
4691
4790
  const userRef = (0, import_react17.useRef)(user);
4692
4791
  const configRef = (0, import_react17.useRef)(config);
@@ -4703,7 +4802,23 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4703
4802
  clearProfileCache();
4704
4803
  setInitialized(true);
4705
4804
  setIsLoading(false);
4706
- }, []);
4805
+ bumpAuthTick();
4806
+ }, [bumpAuthTick]);
4807
+ const handleProactiveRefreshError = (0, import_react17.useCallback)((error) => {
4808
+ authLogger.warn("Proactive token refresh failed \u2014 session is dead, redirecting to login:", error.message);
4809
+ if (onUnauthorizedRef.current) return;
4810
+ onUnauthorizedRef.current = true;
4811
+ clearAuthState("proactiveRefresh:failed");
4812
+ const authCallbackUrl = configRef.current?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
4813
+ router.hardReplace(authCallbackUrl);
4814
+ }, [clearAuthState, router]);
4815
+ useTokenRefresh({
4816
+ enabled: true,
4817
+ onRefresh: /* @__PURE__ */ __name(() => {
4818
+ authLogger.info("Token auto-refreshed successfully");
4819
+ }, "onRefresh"),
4820
+ onRefreshError: handleProactiveRefreshError
4821
+ });
4707
4822
  const handleGlobalAuthError = (0, import_react17.useCallback)((error, context = "API Request") => {
4708
4823
  const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
4709
4824
  if (isAuthError) {
@@ -4716,6 +4831,20 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4716
4831
  }
4717
4832
  return false;
4718
4833
  }, [clearAuthState]);
4834
+ (0, import_react17.useEffect)(() => {
4835
+ const handler = /* @__PURE__ */ __name(() => {
4836
+ if (onUnauthorizedRef.current) return;
4837
+ onUnauthorizedRef.current = true;
4838
+ authLogger.warn("Unrecoverable 401 (refresh failed) \u2014 clearing session and redirecting to login");
4839
+ clearAuthState("onUnauthorized:401");
4840
+ const authCallbackUrl = configRef.current?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
4841
+ router.hardReplace(authCallbackUrl);
4842
+ }, "handler");
4843
+ CfgAccountsApi.onUnauthorized(handler);
4844
+ return () => {
4845
+ CfgAccountsApi.onUnauthorized(null);
4846
+ };
4847
+ }, [clearAuthState, router]);
4719
4848
  const isAutoLoggingOutRef = (0, import_react17.useRef)(false);
4720
4849
  const swrOnError = (0, import_react17.useCallback)((error) => {
4721
4850
  const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
@@ -4780,6 +4909,11 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4780
4909
  authLogger.info("Token from API:", token ? `${token.substring(0, 20)}...` : "null");
4781
4910
  authLogger.info("Refresh token from API:", refreshToken2 ? `${refreshToken2.substring(0, 20)}...` : "null");
4782
4911
  authLogger.info("localStorage keys:", Object.keys(localStorage).filter((k) => k.includes("token") || k.includes("auth")));
4912
+ if (!isInIframe && isSessionDeadOnBootstrap(token, refreshToken2)) {
4913
+ authLogger.warn("Bootstrap: dead session in storage (access + refresh both unusable) \u2014 clearing and showing login");
4914
+ clearAuthState("initializeAuth:deadSession");
4915
+ return;
4916
+ }
4783
4917
  const hasTokens = hasValidTokens();
4784
4918
  authLogger.info("Has tokens:", hasTokens);
4785
4919
  if (userRef.current) {
@@ -4880,7 +5014,8 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4880
5014
  });
4881
5015
  return {
4882
5016
  success: true,
4883
- message: result.message || `OTP code sent to your email address`
5017
+ message: result.message || `OTP code sent to your email address`,
5018
+ webmail: result.webmail ?? null
4884
5019
  };
4885
5020
  } catch (error) {
4886
5021
  authLogger.error("Request OTP error:", error);
@@ -5035,7 +5170,9 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5035
5170
  () => ({
5036
5171
  user,
5037
5172
  isLoading,
5038
- // Consider authenticated if we have valid tokens, even without user profile
5173
+ // Consider authenticated if we have valid tokens, even without user profile.
5174
+ // `authTick` (in deps) forces recompute after tokens are cleared/set, since
5175
+ // token state lives in storage, not React state.
5039
5176
  isAuthenticated: CfgAccountsApi.isAuthenticated(),
5040
5177
  isAdminUser,
5041
5178
  loadCurrentProfile,
@@ -5061,6 +5198,8 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5061
5198
  [
5062
5199
  user,
5063
5200
  isLoading,
5201
+ authTick,
5202
+ // recompute isAuthenticated when tokens are cleared/set
5064
5203
  isAdminUser,
5065
5204
  loadCurrentProfile,
5066
5205
  checkAuthAndRedirect,