@djangocfg/api 2.1.455 → 2.1.457

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 (89) hide show
  1. package/dist/auth.cjs +1611 -2148
  2. package/dist/auth.cjs.map +1 -1
  3. package/dist/auth.d.cts +37 -82
  4. package/dist/auth.d.ts +37 -82
  5. package/dist/auth.mjs +1581 -2118
  6. package/dist/auth.mjs.map +1 -1
  7. package/dist/chunk-2G67QRNU.mjs +2096 -0
  8. package/dist/chunk-2G67QRNU.mjs.map +1 -0
  9. package/dist/chunk-32SRQGAC.mjs +2109 -0
  10. package/dist/chunk-32SRQGAC.mjs.map +1 -0
  11. package/dist/chunk-4BPRCONN.mjs +2098 -0
  12. package/dist/chunk-4BPRCONN.mjs.map +1 -0
  13. package/dist/chunk-5UZ2Z323.mjs +2021 -0
  14. package/dist/chunk-5UZ2Z323.mjs.map +1 -0
  15. package/dist/chunk-7KFTJXNM.mjs +2097 -0
  16. package/dist/chunk-7KFTJXNM.mjs.map +1 -0
  17. package/dist/chunk-BK4K5CVT.mjs +2019 -0
  18. package/dist/chunk-BK4K5CVT.mjs.map +1 -0
  19. package/dist/chunk-JLPJZ6WB.mjs +2020 -0
  20. package/dist/chunk-JLPJZ6WB.mjs.map +1 -0
  21. package/dist/chunk-TVU6PYJH.mjs +2032 -0
  22. package/dist/chunk-TVU6PYJH.mjs.map +1 -0
  23. package/dist/clients.cjs +292 -47
  24. package/dist/clients.cjs.map +1 -1
  25. package/dist/clients.d.cts +23 -0
  26. package/dist/clients.d.ts +23 -0
  27. package/dist/clients.mjs +292 -47
  28. package/dist/clients.mjs.map +1 -1
  29. package/dist/hooks.cjs +200 -32
  30. package/dist/hooks.cjs.map +1 -1
  31. package/dist/hooks.mjs +200 -32
  32. package/dist/hooks.mjs.map +1 -1
  33. package/dist/index.cjs +292 -47
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +81 -4
  36. package/dist/index.d.ts +81 -4
  37. package/dist/index.mjs +292 -47
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/sdk.gen-2SQOPTWJ.mjs +26 -0
  40. package/dist/sdk.gen-2SQOPTWJ.mjs.map +1 -0
  41. package/dist/sdk.gen-HSGK4C5S.mjs +26 -0
  42. package/dist/sdk.gen-HSGK4C5S.mjs.map +1 -0
  43. package/dist/sdk.gen-NUK2VGHO.mjs +25 -0
  44. package/dist/sdk.gen-NUK2VGHO.mjs.map +1 -0
  45. package/dist/sdk.gen-O4KAQUZB.mjs +26 -0
  46. package/dist/sdk.gen-O4KAQUZB.mjs.map +1 -0
  47. package/dist/sdk.gen-PPAVSBNT.mjs +26 -0
  48. package/dist/sdk.gen-PPAVSBNT.mjs.map +1 -0
  49. package/dist/sdk.gen-XLHLOCJ2.mjs +25 -0
  50. package/dist/sdk.gen-XLHLOCJ2.mjs.map +1 -0
  51. package/dist/sdk.gen-ZOE6NQAL.mjs +26 -0
  52. package/dist/sdk.gen-ZOE6NQAL.mjs.map +1 -0
  53. package/dist/sdk.gen-ZT7LGJVO.mjs +26 -0
  54. package/dist/sdk.gen-ZT7LGJVO.mjs.map +1 -0
  55. package/package.json +2 -2
  56. package/src/_api/generated/_cfg_accounts/hooks/index.ts +1 -0
  57. package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenBlacklistCreate.ts +28 -0
  58. package/src/_api/generated/_cfg_accounts/openapi.json +99 -0
  59. package/src/_api/generated/_cfg_accounts/schemas/TokenBlacklistRequest.ts +11 -0
  60. package/src/_api/generated/_cfg_accounts/schemas/index.ts +1 -0
  61. package/src/_api/generated/_cfg_centrifugo/openapi.json +9 -0
  62. package/src/_api/generated/_cfg_totp/openapi.json +33 -0
  63. package/src/_api/generated/helpers/auth.ts +288 -31
  64. package/src/_api/generated/openapi.json +129 -0
  65. package/src/_api/generated/sdk.gen.ts +94 -16
  66. package/src/_api/generated/types.gen.ts +18 -0
  67. package/src/auth/__tests__/guard.test.ts +0 -31
  68. package/src/auth/constants.ts +6 -0
  69. package/src/auth/context/AccountsContext.tsx +14 -24
  70. package/src/auth/context/AuthContext.tsx +263 -607
  71. package/src/auth/context/types.ts +13 -0
  72. package/src/auth/hooks/index.ts +8 -5
  73. package/src/auth/hooks/useAuthRedirect.ts +17 -0
  74. package/src/auth/hooks/useGithubAuth.ts +3 -3
  75. package/src/auth/hooks/useSession.ts +22 -0
  76. package/src/auth/hooks/useTwoFactor.ts +8 -6
  77. package/src/auth/middlewares/index.ts +5 -6
  78. package/src/auth/utils/guard.ts +0 -22
  79. package/src/auth/utils/index.ts +0 -2
  80. package/src/_api/generated/_cfg_accounts/events.ts +0 -198
  81. package/src/_api/generated/_cfg_centrifugo/events.ts +0 -198
  82. package/src/_api/generated/_cfg_totp/events.ts +0 -198
  83. package/src/auth/__tests__/jwt.test.ts +0 -119
  84. package/src/auth/__tests__/sessionBootstrap.test.ts +0 -111
  85. package/src/auth/__tests__/useTokenRefresh.dom.test.tsx +0 -167
  86. package/src/auth/hooks/useAuthGuard.ts +0 -35
  87. package/src/auth/hooks/useTokenRefresh.ts +0 -131
  88. package/src/auth/refreshHandler.ts +0 -79
  89. package/src/auth/utils/jwt.ts +0 -66
package/dist/auth.cjs CHANGED
@@ -41,19 +41,17 @@ __export(auth_exports, {
41
41
  PatchedCfgUserUpdateRequestSchema: () => PatchedCfgUserUpdateRequestSchema,
42
42
  authLogger: () => authLogger,
43
43
  clearProfileCache: () => clearProfileCache,
44
+ consumeSavedRedirect: () => consumeSavedRedirect,
44
45
  decodeBase64: () => decodeBase64,
45
46
  encodeBase64: () => encodeBase64,
46
47
  formatAuthError: () => formatAuthError,
47
48
  getCacheMetadata: () => getCacheMetadata,
48
49
  getCachedProfile: () => getCachedProfile,
49
- getTokenExpiry: () => getTokenExpiry,
50
50
  hasValidCache: () => hasValidCache,
51
51
  isAllowedAuthPath: () => isAllowedAuthPath,
52
- isTokenExpired: () => isTokenExpired,
53
- isTokenExpiringSoon: () => isTokenExpiringSoon,
54
52
  logger: () => logger,
55
- nextAuthEvaluationDelay: () => nextAuthEvaluationDelay,
56
53
  normalizePath: () => normalizePath,
54
+ peekSavedRedirect: () => peekSavedRedirect,
57
55
  resolveGuardIsAuthenticated: () => resolveGuardIsAuthenticated,
58
56
  resolveGuardIsLoading: () => resolveGuardIsLoading,
59
57
  setCachedProfile: () => setCachedProfile,
@@ -62,7 +60,6 @@ __export(auth_exports, {
62
60
  useAuth: () => useAuth,
63
61
  useAuthForm: () => useAuthForm,
64
62
  useAuthFormState: () => useAuthFormState,
65
- useAuthGuard: () => useAuthGuard,
66
63
  useAuthRedirectManager: () => useAuthRedirectManager,
67
64
  useAuthValidation: () => useAuthValidation,
68
65
  useAutoAuth: () => useAutoAuth,
@@ -72,8 +69,8 @@ __export(auth_exports, {
72
69
  useGithubAuth: () => useGithubAuth,
73
70
  useLocalStorage: () => useLocalStorage,
74
71
  useQueryParams: () => useQueryParams,
72
+ useSession: () => useSession,
75
73
  useSessionStorage: () => useSessionStorage,
76
- useTokenRefresh: () => useTokenRefresh,
77
74
  useTwoFactor: () => useTwoFactor,
78
75
  useTwoFactorSetup: () => useTwoFactorSetup,
79
76
  useTwoFactorStatus: () => useTwoFactorStatus,
@@ -92,896 +89,1068 @@ var AUTH_CONSTANTS = {
92
89
  BACKUP_CODE_MAX_LENGTH: 12
93
90
  };
94
91
 
95
- // src/auth/utils/env.ts
96
- var isDev = process.env.NODE_ENV === "development";
97
- var isBrowser = typeof window !== "undefined";
98
- var isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
99
- var dpopEnabled = process.env.NEXT_PUBLIC_DPOP_ENABLED === "true";
100
-
101
92
  // src/auth/context/AuthContext.tsx
102
93
  var import_navigation4 = require("next/navigation");
103
- var import_react17 = require("react");
104
- var import_swr5 = require("swr");
94
+ var import_react16 = require("react");
105
95
 
106
- // src/auth/hooks/useCfgRouter.ts
107
- var import_navigation = require("next/navigation");
108
- var import_react = require("react");
109
- function getBasePath() {
110
- if (typeof process === "undefined") {
111
- return "";
96
+ // src/_api/generated/helpers/errors.ts
97
+ var APIError = class extends Error {
98
+ constructor(statusCode, statusText, response, url, message) {
99
+ super(message || `HTTP ${statusCode}: ${statusText}`);
100
+ this.statusCode = statusCode;
101
+ this.statusText = statusText;
102
+ this.response = response;
103
+ this.url = url;
104
+ this.name = "APIError";
112
105
  }
113
- return process.env.NEXT_PUBLIC_BASE_PATH || "";
114
- }
115
- __name(getBasePath, "getBasePath");
116
- function withBasePath(path, basePath) {
117
- if (!basePath) {
118
- return path;
106
+ static {
107
+ __name(this, "APIError");
119
108
  }
120
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
121
- const normalizedBasePath = basePath.replace(/\/$/, "");
122
- return `${normalizedBasePath}${normalizedPath}`;
109
+ get details() {
110
+ if (typeof this.response === "object" && this.response !== null) {
111
+ return this.response;
112
+ }
113
+ return null;
114
+ }
115
+ get fieldErrors() {
116
+ const details = this.details;
117
+ if (!details) return null;
118
+ const fieldErrors = {};
119
+ for (const [key, value] of Object.entries(details)) {
120
+ if (Array.isArray(value)) fieldErrors[key] = value;
121
+ }
122
+ return Object.keys(fieldErrors).length > 0 ? fieldErrors : null;
123
+ }
124
+ get errorMessage() {
125
+ const details = this.details;
126
+ if (!details) return this.message;
127
+ if (details.detail) {
128
+ return Array.isArray(details.detail) ? details.detail.join(", ") : String(details.detail);
129
+ }
130
+ if (details.error) return String(details.error);
131
+ if (details.message) return String(details.message);
132
+ const fieldErrors = this.fieldErrors;
133
+ if (fieldErrors) {
134
+ const firstField = Object.keys(fieldErrors)[0];
135
+ if (firstField) return `${firstField}: ${fieldErrors[firstField]?.join(", ")}`;
136
+ }
137
+ return this.message;
138
+ }
139
+ get isValidationError() {
140
+ return this.statusCode === 400;
141
+ }
142
+ get isAuthError() {
143
+ return this.statusCode === 401;
144
+ }
145
+ get isPermissionError() {
146
+ return this.statusCode === 403;
147
+ }
148
+ get isNotFoundError() {
149
+ return this.statusCode === 404;
150
+ }
151
+ get isServerError() {
152
+ return this.statusCode >= 500 && this.statusCode < 600;
153
+ }
154
+ };
155
+
156
+ // src/_api/generated/helpers/auth.ts
157
+ var ACCESS_KEY = "cfg.access_token";
158
+ var REFRESH_KEY = "cfg.refresh_token";
159
+ var API_KEY_KEY = "cfg.api_key";
160
+ var isBrowser = typeof window !== "undefined";
161
+ var localStorageBackend = {
162
+ get(key) {
163
+ if (!isBrowser) return null;
164
+ try {
165
+ return window.localStorage.getItem(key);
166
+ } catch {
167
+ return null;
168
+ }
169
+ },
170
+ set(key, value) {
171
+ if (!isBrowser) return;
172
+ try {
173
+ if (value === null) window.localStorage.removeItem(key);
174
+ else window.localStorage.setItem(key, value);
175
+ } catch {
176
+ }
177
+ }
178
+ };
179
+ var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
180
+ var cookieBackend = {
181
+ get(key) {
182
+ if (!isBrowser) return null;
183
+ try {
184
+ const re = new RegExp(`(?:^|;\\s*)${encodeURIComponent(key)}=([^;]*)`);
185
+ const m = document.cookie.match(re);
186
+ return m ? decodeURIComponent(m[1]) : null;
187
+ } catch {
188
+ return null;
189
+ }
190
+ },
191
+ set(key, value) {
192
+ if (!isBrowser) return;
193
+ try {
194
+ const k = encodeURIComponent(key);
195
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
196
+ if (value === null) {
197
+ document.cookie = `${k}=; Path=/; Max-Age=0; SameSite=Lax${secure}`;
198
+ } else {
199
+ const v = encodeURIComponent(value);
200
+ document.cookie = `${k}=${v}; Path=/; Max-Age=${COOKIE_MAX_AGE}; SameSite=Lax${secure}`;
201
+ }
202
+ } catch {
203
+ }
204
+ }
205
+ };
206
+ var _storage = localStorageBackend;
207
+ var _storageMode = "localStorage";
208
+ function detectLocale() {
209
+ try {
210
+ if (typeof document !== "undefined") {
211
+ const m = document.cookie.match(/(?:^|;\s*)NEXT_LOCALE=([^;]*)/);
212
+ if (m) return decodeURIComponent(m[1]);
213
+ }
214
+ if (typeof navigator !== "undefined" && navigator.language) {
215
+ return navigator.language;
216
+ }
217
+ } catch {
218
+ }
219
+ return null;
123
220
  }
124
- __name(withBasePath, "withBasePath");
125
- function useCfgRouter() {
126
- const router = (0, import_navigation.useRouter)();
127
- const basePath = (0, import_react.useMemo)(() => getBasePath(), []);
128
- const isStaticBuild2 = (0, import_react.useMemo)(() => {
129
- return typeof process !== "undefined" && process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
130
- }, []);
131
- const push = (0, import_react.useCallback)((href, options) => {
132
- if (basePath) {
133
- window.location.href = withBasePath(href, basePath);
134
- } else {
135
- router.push(href, options);
221
+ __name(detectLocale, "detectLocale");
222
+ function defaultBaseUrl() {
223
+ if (typeof window !== "undefined") {
224
+ try {
225
+ if (typeof process !== "undefined" && process.env) {
226
+ if (process.env.NEXT_PUBLIC_STATIC_BUILD === "true") return "";
227
+ if (process.env.NEXT_PUBLIC_API_PROXY_URL !== void 0)
228
+ return process.env.NEXT_PUBLIC_API_PROXY_URL;
229
+ return process.env.NEXT_PUBLIC_API_URL || "";
230
+ }
231
+ } catch {
136
232
  }
137
- }, [router, basePath]);
138
- const replace = (0, import_react.useCallback)((href, options) => {
139
- if (basePath) {
140
- window.location.replace(withBasePath(href, basePath));
141
- } else {
142
- router.replace(href, options);
233
+ return "";
234
+ }
235
+ try {
236
+ if (typeof process !== "undefined" && process.env) {
237
+ if (process.env.NEXT_PUBLIC_STATIC_BUILD === "true") return "";
238
+ return process.env.NEXT_PUBLIC_API_URL || "";
143
239
  }
144
- }, [router, basePath]);
145
- const hardPush = (0, import_react.useCallback)((href) => {
146
- window.location.href = withBasePath(href, basePath);
147
- }, [basePath]);
148
- const hardReplace = (0, import_react.useCallback)((href) => {
149
- window.location.replace(withBasePath(href, basePath));
150
- }, [basePath]);
151
- const prefetch = (0, import_react.useCallback)((href) => {
152
- router.prefetch(href);
153
- }, [router]);
154
- const back = (0, import_react.useCallback)(() => {
155
- router.back();
156
- }, [router]);
157
- const forward = (0, import_react.useCallback)(() => {
158
- router.forward();
159
- }, [router]);
160
- const refresh = (0, import_react.useCallback)(() => {
161
- router.refresh();
162
- }, [router]);
240
+ } catch {
241
+ }
242
+ return "";
243
+ }
244
+ __name(defaultBaseUrl, "defaultBaseUrl");
245
+ function defaultApiKey() {
246
+ try {
247
+ if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_API_KEY) {
248
+ return process.env.NEXT_PUBLIC_API_KEY;
249
+ }
250
+ } catch {
251
+ }
252
+ return null;
253
+ }
254
+ __name(defaultApiKey, "defaultApiKey");
255
+ var _localeOverride = null;
256
+ var _apiKeyOverride = null;
257
+ var _baseUrlOverride = null;
258
+ var _withCredentials = true;
259
+ var _onUnauthorized = null;
260
+ var _refreshHandler = null;
261
+ var _refreshInflight = null;
262
+ var RETRY_MARKER = "X-Auth-Retry";
263
+ function jwtExpMs(token) {
264
+ try {
265
+ const payload = token.split(".")[1];
266
+ if (!payload) return null;
267
+ const json = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
268
+ return typeof json.exp === "number" ? json.exp * 1e3 : null;
269
+ } catch {
270
+ return null;
271
+ }
272
+ }
273
+ __name(jwtExpMs, "jwtExpMs");
274
+ function computeSnapshot() {
275
+ const access = _storage.get(ACCESS_KEY);
276
+ const refresh = _storage.get(REFRESH_KEY);
277
+ const now = Date.now();
278
+ const accessExp = access ? jwtExpMs(access) : null;
279
+ const accessAlive = access !== null && (accessExp === null || accessExp > now);
280
+ const refreshExp = refresh ? jwtExpMs(refresh) : null;
281
+ const refreshAlive = refresh !== null && (refreshExp === null || refreshExp > now);
163
282
  return {
164
- push,
165
- replace,
166
- hardPush,
167
- hardReplace,
168
- prefetch,
169
- back,
170
- forward,
171
- refresh
283
+ status: accessAlive || refreshAlive ? "authenticated" : "anonymous",
284
+ accessExpiresAt: accessExp
172
285
  };
173
286
  }
174
- __name(useCfgRouter, "useCfgRouter");
175
-
176
- // src/auth/hooks/useQueryParams.ts
177
- var import_navigation2 = require("next/navigation");
178
- var import_react2 = require("react");
179
- function useQueryParams() {
180
- const pathname = (0, import_navigation2.usePathname)();
181
- const [queryParams, setQueryParams] = (0, import_react2.useState)(() => {
182
- if (typeof window === "undefined") {
183
- return new URLSearchParams();
287
+ __name(computeSnapshot, "computeSnapshot");
288
+ var SERVER_SNAPSHOT = { status: "anonymous", accessExpiresAt: null };
289
+ var SESSION_SYNC_EVENT = `cfg-auth:changed:${ACCESS_KEY}`;
290
+ var _snapshot = computeSnapshot();
291
+ var _sessionListeners = /* @__PURE__ */ new Set();
292
+ var _expiryTimer = null;
293
+ var _storageListenerInstalled = false;
294
+ function scheduleExpiryFlip() {
295
+ if (!isBrowser) return;
296
+ if (_expiryTimer !== null) {
297
+ clearTimeout(_expiryTimer);
298
+ _expiryTimer = null;
299
+ }
300
+ if (_sessionListeners.size === 0) return;
301
+ const now = Date.now();
302
+ const exps = [];
303
+ const access = _storage.get(ACCESS_KEY);
304
+ const refresh = _storage.get(REFRESH_KEY);
305
+ const accessExp = access ? jwtExpMs(access) : null;
306
+ const refreshExp = refresh ? jwtExpMs(refresh) : null;
307
+ if (accessExp !== null && accessExp > now) exps.push(accessExp);
308
+ if (refreshExp !== null && refreshExp > now) exps.push(refreshExp);
309
+ if (!exps.length) return;
310
+ const delay = Math.min(Math.min(...exps) - now + 250, 2147483647);
311
+ _expiryTimer = setTimeout(notifySessionChanged, delay);
312
+ }
313
+ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
314
+ function notifySessionChanged() {
315
+ const next = computeSnapshot();
316
+ const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
317
+ if (changed) _snapshot = next;
318
+ scheduleExpiryFlip();
319
+ if (!changed) return;
320
+ for (const listener of Array.from(_sessionListeners)) {
321
+ try {
322
+ listener();
323
+ } catch {
324
+ }
325
+ }
326
+ if (isBrowser) {
327
+ try {
328
+ window.dispatchEvent(new Event(SESSION_SYNC_EVENT));
329
+ } catch {
330
+ }
331
+ }
332
+ }
333
+ __name(notifySessionChanged, "notifySessionChanged");
334
+ function ensureStorageSync() {
335
+ if (!isBrowser || _storageListenerInstalled) return;
336
+ _storageListenerInstalled = true;
337
+ window.addEventListener("storage", (e) => {
338
+ if (e.key === null || e.key === ACCESS_KEY || e.key === REFRESH_KEY) {
339
+ notifySessionChanged();
184
340
  }
185
- return new URLSearchParams(window.location.search);
186
341
  });
187
- const lastSearchRef = (0, import_react2.useRef)("");
188
- (0, import_react2.useEffect)(() => {
189
- if (typeof window === "undefined") return;
190
- const updateQueryParams = /* @__PURE__ */ __name(() => {
191
- const currentSearch = window.location.search;
192
- if (currentSearch !== lastSearchRef.current) {
193
- lastSearchRef.current = currentSearch;
194
- setQueryParams(new URLSearchParams(currentSearch));
195
- }
196
- }, "updateQueryParams");
197
- updateQueryParams();
198
- window.addEventListener("popstate", updateQueryParams);
199
- const origPush = window.history.pushState;
200
- const origReplace = window.history.replaceState;
201
- window.history.pushState = function(...args) {
202
- const result = origPush.apply(this, args);
203
- updateQueryParams();
204
- return result;
205
- };
206
- window.history.replaceState = function(...args) {
207
- const result = origReplace.apply(this, args);
208
- updateQueryParams();
209
- return result;
342
+ window.addEventListener(SESSION_SYNC_EVENT, () => notifySessionChanged());
343
+ }
344
+ __name(ensureStorageSync, "ensureStorageSync");
345
+ var _sessionExpiredHandlers = /* @__PURE__ */ new Set();
346
+ var _client = null;
347
+ function pushClientConfig() {
348
+ if (!_client) return;
349
+ _client.setConfig({
350
+ baseUrl: auth.getBaseUrl(),
351
+ credentials: _withCredentials ? "include" : "same-origin"
352
+ });
353
+ }
354
+ __name(pushClientConfig, "pushClientConfig");
355
+ var auth = {
356
+ // ── Storage mode ──────────────────────────────────────────────────
357
+ getStorageMode() {
358
+ return _storageMode;
359
+ },
360
+ setStorageMode(mode) {
361
+ _storageMode = mode;
362
+ _storage = mode === "cookie" ? cookieBackend : localStorageBackend;
363
+ notifySessionChanged();
364
+ },
365
+ // ── Bearer token ──────────────────────────────────────────────────
366
+ getToken() {
367
+ return _storage.get(ACCESS_KEY);
368
+ },
369
+ setToken(token) {
370
+ _storage.set(ACCESS_KEY, token);
371
+ notifySessionChanged();
372
+ },
373
+ getRefreshToken() {
374
+ return _storage.get(REFRESH_KEY);
375
+ },
376
+ setRefreshToken(token) {
377
+ _storage.set(REFRESH_KEY, token);
378
+ notifySessionChanged();
379
+ },
380
+ clearTokens() {
381
+ _storage.set(ACCESS_KEY, null);
382
+ _storage.set(REFRESH_KEY, null);
383
+ notifySessionChanged();
384
+ },
385
+ /** Session-aware: token PRESENT and not past its `exp` (or a live refresh
386
+ * token exists that can mint one). Prefer `getSnapshot().status` in React. */
387
+ isAuthenticated() {
388
+ return computeSnapshot().status === "authenticated";
389
+ },
390
+ // ── Session (the ONE write path for login/logout flows) ──────────
391
+ /**
392
+ * Persist a token pair atomically. Every login flow (OTP verify, 2FA,
393
+ * OAuth callback) and the refresh handler should end here — do NOT
394
+ * scatter `setToken`/`setRefreshToken` pairs through app code.
395
+ * `refresh: undefined` keeps the current refresh token (access-only
396
+ * rotation); `refresh: null` explicitly drops it.
397
+ */
398
+ setSession(tokens) {
399
+ _storage.set(ACCESS_KEY, tokens.access);
400
+ if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
401
+ notifySessionChanged();
402
+ },
403
+ clearSession() {
404
+ auth.clearTokens();
405
+ },
406
+ // ── Reactive snapshot (for useSyncExternalStore) ──────────────────
407
+ /**
408
+ * @example React:
409
+ * const session = useSyncExternalStore(
410
+ * auth.subscribe, auth.getSnapshot, auth.getServerSnapshot,
411
+ * );
412
+ * const isAuthenticated = session.status === 'authenticated';
413
+ */
414
+ getSnapshot() {
415
+ return _snapshot;
416
+ },
417
+ getServerSnapshot() {
418
+ return SERVER_SNAPSHOT;
419
+ },
420
+ subscribe(listener) {
421
+ ensureStorageSync();
422
+ _sessionListeners.add(listener);
423
+ notifySessionChanged();
424
+ return () => {
425
+ _sessionListeners.delete(listener);
426
+ if (_sessionListeners.size === 0 && _expiryTimer !== null) {
427
+ clearTimeout(_expiryTimer);
428
+ _expiryTimer = null;
429
+ }
210
430
  };
431
+ },
432
+ /**
433
+ * Fired on TERMINAL 401 — after the refresh+retry path is exhausted.
434
+ * The store has already cleared the session before calling back, so
435
+ * handlers only need to route to login (no clear-then-redirect
436
+ * ordering to get wrong). Returns an unsubscribe function; multiple
437
+ * handlers compose (unlike the legacy single-slot `onUnauthorized`).
438
+ */
439
+ onSessionExpired(cb) {
440
+ _sessionExpiredHandlers.add(cb);
211
441
  return () => {
212
- window.removeEventListener("popstate", updateQueryParams);
213
- window.history.pushState = origPush;
214
- window.history.replaceState = origReplace;
442
+ _sessionExpiredHandlers.delete(cb);
215
443
  };
216
- }, [pathname]);
217
- return queryParams;
218
- }
219
- __name(useQueryParams, "useQueryParams");
220
-
221
- // src/auth/hooks/useAuthFormState.ts
222
- var import_react3 = require("react");
223
- var formatCountdown = /* @__PURE__ */ __name((s) => {
224
- if (s <= 0) return "";
225
- const m = Math.floor(s / 60);
226
- return m > 0 ? `${m}:${String(s % 60).padStart(2, "0")}` : `${s}s`;
227
- }, "formatCountdown");
228
- var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
229
- const [identifier, setIdentifier] = (0, import_react3.useState)(initialIdentifier);
230
- const [otp, setOtp] = (0, import_react3.useState)("");
231
- const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
232
- const [acceptedTerms, setAcceptedTerms] = (0, import_react3.useState)(true);
233
- const [step, setStep] = (0, import_react3.useState)("identifier");
234
- const [error, setError] = (0, import_react3.useState)("");
235
- const [webmail, setWebmail] = (0, import_react3.useState)(null);
236
- const [twoFactorSessionId, setTwoFactorSessionId] = (0, import_react3.useState)(null);
237
- const [shouldPrompt2FA, setShouldPrompt2FA] = (0, import_react3.useState)(false);
238
- const [twoFactorCode, setTwoFactorCode] = (0, import_react3.useState)("");
239
- const [useBackupCode, setUseBackupCode] = (0, import_react3.useState)(false);
240
- const [rateLimitSeconds, setRateLimitSeconds] = (0, import_react3.useState)(0);
241
- const rateLimitTimerRef = (0, import_react3.useRef)(null);
242
- const startRateLimitCountdown = (0, import_react3.useCallback)((seconds) => {
243
- if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
244
- setRateLimitSeconds(seconds);
245
- rateLimitTimerRef.current = setInterval(() => {
246
- setRateLimitSeconds((prev) => {
247
- if (prev <= 1) {
248
- clearInterval(rateLimitTimerRef.current);
249
- rateLimitTimerRef.current = null;
250
- return 0;
251
- }
252
- return prev - 1;
253
- });
254
- }, 1e3);
255
- }, []);
256
- (0, import_react3.useEffect)(() => () => {
257
- if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
258
- }, []);
259
- const clearError = (0, import_react3.useCallback)(() => setError(""), []);
260
- return {
261
- // State
262
- identifier,
263
- otp,
264
- isLoading,
265
- acceptedTerms,
266
- step,
267
- error,
268
- twoFactorSessionId,
269
- shouldPrompt2FA,
270
- twoFactorCode,
271
- useBackupCode,
272
- rateLimitSeconds,
273
- isRateLimited: rateLimitSeconds > 0,
274
- rateLimitLabel: formatCountdown(rateLimitSeconds),
275
- webmail,
276
- // Handlers
277
- setIdentifier,
278
- setOtp,
279
- setAcceptedTerms,
280
- setError,
281
- clearError,
282
- setStep,
283
- setIsLoading,
284
- setTwoFactorSessionId,
285
- setShouldPrompt2FA,
286
- setTwoFactorCode,
287
- setUseBackupCode,
288
- startRateLimitCountdown,
289
- setWebmail
290
- };
291
- }, "useAuthFormState");
292
-
293
- // src/auth/hooks/useAuthValidation.ts
294
- var import_react4 = require("react");
295
- var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
296
- var useAuthValidation = /* @__PURE__ */ __name(() => {
297
- const validateIdentifier2 = (0, import_react4.useCallback)((id) => {
298
- return EMAIL_REGEX.test(id);
299
- }, []);
300
- return { validateIdentifier: validateIdentifier2 };
301
- }, "useAuthValidation");
302
- var validateIdentifier = /* @__PURE__ */ __name((id) => EMAIL_REGEX.test(id), "validateIdentifier");
303
-
304
- // src/auth/hooks/useAuthForm.ts
305
- var import_react7 = require("react");
306
-
307
- // src/auth/utils/logger.ts
308
- var import_consola = require("consola");
309
-
310
- // src/log-control.ts
311
- var verboseByDefault = isDev || isStaticBuild;
312
- var DEFAULT_LEVEL = verboseByDefault ? 4 : 1;
313
- var _level = DEFAULT_LEVEL;
314
- var _subscribers = /* @__PURE__ */ new Set();
315
- function getLogLevel() {
316
- return _level;
444
+ },
445
+ // ── API key ───────────────────────────────────────────────────────
446
+ getApiKey() {
447
+ return _apiKeyOverride ?? _storage.get(API_KEY_KEY) ?? defaultApiKey();
448
+ },
449
+ setApiKey(key) {
450
+ _apiKeyOverride = key;
451
+ },
452
+ setApiKeyPersist(key) {
453
+ _apiKeyOverride = key;
454
+ _storage.set(API_KEY_KEY, key);
455
+ },
456
+ clearApiKey() {
457
+ _apiKeyOverride = null;
458
+ _storage.set(API_KEY_KEY, null);
459
+ },
460
+ // ── Locale ────────────────────────────────────────────────────────
461
+ getLocale() {
462
+ return _localeOverride ?? detectLocale();
463
+ },
464
+ setLocale(locale) {
465
+ _localeOverride = locale;
466
+ },
467
+ // ── Base URL ──────────────────────────────────────────────────────
468
+ getBaseUrl() {
469
+ const url = _baseUrlOverride ?? defaultBaseUrl();
470
+ return url.replace(/\/$/, "");
471
+ },
472
+ setBaseUrl(url) {
473
+ _baseUrlOverride = url ? url.replace(/\/$/, "") : null;
474
+ pushClientConfig();
475
+ },
476
+ // ── Credentials toggle ────────────────────────────────────────────
477
+ getWithCredentials() {
478
+ return _withCredentials;
479
+ },
480
+ setWithCredentials(value) {
481
+ _withCredentials = value;
482
+ pushClientConfig();
483
+ },
484
+ // ── 401 handler ───────────────────────────────────────────────────
485
+ /**
486
+ * Fired when the server returns 401 AND no refresh path recovers it
487
+ * (no refresh token, no refresh handler, refresh failed, or retry
488
+ * still 401). The app should clear local state and redirect to login.
489
+ *
490
+ * NOT fired for 401 that gets transparently recovered by the refresh
491
+ * handler — those are invisible to callers.
492
+ */
493
+ onUnauthorized(cb) {
494
+ _onUnauthorized = cb;
495
+ },
496
+ /**
497
+ * Register the refresh strategy. The handler receives the current
498
+ * refresh token and must call your refresh endpoint, returning
499
+ * `{ access, refresh? }` on success or `null` on failure.
500
+ *
501
+ * @example
502
+ * auth.setRefreshHandler(async (refresh) => {
503
+ * const { data } = await Auth.tokenRefreshCreate({ body: { refresh } });
504
+ * return data ? { access: data.access, refresh: data.refresh } : null;
505
+ * });
506
+ */
507
+ setRefreshHandler(fn) {
508
+ _refreshHandler = fn;
509
+ },
510
+ /**
511
+ * Proactively run the registered refresh handler right now, reusing the
512
+ * SAME single-flight promise as the 401-recovery interceptor. Callers
513
+ * (e.g. an expiry timer / focus / reconnect) get token rotation,
514
+ * de-duplication and rotated-token persistence for free — they must NOT
515
+ * re-implement any of it. Returns the fresh access token, or null if
516
+ * there is no handler / no refresh token / the refresh failed.
517
+ */
518
+ refreshNow() {
519
+ return tryRefresh();
520
+ }
521
+ };
522
+ async function tryRefresh() {
523
+ if (_refreshInflight) return _refreshInflight;
524
+ if (!_refreshHandler) return null;
525
+ const runRefresh = /* @__PURE__ */ __name(async () => {
526
+ const refresh = auth.getRefreshToken();
527
+ if (!refresh) return null;
528
+ const result = await _refreshHandler(refresh);
529
+ if (!result?.access) return null;
530
+ auth.setToken(result.access);
531
+ if (result.refresh) auth.setRefreshToken(result.refresh);
532
+ return result.access;
533
+ }, "runRefresh");
534
+ _refreshInflight = (async () => {
535
+ try {
536
+ const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
537
+ if (locks?.request) {
538
+ return await locks.request(`${REFRESH_KEY}:refresh`, runRefresh);
539
+ }
540
+ return await runRefresh();
541
+ } catch {
542
+ return null;
543
+ } finally {
544
+ _refreshInflight = null;
545
+ }
546
+ })();
547
+ return _refreshInflight;
317
548
  }
318
- __name(getLogLevel, "getLogLevel");
319
- function setLogLevel(level) {
320
- _level = level;
321
- for (const fn of _subscribers) {
549
+ __name(tryRefresh, "tryRefresh");
550
+ function expireSession(response) {
551
+ auth.clearTokens();
552
+ for (const cb of Array.from(_sessionExpiredHandlers)) {
322
553
  try {
323
- fn(level);
554
+ cb(response);
555
+ } catch {
556
+ }
557
+ }
558
+ if (_onUnauthorized) {
559
+ try {
560
+ _onUnauthorized(response);
324
561
  } catch {
325
562
  }
326
563
  }
327
564
  }
328
- __name(setLogLevel, "setLogLevel");
329
- function onLogLevelChange(fn) {
330
- _subscribers.add(fn);
565
+ __name(expireSession, "expireSession");
566
+ function dpopEnabled() {
331
567
  try {
332
- fn(_level);
568
+ return typeof process !== "undefined" && process.env?.NEXT_PUBLIC_DPOP_ENABLED === "true";
333
569
  } catch {
570
+ return false;
334
571
  }
335
- return () => _subscribers.delete(fn);
336
572
  }
337
- __name(onLogLevelChange, "onLogLevelChange");
338
- function applyRoleLogPolicy(opts) {
339
- const devMode = opts.isDev ?? verboseByDefault;
340
- if (opts.isAdmin || devMode) {
341
- setLogLevel(4);
342
- } else {
343
- setLogLevel(1);
344
- }
573
+ __name(dpopEnabled, "dpopEnabled");
574
+ var _DPOP_DB = "cfg-auth";
575
+ var _DPOP_STORE = "keys";
576
+ var _DPOP_KEY_ID = "dpop-ec-p256";
577
+ function _idbOpen() {
578
+ return new Promise((resolve, reject) => {
579
+ const req = indexedDB.open(_DPOP_DB, 1);
580
+ req.onupgradeneeded = () => req.result.createObjectStore(_DPOP_STORE);
581
+ req.onsuccess = () => resolve(req.result);
582
+ req.onerror = () => reject(req.error);
583
+ });
345
584
  }
346
- __name(applyRoleLogPolicy, "applyRoleLogPolicy");
347
-
348
- // src/auth/utils/logger.ts
349
- var logger = (0, import_consola.createConsola)({
350
- level: getLogLevel()
351
- }).withTag("api");
352
- onLogLevelChange((level) => {
353
- logger.level = level;
354
- });
355
- var authLogger = logger.withTag("auth");
356
-
357
- // src/auth/hooks/useAutoAuth.ts
358
- var import_navigation3 = require("next/navigation");
359
- var import_react5 = require("react");
360
-
361
- // src/auth/utils/path.ts
362
- var normalizePath = /* @__PURE__ */ __name((path) => {
363
- if (!path) return "";
364
- const withoutLocale = path.replace(/^\/[a-z]{2}(?=\/|$)/, "");
365
- const trimmed = withoutLocale.replace(/\/+$/, "");
366
- return trimmed || "/";
367
- }, "normalizePath");
368
- var isAllowedAuthPath = /* @__PURE__ */ __name((pathname, allowedPaths) => {
369
- const normalized = normalizePath(pathname);
370
- return allowedPaths.some(
371
- (p) => normalized === p || normalized.startsWith(p + "/")
372
- );
373
- }, "isAllowedAuthPath");
374
-
375
- // src/auth/hooks/useAutoAuth.ts
376
- var useAutoAuth = /* @__PURE__ */ __name((options = {}) => {
377
- const { onOTPDetected, cleanupUrl = true, allowedPaths = ["/auth"] } = options;
378
- const queryParams = useQueryParams();
379
- const pathname = (0, import_navigation3.usePathname)();
380
- const router = useCfgRouter();
381
- const isAllowedPath = isAllowedAuthPath(pathname, allowedPaths);
382
- const queryOtp = queryParams.get("otp") || "";
383
- const queryEmail = queryParams.get("email") || void 0;
384
- const hasOTP = !!queryOtp;
385
- const isReady = !!pathname && hasOTP && isAllowedPath;
386
- const otpValid = queryOtp.length === AUTH_CONSTANTS.EMAIL_OTP_LENGTH;
387
- const onOTPDetectedRef = (0, import_react5.useRef)(onOTPDetected);
388
- onOTPDetectedRef.current = onOTPDetected;
389
- (0, import_react5.useEffect)(() => {
390
- if (!isReady || !otpValid) return;
391
- authLogger.info("OTP detected in URL on auth page:", queryOtp);
392
- onOTPDetectedRef.current?.(queryOtp, queryEmail);
393
- }, [isReady, otpValid, queryOtp, queryEmail]);
394
- (0, import_react5.useEffect)(() => {
395
- if (!isReady || !cleanupUrl || !queryOtp) return;
396
- const cleanQuery = Object.fromEntries(queryParams.entries());
397
- delete cleanQuery.otp;
398
- delete cleanQuery.email;
399
- const queryString = new URLSearchParams(cleanQuery).toString();
400
- const realPathname = typeof window !== "undefined" ? window.location.pathname : pathname;
401
- router.replace(queryString ? `${realPathname}?${queryString}` : realPathname);
402
- }, [isReady, cleanupUrl, queryOtp]);
403
- return {
404
- isReady,
405
- hasOTP,
406
- isAllowedPath
407
- };
408
- }, "useAutoAuth");
409
-
410
- // src/auth/hooks/useTwoFactor.ts
411
- var import_react6 = require("react");
412
-
413
- // src/_api/generated/helpers/errors.ts
414
- var APIError = class extends Error {
415
- constructor(statusCode, statusText, response, url, message) {
416
- super(message || `HTTP ${statusCode}: ${statusText}`);
417
- this.statusCode = statusCode;
418
- this.statusText = statusText;
419
- this.response = response;
420
- this.url = url;
421
- this.name = "APIError";
422
- }
423
- static {
424
- __name(this, "APIError");
425
- }
426
- get details() {
427
- if (typeof this.response === "object" && this.response !== null) {
428
- return this.response;
429
- }
585
+ __name(_idbOpen, "_idbOpen");
586
+ function _idbGet(key) {
587
+ return _idbOpen().then((db) => new Promise((resolve, reject) => {
588
+ const tx = db.transaction(_DPOP_STORE, "readonly");
589
+ const req = tx.objectStore(_DPOP_STORE).get(key);
590
+ req.onsuccess = () => resolve(req.result);
591
+ req.onerror = () => reject(req.error);
592
+ }));
593
+ }
594
+ __name(_idbGet, "_idbGet");
595
+ function _idbPut(key, value) {
596
+ return _idbOpen().then((db) => new Promise((resolve, reject) => {
597
+ const tx = db.transaction(_DPOP_STORE, "readwrite");
598
+ tx.objectStore(_DPOP_STORE).put(value, key);
599
+ tx.oncomplete = () => resolve();
600
+ tx.onerror = () => reject(tx.error);
601
+ }));
602
+ }
603
+ __name(_idbPut, "_idbPut");
604
+ var _dpopKeyPromise = null;
605
+ function _getDpopKeyPair() {
606
+ if (_dpopKeyPromise) return _dpopKeyPromise;
607
+ _dpopKeyPromise = (async () => {
608
+ const existing = await _idbGet(_DPOP_KEY_ID).catch(() => void 0);
609
+ if (existing) return existing;
610
+ const pair = await crypto.subtle.generateKey(
611
+ { name: "ECDSA", namedCurve: "P-256" },
612
+ false,
613
+ // extractable:false — JS can sign but never export the private key
614
+ ["sign"]
615
+ );
616
+ await _idbPut(_DPOP_KEY_ID, pair).catch(() => {
617
+ });
618
+ return pair;
619
+ })();
620
+ return _dpopKeyPromise;
621
+ }
622
+ __name(_getDpopKeyPair, "_getDpopKeyPair");
623
+ function _b64urlFromBytes(bytes) {
624
+ const arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
625
+ let s = "";
626
+ for (let i = 0; i < arr.length; i++) s += String.fromCharCode(arr[i]);
627
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
628
+ }
629
+ __name(_b64urlFromBytes, "_b64urlFromBytes");
630
+ function _b64urlFromString(str) {
631
+ return _b64urlFromBytes(new TextEncoder().encode(str));
632
+ }
633
+ __name(_b64urlFromString, "_b64urlFromString");
634
+ async function _publicJwk(pub) {
635
+ const jwk = await crypto.subtle.exportKey("jwk", pub);
636
+ return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
637
+ }
638
+ __name(_publicJwk, "_publicJwk");
639
+ async function _makeDpopProof(method, url) {
640
+ try {
641
+ const pair = await _getDpopKeyPair();
642
+ const jwk = await _publicJwk(pair.publicKey);
643
+ const header = { typ: "dpop+jwt", alg: "ES256", jwk };
644
+ const htu = url.split("#")[0].split("?")[0];
645
+ const jti = crypto.randomUUID && crypto.randomUUID() || _b64urlFromBytes(crypto.getRandomValues(new Uint8Array(16)));
646
+ const payload = { htm: method.toUpperCase(), htu, iat: Math.floor(Date.now() / 1e3), jti };
647
+ const signingInput = `${_b64urlFromString(JSON.stringify(header))}.${_b64urlFromString(JSON.stringify(payload))}`;
648
+ const sig = await crypto.subtle.sign(
649
+ { name: "ECDSA", hash: "SHA-256" },
650
+ pair.privateKey,
651
+ new TextEncoder().encode(signingInput)
652
+ );
653
+ return `${signingInput}.${_b64urlFromBytes(sig)}`;
654
+ } catch {
430
655
  return null;
431
656
  }
432
- get fieldErrors() {
433
- const details = this.details;
434
- if (!details) return null;
435
- const fieldErrors = {};
436
- for (const [key, value] of Object.entries(details)) {
437
- if (Array.isArray(value)) fieldErrors[key] = value;
438
- }
439
- return Object.keys(fieldErrors).length > 0 ? fieldErrors : null;
440
- }
441
- get errorMessage() {
442
- const details = this.details;
443
- if (!details) return this.message;
444
- if (details.detail) {
445
- return Array.isArray(details.detail) ? details.detail.join(", ") : String(details.detail);
446
- }
447
- if (details.error) return String(details.error);
448
- if (details.message) return String(details.message);
449
- const fieldErrors = this.fieldErrors;
450
- if (fieldErrors) {
451
- const firstField = Object.keys(fieldErrors)[0];
452
- if (firstField) return `${firstField}: ${fieldErrors[firstField]?.join(", ")}`;
453
- }
454
- return this.message;
455
- }
456
- get isValidationError() {
457
- return this.statusCode === 400;
458
- }
459
- get isAuthError() {
460
- return this.statusCode === 401;
461
- }
462
- get isPermissionError() {
463
- return this.statusCode === 403;
464
- }
465
- get isNotFoundError() {
466
- return this.statusCode === 404;
467
- }
468
- get isServerError() {
469
- return this.statusCode >= 500 && this.statusCode < 600;
470
- }
471
- };
472
-
473
- // src/_api/generated/helpers/auth.ts
474
- var ACCESS_KEY = "cfg.access_token";
475
- var REFRESH_KEY = "cfg.refresh_token";
476
- var API_KEY_KEY = "cfg.api_key";
477
- var isBrowser2 = typeof window !== "undefined";
478
- var localStorageBackend = {
479
- get(key) {
480
- if (!isBrowser2) return null;
481
- try {
482
- return window.localStorage.getItem(key);
483
- } catch {
484
- return null;
485
- }
486
- },
487
- set(key, value) {
488
- if (!isBrowser2) return;
657
+ }
658
+ __name(_makeDpopProof, "_makeDpopProof");
659
+ function installAuthOnClient(client2) {
660
+ if (_client) return;
661
+ _client = client2;
662
+ client2.setConfig({
663
+ baseUrl: auth.getBaseUrl(),
664
+ credentials: _withCredentials ? "include" : "same-origin"
665
+ });
666
+ client2.interceptors.request.use(async (request) => {
667
+ const token = auth.getToken();
668
+ if (token) request.headers.set("Authorization", `Bearer ${token}`);
669
+ const locale = auth.getLocale();
670
+ if (locale) request.headers.set("Accept-Language", locale);
671
+ const apiKey = auth.getApiKey();
672
+ if (apiKey && !request.headers.has("X-API-Key")) request.headers.set("X-API-Key", apiKey);
489
673
  try {
490
- if (value === null) window.localStorage.removeItem(key);
491
- else window.localStorage.setItem(key, value);
674
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
675
+ if (tz) request.headers.set("X-Timezone", tz);
492
676
  } catch {
493
677
  }
494
- }
495
- };
496
- var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
497
- var cookieBackend = {
498
- get(key) {
499
- if (!isBrowser2) return null;
500
- try {
501
- const re = new RegExp(`(?:^|;\\s*)${encodeURIComponent(key)}=([^;]*)`);
502
- const m = document.cookie.match(re);
503
- return m ? decodeURIComponent(m[1]) : null;
504
- } catch {
505
- return null;
678
+ request.headers.set("X-Client-Time", (/* @__PURE__ */ new Date()).toISOString());
679
+ if (dpopEnabled() && typeof window !== "undefined") {
680
+ const proof = await _makeDpopProof(request.method, request.url);
681
+ if (proof) request.headers.set("DPoP", proof);
506
682
  }
507
- },
508
- set(key, value) {
509
- if (!isBrowser2) return;
510
- try {
511
- const k = encodeURIComponent(key);
512
- const secure = window.location.protocol === "https:" ? "; Secure" : "";
513
- if (value === null) {
514
- document.cookie = `${k}=; Path=/; Max-Age=0; SameSite=Lax${secure}`;
515
- } else {
516
- const v = encodeURIComponent(value);
517
- document.cookie = `${k}=${v}; Path=/; Max-Age=${COOKIE_MAX_AGE}; SameSite=Lax${secure}`;
518
- }
683
+ return request;
684
+ });
685
+ client2.interceptors.error.use((err, res, req) => {
686
+ if (err instanceof APIError) return err;
687
+ const url = req?.url ?? "";
688
+ const status = res?.status ?? 0;
689
+ const statusText = res?.statusText ?? "";
690
+ return new APIError(status, statusText, err, url);
691
+ });
692
+ client2.interceptors.response.use(async (response, request) => {
693
+ if (response.status !== 401) return response;
694
+ if (request.headers.get(RETRY_MARKER)) {
695
+ expireSession(response);
696
+ return response;
697
+ }
698
+ const newToken = await tryRefresh();
699
+ if (!newToken) {
700
+ expireSession(response);
701
+ return response;
702
+ }
703
+ const retry = request.clone();
704
+ retry.headers.set("Authorization", `Bearer ${newToken}`);
705
+ retry.headers.set(RETRY_MARKER, "1");
706
+ if (dpopEnabled() && typeof window !== "undefined") {
707
+ const proof = await _makeDpopProof(retry.method, retry.url);
708
+ if (proof) retry.headers.set("DPoP", proof);
709
+ }
710
+ try {
711
+ const retried = await fetch(retry);
712
+ if (retried.status === 401) expireSession(retried);
713
+ return retried;
519
714
  } catch {
715
+ return response;
520
716
  }
521
- }
522
- };
523
- var _storage = localStorageBackend;
524
- var _storageMode = "localStorage";
525
- function detectLocale() {
717
+ });
718
+ }
719
+ __name(installAuthOnClient, "installAuthOnClient");
720
+ var REFRESH_ENDPOINT = "/cfg/accounts/token/refresh/";
721
+ auth.setRefreshHandler(async (refresh) => {
526
722
  try {
527
- if (typeof document !== "undefined") {
528
- const m = document.cookie.match(/(?:^|;\s*)NEXT_LOCALE=([^;]*)/);
529
- if (m) return decodeURIComponent(m[1]);
530
- }
531
- if (typeof navigator !== "undefined" && navigator.language) {
532
- return navigator.language;
723
+ const url = `${auth.getBaseUrl()}${REFRESH_ENDPOINT}`;
724
+ const headers = { "Content-Type": "application/json" };
725
+ if (dpopEnabled() && typeof window !== "undefined") {
726
+ const proof = await _makeDpopProof("POST", url);
727
+ if (proof) headers["DPoP"] = proof;
533
728
  }
729
+ const res = await fetch(url, {
730
+ method: "POST",
731
+ headers,
732
+ credentials: auth.getWithCredentials() ? "include" : "same-origin",
733
+ body: JSON.stringify({ refresh })
734
+ });
735
+ if (!res.ok) return null;
736
+ const data = await res.json();
737
+ return data?.access ? { access: data.access, refresh: data.refresh ?? refresh } : null;
534
738
  } catch {
739
+ return null;
535
740
  }
536
- return null;
741
+ });
742
+
743
+ // src/_api/generated/helpers/logger.ts
744
+ var import_consola = require("consola");
745
+ var DEFAULT_CONFIG = {
746
+ enabled: typeof process !== "undefined" && process.env.NODE_ENV !== "production",
747
+ logRequests: true,
748
+ logResponses: true,
749
+ logErrors: true,
750
+ logBodies: true,
751
+ logHeaders: false
752
+ };
753
+ var SENSITIVE_HEADERS = [
754
+ "authorization",
755
+ "cookie",
756
+ "set-cookie",
757
+ "x-api-key",
758
+ "x-csrf-token"
759
+ ];
760
+ var APILogger = class {
761
+ static {
762
+ __name(this, "APILogger");
763
+ }
764
+ config;
765
+ consola;
766
+ constructor(config = {}) {
767
+ this.config = { ...DEFAULT_CONFIG, ...config };
768
+ this.consola = config.consola || (0, import_consola.createConsola)({
769
+ level: this.config.enabled ? 4 : 0
770
+ });
771
+ }
772
+ enable() {
773
+ this.config.enabled = true;
774
+ }
775
+ disable() {
776
+ this.config.enabled = false;
777
+ }
778
+ setConfig(config) {
779
+ this.config = { ...this.config, ...config };
780
+ }
781
+ filterHeaders(headers) {
782
+ if (!headers) return {};
783
+ const filtered = {};
784
+ Object.keys(headers).forEach((key) => {
785
+ filtered[key] = SENSITIVE_HEADERS.includes(key.toLowerCase()) ? "***" : headers[key] || "";
786
+ });
787
+ return filtered;
788
+ }
789
+ logRequest(request) {
790
+ if (!this.config.enabled || !this.config.logRequests) return;
791
+ const { method, url, headers, body } = request;
792
+ this.consola.start(`${method} ${url}`);
793
+ if (this.config.logHeaders && headers) this.consola.debug("Headers:", this.filterHeaders(headers));
794
+ if (this.config.logBodies && body) this.consola.debug("Body:", body);
795
+ }
796
+ logResponse(request, response) {
797
+ if (!this.config.enabled || !this.config.logResponses) return;
798
+ const { method, url } = request;
799
+ const { status, statusText, data, duration } = response;
800
+ this.consola.success(`${method} ${url} ${status} ${statusText} (${duration}ms)`);
801
+ if (this.config.logBodies && data) this.consola.debug("Response:", data);
802
+ }
803
+ logError(request, error) {
804
+ if (!this.config.enabled || !this.config.logErrors) return;
805
+ const { method, url } = request;
806
+ const { message, statusCode, fieldErrors, duration } = error;
807
+ this.consola.error(`${method} ${url} ${statusCode || "Network"} Error (${duration}ms)`);
808
+ this.consola.error("Message:", message);
809
+ if (fieldErrors && Object.keys(fieldErrors).length > 0) {
810
+ this.consola.error("Field Errors:");
811
+ Object.entries(fieldErrors).forEach(([field, errors]) => {
812
+ errors.forEach((err) => this.consola.error(` \u2022 ${field}: ${err}`));
813
+ });
814
+ }
815
+ }
816
+ info(message, ...args) {
817
+ if (this.config.enabled) this.consola.info(message, ...args);
818
+ }
819
+ warn(message, ...args) {
820
+ if (this.config.enabled) this.consola.warn(message, ...args);
821
+ }
822
+ error(message, ...args) {
823
+ if (this.config.enabled) this.consola.error(message, ...args);
824
+ }
825
+ debug(message, ...args) {
826
+ if (this.config.enabled) this.consola.debug(message, ...args);
827
+ }
828
+ success(message, ...args) {
829
+ if (this.config.enabled) this.consola.success(message, ...args);
830
+ }
831
+ withTag(tag) {
832
+ return this.consola.withTag(tag);
833
+ }
834
+ };
835
+ var defaultLogger = new APILogger();
836
+
837
+ // src/auth/utils/env.ts
838
+ var isDev = process.env.NODE_ENV === "development";
839
+ var isBrowser2 = typeof window !== "undefined";
840
+ var isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
841
+ var dpopEnabled2 = process.env.NEXT_PUBLIC_DPOP_ENABLED === "true";
842
+
843
+ // src/log-control.ts
844
+ var verboseByDefault = isDev || isStaticBuild;
845
+ var DEFAULT_LEVEL = verboseByDefault ? 4 : 1;
846
+ var _level = DEFAULT_LEVEL;
847
+ var _subscribers = /* @__PURE__ */ new Set();
848
+ function getLogLevel() {
849
+ return _level;
537
850
  }
538
- __name(detectLocale, "detectLocale");
539
- function defaultBaseUrl() {
540
- if (typeof window !== "undefined") {
851
+ __name(getLogLevel, "getLogLevel");
852
+ function setLogLevel(level) {
853
+ _level = level;
854
+ for (const fn of _subscribers) {
541
855
  try {
542
- if (typeof process !== "undefined" && process.env) {
543
- if (process.env.NEXT_PUBLIC_STATIC_BUILD === "true") return "";
544
- if (process.env.NEXT_PUBLIC_API_PROXY_URL !== void 0)
545
- return process.env.NEXT_PUBLIC_API_PROXY_URL;
546
- return process.env.NEXT_PUBLIC_API_URL || "";
547
- }
856
+ fn(level);
548
857
  } catch {
549
858
  }
550
- return "";
551
859
  }
860
+ }
861
+ __name(setLogLevel, "setLogLevel");
862
+ function onLogLevelChange(fn) {
863
+ _subscribers.add(fn);
552
864
  try {
553
- if (typeof process !== "undefined" && process.env) {
554
- if (process.env.NEXT_PUBLIC_STATIC_BUILD === "true") return "";
555
- return process.env.NEXT_PUBLIC_API_URL || "";
556
- }
865
+ fn(_level);
557
866
  } catch {
558
867
  }
559
- return "";
868
+ return () => _subscribers.delete(fn);
560
869
  }
561
- __name(defaultBaseUrl, "defaultBaseUrl");
562
- function defaultApiKey() {
563
- try {
564
- if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_API_KEY) {
565
- return process.env.NEXT_PUBLIC_API_KEY;
566
- }
567
- } catch {
870
+ __name(onLogLevelChange, "onLogLevelChange");
871
+ function applyRoleLogPolicy(opts) {
872
+ const devMode = opts.isDev ?? verboseByDefault;
873
+ if (opts.isAdmin || devMode) {
874
+ setLogLevel(4);
875
+ } else {
876
+ setLogLevel(1);
568
877
  }
569
- return null;
570
878
  }
571
- __name(defaultApiKey, "defaultApiKey");
572
- var _localeOverride = null;
573
- var _apiKeyOverride = null;
574
- var _baseUrlOverride = null;
575
- var _withCredentials = true;
576
- var _onUnauthorized = null;
577
- var _refreshHandler = null;
578
- var _refreshInflight = null;
579
- var RETRY_MARKER = "X-Auth-Retry";
580
- var _client = null;
581
- function pushClientConfig() {
582
- if (!_client) return;
583
- _client.setConfig({
584
- baseUrl: auth.getBaseUrl(),
585
- credentials: _withCredentials ? "include" : "same-origin"
586
- });
879
+ __name(applyRoleLogPolicy, "applyRoleLogPolicy");
880
+
881
+ // src/auth/hooks/useSession.ts
882
+ var import_react = require("react");
883
+ function useSession() {
884
+ return (0, import_react.useSyncExternalStore)(auth.subscribe, auth.getSnapshot, auth.getServerSnapshot);
587
885
  }
588
- __name(pushClientConfig, "pushClientConfig");
589
- var auth = {
590
- // ── Storage mode ──────────────────────────────────────────────────
591
- getStorageMode() {
592
- return _storageMode;
593
- },
594
- setStorageMode(mode) {
595
- _storageMode = mode;
596
- _storage = mode === "cookie" ? cookieBackend : localStorageBackend;
597
- },
598
- // ── Bearer token ──────────────────────────────────────────────────
599
- getToken() {
600
- return _storage.get(ACCESS_KEY);
601
- },
602
- setToken(token) {
603
- _storage.set(ACCESS_KEY, token);
604
- },
605
- getRefreshToken() {
606
- return _storage.get(REFRESH_KEY);
607
- },
608
- setRefreshToken(token) {
609
- _storage.set(REFRESH_KEY, token);
610
- },
611
- clearTokens() {
612
- _storage.set(ACCESS_KEY, null);
613
- _storage.set(REFRESH_KEY, null);
614
- },
615
- isAuthenticated() {
616
- return _storage.get(ACCESS_KEY) !== null;
617
- },
618
- // ── API key ───────────────────────────────────────────────────────
619
- getApiKey() {
620
- return _apiKeyOverride ?? _storage.get(API_KEY_KEY) ?? defaultApiKey();
621
- },
622
- setApiKey(key) {
623
- _apiKeyOverride = key;
624
- },
625
- setApiKeyPersist(key) {
626
- _apiKeyOverride = key;
627
- _storage.set(API_KEY_KEY, key);
628
- },
629
- clearApiKey() {
630
- _apiKeyOverride = null;
631
- _storage.set(API_KEY_KEY, null);
632
- },
633
- // ── Locale ────────────────────────────────────────────────────────
634
- getLocale() {
635
- return _localeOverride ?? detectLocale();
636
- },
637
- setLocale(locale) {
638
- _localeOverride = locale;
639
- },
640
- // ── Base URL ──────────────────────────────────────────────────────
641
- getBaseUrl() {
642
- const url = _baseUrlOverride ?? defaultBaseUrl();
643
- return url.replace(/\/$/, "");
644
- },
645
- setBaseUrl(url) {
646
- _baseUrlOverride = url ? url.replace(/\/$/, "") : null;
647
- pushClientConfig();
648
- },
649
- // ── Credentials toggle ────────────────────────────────────────────
650
- getWithCredentials() {
651
- return _withCredentials;
652
- },
653
- setWithCredentials(value) {
654
- _withCredentials = value;
655
- pushClientConfig();
656
- },
657
- // ── 401 handler ───────────────────────────────────────────────────
658
- /**
659
- * Fired when the server returns 401 AND no refresh path recovers it
660
- * (no refresh token, no refresh handler, refresh failed, or retry
661
- * still 401). The app should clear local state and redirect to login.
662
- *
663
- * NOT fired for 401 that gets transparently recovered by the refresh
664
- * handler — those are invisible to callers.
665
- */
666
- onUnauthorized(cb) {
667
- _onUnauthorized = cb;
668
- },
669
- /**
670
- * Register the refresh strategy. The handler receives the current
671
- * refresh token and must call your refresh endpoint, returning
672
- * `{ access, refresh? }` on success or `null` on failure.
673
- *
674
- * @example
675
- * auth.setRefreshHandler(async (refresh) => {
676
- * const { data } = await Auth.tokenRefreshCreate({ body: { refresh } });
677
- * return data ? { access: data.access, refresh: data.refresh } : null;
678
- * });
679
- */
680
- setRefreshHandler(fn) {
681
- _refreshHandler = fn;
682
- },
683
- /**
684
- * Proactively run the registered refresh handler right now, reusing the
685
- * SAME single-flight promise as the 401-recovery interceptor. Callers
686
- * (e.g. an expiry timer / focus / reconnect) get token rotation,
687
- * de-duplication and rotated-token persistence for free — they must NOT
688
- * re-implement any of it. Returns the fresh access token, or null if
689
- * there is no handler / no refresh token / the refresh failed.
690
- */
691
- refreshNow() {
692
- return tryRefresh();
693
- }
694
- };
695
- async function tryRefresh() {
696
- if (_refreshInflight) return _refreshInflight;
697
- if (!_refreshHandler) return null;
698
- const refresh = auth.getRefreshToken();
699
- if (!refresh) return null;
700
- _refreshInflight = (async () => {
701
- try {
702
- const result = await _refreshHandler(refresh);
703
- if (!result?.access) return null;
704
- auth.setToken(result.access);
705
- if (result.refresh) auth.setRefreshToken(result.refresh);
706
- return result.access;
707
- } catch {
708
- return null;
709
- } finally {
710
- _refreshInflight = null;
711
- }
712
- })();
713
- return _refreshInflight;
714
- }
715
- __name(tryRefresh, "tryRefresh");
716
- function dpopEnabled2() {
717
- try {
718
- return typeof process !== "undefined" && process.env?.NEXT_PUBLIC_DPOP_ENABLED === "true";
719
- } catch {
720
- return false;
886
+ __name(useSession, "useSession");
887
+
888
+ // src/auth/hooks/useCfgRouter.ts
889
+ var import_navigation = require("next/navigation");
890
+ var import_react2 = require("react");
891
+ function getBasePath() {
892
+ if (typeof process === "undefined") {
893
+ return "";
721
894
  }
895
+ return process.env.NEXT_PUBLIC_BASE_PATH || "";
722
896
  }
723
- __name(dpopEnabled2, "dpopEnabled");
724
- var _DPOP_DB = "cfg-auth";
725
- var _DPOP_STORE = "keys";
726
- var _DPOP_KEY_ID = "dpop-ec-p256";
727
- function _idbOpen() {
728
- return new Promise((resolve, reject) => {
729
- const req = indexedDB.open(_DPOP_DB, 1);
730
- req.onupgradeneeded = () => req.result.createObjectStore(_DPOP_STORE);
731
- req.onsuccess = () => resolve(req.result);
732
- req.onerror = () => reject(req.error);
733
- });
734
- }
735
- __name(_idbOpen, "_idbOpen");
736
- function _idbGet(key) {
737
- return _idbOpen().then((db) => new Promise((resolve, reject) => {
738
- const tx = db.transaction(_DPOP_STORE, "readonly");
739
- const req = tx.objectStore(_DPOP_STORE).get(key);
740
- req.onsuccess = () => resolve(req.result);
741
- req.onerror = () => reject(req.error);
742
- }));
743
- }
744
- __name(_idbGet, "_idbGet");
745
- function _idbPut(key, value) {
746
- return _idbOpen().then((db) => new Promise((resolve, reject) => {
747
- const tx = db.transaction(_DPOP_STORE, "readwrite");
748
- tx.objectStore(_DPOP_STORE).put(value, key);
749
- tx.oncomplete = () => resolve();
750
- tx.onerror = () => reject(tx.error);
751
- }));
752
- }
753
- __name(_idbPut, "_idbPut");
754
- var _dpopKeyPromise = null;
755
- function _getDpopKeyPair() {
756
- if (_dpopKeyPromise) return _dpopKeyPromise;
757
- _dpopKeyPromise = (async () => {
758
- const existing = await _idbGet(_DPOP_KEY_ID).catch(() => void 0);
759
- if (existing) return existing;
760
- const pair = await crypto.subtle.generateKey(
761
- { name: "ECDSA", namedCurve: "P-256" },
762
- false,
763
- // extractable:false — JS can sign but never export the private key
764
- ["sign"]
765
- );
766
- await _idbPut(_DPOP_KEY_ID, pair).catch(() => {
767
- });
768
- return pair;
769
- })();
770
- return _dpopKeyPromise;
771
- }
772
- __name(_getDpopKeyPair, "_getDpopKeyPair");
773
- function _b64urlFromBytes(bytes) {
774
- const arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
775
- let s = "";
776
- for (let i = 0; i < arr.length; i++) s += String.fromCharCode(arr[i]);
777
- return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
778
- }
779
- __name(_b64urlFromBytes, "_b64urlFromBytes");
780
- function _b64urlFromString(str) {
781
- return _b64urlFromBytes(new TextEncoder().encode(str));
782
- }
783
- __name(_b64urlFromString, "_b64urlFromString");
784
- async function _publicJwk(pub) {
785
- const jwk = await crypto.subtle.exportKey("jwk", pub);
786
- return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
787
- }
788
- __name(_publicJwk, "_publicJwk");
789
- async function _makeDpopProof(method, url) {
790
- try {
791
- const pair = await _getDpopKeyPair();
792
- const jwk = await _publicJwk(pair.publicKey);
793
- const header = { typ: "dpop+jwt", alg: "ES256", jwk };
794
- const htu = url.split("#")[0].split("?")[0];
795
- const jti = crypto.randomUUID && crypto.randomUUID() || _b64urlFromBytes(crypto.getRandomValues(new Uint8Array(16)));
796
- const payload = { htm: method.toUpperCase(), htu, iat: Math.floor(Date.now() / 1e3), jti };
797
- const signingInput = `${_b64urlFromString(JSON.stringify(header))}.${_b64urlFromString(JSON.stringify(payload))}`;
798
- const sig = await crypto.subtle.sign(
799
- { name: "ECDSA", hash: "SHA-256" },
800
- pair.privateKey,
801
- new TextEncoder().encode(signingInput)
802
- );
803
- return `${signingInput}.${_b64urlFromBytes(sig)}`;
804
- } catch {
805
- return null;
897
+ __name(getBasePath, "getBasePath");
898
+ function withBasePath(path, basePath) {
899
+ if (!basePath) {
900
+ return path;
806
901
  }
902
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
903
+ const normalizedBasePath = basePath.replace(/\/$/, "");
904
+ return `${normalizedBasePath}${normalizedPath}`;
807
905
  }
808
- __name(_makeDpopProof, "_makeDpopProof");
809
- function installAuthOnClient(client2) {
810
- if (_client) return;
811
- _client = client2;
812
- client2.setConfig({
813
- baseUrl: auth.getBaseUrl(),
814
- credentials: _withCredentials ? "include" : "same-origin"
815
- });
816
- client2.interceptors.request.use(async (request) => {
817
- const token = auth.getToken();
818
- if (token) request.headers.set("Authorization", `Bearer ${token}`);
819
- const locale = auth.getLocale();
820
- if (locale) request.headers.set("Accept-Language", locale);
821
- const apiKey = auth.getApiKey();
822
- if (apiKey && !request.headers.has("X-API-Key")) request.headers.set("X-API-Key", apiKey);
823
- try {
824
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
825
- if (tz) request.headers.set("X-Timezone", tz);
826
- } catch {
906
+ __name(withBasePath, "withBasePath");
907
+ function useCfgRouter() {
908
+ const router = (0, import_navigation.useRouter)();
909
+ const basePath = (0, import_react2.useMemo)(() => getBasePath(), []);
910
+ const isStaticBuild2 = (0, import_react2.useMemo)(() => {
911
+ return typeof process !== "undefined" && process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
912
+ }, []);
913
+ const push = (0, import_react2.useCallback)((href, options) => {
914
+ if (basePath) {
915
+ window.location.href = withBasePath(href, basePath);
916
+ } else {
917
+ router.push(href, options);
827
918
  }
828
- request.headers.set("X-Client-Time", (/* @__PURE__ */ new Date()).toISOString());
829
- if (dpopEnabled2() && typeof window !== "undefined") {
830
- const proof = await _makeDpopProof(request.method, request.url);
831
- if (proof) request.headers.set("DPoP", proof);
919
+ }, [router, basePath]);
920
+ const replace = (0, import_react2.useCallback)((href, options) => {
921
+ if (basePath) {
922
+ window.location.replace(withBasePath(href, basePath));
923
+ } else {
924
+ router.replace(href, options);
832
925
  }
833
- return request;
926
+ }, [router, basePath]);
927
+ const hardPush = (0, import_react2.useCallback)((href) => {
928
+ window.location.href = withBasePath(href, basePath);
929
+ }, [basePath]);
930
+ const hardReplace = (0, import_react2.useCallback)((href) => {
931
+ window.location.replace(withBasePath(href, basePath));
932
+ }, [basePath]);
933
+ const prefetch = (0, import_react2.useCallback)((href) => {
934
+ router.prefetch(href);
935
+ }, [router]);
936
+ const back = (0, import_react2.useCallback)(() => {
937
+ router.back();
938
+ }, [router]);
939
+ const forward = (0, import_react2.useCallback)(() => {
940
+ router.forward();
941
+ }, [router]);
942
+ const refresh = (0, import_react2.useCallback)(() => {
943
+ router.refresh();
944
+ }, [router]);
945
+ return {
946
+ push,
947
+ replace,
948
+ hardPush,
949
+ hardReplace,
950
+ prefetch,
951
+ back,
952
+ forward,
953
+ refresh
954
+ };
955
+ }
956
+ __name(useCfgRouter, "useCfgRouter");
957
+
958
+ // src/auth/hooks/useQueryParams.ts
959
+ var import_navigation2 = require("next/navigation");
960
+ var import_react3 = require("react");
961
+ function useQueryParams() {
962
+ const pathname = (0, import_navigation2.usePathname)();
963
+ const [queryParams, setQueryParams] = (0, import_react3.useState)(() => {
964
+ if (typeof window === "undefined") {
965
+ return new URLSearchParams();
966
+ }
967
+ return new URLSearchParams(window.location.search);
834
968
  });
835
- client2.interceptors.error.use((err, res, req) => {
836
- if (err instanceof APIError) return err;
837
- const url = req?.url ?? "";
838
- const status = res?.status ?? 0;
839
- const statusText = res?.statusText ?? "";
840
- return new APIError(status, statusText, err, url);
841
- });
842
- client2.interceptors.response.use(async (response, request) => {
843
- if (response.status !== 401) return response;
844
- if (request.headers.get(RETRY_MARKER)) {
845
- if (_onUnauthorized) {
846
- try {
847
- _onUnauthorized(response);
848
- } catch {
849
- }
850
- }
851
- return response;
852
- }
853
- const newToken = await tryRefresh();
854
- if (!newToken) {
855
- if (_onUnauthorized) {
856
- try {
857
- _onUnauthorized(response);
858
- } catch {
859
- }
860
- }
861
- return response;
862
- }
863
- const retry = request.clone();
864
- retry.headers.set("Authorization", `Bearer ${newToken}`);
865
- retry.headers.set(RETRY_MARKER, "1");
866
- if (dpopEnabled2() && typeof window !== "undefined") {
867
- const proof = await _makeDpopProof(retry.method, retry.url);
868
- if (proof) retry.headers.set("DPoP", proof);
869
- }
870
- try {
871
- const retried = await fetch(retry);
872
- if (retried.status === 401 && _onUnauthorized) {
873
- try {
874
- _onUnauthorized(retried);
875
- } catch {
876
- }
877
- }
878
- return retried;
879
- } catch {
880
- if (_onUnauthorized) {
881
- try {
882
- _onUnauthorized(response);
883
- } catch {
884
- }
969
+ const lastSearchRef = (0, import_react3.useRef)("");
970
+ (0, import_react3.useEffect)(() => {
971
+ if (typeof window === "undefined") return;
972
+ const updateQueryParams = /* @__PURE__ */ __name(() => {
973
+ const currentSearch = window.location.search;
974
+ if (currentSearch !== lastSearchRef.current) {
975
+ lastSearchRef.current = currentSearch;
976
+ setQueryParams(new URLSearchParams(currentSearch));
885
977
  }
886
- return response;
887
- }
888
- });
978
+ }, "updateQueryParams");
979
+ updateQueryParams();
980
+ window.addEventListener("popstate", updateQueryParams);
981
+ const origPush = window.history.pushState;
982
+ const origReplace = window.history.replaceState;
983
+ window.history.pushState = function(...args) {
984
+ const result = origPush.apply(this, args);
985
+ updateQueryParams();
986
+ return result;
987
+ };
988
+ window.history.replaceState = function(...args) {
989
+ const result = origReplace.apply(this, args);
990
+ updateQueryParams();
991
+ return result;
992
+ };
993
+ return () => {
994
+ window.removeEventListener("popstate", updateQueryParams);
995
+ window.history.pushState = origPush;
996
+ window.history.replaceState = origReplace;
997
+ };
998
+ }, [pathname]);
999
+ return queryParams;
889
1000
  }
890
- __name(installAuthOnClient, "installAuthOnClient");
1001
+ __name(useQueryParams, "useQueryParams");
891
1002
 
892
- // src/_api/generated/helpers/logger.ts
893
- var import_consola2 = require("consola");
894
- var DEFAULT_CONFIG = {
895
- enabled: typeof process !== "undefined" && process.env.NODE_ENV !== "production",
896
- logRequests: true,
897
- logResponses: true,
898
- logErrors: true,
899
- logBodies: true,
900
- logHeaders: false
901
- };
902
- var SENSITIVE_HEADERS = [
903
- "authorization",
904
- "cookie",
905
- "set-cookie",
906
- "x-api-key",
907
- "x-csrf-token"
908
- ];
909
- var APILogger = class {
910
- static {
911
- __name(this, "APILogger");
912
- }
913
- config;
914
- consola;
915
- constructor(config = {}) {
916
- this.config = { ...DEFAULT_CONFIG, ...config };
917
- this.consola = config.consola || (0, import_consola2.createConsola)({
918
- level: this.config.enabled ? 4 : 0
919
- });
920
- }
921
- enable() {
922
- this.config.enabled = true;
923
- }
924
- disable() {
925
- this.config.enabled = false;
926
- }
927
- setConfig(config) {
928
- this.config = { ...this.config, ...config };
929
- }
930
- filterHeaders(headers) {
931
- if (!headers) return {};
932
- const filtered = {};
933
- Object.keys(headers).forEach((key) => {
934
- filtered[key] = SENSITIVE_HEADERS.includes(key.toLowerCase()) ? "***" : headers[key] || "";
935
- });
936
- return filtered;
937
- }
938
- logRequest(request) {
939
- if (!this.config.enabled || !this.config.logRequests) return;
940
- const { method, url, headers, body } = request;
941
- this.consola.start(`${method} ${url}`);
942
- if (this.config.logHeaders && headers) this.consola.debug("Headers:", this.filterHeaders(headers));
943
- if (this.config.logBodies && body) this.consola.debug("Body:", body);
944
- }
945
- logResponse(request, response) {
946
- if (!this.config.enabled || !this.config.logResponses) return;
947
- const { method, url } = request;
948
- const { status, statusText, data, duration } = response;
949
- this.consola.success(`${method} ${url} ${status} ${statusText} (${duration}ms)`);
950
- if (this.config.logBodies && data) this.consola.debug("Response:", data);
951
- }
952
- logError(request, error) {
953
- if (!this.config.enabled || !this.config.logErrors) return;
954
- const { method, url } = request;
955
- const { message, statusCode, fieldErrors, duration } = error;
956
- this.consola.error(`${method} ${url} ${statusCode || "Network"} Error (${duration}ms)`);
957
- this.consola.error("Message:", message);
958
- if (fieldErrors && Object.keys(fieldErrors).length > 0) {
959
- this.consola.error("Field Errors:");
960
- Object.entries(fieldErrors).forEach(([field, errors]) => {
961
- errors.forEach((err) => this.consola.error(` \u2022 ${field}: ${err}`));
1003
+ // src/auth/hooks/useAuthFormState.ts
1004
+ var import_react4 = require("react");
1005
+ var formatCountdown = /* @__PURE__ */ __name((s) => {
1006
+ if (s <= 0) return "";
1007
+ const m = Math.floor(s / 60);
1008
+ return m > 0 ? `${m}:${String(s % 60).padStart(2, "0")}` : `${s}s`;
1009
+ }, "formatCountdown");
1010
+ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
1011
+ const [identifier, setIdentifier] = (0, import_react4.useState)(initialIdentifier);
1012
+ const [otp, setOtp] = (0, import_react4.useState)("");
1013
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
1014
+ const [acceptedTerms, setAcceptedTerms] = (0, import_react4.useState)(true);
1015
+ const [step, setStep] = (0, import_react4.useState)("identifier");
1016
+ const [error, setError] = (0, import_react4.useState)("");
1017
+ const [webmail, setWebmail] = (0, import_react4.useState)(null);
1018
+ const [twoFactorSessionId, setTwoFactorSessionId] = (0, import_react4.useState)(null);
1019
+ const [shouldPrompt2FA, setShouldPrompt2FA] = (0, import_react4.useState)(false);
1020
+ const [twoFactorCode, setTwoFactorCode] = (0, import_react4.useState)("");
1021
+ const [useBackupCode, setUseBackupCode] = (0, import_react4.useState)(false);
1022
+ const [rateLimitSeconds, setRateLimitSeconds] = (0, import_react4.useState)(0);
1023
+ const rateLimitTimerRef = (0, import_react4.useRef)(null);
1024
+ const startRateLimitCountdown = (0, import_react4.useCallback)((seconds) => {
1025
+ if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
1026
+ setRateLimitSeconds(seconds);
1027
+ rateLimitTimerRef.current = setInterval(() => {
1028
+ setRateLimitSeconds((prev) => {
1029
+ if (prev <= 1) {
1030
+ clearInterval(rateLimitTimerRef.current);
1031
+ rateLimitTimerRef.current = null;
1032
+ return 0;
1033
+ }
1034
+ return prev - 1;
962
1035
  });
963
- }
964
- }
965
- info(message, ...args) {
966
- if (this.config.enabled) this.consola.info(message, ...args);
967
- }
968
- warn(message, ...args) {
969
- if (this.config.enabled) this.consola.warn(message, ...args);
970
- }
971
- error(message, ...args) {
972
- if (this.config.enabled) this.consola.error(message, ...args);
973
- }
974
- debug(message, ...args) {
975
- if (this.config.enabled) this.consola.debug(message, ...args);
976
- }
977
- success(message, ...args) {
978
- if (this.config.enabled) this.consola.success(message, ...args);
979
- }
980
- withTag(tag) {
981
- return this.consola.withTag(tag);
982
- }
983
- };
984
- var defaultLogger = new APILogger();
1036
+ }, 1e3);
1037
+ }, []);
1038
+ (0, import_react4.useEffect)(() => () => {
1039
+ if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
1040
+ }, []);
1041
+ const clearError = (0, import_react4.useCallback)(() => setError(""), []);
1042
+ return {
1043
+ // State
1044
+ identifier,
1045
+ otp,
1046
+ isLoading,
1047
+ acceptedTerms,
1048
+ step,
1049
+ error,
1050
+ twoFactorSessionId,
1051
+ shouldPrompt2FA,
1052
+ twoFactorCode,
1053
+ useBackupCode,
1054
+ rateLimitSeconds,
1055
+ isRateLimited: rateLimitSeconds > 0,
1056
+ rateLimitLabel: formatCountdown(rateLimitSeconds),
1057
+ webmail,
1058
+ // Handlers
1059
+ setIdentifier,
1060
+ setOtp,
1061
+ setAcceptedTerms,
1062
+ setError,
1063
+ clearError,
1064
+ setStep,
1065
+ setIsLoading,
1066
+ setTwoFactorSessionId,
1067
+ setShouldPrompt2FA,
1068
+ setTwoFactorCode,
1069
+ setUseBackupCode,
1070
+ startRateLimitCountdown,
1071
+ setWebmail
1072
+ };
1073
+ }, "useAuthFormState");
1074
+
1075
+ // src/auth/hooks/useAuthValidation.ts
1076
+ var import_react5 = require("react");
1077
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1078
+ var useAuthValidation = /* @__PURE__ */ __name(() => {
1079
+ const validateIdentifier2 = (0, import_react5.useCallback)((id) => {
1080
+ return EMAIL_REGEX.test(id);
1081
+ }, []);
1082
+ return { validateIdentifier: validateIdentifier2 };
1083
+ }, "useAuthValidation");
1084
+ var validateIdentifier = /* @__PURE__ */ __name((id) => EMAIL_REGEX.test(id), "validateIdentifier");
1085
+
1086
+ // src/auth/hooks/useAuthForm.ts
1087
+ var import_react9 = require("react");
1088
+
1089
+ // src/auth/utils/logger.ts
1090
+ var import_consola2 = require("consola");
1091
+ var logger = (0, import_consola2.createConsola)({
1092
+ level: getLogLevel()
1093
+ }).withTag("api");
1094
+ onLogLevelChange((level) => {
1095
+ logger.level = level;
1096
+ });
1097
+ var authLogger = logger.withTag("auth");
1098
+
1099
+ // src/auth/hooks/useAutoAuth.ts
1100
+ var import_navigation3 = require("next/navigation");
1101
+ var import_react6 = require("react");
1102
+
1103
+ // src/auth/utils/path.ts
1104
+ var normalizePath = /* @__PURE__ */ __name((path) => {
1105
+ if (!path) return "";
1106
+ const withoutLocale = path.replace(/^\/[a-z]{2}(?=\/|$)/, "");
1107
+ const trimmed = withoutLocale.replace(/\/+$/, "");
1108
+ return trimmed || "/";
1109
+ }, "normalizePath");
1110
+ var isAllowedAuthPath = /* @__PURE__ */ __name((pathname, allowedPaths) => {
1111
+ const normalized = normalizePath(pathname);
1112
+ return allowedPaths.some(
1113
+ (p) => normalized === p || normalized.startsWith(p + "/")
1114
+ );
1115
+ }, "isAllowedAuthPath");
1116
+
1117
+ // src/auth/hooks/useAutoAuth.ts
1118
+ var useAutoAuth = /* @__PURE__ */ __name((options = {}) => {
1119
+ const { onOTPDetected, cleanupUrl = true, allowedPaths = ["/auth"] } = options;
1120
+ const queryParams = useQueryParams();
1121
+ const pathname = (0, import_navigation3.usePathname)();
1122
+ const router = useCfgRouter();
1123
+ const isAllowedPath = isAllowedAuthPath(pathname, allowedPaths);
1124
+ const queryOtp = queryParams.get("otp") || "";
1125
+ const queryEmail = queryParams.get("email") || void 0;
1126
+ const hasOTP = !!queryOtp;
1127
+ const isReady = !!pathname && hasOTP && isAllowedPath;
1128
+ const otpValid = queryOtp.length === AUTH_CONSTANTS.EMAIL_OTP_LENGTH;
1129
+ const onOTPDetectedRef = (0, import_react6.useRef)(onOTPDetected);
1130
+ onOTPDetectedRef.current = onOTPDetected;
1131
+ (0, import_react6.useEffect)(() => {
1132
+ if (!isReady || !otpValid) return;
1133
+ authLogger.info("OTP detected in URL on auth page:", queryOtp);
1134
+ onOTPDetectedRef.current?.(queryOtp, queryEmail);
1135
+ }, [isReady, otpValid, queryOtp, queryEmail]);
1136
+ (0, import_react6.useEffect)(() => {
1137
+ if (!isReady || !cleanupUrl || !queryOtp) return;
1138
+ const cleanQuery = Object.fromEntries(queryParams.entries());
1139
+ delete cleanQuery.otp;
1140
+ delete cleanQuery.email;
1141
+ const queryString = new URLSearchParams(cleanQuery).toString();
1142
+ const realPathname = typeof window !== "undefined" ? window.location.pathname : pathname;
1143
+ router.replace(queryString ? `${realPathname}?${queryString}` : realPathname);
1144
+ }, [isReady, cleanupUrl, queryOtp]);
1145
+ return {
1146
+ isReady,
1147
+ hasOTP,
1148
+ isAllowedPath
1149
+ };
1150
+ }, "useAutoAuth");
1151
+
1152
+ // src/auth/hooks/useTwoFactor.ts
1153
+ var import_react8 = require("react");
985
1154
 
986
1155
  // src/_api/generated/core/bodySerializer.gen.ts
987
1156
  var serializeFormDataPair = /* @__PURE__ */ __name((data, key, value) => {
@@ -1804,103 +1973,6 @@ var client = createClient(createConfig({ baseUrl: "http://localhost:8000" }));
1804
1973
  installAuthOnClient(client);
1805
1974
 
1806
1975
  // src/_api/generated/sdk.gen.ts
1807
- var CfgAccountsApiKey = class {
1808
- static {
1809
- __name(this, "CfgAccountsApiKey");
1810
- }
1811
- /**
1812
- * Get API key details
1813
- *
1814
- * Retrieve the current user's API key (masked) and metadata.
1815
- */
1816
- static cfgAccountsApiKeyRetrieve(options) {
1817
- return (options?.client ?? client).get({
1818
- security: [
1819
- { scheme: "bearer", type: "http" },
1820
- {
1821
- in: "cookie",
1822
- name: "sessionid",
1823
- type: "apiKey"
1824
- },
1825
- { name: "X-API-Key", type: "apiKey" }
1826
- ],
1827
- url: "/cfg/accounts/api-key/",
1828
- ...options
1829
- });
1830
- }
1831
- /**
1832
- * Regenerate API key
1833
- *
1834
- * Generate a new API key. The full key is returned only once.
1835
- */
1836
- static cfgAccountsApiKeyRegenerateCreate(options) {
1837
- return (options.client ?? client).post({
1838
- security: [
1839
- { scheme: "bearer", type: "http" },
1840
- {
1841
- in: "cookie",
1842
- name: "sessionid",
1843
- type: "apiKey"
1844
- },
1845
- { name: "X-API-Key", type: "apiKey" }
1846
- ],
1847
- url: "/cfg/accounts/api-key/regenerate/",
1848
- ...options,
1849
- headers: {
1850
- "Content-Type": "application/json",
1851
- ...options.headers
1852
- }
1853
- });
1854
- }
1855
- /**
1856
- * Reveal API key
1857
- *
1858
- * Return the current full API key WITHOUT rotating it. The same durable value every one of the user's agents uses — for the signed-in user to copy and paste into agent onboarding. Default GET stays masked; this is the explicit reveal action.
1859
- */
1860
- static cfgAccountsApiKeyRevealCreate(options) {
1861
- return (options.client ?? client).post({
1862
- security: [
1863
- { scheme: "bearer", type: "http" },
1864
- {
1865
- in: "cookie",
1866
- name: "sessionid",
1867
- type: "apiKey"
1868
- },
1869
- { name: "X-API-Key", type: "apiKey" }
1870
- ],
1871
- url: "/cfg/accounts/api-key/reveal/",
1872
- ...options,
1873
- headers: {
1874
- "Content-Type": "application/json",
1875
- ...options.headers
1876
- }
1877
- });
1878
- }
1879
- /**
1880
- * Test API key
1881
- *
1882
- * Test whether an API key is valid without consuming it.
1883
- */
1884
- static cfgAccountsApiKeyTestCreate(options) {
1885
- return (options.client ?? client).post({
1886
- security: [
1887
- { scheme: "bearer", type: "http" },
1888
- {
1889
- in: "cookie",
1890
- name: "sessionid",
1891
- type: "apiKey"
1892
- },
1893
- { name: "X-API-Key", type: "apiKey" }
1894
- ],
1895
- url: "/cfg/accounts/api-key/test/",
1896
- ...options,
1897
- headers: {
1898
- "Content-Type": "application/json",
1899
- ...options.headers
1900
- }
1901
- });
1902
- }
1903
- };
1904
1976
  var CfgAccountsOauth = class {
1905
1977
  static {
1906
1978
  __name(this, "CfgAccountsOauth");
@@ -1912,7 +1984,11 @@ var CfgAccountsOauth = class {
1912
1984
  */
1913
1985
  static cfgAccountsOauthConnectionsList(options) {
1914
1986
  return (options?.client ?? client).get({
1915
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
1987
+ security: [
1988
+ { name: "X-API-Key", type: "apiKey" },
1989
+ { scheme: "bearer", type: "http" },
1990
+ { name: "Authorization", type: "apiKey" }
1991
+ ],
1916
1992
  url: "/cfg/accounts/oauth/connections/",
1917
1993
  ...options
1918
1994
  });
@@ -1924,7 +2000,11 @@ var CfgAccountsOauth = class {
1924
2000
  */
1925
2001
  static cfgAccountsOauthDisconnectCreate(options) {
1926
2002
  return (options.client ?? client).post({
1927
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2003
+ security: [
2004
+ { name: "X-API-Key", type: "apiKey" },
2005
+ { scheme: "bearer", type: "http" },
2006
+ { name: "Authorization", type: "apiKey" }
2007
+ ],
1928
2008
  url: "/cfg/accounts/oauth/disconnect/",
1929
2009
  ...options,
1930
2010
  headers: {
@@ -1981,7 +2061,11 @@ var CfgAccounts = class {
1981
2061
  */
1982
2062
  static cfgAccountsOtpRequestCreate(options) {
1983
2063
  return (options.client ?? client).post({
1984
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2064
+ security: [
2065
+ { name: "X-API-Key", type: "apiKey" },
2066
+ { scheme: "bearer", type: "http" },
2067
+ { name: "Authorization", type: "apiKey" }
2068
+ ],
1985
2069
  url: "/cfg/accounts/otp/request/",
1986
2070
  ...options,
1987
2071
  headers: {
@@ -2002,7 +2086,11 @@ var CfgAccounts = class {
2002
2086
  */
2003
2087
  static cfgAccountsOtpVerifyCreate(options) {
2004
2088
  return (options.client ?? client).post({
2005
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2089
+ security: [
2090
+ { name: "X-API-Key", type: "apiKey" },
2091
+ { scheme: "bearer", type: "http" },
2092
+ { name: "Authorization", type: "apiKey" }
2093
+ ],
2006
2094
  url: "/cfg/accounts/otp/verify/",
2007
2095
  ...options,
2008
2096
  headers: {
@@ -2040,7 +2128,11 @@ var CfgAccountsProfile = class {
2040
2128
  static cfgAccountsProfileAvatarCreate(options) {
2041
2129
  return (options?.client ?? client).post({
2042
2130
  ...formDataBodySerializer,
2043
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2131
+ security: [
2132
+ { name: "X-API-Key", type: "apiKey" },
2133
+ { scheme: "bearer", type: "http" },
2134
+ { name: "Authorization", type: "apiKey" }
2135
+ ],
2044
2136
  url: "/cfg/accounts/profile/avatar/",
2045
2137
  ...options,
2046
2138
  headers: {
@@ -2161,17 +2253,15 @@ var CfgAccountsAuth = class {
2161
2253
  __name(this, "CfgAccountsAuth");
2162
2254
  }
2163
2255
  /**
2164
- * Refresh JWT token.
2256
+ * Revoke a refresh token (logout).
2165
2257
  *
2166
- * DPoP-aware: when the incoming refresh token is key-bound (`cnf.jkt`), the
2167
- * rotated access/refresh in the response are re-stamped with the same `cnf`
2168
- * (stock SimpleJWT drops it from the derived access), and a matching DPoP
2169
- * proof is required on the refresh request — so a stolen refresh token can't
2170
- * be used to mint fresh tokens.
2258
+ * Blacklists the posted refresh token so it can never mint another access
2259
+ * token. Called best-effort by the client's logout without it, "logout"
2260
+ * is purely client-side and a stolen refresh token survives until expiry.
2171
2261
  */
2172
- static cfgAccountsTokenRefreshCreate(options) {
2262
+ static cfgAccountsTokenBlacklistCreate(options) {
2173
2263
  return (options.client ?? client).post({
2174
- url: "/cfg/accounts/token/refresh/",
2264
+ url: "/cfg/accounts/token/blacklist/",
2175
2265
  ...options,
2176
2266
  headers: {
2177
2267
  "Content-Type": "application/json",
@@ -2179,48 +2269,18 @@ var CfgAccountsAuth = class {
2179
2269
  }
2180
2270
  });
2181
2271
  }
2182
- };
2183
- var CfgCentrifugo = class {
2184
- static {
2185
- __name(this, "CfgCentrifugo");
2186
- }
2187
- /**
2188
- * Get Centrifugo connection token
2189
- *
2190
- * Generate JWT token for WebSocket connection to Centrifugo. Token includes user's allowed channels based on their permissions. Requires authentication.
2191
- */
2192
- static cfgCentrifugoAuthTokenRetrieve(options) {
2193
- return (options?.client ?? client).get({
2194
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2195
- url: "/cfg/centrifugo/auth/token/",
2196
- ...options
2197
- });
2198
- }
2199
- };
2200
- var CfgTotpBackupCodes = class {
2201
- static {
2202
- __name(this, "CfgTotpBackupCodes");
2203
- }
2204
- /**
2205
- * Get backup codes status for user.
2206
- */
2207
- static cfgTotpBackupCodesRetrieve(options) {
2208
- return (options?.client ?? client).get({
2209
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2210
- url: "/cfg/totp/backup-codes/",
2211
- ...options
2212
- });
2213
- }
2214
2272
  /**
2215
- * Regenerate backup codes.
2273
+ * Refresh JWT token.
2216
2274
  *
2217
- * Requires TOTP code for verification.
2218
- * Invalidates all existing codes.
2275
+ * DPoP-aware: when the incoming refresh token is key-bound (`cnf.jkt`), the
2276
+ * rotated access/refresh in the response are re-stamped with the same `cnf`
2277
+ * (stock SimpleJWT drops it from the derived access), and a matching DPoP
2278
+ * proof is required on the refresh request — so a stolen refresh token can't
2279
+ * be used to mint fresh tokens.
2219
2280
  */
2220
- static cfgTotpBackupCodesRegenerateCreate(options) {
2281
+ static cfgAccountsTokenRefreshCreate(options) {
2221
2282
  return (options.client ?? client).post({
2222
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2223
- url: "/cfg/totp/backup-codes/regenerate/",
2283
+ url: "/cfg/accounts/token/refresh/",
2224
2284
  ...options,
2225
2285
  headers: {
2226
2286
  "Content-Type": "application/json",
@@ -2238,7 +2298,11 @@ var CfgTotp = class {
2238
2298
  */
2239
2299
  static cfgTotpDevicesRetrieve(options) {
2240
2300
  return (options?.client ?? client).get({
2241
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2301
+ security: [
2302
+ { name: "X-API-Key", type: "apiKey" },
2303
+ { scheme: "bearer", type: "http" },
2304
+ { name: "Authorization", type: "apiKey" }
2305
+ ],
2242
2306
  url: "/cfg/totp/devices/",
2243
2307
  ...options
2244
2308
  });
@@ -2250,7 +2314,11 @@ var CfgTotp = class {
2250
2314
  */
2251
2315
  static cfgTotpDevicesDestroy(options) {
2252
2316
  return (options.client ?? client).delete({
2253
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2317
+ security: [
2318
+ { name: "X-API-Key", type: "apiKey" },
2319
+ { scheme: "bearer", type: "http" },
2320
+ { name: "Authorization", type: "apiKey" }
2321
+ ],
2254
2322
  url: "/cfg/totp/devices/{id}/",
2255
2323
  ...options
2256
2324
  });
@@ -2262,7 +2330,11 @@ var CfgTotp = class {
2262
2330
  */
2263
2331
  static cfgTotpDisableCreate(options) {
2264
2332
  return (options.client ?? client).post({
2265
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2333
+ security: [
2334
+ { name: "X-API-Key", type: "apiKey" },
2335
+ { scheme: "bearer", type: "http" },
2336
+ { name: "Authorization", type: "apiKey" }
2337
+ ],
2266
2338
  url: "/cfg/totp/disable/",
2267
2339
  ...options,
2268
2340
  headers: {
@@ -2283,298 +2355,86 @@ var CfgTotpSetup = class {
2283
2355
  */
2284
2356
  static cfgTotpSetupCreate(options) {
2285
2357
  return (options?.client ?? client).post({
2286
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2358
+ security: [
2359
+ { name: "X-API-Key", type: "apiKey" },
2360
+ { scheme: "bearer", type: "http" },
2361
+ { name: "Authorization", type: "apiKey" }
2362
+ ],
2287
2363
  url: "/cfg/totp/setup/",
2288
2364
  ...options,
2289
2365
  headers: {
2290
- "Content-Type": "application/json",
2291
- ...options?.headers
2292
- }
2293
- });
2294
- }
2295
- /**
2296
- * Confirm 2FA setup with first valid code.
2297
- *
2298
- * Activates the device and generates backup codes.
2299
- */
2300
- static cfgTotpSetupConfirmCreate(options) {
2301
- return (options.client ?? client).post({
2302
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2303
- url: "/cfg/totp/setup/confirm/",
2304
- ...options,
2305
- headers: {
2306
- "Content-Type": "application/json",
2307
- ...options.headers
2308
- }
2309
- });
2310
- }
2311
- };
2312
- var CfgTotpVerify = class {
2313
- static {
2314
- __name(this, "CfgTotpVerify");
2315
- }
2316
- /**
2317
- * Verify TOTP code for 2FA session.
2318
- *
2319
- * Completes authentication and returns JWT tokens on success.
2320
- */
2321
- static cfgTotpVerifyCreate(options) {
2322
- return (options.client ?? client).post({
2323
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2324
- url: "/cfg/totp/verify/",
2325
- ...options,
2326
- headers: {
2327
- "Content-Type": "application/json",
2328
- ...options.headers
2329
- }
2330
- });
2331
- }
2332
- /**
2333
- * Verify backup recovery code for 2FA session.
2334
- *
2335
- * Alternative verification method when TOTP device unavailable.
2336
- */
2337
- static cfgTotpVerifyBackupCreate(options) {
2338
- return (options.client ?? client).post({
2339
- security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
2340
- url: "/cfg/totp/verify/backup/",
2341
- ...options,
2342
- headers: {
2343
- "Content-Type": "application/json",
2344
- ...options.headers
2345
- }
2346
- });
2347
- }
2348
- };
2349
-
2350
- // src/_api/generated/_cfg_accounts/api.ts
2351
- var API = class {
2352
- static {
2353
- __name(this, "API");
2354
- }
2355
- logger;
2356
- cfgAccountsApiKey = CfgAccountsApiKey;
2357
- cfgAccountsOauth = CfgAccountsOauth;
2358
- cfgAccounts = CfgAccounts;
2359
- cfgAccountsProfile = CfgAccountsProfile;
2360
- cfgAccountsAuth = CfgAccountsAuth;
2361
- constructor(_baseUrl, opts = {}) {
2362
- this.logger = new APILogger(opts.logger);
2363
- if (_baseUrl) auth.setBaseUrl(_baseUrl);
2364
- if (opts.locale !== void 0) auth.setLocale(opts.locale);
2365
- if (opts.apiKey !== void 0) auth.setApiKey(opts.apiKey);
2366
- if (opts.withCredentials !== void 0) auth.setWithCredentials(opts.withCredentials);
2367
- }
2368
- // ── Base URL ────────────────────────────────────────────────────────────
2369
- getBaseUrl() {
2370
- return auth.getBaseUrl();
2371
- }
2372
- setBaseUrl(url) {
2373
- auth.setBaseUrl(url);
2374
- }
2375
- // ── Tokens ──────────────────────────────────────────────────────────────
2376
- getToken() {
2377
- return auth.getToken();
2378
- }
2379
- setToken(token) {
2380
- auth.setToken(token);
2381
- }
2382
- getRefreshToken() {
2383
- return auth.getRefreshToken();
2384
- }
2385
- setRefreshToken(token) {
2386
- auth.setRefreshToken(token);
2387
- }
2388
- clearToken() {
2389
- auth.clearTokens();
2390
- }
2391
- isAuthenticated() {
2392
- return auth.isAuthenticated();
2393
- }
2394
- // ── Locale / API key ────────────────────────────────────────────────────
2395
- getLocale() {
2396
- return auth.getLocale();
2397
- }
2398
- setLocale(locale) {
2399
- auth.setLocale(locale);
2400
- }
2401
- getApiKey() {
2402
- return auth.getApiKey();
2403
- }
2404
- setApiKey(key) {
2405
- auth.setApiKey(key);
2406
- }
2407
- // ── 401 handling ────────────────────────────────────────────────────────
2408
- /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
2409
- onUnauthorized(cb) {
2410
- auth.onUnauthorized(cb);
2411
- }
2412
- /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
2413
- setRefreshHandler(fn) {
2414
- auth.setRefreshHandler(fn);
2415
- }
2416
- /**
2417
- * Proactively refresh now via the registered handler, sharing the single
2418
- * 401-recovery flight (rotation + dedup + rotated-token persistence).
2419
- * See `auth.refreshNow`.
2420
- */
2421
- refreshNow() {
2422
- return auth.refreshNow();
2423
- }
2424
- };
2425
-
2426
- // src/_api/generated/_cfg_centrifugo/api.ts
2427
- var API2 = class {
2428
- static {
2429
- __name(this, "API");
2430
- }
2431
- logger;
2432
- cfgCentrifugo = CfgCentrifugo;
2433
- constructor(_baseUrl, opts = {}) {
2434
- this.logger = new APILogger(opts.logger);
2435
- if (_baseUrl) auth.setBaseUrl(_baseUrl);
2436
- if (opts.locale !== void 0) auth.setLocale(opts.locale);
2437
- if (opts.apiKey !== void 0) auth.setApiKey(opts.apiKey);
2438
- if (opts.withCredentials !== void 0) auth.setWithCredentials(opts.withCredentials);
2439
- }
2440
- // ── Base URL ────────────────────────────────────────────────────────────
2441
- getBaseUrl() {
2442
- return auth.getBaseUrl();
2443
- }
2444
- setBaseUrl(url) {
2445
- auth.setBaseUrl(url);
2446
- }
2447
- // ── Tokens ──────────────────────────────────────────────────────────────
2448
- getToken() {
2449
- return auth.getToken();
2450
- }
2451
- setToken(token) {
2452
- auth.setToken(token);
2453
- }
2454
- getRefreshToken() {
2455
- return auth.getRefreshToken();
2456
- }
2457
- setRefreshToken(token) {
2458
- auth.setRefreshToken(token);
2459
- }
2460
- clearToken() {
2461
- auth.clearTokens();
2462
- }
2463
- isAuthenticated() {
2464
- return auth.isAuthenticated();
2465
- }
2466
- // ── Locale / API key ────────────────────────────────────────────────────
2467
- getLocale() {
2468
- return auth.getLocale();
2469
- }
2470
- setLocale(locale) {
2471
- auth.setLocale(locale);
2472
- }
2473
- getApiKey() {
2474
- return auth.getApiKey();
2475
- }
2476
- setApiKey(key) {
2477
- auth.setApiKey(key);
2478
- }
2479
- // ── 401 handling ────────────────────────────────────────────────────────
2480
- /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
2481
- onUnauthorized(cb) {
2482
- auth.onUnauthorized(cb);
2483
- }
2484
- /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
2485
- setRefreshHandler(fn) {
2486
- auth.setRefreshHandler(fn);
2366
+ "Content-Type": "application/json",
2367
+ ...options?.headers
2368
+ }
2369
+ });
2487
2370
  }
2488
2371
  /**
2489
- * Proactively refresh now via the registered handler, sharing the single
2490
- * 401-recovery flight (rotation + dedup + rotated-token persistence).
2491
- * See `auth.refreshNow`.
2372
+ * Confirm 2FA setup with first valid code.
2373
+ *
2374
+ * Activates the device and generates backup codes.
2492
2375
  */
2493
- refreshNow() {
2494
- return auth.refreshNow();
2376
+ static cfgTotpSetupConfirmCreate(options) {
2377
+ return (options.client ?? client).post({
2378
+ security: [
2379
+ { name: "X-API-Key", type: "apiKey" },
2380
+ { scheme: "bearer", type: "http" },
2381
+ { name: "Authorization", type: "apiKey" }
2382
+ ],
2383
+ url: "/cfg/totp/setup/confirm/",
2384
+ ...options,
2385
+ headers: {
2386
+ "Content-Type": "application/json",
2387
+ ...options.headers
2388
+ }
2389
+ });
2495
2390
  }
2496
2391
  };
2497
-
2498
- // src/_api/generated/_cfg_totp/api.ts
2499
- var API3 = class {
2392
+ var CfgTotpVerify = class {
2500
2393
  static {
2501
- __name(this, "API");
2502
- }
2503
- logger;
2504
- cfgTotpBackupCodes = CfgTotpBackupCodes;
2505
- cfgTotp = CfgTotp;
2506
- cfgTotpSetup = CfgTotpSetup;
2507
- cfgTotpVerify = CfgTotpVerify;
2508
- constructor(_baseUrl, opts = {}) {
2509
- this.logger = new APILogger(opts.logger);
2510
- if (_baseUrl) auth.setBaseUrl(_baseUrl);
2511
- if (opts.locale !== void 0) auth.setLocale(opts.locale);
2512
- if (opts.apiKey !== void 0) auth.setApiKey(opts.apiKey);
2513
- if (opts.withCredentials !== void 0) auth.setWithCredentials(opts.withCredentials);
2514
- }
2515
- // ── Base URL ────────────────────────────────────────────────────────────
2516
- getBaseUrl() {
2517
- return auth.getBaseUrl();
2518
- }
2519
- setBaseUrl(url) {
2520
- auth.setBaseUrl(url);
2521
- }
2522
- // ── Tokens ──────────────────────────────────────────────────────────────
2523
- getToken() {
2524
- return auth.getToken();
2525
- }
2526
- setToken(token) {
2527
- auth.setToken(token);
2528
- }
2529
- getRefreshToken() {
2530
- return auth.getRefreshToken();
2531
- }
2532
- setRefreshToken(token) {
2533
- auth.setRefreshToken(token);
2534
- }
2535
- clearToken() {
2536
- auth.clearTokens();
2537
- }
2538
- isAuthenticated() {
2539
- return auth.isAuthenticated();
2540
- }
2541
- // ── Locale / API key ────────────────────────────────────────────────────
2542
- getLocale() {
2543
- return auth.getLocale();
2544
- }
2545
- setLocale(locale) {
2546
- auth.setLocale(locale);
2547
- }
2548
- getApiKey() {
2549
- return auth.getApiKey();
2550
- }
2551
- setApiKey(key) {
2552
- auth.setApiKey(key);
2553
- }
2554
- // ── 401 handling ────────────────────────────────────────────────────────
2555
- /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
2556
- onUnauthorized(cb) {
2557
- auth.onUnauthorized(cb);
2394
+ __name(this, "CfgTotpVerify");
2558
2395
  }
2559
- /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
2560
- setRefreshHandler(fn) {
2561
- auth.setRefreshHandler(fn);
2396
+ /**
2397
+ * Verify TOTP code for 2FA session.
2398
+ *
2399
+ * Completes authentication and returns JWT tokens on success.
2400
+ */
2401
+ static cfgTotpVerifyCreate(options) {
2402
+ return (options.client ?? client).post({
2403
+ security: [
2404
+ { name: "X-API-Key", type: "apiKey" },
2405
+ { scheme: "bearer", type: "http" },
2406
+ { name: "Authorization", type: "apiKey" }
2407
+ ],
2408
+ url: "/cfg/totp/verify/",
2409
+ ...options,
2410
+ headers: {
2411
+ "Content-Type": "application/json",
2412
+ ...options.headers
2413
+ }
2414
+ });
2562
2415
  }
2563
2416
  /**
2564
- * Proactively refresh now via the registered handler, sharing the single
2565
- * 401-recovery flight (rotation + dedup + rotated-token persistence).
2566
- * See `auth.refreshNow`.
2417
+ * Verify backup recovery code for 2FA session.
2418
+ *
2419
+ * Alternative verification method when TOTP device unavailable.
2567
2420
  */
2568
- refreshNow() {
2569
- return auth.refreshNow();
2421
+ static cfgTotpVerifyBackupCreate(options) {
2422
+ return (options.client ?? client).post({
2423
+ security: [
2424
+ { name: "X-API-Key", type: "apiKey" },
2425
+ { scheme: "bearer", type: "http" },
2426
+ { name: "Authorization", type: "apiKey" }
2427
+ ],
2428
+ url: "/cfg/totp/verify/backup/",
2429
+ ...options,
2430
+ headers: {
2431
+ "Content-Type": "application/json",
2432
+ ...options.headers
2433
+ }
2434
+ });
2570
2435
  }
2571
2436
  };
2572
2437
 
2573
- // src/_api/generated/index.ts
2574
- var CfgAccountsApi = new API();
2575
- var CfgCentrifugoApi = new API2();
2576
- var CfgTotpApi = new API3();
2577
-
2578
2438
  // src/auth/utils/analytics.ts
2579
2439
  var AnalyticsEvent = /* @__PURE__ */ ((AnalyticsEvent2) => {
2580
2440
  AnalyticsEvent2["AUTH_OTP_REQUEST"] = "auth_otp_request";
@@ -2608,24 +2468,229 @@ var Analytics = {
2608
2468
  if (isDev) {
2609
2469
  console.log("[Analytics] Set user:", userId);
2610
2470
  }
2611
- }
2612
- };
2471
+ }
2472
+ };
2473
+
2474
+ // src/auth/hooks/useSessionStorage.ts
2475
+ var import_react7 = require("react");
2476
+ function useSessionStorage(key, initialValue) {
2477
+ const [storedValue, setStoredValue] = (0, import_react7.useState)(() => {
2478
+ if (typeof window === "undefined") {
2479
+ return initialValue;
2480
+ }
2481
+ try {
2482
+ const item = window.sessionStorage.getItem(key);
2483
+ return item ? JSON.parse(item) : initialValue;
2484
+ } catch (error) {
2485
+ authLogger.error(`Error reading sessionStorage key "${key}":`, error);
2486
+ return initialValue;
2487
+ }
2488
+ });
2489
+ const checkDataSize = /* @__PURE__ */ __name((data) => {
2490
+ try {
2491
+ const jsonString = JSON.stringify(data);
2492
+ const sizeInBytes = new Blob([jsonString]).size;
2493
+ const sizeInKB = sizeInBytes / 1024;
2494
+ if (sizeInKB > 1024) {
2495
+ authLogger.warn(`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`);
2496
+ return false;
2497
+ }
2498
+ return true;
2499
+ } catch (error) {
2500
+ authLogger.error(`Error checking data size for key "${key}":`, error);
2501
+ return false;
2502
+ }
2503
+ }, "checkDataSize");
2504
+ const clearOldData = /* @__PURE__ */ __name(() => {
2505
+ try {
2506
+ const keys = Object.keys(sessionStorage).filter((key2) => key2 && typeof key2 === "string");
2507
+ if (keys.length > 50) {
2508
+ const itemsToRemove = Math.ceil(keys.length * 0.2);
2509
+ for (let i = 0; i < itemsToRemove; i++) {
2510
+ try {
2511
+ const key2 = keys[i];
2512
+ if (key2) {
2513
+ sessionStorage.removeItem(key2);
2514
+ sessionStorage.removeItem(`${key2}_timestamp`);
2515
+ }
2516
+ } catch {
2517
+ }
2518
+ }
2519
+ }
2520
+ } catch (error) {
2521
+ authLogger.error("Error clearing old sessionStorage data:", error);
2522
+ }
2523
+ }, "clearOldData");
2524
+ const forceClearAll = /* @__PURE__ */ __name(() => {
2525
+ try {
2526
+ const keys = Object.keys(sessionStorage);
2527
+ for (const key2 of keys) {
2528
+ try {
2529
+ sessionStorage.removeItem(key2);
2530
+ } catch {
2531
+ }
2532
+ }
2533
+ } catch (error) {
2534
+ authLogger.error("Error force clearing sessionStorage:", error);
2535
+ }
2536
+ }, "forceClearAll");
2537
+ const setValue = /* @__PURE__ */ __name((value) => {
2538
+ try {
2539
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
2540
+ if (!checkDataSize(valueToStore)) {
2541
+ authLogger.warn(`Data size too large for key "${key}", removing key`);
2542
+ try {
2543
+ window.sessionStorage.removeItem(key);
2544
+ window.sessionStorage.removeItem(`${key}_timestamp`);
2545
+ } catch {
2546
+ }
2547
+ setStoredValue(valueToStore);
2548
+ return;
2549
+ }
2550
+ setStoredValue(valueToStore);
2551
+ if (typeof window !== "undefined") {
2552
+ try {
2553
+ window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
2554
+ window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
2555
+ } catch (storageError) {
2556
+ if (storageError.name === "QuotaExceededError" || storageError.code === 22 || storageError.message?.includes("quota")) {
2557
+ authLogger.warn("sessionStorage quota exceeded, clearing old data...");
2558
+ clearOldData();
2559
+ try {
2560
+ window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
2561
+ window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
2562
+ } catch (retryError) {
2563
+ authLogger.error(`Failed to set sessionStorage key "${key}" after clearing old data:`, retryError);
2564
+ try {
2565
+ forceClearAll();
2566
+ window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
2567
+ window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
2568
+ } catch (finalError) {
2569
+ authLogger.error(`Failed to set sessionStorage key "${key}" after force clearing:`, finalError);
2570
+ setStoredValue(valueToStore);
2571
+ }
2572
+ }
2573
+ } else {
2574
+ throw storageError;
2575
+ }
2576
+ }
2577
+ }
2578
+ } catch (error) {
2579
+ authLogger.error(`Error setting sessionStorage key "${key}":`, error);
2580
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
2581
+ setStoredValue(valueToStore);
2582
+ }
2583
+ }, "setValue");
2584
+ const removeValue = /* @__PURE__ */ __name(() => {
2585
+ try {
2586
+ setStoredValue(initialValue);
2587
+ if (typeof window !== "undefined") {
2588
+ try {
2589
+ window.sessionStorage.removeItem(key);
2590
+ window.sessionStorage.removeItem(`${key}_timestamp`);
2591
+ } catch (removeError) {
2592
+ if (removeError.name === "QuotaExceededError" || removeError.code === 22 || removeError.message?.includes("quota")) {
2593
+ authLogger.warn("sessionStorage quota exceeded during removal, clearing old data...");
2594
+ clearOldData();
2595
+ try {
2596
+ window.sessionStorage.removeItem(key);
2597
+ window.sessionStorage.removeItem(`${key}_timestamp`);
2598
+ } catch (retryError) {
2599
+ authLogger.error(`Failed to remove sessionStorage key "${key}" after clearing:`, retryError);
2600
+ forceClearAll();
2601
+ }
2602
+ } else {
2603
+ throw removeError;
2604
+ }
2605
+ }
2606
+ }
2607
+ } catch (error) {
2608
+ authLogger.error(`Error removing sessionStorage key "${key}":`, error);
2609
+ }
2610
+ }, "removeValue");
2611
+ return [storedValue, setValue, removeValue];
2612
+ }
2613
+ __name(useSessionStorage, "useSessionStorage");
2614
+
2615
+ // src/auth/hooks/useAuthRedirect.ts
2616
+ var AUTH_REDIRECT_KEY = "auth_redirect_url";
2617
+ var getRedirectFromStorage = /* @__PURE__ */ __name(() => {
2618
+ if (typeof window === "undefined") return "";
2619
+ try {
2620
+ const item = window.sessionStorage.getItem(AUTH_REDIRECT_KEY);
2621
+ return item ? JSON.parse(item) : "";
2622
+ } catch {
2623
+ return "";
2624
+ }
2625
+ }, "getRedirectFromStorage");
2626
+ var clearRedirectFromStorage = /* @__PURE__ */ __name(() => {
2627
+ if (typeof window === "undefined") return;
2628
+ try {
2629
+ window.sessionStorage.removeItem(AUTH_REDIRECT_KEY);
2630
+ window.sessionStorage.removeItem(`${AUTH_REDIRECT_KEY}_timestamp`);
2631
+ } catch {
2632
+ }
2633
+ }, "clearRedirectFromStorage");
2634
+ var peekSavedRedirect = /* @__PURE__ */ __name(() => getRedirectFromStorage() || null, "peekSavedRedirect");
2635
+ var consumeSavedRedirect = /* @__PURE__ */ __name(() => {
2636
+ const stored = getRedirectFromStorage();
2637
+ if (stored) clearRedirectFromStorage();
2638
+ return stored || null;
2639
+ }, "consumeSavedRedirect");
2640
+ var useAuthRedirectManager = /* @__PURE__ */ __name((options = {}) => {
2641
+ const { fallbackUrl = "/dashboard", clearOnUse = true } = options;
2642
+ const [redirectUrl, setRedirectUrl, removeRedirectUrl] = useSessionStorage(AUTH_REDIRECT_KEY, "");
2643
+ const setRedirect = /* @__PURE__ */ __name((url) => {
2644
+ setRedirectUrl(url);
2645
+ }, "setRedirect");
2646
+ const getRedirect = /* @__PURE__ */ __name(() => {
2647
+ return getRedirectFromStorage() || redirectUrl;
2648
+ }, "getRedirect");
2649
+ const clearRedirect = /* @__PURE__ */ __name(() => {
2650
+ removeRedirectUrl();
2651
+ clearRedirectFromStorage();
2652
+ }, "clearRedirect");
2653
+ const hasRedirect = /* @__PURE__ */ __name(() => {
2654
+ const stored = getRedirectFromStorage();
2655
+ return stored.length > 0 || redirectUrl.length > 0;
2656
+ }, "hasRedirect");
2657
+ const getFinalRedirectUrl = /* @__PURE__ */ __name(() => {
2658
+ const stored = getRedirectFromStorage();
2659
+ return stored || redirectUrl || fallbackUrl;
2660
+ }, "getFinalRedirectUrl");
2661
+ const useAndClearRedirect = /* @__PURE__ */ __name(() => {
2662
+ const stored = getRedirectFromStorage();
2663
+ const finalUrl = stored || redirectUrl || fallbackUrl;
2664
+ if (clearOnUse) {
2665
+ clearRedirect();
2666
+ }
2667
+ return finalUrl;
2668
+ }, "useAndClearRedirect");
2669
+ return {
2670
+ redirectUrl,
2671
+ setRedirect,
2672
+ getRedirect,
2673
+ clearRedirect,
2674
+ hasRedirect,
2675
+ getFinalRedirectUrl,
2676
+ useAndClearRedirect
2677
+ };
2678
+ }, "useAuthRedirectManager");
2613
2679
 
2614
2680
  // src/auth/hooks/useTwoFactor.ts
2615
2681
  var useTwoFactor = /* @__PURE__ */ __name((options = {}) => {
2616
2682
  const { onSuccess, onError, redirectUrl, skipRedirect = false } = options;
2617
2683
  const router = useCfgRouter();
2618
- const [isLoading, setIsLoading] = (0, import_react6.useState)(false);
2619
- const [error, setError] = (0, import_react6.useState)(null);
2620
- const [warning, setWarning] = (0, import_react6.useState)(null);
2621
- const [remainingBackupCodes, setRemainingBackupCodes] = (0, import_react6.useState)(null);
2622
- const [attemptsRemaining, setAttemptsRemaining] = (0, import_react6.useState)(null);
2623
- const clearError = (0, import_react6.useCallback)(() => {
2684
+ const [isLoading, setIsLoading] = (0, import_react8.useState)(false);
2685
+ const [error, setError] = (0, import_react8.useState)(null);
2686
+ const [warning, setWarning] = (0, import_react8.useState)(null);
2687
+ const [remainingBackupCodes, setRemainingBackupCodes] = (0, import_react8.useState)(null);
2688
+ const [attemptsRemaining, setAttemptsRemaining] = (0, import_react8.useState)(null);
2689
+ const clearError = (0, import_react8.useCallback)(() => {
2624
2690
  setError(null);
2625
2691
  }, []);
2626
- const handleSuccess = (0, import_react6.useCallback)((response) => {
2627
- CfgAccountsApi.setToken(response.access_token);
2628
- CfgAccountsApi.setRefreshToken(response.refresh_token);
2692
+ const handleSuccess = (0, import_react8.useCallback)((response) => {
2693
+ auth.setSession({ access: response.access_token, refresh: response.refresh_token });
2629
2694
  if (response.warning) {
2630
2695
  setWarning(response.warning);
2631
2696
  }
@@ -2641,12 +2706,12 @@ var useTwoFactor = /* @__PURE__ */ __name((options = {}) => {
2641
2706
  }
2642
2707
  onSuccess?.(response.user);
2643
2708
  if (!skipRedirect) {
2644
- const finalRedirectUrl = redirectUrl || "/dashboard";
2709
+ const finalRedirectUrl = consumeSavedRedirect() || redirectUrl || "/";
2645
2710
  authLogger.info("2FA successful, redirecting to:", finalRedirectUrl);
2646
2711
  router.hardPush(finalRedirectUrl);
2647
2712
  }
2648
2713
  }, [onSuccess, redirectUrl, router, skipRedirect]);
2649
- const verifyTOTP = (0, import_react6.useCallback)(async (sessionId, code) => {
2714
+ const verifyTOTP = (0, import_react8.useCallback)(async (sessionId, code) => {
2650
2715
  if (!sessionId) {
2651
2716
  const msg = "Missing 2FA session ID";
2652
2717
  setError(msg);
@@ -2690,7 +2755,7 @@ var useTwoFactor = /* @__PURE__ */ __name((options = {}) => {
2690
2755
  setIsLoading(false);
2691
2756
  }
2692
2757
  }, [handleSuccess, onError]);
2693
- const verifyBackupCode = (0, import_react6.useCallback)(async (sessionId, backupCode) => {
2758
+ const verifyBackupCode = (0, import_react8.useCallback)(async (sessionId, backupCode) => {
2694
2759
  if (!sessionId) {
2695
2760
  const msg = "Missing 2FA session ID";
2696
2761
  setError(msg);
@@ -2762,8 +2827,8 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2762
2827
  } = options;
2763
2828
  const formState = useAuthFormState();
2764
2829
  const validation = useAuthValidation();
2765
- const isAutoSubmitFromUrlRef = (0, import_react7.useRef)(false);
2766
- const processedOtpKeyRef = (0, import_react7.useRef)(null);
2830
+ const isAutoSubmitFromUrlRef = (0, import_react9.useRef)(false);
2831
+ const processedOtpKeyRef = (0, import_react9.useRef)(null);
2767
2832
  const {
2768
2833
  requestOTP,
2769
2834
  verifyOTP,
@@ -2806,16 +2871,16 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2806
2871
  // We handle navigation via success step
2807
2872
  });
2808
2873
  const { validateIdentifier: validateIdentifier2 } = validation;
2809
- const saveIdentifierToStorage = (0, import_react7.useCallback)((id) => {
2874
+ const saveIdentifierToStorage = (0, import_react9.useCallback)((id) => {
2810
2875
  saveEmail(id);
2811
2876
  }, [saveEmail]);
2812
- (0, import_react7.useEffect)(() => {
2877
+ (0, import_react9.useEffect)(() => {
2813
2878
  const savedEmail = getSavedEmail();
2814
2879
  if (savedEmail) {
2815
2880
  setIdentifier(savedEmail);
2816
2881
  }
2817
2882
  }, []);
2818
- const handleIdentifierSubmit = (0, import_react7.useCallback)(async (e) => {
2883
+ const handleIdentifierSubmit = (0, import_react9.useCallback)(async (e) => {
2819
2884
  e.preventDefault();
2820
2885
  if (!identifier) {
2821
2886
  const msg = "Please enter your email address";
@@ -2877,7 +2942,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2877
2942
  onError,
2878
2943
  sourceUrl
2879
2944
  ]);
2880
- const submitOTP = (0, import_react7.useCallback)(async (submitIdentifier, submitOtp) => {
2945
+ const submitOTP = (0, import_react9.useCallback)(async (submitIdentifier, submitOtp) => {
2881
2946
  if (!submitOtp || submitOtp.length < AUTH_CONSTANTS.EMAIL_OTP_LENGTH) {
2882
2947
  const msg = `Please enter the ${AUTH_CONSTANTS.EMAIL_OTP_LENGTH}-digit verification code`;
2883
2948
  setError(msg);
@@ -2916,11 +2981,11 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2916
2981
  setIsLoading(false);
2917
2982
  }
2918
2983
  }, [verifyOTP, saveIdentifierToStorage, setError, setIsLoading, clearError, onOTPSuccess, onError, sourceUrl, redirectUrl, setTwoFactorSessionId, setShouldPrompt2FA, setStep]);
2919
- const handleOTPSubmit = (0, import_react7.useCallback)(async (e) => {
2984
+ const handleOTPSubmit = (0, import_react9.useCallback)(async (e) => {
2920
2985
  e.preventDefault();
2921
2986
  await submitOTP(identifier, otp);
2922
2987
  }, [identifier, otp, submitOTP]);
2923
- const handleResendOTP = (0, import_react7.useCallback)(async () => {
2988
+ const handleResendOTP = (0, import_react9.useCallback)(async () => {
2924
2989
  setIsLoading(true);
2925
2990
  clearError();
2926
2991
  try {
@@ -2946,15 +3011,15 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2946
3011
  setIsLoading(false);
2947
3012
  }
2948
3013
  }, [identifier, requestOTP, saveIdentifierToStorage, setOtp, setError, setIsLoading, clearError, startRateLimitCountdown, setWebmail, onError, sourceUrl]);
2949
- const handleBackToIdentifier = (0, import_react7.useCallback)(() => {
3014
+ const handleBackToIdentifier = (0, import_react9.useCallback)(() => {
2950
3015
  setStep("identifier");
2951
3016
  clearError();
2952
3017
  }, [setStep, clearError]);
2953
- const forceOTPStep = (0, import_react7.useCallback)(() => {
3018
+ const forceOTPStep = (0, import_react9.useCallback)(() => {
2954
3019
  setStep("otp");
2955
3020
  clearError();
2956
3021
  }, [setStep, clearError]);
2957
- const handle2FASubmit = (0, import_react7.useCallback)(async (e) => {
3022
+ const handle2FASubmit = (0, import_react9.useCallback)(async (e) => {
2958
3023
  e.preventDefault();
2959
3024
  if (!twoFactorSessionId) {
2960
3025
  const msg = "Missing 2FA session";
@@ -2968,17 +3033,17 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2968
3033
  await twoFactor.verifyTOTP(twoFactorSessionId, twoFactorCode);
2969
3034
  }
2970
3035
  }, [twoFactorSessionId, twoFactorCode, useBackupCode, twoFactor, setError, onError]);
2971
- const handleUseBackupCode = (0, import_react7.useCallback)(() => {
3036
+ const handleUseBackupCode = (0, import_react9.useCallback)(() => {
2972
3037
  setUseBackupCode(true);
2973
3038
  setTwoFactorCode("");
2974
3039
  clearError();
2975
3040
  }, [setUseBackupCode, setTwoFactorCode, clearError]);
2976
- const handleUseTOTP = (0, import_react7.useCallback)(() => {
3041
+ const handleUseTOTP = (0, import_react9.useCallback)(() => {
2977
3042
  setUseBackupCode(false);
2978
3043
  setTwoFactorCode("");
2979
3044
  clearError();
2980
3045
  }, [setUseBackupCode, setTwoFactorCode, clearError]);
2981
- const handleOTPFromUrl = (0, import_react7.useCallback)((detectedOtp, detectedEmail) => {
3046
+ const handleOTPFromUrl = (0, import_react9.useCallback)((detectedOtp, detectedEmail) => {
2982
3047
  const autoIdentifier = detectedEmail || getSavedEmail() || "";
2983
3048
  if (!autoIdentifier) {
2984
3049
  authLogger.warn("No identifier found for auto-submit (no email in URL or storage)");
@@ -3031,13 +3096,13 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
3031
3096
  }, "useAuthForm");
3032
3097
 
3033
3098
  // src/auth/hooks/useGithubAuth.ts
3034
- var import_react8 = require("react");
3099
+ var import_react10 = require("react");
3035
3100
  var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
3036
3101
  const { sourceUrl, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
3037
3102
  const router = useCfgRouter();
3038
- const [isLoading, setIsLoading] = (0, import_react8.useState)(false);
3039
- const [error, setError] = (0, import_react8.useState)(null);
3040
- const startGithubAuth = (0, import_react8.useCallback)(async () => {
3103
+ const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
3104
+ const [error, setError] = (0, import_react10.useState)(null);
3105
+ const startGithubAuth = (0, import_react10.useCallback)(async () => {
3041
3106
  setIsLoading(true);
3042
3107
  setError(null);
3043
3108
  try {
@@ -3075,7 +3140,7 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
3075
3140
  setIsLoading(false);
3076
3141
  }
3077
3142
  }, [sourceUrl, onError]);
3078
- const handleGithubCallback = (0, import_react8.useCallback)(async (code, state) => {
3143
+ const handleGithubCallback = (0, import_react10.useCallback)(async (code, state) => {
3079
3144
  setIsLoading(true);
3080
3145
  setError(null);
3081
3146
  try {
@@ -3106,8 +3171,7 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
3106
3171
  throw new Error("Invalid response from OAuth callback");
3107
3172
  }
3108
3173
  authLogger.info("GitHub OAuth successful, user:", response.user);
3109
- CfgAccountsApi.setToken(response.access);
3110
- CfgAccountsApi.setRefreshToken(response.refresh);
3174
+ auth.setSession({ access: response.access, refresh: response.refresh });
3111
3175
  Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
3112
3176
  category: "auth" /* AUTH */,
3113
3177
  label: "github"
@@ -3142,26 +3206,26 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
3142
3206
  }, "useGithubAuth");
3143
3207
 
3144
3208
  // src/auth/hooks/useTwoFactorSetup.ts
3145
- var import_react9 = require("react");
3209
+ var import_react11 = require("react");
3146
3210
  var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
3147
3211
  const { onComplete, onError } = options;
3148
- const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
3149
- const [error, setError] = (0, import_react9.useState)(null);
3150
- const [setupData, setSetupData] = (0, import_react9.useState)(null);
3151
- const [backupCodes, setBackupCodes] = (0, import_react9.useState)(null);
3152
- const [backupCodesWarning, setBackupCodesWarning] = (0, import_react9.useState)(null);
3153
- const [setupStep, setSetupStep] = (0, import_react9.useState)("idle");
3154
- const clearError = (0, import_react9.useCallback)(() => {
3212
+ const [isLoading, setIsLoading] = (0, import_react11.useState)(false);
3213
+ const [error, setError] = (0, import_react11.useState)(null);
3214
+ const [setupData, setSetupData] = (0, import_react11.useState)(null);
3215
+ const [backupCodes, setBackupCodes] = (0, import_react11.useState)(null);
3216
+ const [backupCodesWarning, setBackupCodesWarning] = (0, import_react11.useState)(null);
3217
+ const [setupStep, setSetupStep] = (0, import_react11.useState)("idle");
3218
+ const clearError = (0, import_react11.useCallback)(() => {
3155
3219
  setError(null);
3156
3220
  }, []);
3157
- const resetSetup = (0, import_react9.useCallback)(() => {
3221
+ const resetSetup = (0, import_react11.useCallback)(() => {
3158
3222
  setSetupData(null);
3159
3223
  setBackupCodes(null);
3160
3224
  setBackupCodesWarning(null);
3161
3225
  setSetupStep("idle");
3162
3226
  setError(null);
3163
3227
  }, []);
3164
- const startSetup = (0, import_react9.useCallback)(async (deviceName) => {
3228
+ const startSetup = (0, import_react11.useCallback)(async (deviceName) => {
3165
3229
  setIsLoading(true);
3166
3230
  setError(null);
3167
3231
  setSetupStep("scanning");
@@ -3193,7 +3257,7 @@ var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
3193
3257
  setIsLoading(false);
3194
3258
  }
3195
3259
  }, [onError]);
3196
- const confirmSetup = (0, import_react9.useCallback)(async (code) => {
3260
+ const confirmSetup = (0, import_react11.useCallback)(async (code) => {
3197
3261
  if (!setupData) {
3198
3262
  const msg = "Setup not started. Call startSetup() first.";
3199
3263
  setError(msg);
@@ -3249,7 +3313,7 @@ var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
3249
3313
  }, "useTwoFactorSetup");
3250
3314
 
3251
3315
  // src/auth/hooks/useTwoFactorStatus.ts
3252
- var import_react10 = require("react");
3316
+ var import_react12 = require("react");
3253
3317
  function extractErrorMessage(err, fallback) {
3254
3318
  if (err instanceof APIError) {
3255
3319
  const body = err.response;
@@ -3262,14 +3326,14 @@ function extractErrorMessage(err, fallback) {
3262
3326
  }
3263
3327
  __name(extractErrorMessage, "extractErrorMessage");
3264
3328
  var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
3265
- const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
3266
- const [error, setError] = (0, import_react10.useState)(null);
3267
- const [has2FAEnabled, setHas2FAEnabled] = (0, import_react10.useState)(null);
3268
- const [devices, setDevices] = (0, import_react10.useState)([]);
3269
- const clearError = (0, import_react10.useCallback)(() => {
3329
+ const [isLoading, setIsLoading] = (0, import_react12.useState)(false);
3330
+ const [error, setError] = (0, import_react12.useState)(null);
3331
+ const [has2FAEnabled, setHas2FAEnabled] = (0, import_react12.useState)(null);
3332
+ const [devices, setDevices] = (0, import_react12.useState)([]);
3333
+ const clearError = (0, import_react12.useCallback)(() => {
3270
3334
  setError(null);
3271
3335
  }, []);
3272
- const fetchStatus = (0, import_react10.useCallback)(async () => {
3336
+ const fetchStatus = (0, import_react12.useCallback)(async () => {
3273
3337
  setIsLoading(true);
3274
3338
  setError(null);
3275
3339
  try {
@@ -3294,259 +3358,39 @@ var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
3294
3358
  setIsLoading(false);
3295
3359
  }
3296
3360
  }, []);
3297
- const disable2FA = (0, import_react10.useCallback)(async (code) => {
3361
+ const disable2FA = (0, import_react12.useCallback)(async (code) => {
3298
3362
  if (!code || code.length !== 6) {
3299
3363
  setError("Please enter a 6-digit code");
3300
3364
  return false;
3301
3365
  }
3302
3366
  setIsLoading(true);
3303
3367
  setError(null);
3304
- try {
3305
- authLogger.info("Disabling 2FA...");
3306
- await CfgTotp.cfgTotpDisableCreate({ body: { code }, throwOnError: true });
3307
- setHas2FAEnabled(false);
3308
- setDevices([]);
3309
- authLogger.info("2FA disabled successfully");
3310
- return true;
3311
- } catch (err) {
3312
- const errorMessage = extractErrorMessage(err, "Invalid verification code");
3313
- authLogger.error("Failed to disable 2FA:", err);
3314
- setError(errorMessage);
3315
- return false;
3316
- } finally {
3317
- setIsLoading(false);
3318
- }
3319
- }, []);
3320
- return {
3321
- isLoading,
3322
- error,
3323
- has2FAEnabled,
3324
- devices,
3325
- fetchStatus,
3326
- disable2FA,
3327
- clearError
3328
- };
3329
- }, "useTwoFactorStatus");
3330
-
3331
- // src/auth/hooks/useSessionStorage.ts
3332
- var import_react11 = require("react");
3333
- function useSessionStorage(key, initialValue) {
3334
- const [storedValue, setStoredValue] = (0, import_react11.useState)(() => {
3335
- if (typeof window === "undefined") {
3336
- return initialValue;
3337
- }
3338
- try {
3339
- const item = window.sessionStorage.getItem(key);
3340
- return item ? JSON.parse(item) : initialValue;
3341
- } catch (error) {
3342
- authLogger.error(`Error reading sessionStorage key "${key}":`, error);
3343
- return initialValue;
3344
- }
3345
- });
3346
- const checkDataSize = /* @__PURE__ */ __name((data) => {
3347
- try {
3348
- const jsonString = JSON.stringify(data);
3349
- const sizeInBytes = new Blob([jsonString]).size;
3350
- const sizeInKB = sizeInBytes / 1024;
3351
- if (sizeInKB > 1024) {
3352
- authLogger.warn(`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`);
3353
- return false;
3354
- }
3355
- return true;
3356
- } catch (error) {
3357
- authLogger.error(`Error checking data size for key "${key}":`, error);
3358
- return false;
3359
- }
3360
- }, "checkDataSize");
3361
- const clearOldData = /* @__PURE__ */ __name(() => {
3362
- try {
3363
- const keys = Object.keys(sessionStorage).filter((key2) => key2 && typeof key2 === "string");
3364
- if (keys.length > 50) {
3365
- const itemsToRemove = Math.ceil(keys.length * 0.2);
3366
- for (let i = 0; i < itemsToRemove; i++) {
3367
- try {
3368
- const key2 = keys[i];
3369
- if (key2) {
3370
- sessionStorage.removeItem(key2);
3371
- sessionStorage.removeItem(`${key2}_timestamp`);
3372
- }
3373
- } catch {
3374
- }
3375
- }
3376
- }
3377
- } catch (error) {
3378
- authLogger.error("Error clearing old sessionStorage data:", error);
3379
- }
3380
- }, "clearOldData");
3381
- const forceClearAll = /* @__PURE__ */ __name(() => {
3382
- try {
3383
- const keys = Object.keys(sessionStorage);
3384
- for (const key2 of keys) {
3385
- try {
3386
- sessionStorage.removeItem(key2);
3387
- } catch {
3388
- }
3389
- }
3390
- } catch (error) {
3391
- authLogger.error("Error force clearing sessionStorage:", error);
3392
- }
3393
- }, "forceClearAll");
3394
- const setValue = /* @__PURE__ */ __name((value) => {
3395
- try {
3396
- const valueToStore = value instanceof Function ? value(storedValue) : value;
3397
- if (!checkDataSize(valueToStore)) {
3398
- authLogger.warn(`Data size too large for key "${key}", removing key`);
3399
- try {
3400
- window.sessionStorage.removeItem(key);
3401
- window.sessionStorage.removeItem(`${key}_timestamp`);
3402
- } catch {
3403
- }
3404
- setStoredValue(valueToStore);
3405
- return;
3406
- }
3407
- setStoredValue(valueToStore);
3408
- if (typeof window !== "undefined") {
3409
- try {
3410
- window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
3411
- window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
3412
- } catch (storageError) {
3413
- if (storageError.name === "QuotaExceededError" || storageError.code === 22 || storageError.message?.includes("quota")) {
3414
- authLogger.warn("sessionStorage quota exceeded, clearing old data...");
3415
- clearOldData();
3416
- try {
3417
- window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
3418
- window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
3419
- } catch (retryError) {
3420
- authLogger.error(`Failed to set sessionStorage key "${key}" after clearing old data:`, retryError);
3421
- try {
3422
- forceClearAll();
3423
- window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
3424
- window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
3425
- } catch (finalError) {
3426
- authLogger.error(`Failed to set sessionStorage key "${key}" after force clearing:`, finalError);
3427
- setStoredValue(valueToStore);
3428
- }
3429
- }
3430
- } else {
3431
- throw storageError;
3432
- }
3433
- }
3434
- }
3435
- } catch (error) {
3436
- authLogger.error(`Error setting sessionStorage key "${key}":`, error);
3437
- const valueToStore = value instanceof Function ? value(storedValue) : value;
3438
- setStoredValue(valueToStore);
3439
- }
3440
- }, "setValue");
3441
- const removeValue = /* @__PURE__ */ __name(() => {
3442
- try {
3443
- setStoredValue(initialValue);
3444
- if (typeof window !== "undefined") {
3445
- try {
3446
- window.sessionStorage.removeItem(key);
3447
- window.sessionStorage.removeItem(`${key}_timestamp`);
3448
- } catch (removeError) {
3449
- if (removeError.name === "QuotaExceededError" || removeError.code === 22 || removeError.message?.includes("quota")) {
3450
- authLogger.warn("sessionStorage quota exceeded during removal, clearing old data...");
3451
- clearOldData();
3452
- try {
3453
- window.sessionStorage.removeItem(key);
3454
- window.sessionStorage.removeItem(`${key}_timestamp`);
3455
- } catch (retryError) {
3456
- authLogger.error(`Failed to remove sessionStorage key "${key}" after clearing:`, retryError);
3457
- forceClearAll();
3458
- }
3459
- } else {
3460
- throw removeError;
3461
- }
3462
- }
3463
- }
3464
- } catch (error) {
3465
- authLogger.error(`Error removing sessionStorage key "${key}":`, error);
3466
- }
3467
- }, "removeValue");
3468
- return [storedValue, setValue, removeValue];
3469
- }
3470
- __name(useSessionStorage, "useSessionStorage");
3471
-
3472
- // src/auth/hooks/useAuthRedirect.ts
3473
- var AUTH_REDIRECT_KEY = "auth_redirect_url";
3474
- var getRedirectFromStorage = /* @__PURE__ */ __name(() => {
3475
- if (typeof window === "undefined") return "";
3476
- try {
3477
- const item = window.sessionStorage.getItem(AUTH_REDIRECT_KEY);
3478
- return item ? JSON.parse(item) : "";
3479
- } catch {
3480
- return "";
3481
- }
3482
- }, "getRedirectFromStorage");
3483
- var clearRedirectFromStorage = /* @__PURE__ */ __name(() => {
3484
- if (typeof window === "undefined") return;
3485
- try {
3486
- window.sessionStorage.removeItem(AUTH_REDIRECT_KEY);
3487
- window.sessionStorage.removeItem(`${AUTH_REDIRECT_KEY}_timestamp`);
3488
- } catch {
3489
- }
3490
- }, "clearRedirectFromStorage");
3491
- var useAuthRedirectManager = /* @__PURE__ */ __name((options = {}) => {
3492
- const { fallbackUrl = "/dashboard", clearOnUse = true } = options;
3493
- const [redirectUrl, setRedirectUrl, removeRedirectUrl] = useSessionStorage(AUTH_REDIRECT_KEY, "");
3494
- const setRedirect = /* @__PURE__ */ __name((url) => {
3495
- setRedirectUrl(url);
3496
- }, "setRedirect");
3497
- const getRedirect = /* @__PURE__ */ __name(() => {
3498
- return getRedirectFromStorage() || redirectUrl;
3499
- }, "getRedirect");
3500
- const clearRedirect = /* @__PURE__ */ __name(() => {
3501
- removeRedirectUrl();
3502
- clearRedirectFromStorage();
3503
- }, "clearRedirect");
3504
- const hasRedirect = /* @__PURE__ */ __name(() => {
3505
- const stored = getRedirectFromStorage();
3506
- return stored.length > 0 || redirectUrl.length > 0;
3507
- }, "hasRedirect");
3508
- const getFinalRedirectUrl = /* @__PURE__ */ __name(() => {
3509
- const stored = getRedirectFromStorage();
3510
- return stored || redirectUrl || fallbackUrl;
3511
- }, "getFinalRedirectUrl");
3512
- const useAndClearRedirect = /* @__PURE__ */ __name(() => {
3513
- const stored = getRedirectFromStorage();
3514
- const finalUrl = stored || redirectUrl || fallbackUrl;
3515
- if (clearOnUse) {
3516
- clearRedirect();
3368
+ try {
3369
+ authLogger.info("Disabling 2FA...");
3370
+ await CfgTotp.cfgTotpDisableCreate({ body: { code }, throwOnError: true });
3371
+ setHas2FAEnabled(false);
3372
+ setDevices([]);
3373
+ authLogger.info("2FA disabled successfully");
3374
+ return true;
3375
+ } catch (err) {
3376
+ const errorMessage = extractErrorMessage(err, "Invalid verification code");
3377
+ authLogger.error("Failed to disable 2FA:", err);
3378
+ setError(errorMessage);
3379
+ return false;
3380
+ } finally {
3381
+ setIsLoading(false);
3517
3382
  }
3518
- return finalUrl;
3519
- }, "useAndClearRedirect");
3383
+ }, []);
3520
3384
  return {
3521
- redirectUrl,
3522
- setRedirect,
3523
- getRedirect,
3524
- clearRedirect,
3525
- hasRedirect,
3526
- getFinalRedirectUrl,
3527
- useAndClearRedirect
3385
+ isLoading,
3386
+ error,
3387
+ has2FAEnabled,
3388
+ devices,
3389
+ fetchStatus,
3390
+ disable2FA,
3391
+ clearError
3528
3392
  };
3529
- }, "useAuthRedirectManager");
3530
-
3531
- // src/auth/hooks/useAuthGuard.ts
3532
- var import_react12 = require("react");
3533
- var useAuthGuard = /* @__PURE__ */ __name((options = {}) => {
3534
- const { redirectTo = "/auth", requireAuth = true, saveRedirectUrl: shouldSaveUrl = true } = options;
3535
- const { isAuthenticated, isLoading, saveRedirectUrl } = useAuth();
3536
- const router = useCfgRouter();
3537
- const [isRedirecting, setIsRedirecting] = (0, import_react12.useState)(false);
3538
- (0, import_react12.useEffect)(() => {
3539
- if (!isLoading && requireAuth && !isAuthenticated && !isRedirecting) {
3540
- if (shouldSaveUrl && typeof window !== "undefined") {
3541
- const currentUrl = window.location.pathname + window.location.search;
3542
- saveRedirectUrl(currentUrl);
3543
- }
3544
- setIsRedirecting(true);
3545
- router.push(redirectTo);
3546
- }
3547
- }, [isAuthenticated, isLoading, router, redirectTo, requireAuth, isRedirecting, shouldSaveUrl, saveRedirectUrl]);
3548
- return { isAuthenticated, isLoading, isRedirecting };
3549
- }, "useAuthGuard");
3393
+ }, "useTwoFactorStatus");
3550
3394
 
3551
3395
  // src/auth/hooks/useLocalStorage.ts
3552
3396
  var import_react13 = require("react");
@@ -3846,150 +3690,16 @@ function getCacheMetadata() {
3846
3690
  }
3847
3691
  __name(getCacheMetadata, "getCacheMetadata");
3848
3692
 
3849
- // src/auth/hooks/useTokenRefresh.ts
3850
- var import_react14 = require("react");
3851
-
3852
- // src/auth/refreshHandler.ts
3853
- var _registered = false;
3854
- function ensureRefreshHandler() {
3855
- if (_registered) return;
3856
- _registered = true;
3857
- CfgAccountsApi.setRefreshHandler(async (refresh) => {
3858
- const result = await CfgAccountsAuth.cfgAccountsTokenRefreshCreate({
3859
- body: { refresh },
3860
- throwOnError: true
3861
- });
3862
- const access = result.data?.access;
3863
- if (!access) {
3864
- authLogger.error("Refresh response missing access token");
3865
- return null;
3866
- }
3867
- return { access, refresh: result.data?.refresh ?? refresh };
3868
- });
3869
- authLogger.debug("Canonical refresh handler registered");
3870
- }
3871
- __name(ensureRefreshHandler, "ensureRefreshHandler");
3872
- function triggerRefresh() {
3873
- ensureRefreshHandler();
3874
- const maybe = CfgAccountsApi;
3875
- if (typeof maybe.refreshNow === "function") {
3876
- return maybe.refreshNow();
3877
- }
3878
- authLogger.warn("api.refreshNow() unavailable \u2014 run `make generate` to regenerate the client");
3879
- return Promise.resolve(null);
3880
- }
3881
- __name(triggerRefresh, "triggerRefresh");
3882
-
3883
- // src/auth/utils/jwt.ts
3884
- function decodeSegment(segment) {
3885
- try {
3886
- const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
3887
- if (typeof atob !== "function") return null;
3888
- const json = atob(base64);
3889
- return JSON.parse(json);
3890
- } catch {
3891
- return null;
3892
- }
3893
- }
3894
- __name(decodeSegment, "decodeSegment");
3895
- function getTokenExpiry(token) {
3896
- if (!token || typeof token !== "string") return null;
3897
- const parts = token.split(".");
3898
- if (parts.length !== 3) return null;
3899
- const payload = decodeSegment(parts[1]);
3900
- if (!payload || typeof payload !== "object") return null;
3901
- const exp = payload.exp;
3902
- if (typeof exp !== "number" || !Number.isFinite(exp)) return null;
3903
- return exp * 1e3;
3904
- }
3905
- __name(getTokenExpiry, "getTokenExpiry");
3906
- function isTokenExpired(token, skewMs = 0, now = Date.now()) {
3907
- const expiry = getTokenExpiry(token);
3908
- if (expiry === null) return true;
3909
- return expiry - skewMs <= now;
3910
- }
3911
- __name(isTokenExpired, "isTokenExpired");
3912
- function isTokenExpiringSoon(token, thresholdMs, now = Date.now()) {
3913
- const expiry = getTokenExpiry(token);
3914
- if (expiry === null) return false;
3915
- return expiry - now < thresholdMs;
3916
- }
3917
- __name(isTokenExpiringSoon, "isTokenExpiringSoon");
3918
-
3919
- // src/auth/hooks/useTokenRefresh.ts
3920
- var TOKEN_REFRESH_THRESHOLD_MS = 10 * 60 * 1e3;
3921
- var CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3922
- function useTokenRefresh(options = {}) {
3923
- const { enabled = true, onRefresh, onRefreshError } = options;
3924
- const refreshToken = (0, import_react14.useCallback)(async () => {
3925
- if (!CfgAccountsApi.getRefreshToken()) {
3926
- authLogger.warn("No refresh token available");
3927
- return false;
3928
- }
3929
- authLogger.info("Refreshing token...");
3930
- try {
3931
- const newAccessToken = await triggerRefresh();
3932
- if (!newAccessToken) {
3933
- throw new Error("Token refresh failed");
3934
- }
3935
- authLogger.info("Token refreshed successfully");
3936
- onRefresh?.(newAccessToken);
3937
- return true;
3938
- } catch (error) {
3939
- authLogger.error("Token refresh error:", error);
3940
- onRefreshError?.(error instanceof Error ? error : new Error(String(error)));
3941
- return false;
3942
- }
3943
- }, [onRefresh, onRefreshError]);
3944
- const checkAndRefresh = (0, import_react14.useCallback)(async () => {
3945
- const token = CfgAccountsApi.getToken();
3946
- if (!token) return;
3947
- if (isTokenExpiringSoon(token, TOKEN_REFRESH_THRESHOLD_MS)) {
3948
- authLogger.info("Token expiring soon, refreshing proactively");
3949
- await refreshToken();
3950
- }
3951
- }, [refreshToken]);
3952
- (0, import_react14.useEffect)(() => {
3953
- if (!enabled) return;
3954
- checkAndRefresh();
3955
- const intervalId = setInterval(checkAndRefresh, CHECK_INTERVAL_MS);
3956
- return () => clearInterval(intervalId);
3957
- }, [enabled, checkAndRefresh]);
3958
- (0, import_react14.useEffect)(() => {
3959
- if (!enabled) return;
3960
- const handleFocus = /* @__PURE__ */ __name(() => {
3961
- authLogger.debug("Window focused, checking token...");
3962
- checkAndRefresh();
3963
- }, "handleFocus");
3964
- window.addEventListener("focus", handleFocus);
3965
- return () => window.removeEventListener("focus", handleFocus);
3966
- }, [enabled, checkAndRefresh]);
3967
- (0, import_react14.useEffect)(() => {
3968
- if (!enabled) return;
3969
- const handleOnline = /* @__PURE__ */ __name(() => {
3970
- authLogger.info("Network reconnected, checking token...");
3971
- checkAndRefresh();
3972
- }, "handleOnline");
3973
- window.addEventListener("online", handleOnline);
3974
- return () => window.removeEventListener("online", handleOnline);
3975
- }, [enabled, checkAndRefresh]);
3976
- return {
3977
- refreshToken,
3978
- checkAndRefresh
3979
- };
3980
- }
3981
- __name(useTokenRefresh, "useTokenRefresh");
3982
-
3983
3693
  // src/auth/hooks/useDeleteAccount.ts
3984
- var import_react15 = require("react");
3694
+ var import_react14 = require("react");
3985
3695
  var useDeleteAccount = /* @__PURE__ */ __name(() => {
3986
- const [isLoading, setIsLoading] = (0, import_react15.useState)(false);
3987
- const [error, setError] = (0, import_react15.useState)(null);
3696
+ const [isLoading, setIsLoading] = (0, import_react14.useState)(false);
3697
+ const [error, setError] = (0, import_react14.useState)(null);
3988
3698
  const { logout } = useAuth();
3989
- const clearError = (0, import_react15.useCallback)(() => {
3699
+ const clearError = (0, import_react14.useCallback)(() => {
3990
3700
  setError(null);
3991
3701
  }, []);
3992
- const deleteAccount = (0, import_react15.useCallback)(async () => {
3702
+ const deleteAccount = (0, import_react14.useCallback)(async () => {
3993
3703
  setIsLoading(true);
3994
3704
  setError(null);
3995
3705
  try {
@@ -4018,32 +3728,8 @@ var useDeleteAccount = /* @__PURE__ */ __name(() => {
4018
3728
  };
4019
3729
  }, "useDeleteAccount");
4020
3730
 
4021
- // src/auth/utils/guard.ts
4022
- function resolveGuardIsLoading(s) {
4023
- if (!s.mounted) return true;
4024
- if (!s.requireAuth) return false;
4025
- return s.authLoading || s.isRedirecting || !s.isAuthenticated;
4026
- }
4027
- __name(resolveGuardIsLoading, "resolveGuardIsLoading");
4028
- function shouldRedirectToAuth(s) {
4029
- return s.mounted && s.requireAuth && !s.authLoading && !s.isAuthenticated && !s.isRedirecting;
4030
- }
4031
- __name(shouldRedirectToAuth, "shouldRedirectToAuth");
4032
- function resolveGuardIsAuthenticated(s) {
4033
- return !s.requireAuth || s.isAuthenticated;
4034
- }
4035
- __name(resolveGuardIsAuthenticated, "resolveGuardIsAuthenticated");
4036
- function nextAuthEvaluationDelay(accessExp, refreshExp, now) {
4037
- const future = [accessExp, refreshExp].filter(
4038
- (d) => d !== null && d > now
4039
- );
4040
- if (future.length === 0) return null;
4041
- return Math.max(0, Math.min(...future) - now) + 1e3;
4042
- }
4043
- __name(nextAuthEvaluationDelay, "nextAuthEvaluationDelay");
4044
-
4045
3731
  // src/auth/context/AccountsContext.tsx
4046
- var import_react16 = require("react");
3732
+ var import_react15 = require("react");
4047
3733
 
4048
3734
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsApiKeyRegenerateCreate.ts
4049
3735
  var import_mutation = __toESM(require("swr/mutation"), 1);
@@ -4495,9 +4181,12 @@ function useCfgAccountsProfileUpdateUpdate(config) {
4495
4181
  }
4496
4182
  __name(useCfgAccountsProfileUpdateUpdate, "useCfgAccountsProfileUpdateUpdate");
4497
4183
 
4498
- // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
4184
+ // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenBlacklistCreate.ts
4499
4185
  var import_mutation15 = __toESM(require("swr/mutation"), 1);
4500
4186
 
4187
+ // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
4188
+ var import_mutation16 = __toESM(require("swr/mutation"), 1);
4189
+
4501
4190
  // src/_api/generated/_cfg_accounts/schemas/TokenRefresh.ts
4502
4191
  var import_zod19 = require("zod");
4503
4192
  var TokenRefreshSchema = import_zod19.z.object({
@@ -4505,53 +4194,6 @@ var TokenRefreshSchema = import_zod19.z.object({
4505
4194
  refresh: import_zod19.z.string()
4506
4195
  });
4507
4196
 
4508
- // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
4509
- function useCfgAccountsTokenRefreshCreate(config) {
4510
- return (0, import_mutation15.default)(
4511
- ["cfg_accounts_token_refresh_create"],
4512
- async (_key, { arg }) => {
4513
- const res = await CfgAccountsAuth.cfgAccountsTokenRefreshCreate({ ...arg, throwOnError: true });
4514
- const data = res.data;
4515
- const parsed = TokenRefreshSchema.safeParse(data);
4516
- if (!parsed.success) {
4517
- console.warn(
4518
- "[zod] response did not match schema",
4519
- {
4520
- operation: "cfg_accounts_token_refresh_create",
4521
- method: "POST",
4522
- path: "/cfg/accounts/token/refresh/",
4523
- issues: parsed.error.issues,
4524
- data
4525
- }
4526
- );
4527
- if (typeof window !== "undefined") {
4528
- try {
4529
- window.dispatchEvent(
4530
- new CustomEvent("zod-validation-error", {
4531
- detail: {
4532
- operation: "cfg_accounts_token_refresh_create",
4533
- method: "POST",
4534
- path: "/cfg/accounts/token/refresh/",
4535
- issues: parsed.error.issues,
4536
- data,
4537
- timestamp: /* @__PURE__ */ new Date()
4538
- },
4539
- bubbles: true,
4540
- cancelable: false
4541
- })
4542
- );
4543
- } catch {
4544
- }
4545
- }
4546
- return data;
4547
- }
4548
- return parsed.data;
4549
- },
4550
- config
4551
- );
4552
- }
4553
- __name(useCfgAccountsTokenRefreshCreate, "useCfgAccountsTokenRefreshCreate");
4554
-
4555
4197
  // src/_api/generated/_cfg_accounts/schemas/APIKeyRequest.ts
4556
4198
  var import_zod20 = require("zod");
4557
4199
  var APIKeyRequestSchema = import_zod20.z.object({
@@ -4641,28 +4283,34 @@ var PatchedCfgUserUpdateRequestSchema = import_zod30.z.object({
4641
4283
  timezone: import_zod30.z.string().max(64).optional()
4642
4284
  });
4643
4285
 
4644
- // src/_api/generated/_cfg_accounts/schemas/TokenRefreshRequest.ts
4286
+ // src/_api/generated/_cfg_accounts/schemas/TokenBlacklistRequest.ts
4645
4287
  var import_zod31 = require("zod");
4646
- var TokenRefreshRequestSchema = import_zod31.z.object({
4288
+ var TokenBlacklistRequestSchema = import_zod31.z.object({
4647
4289
  refresh: import_zod31.z.string().min(1)
4648
4290
  });
4649
4291
 
4292
+ // src/_api/generated/_cfg_accounts/schemas/TokenRefreshRequest.ts
4293
+ var import_zod32 = require("zod");
4294
+ var TokenRefreshRequestSchema = import_zod32.z.object({
4295
+ refresh: import_zod32.z.string().min(1)
4296
+ });
4297
+
4650
4298
  // src/auth/context/AccountsContext.tsx
4651
4299
  var import_jsx_runtime = require("react/jsx-runtime");
4652
- var AccountsContext = (0, import_react16.createContext)(void 0);
4300
+ var AccountsContext = (0, import_react15.createContext)(void 0);
4653
4301
  function AccountsProvider({ children }) {
4654
- const [profile, setProfile] = (0, import_react16.useState)(() => {
4302
+ const [profile, setProfile] = (0, import_react15.useState)(() => {
4655
4303
  const cached = getCachedProfile();
4656
4304
  return cached || void 0;
4657
4305
  });
4658
- const [isLoadingProfile, setIsLoadingProfile] = (0, import_react16.useState)(false);
4659
- const [profileError, setProfileError] = (0, import_react16.useState)(null);
4660
- const profileRef = (0, import_react16.useRef)(profile);
4661
- const isLoadingRef = (0, import_react16.useRef)(false);
4662
- (0, import_react16.useEffect)(() => {
4306
+ const [isLoadingProfile, setIsLoadingProfile] = (0, import_react15.useState)(false);
4307
+ const [profileError, setProfileError] = (0, import_react15.useState)(null);
4308
+ const profileRef = (0, import_react15.useRef)(profile);
4309
+ const isLoadingRef = (0, import_react15.useRef)(false);
4310
+ (0, import_react15.useEffect)(() => {
4663
4311
  profileRef.current = profile;
4664
4312
  }, [profile]);
4665
- (0, import_react16.useEffect)(() => {
4313
+ (0, import_react15.useEffect)(() => {
4666
4314
  isLoadingRef.current = isLoadingProfile;
4667
4315
  }, [isLoadingProfile]);
4668
4316
  const { trigger: triggerProfileUpdate } = useCfgAccountsProfileUpdateUpdate();
@@ -4670,8 +4318,7 @@ function AccountsProvider({ children }) {
4670
4318
  const { trigger: triggerAvatar } = useCfgAccountsProfileAvatarCreate();
4671
4319
  const { trigger: triggerOtpRequest } = useCfgAccountsOtpRequestCreate();
4672
4320
  const { trigger: triggerOtpVerify } = useCfgAccountsOtpVerifyCreate();
4673
- const { trigger: triggerTokenRefresh } = useCfgAccountsTokenRefreshCreate();
4674
- const refreshProfile = (0, import_react16.useCallback)(async (options) => {
4321
+ const refreshProfile = (0, import_react15.useCallback)(async (options) => {
4675
4322
  const { callerId, force } = options || {};
4676
4323
  if (isLoadingRef.current) {
4677
4324
  authLogger.debug(`Profile loading in progress, skipping (caller: ${callerId})`);
@@ -4723,8 +4370,7 @@ function AccountsProvider({ children }) {
4723
4370
  return result;
4724
4371
  }
4725
4372
  if (result.access && result.refresh) {
4726
- CfgAccountsApi.setToken(result.access);
4727
- CfgAccountsApi.setRefreshToken(result.refresh);
4373
+ auth.setSession({ access: result.access, refresh: result.refresh });
4728
4374
  try {
4729
4375
  await refreshProfile({ callerId: "verifyOTP", force: true });
4730
4376
  } catch (profileError2) {
@@ -4733,22 +4379,19 @@ function AccountsProvider({ children }) {
4733
4379
  }
4734
4380
  return result;
4735
4381
  }, "verifyOTP");
4736
- const refreshToken = /* @__PURE__ */ __name(async (refresh) => {
4737
- const body = { refresh };
4738
- const result = await triggerTokenRefresh({ body });
4739
- if (result.access) {
4740
- CfgAccountsApi.setToken(result.access);
4741
- CfgAccountsApi.setRefreshToken(refresh);
4382
+ const logout = (0, import_react15.useCallback)(() => {
4383
+ const refresh = auth.getRefreshToken();
4384
+ if (refresh) {
4385
+ CfgAccountsAuth.cfgAccountsTokenBlacklistCreate({ body: { refresh } }).catch(() => {
4386
+ authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
4387
+ });
4742
4388
  }
4743
- return result;
4744
- }, "refreshToken");
4745
- const logout = (0, import_react16.useCallback)(() => {
4746
- CfgAccountsApi.clearToken();
4389
+ auth.clearSession();
4747
4390
  setProfile(void 0);
4748
4391
  setProfileError(null);
4749
4392
  clearProfileCache();
4750
4393
  }, []);
4751
- const value = (0, import_react16.useMemo)(() => ({
4394
+ const value = (0, import_react15.useMemo)(() => ({
4752
4395
  profile,
4753
4396
  isLoadingProfile,
4754
4397
  profileError,
@@ -4758,7 +4401,6 @@ function AccountsProvider({ children }) {
4758
4401
  refreshProfile,
4759
4402
  requestOTP,
4760
4403
  verifyOTP,
4761
- refreshToken,
4762
4404
  logout
4763
4405
  }), [
4764
4406
  profile,
@@ -4770,14 +4412,13 @@ function AccountsProvider({ children }) {
4770
4412
  refreshProfile,
4771
4413
  requestOTP,
4772
4414
  verifyOTP,
4773
- refreshToken,
4774
4415
  logout
4775
4416
  ]);
4776
4417
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccountsContext.Provider, { value, children });
4777
4418
  }
4778
4419
  __name(AccountsProvider, "AccountsProvider");
4779
4420
  function useAccountsContext() {
4780
- const context = (0, import_react16.useContext)(AccountsContext);
4421
+ const context = (0, import_react15.useContext)(AccountsContext);
4781
4422
  if (!context) {
4782
4423
  throw new Error("useAccountsContext must be used within AccountsProvider");
4783
4424
  }
@@ -4789,281 +4430,127 @@ __name(useAccountsContext, "useAccountsContext");
4789
4430
  var import_jsx_runtime2 = require("react/jsx-runtime");
4790
4431
  var defaultRoutes = {
4791
4432
  auth: "/auth",
4792
- defaultCallback: "/dashboard",
4433
+ defaultCallback: "/",
4793
4434
  defaultAuthCallback: "/auth"
4794
4435
  };
4795
- var AuthContext = (0, import_react17.createContext)(void 0);
4436
+ var resolveAuthRoutes = /* @__PURE__ */ __name((routes) => ({
4437
+ auth: routes?.auth || defaultRoutes.auth,
4438
+ defaultCallback: routes?.defaultCallback || defaultRoutes.defaultCallback,
4439
+ defaultAuthCallback: routes?.defaultAuthCallback || routes?.auth || defaultRoutes.defaultAuthCallback
4440
+ }), "resolveAuthRoutes");
4796
4441
  var EMAIL_STORAGE_KEY = "auth_email";
4797
- var hasValidTokens = /* @__PURE__ */ __name(() => {
4798
- if (typeof window === "undefined") return false;
4799
- return CfgAccountsApi.isAuthenticated();
4800
- }, "hasValidTokens");
4801
- var isSessionDeadOnBootstrap = /* @__PURE__ */ __name((accessToken, refreshToken, now = Date.now()) => {
4802
- const accessDead = isTokenExpired(accessToken, 0, now);
4803
- if (!accessDead) return false;
4804
- const refreshDead = isTokenExpired(refreshToken, 0, now);
4805
- return refreshDead;
4806
- }, "isSessionDeadOnBootstrap");
4807
- var isSessionAlive = /* @__PURE__ */ __name((now = Date.now()) => {
4808
- if (typeof window === "undefined") return false;
4809
- return !isSessionDeadOnBootstrap(
4810
- CfgAccountsApi.getToken(),
4811
- CfgAccountsApi.getRefreshToken(),
4812
- now
4813
- );
4814
- }, "isSessionAlive");
4442
+ var AuthContext = (0, import_react16.createContext)(void 0);
4443
+ var apiErrorMessage = /* @__PURE__ */ __name((error) => {
4444
+ const body = error.response;
4445
+ return body?.error || body?.detail || body?.message || error.errorMessage;
4446
+ }, "apiErrorMessage");
4815
4447
  var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4816
4448
  const accounts = useAccountsContext();
4817
- ensureRefreshHandler();
4818
- const redirectManager = useAuthRedirectManager({
4819
- fallbackUrl: config?.routes?.defaultCallback || defaultRoutes.defaultCallback,
4820
- clearOnUse: true
4821
- });
4822
- const [isLoading, setIsLoading] = (0, import_react17.useState)(() => {
4823
- if (typeof window !== "undefined") {
4824
- const hasTokens = hasValidTokens();
4825
- return !hasTokens;
4826
- }
4827
- return true;
4828
- });
4829
- const [initialized, setInitialized] = (0, import_react17.useState)(false);
4830
- const [authTick, setAuthTick] = (0, import_react17.useState)(0);
4831
- const bumpAuthTick = (0, import_react17.useCallback)(() => setAuthTick((t) => t + 1), []);
4832
- (0, import_react17.useEffect)(() => {
4833
- if (typeof window === "undefined") return;
4834
- const delay = nextAuthEvaluationDelay(
4835
- getTokenExpiry(CfgAccountsApi.getToken()),
4836
- getTokenExpiry(CfgAccountsApi.getRefreshToken()),
4837
- Date.now()
4838
- );
4839
- if (delay === null) return;
4840
- const timer = setTimeout(bumpAuthTick, Math.min(delay, 2e9));
4841
- return () => clearTimeout(timer);
4842
- }, [authTick, bumpAuthTick]);
4843
- const onUnauthorizedRef = (0, import_react17.useRef)(false);
4449
+ const session = useSession();
4450
+ const isAuthenticated = session.status === "authenticated";
4844
4451
  const router = useCfgRouter();
4845
4452
  const pathname = (0, import_navigation4.usePathname)();
4846
4453
  const queryParams = useQueryParams();
4847
- const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage(EMAIL_STORAGE_KEY, null);
4848
- const user = accounts.profile;
4849
- const userRef = (0, import_react17.useRef)(user);
4850
- const configRef = (0, import_react17.useRef)(config);
4851
- const isLoadingProfileRef = (0, import_react17.useRef)(false);
4852
- (0, import_react17.useEffect)(() => {
4853
- userRef.current = user;
4854
- }, [user]);
4855
- (0, import_react17.useEffect)(() => {
4454
+ const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage(
4455
+ EMAIL_STORAGE_KEY,
4456
+ null
4457
+ );
4458
+ const routes = (0, import_react16.useMemo)(() => resolveAuthRoutes(config?.routes), [config?.routes]);
4459
+ const routesRef = (0, import_react16.useRef)(routes);
4460
+ (0, import_react16.useEffect)(() => {
4461
+ routesRef.current = routes;
4462
+ }, [routes]);
4463
+ const redirectManager = useAuthRedirectManager({
4464
+ fallbackUrl: routes.defaultCallback,
4465
+ clearOnUse: true
4466
+ });
4467
+ const configRef = (0, import_react16.useRef)(config);
4468
+ (0, import_react16.useEffect)(() => {
4856
4469
  configRef.current = config;
4857
4470
  }, [config]);
4858
- const clearAuthState = (0, import_react17.useCallback)((caller) => {
4859
- authLogger.info("clearAuthState >> caller", caller);
4860
- CfgAccountsApi.clearToken();
4861
- clearProfileCache();
4862
- setInitialized(true);
4863
- setIsLoading(false);
4864
- bumpAuthTick();
4865
- }, [bumpAuthTick]);
4866
- const handleProactiveRefreshError = (0, import_react17.useCallback)((error) => {
4867
- authLogger.warn("Proactive token refresh failed \u2014 session is dead, redirecting to login:", error.message);
4868
- if (onUnauthorizedRef.current) return;
4869
- onUnauthorizedRef.current = true;
4870
- clearAuthState("proactiveRefresh:failed");
4871
- const authCallbackUrl = configRef.current?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
4872
- router.hardReplace(authCallbackUrl);
4873
- }, [clearAuthState, router]);
4874
- useTokenRefresh({
4875
- enabled: true,
4876
- onRefresh: /* @__PURE__ */ __name(() => {
4877
- authLogger.info("Token auto-refreshed successfully");
4878
- }, "onRefresh"),
4879
- onRefreshError: handleProactiveRefreshError
4880
- });
4881
- const handleGlobalAuthError = (0, import_react17.useCallback)((error, context = "API Request") => {
4882
- const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
4883
- if (isAuthError) {
4884
- authLogger.warn(`Authentication error in ${context}, clearing tokens`);
4885
- clearAuthState(`globalAuthError:${context}`);
4886
- return true;
4471
+ const accountsRef = (0, import_react16.useRef)(accounts);
4472
+ (0, import_react16.useEffect)(() => {
4473
+ accountsRef.current = accounts;
4474
+ }, [accounts]);
4475
+ const pendingManualNavRef = (0, import_react16.useRef)(false);
4476
+ const [profileSettled, setProfileSettled] = (0, import_react16.useState)(false);
4477
+ const hasProfile = Boolean(accounts.profile);
4478
+ const profileAttemptedRef = (0, import_react16.useRef)(false);
4479
+ (0, import_react16.useEffect)(() => {
4480
+ if (!isAuthenticated) {
4481
+ profileAttemptedRef.current = false;
4482
+ const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
4483
+ if (hasTokens && !auth.isAuthenticated()) {
4484
+ authLogger.warn("Dead session in storage \u2014 clearing");
4485
+ auth.clearSession();
4486
+ clearProfileCache();
4487
+ }
4488
+ setProfileSettled(true);
4489
+ return;
4887
4490
  }
4888
- if (error?.success === false) {
4889
- authLogger.warn(`Non-auth error in ${context} (not clearing session):`, error?.message || error);
4491
+ if (hasProfile || profileAttemptedRef.current) {
4492
+ setProfileSettled(true);
4493
+ return;
4890
4494
  }
4891
- return false;
4892
- }, [clearAuthState]);
4893
- (0, import_react17.useEffect)(() => {
4894
- const handler = /* @__PURE__ */ __name(() => {
4895
- if (onUnauthorizedRef.current) return;
4896
- onUnauthorizedRef.current = true;
4897
- authLogger.warn("Unrecoverable 401 (refresh failed) \u2014 clearing session and redirecting to login");
4898
- clearAuthState("onUnauthorized:401");
4899
- const authCallbackUrl = configRef.current?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
4900
- router.hardReplace(authCallbackUrl);
4901
- }, "handler");
4902
- CfgAccountsApi.onUnauthorized(handler);
4495
+ profileAttemptedRef.current = true;
4496
+ let cancelled = false;
4497
+ accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
4498
+ authLogger.warn("Profile load failed (session kept):", error);
4499
+ }).finally(() => {
4500
+ if (!cancelled) setProfileSettled(true);
4501
+ });
4903
4502
  return () => {
4904
- CfgAccountsApi.onUnauthorized(null);
4503
+ cancelled = true;
4905
4504
  };
4906
- }, [clearAuthState, router]);
4907
- const isAutoLoggingOutRef = (0, import_react17.useRef)(false);
4908
- const swrOnError = (0, import_react17.useCallback)((error) => {
4909
- const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
4910
- if (isAuthError && !isAutoLoggingOutRef.current) {
4911
- isAutoLoggingOutRef.current = true;
4912
- authLogger.warn("SWR 401 error detected, auto-logout");
4913
- clearAuthState("swrOnError:401");
4914
- const authCallbackUrl = configRef.current?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
4915
- router.hardReplace(authCallbackUrl);
4916
- }
4917
- }, [clearAuthState, router]);
4918
- const swrConfig = (0, import_react17.useMemo)(() => ({ onError: swrOnError }), [swrOnError]);
4919
- const loadCurrentProfile = (0, import_react17.useCallback)(async (callerId) => {
4920
- const finalCallerId = callerId || "AuthContext.loadCurrentProfile";
4921
- if (isLoadingProfileRef.current) {
4922
- authLogger.debug(`Profile loading already in progress, skipping duplicate call from: ${finalCallerId}`);
4923
- return;
4924
- }
4925
- authLogger.debug(`loadCurrentProfile called by: ${finalCallerId}`);
4926
- try {
4927
- isLoadingProfileRef.current = true;
4928
- const isAuth = CfgAccountsApi.isAuthenticated();
4929
- const token = CfgAccountsApi.getToken();
4930
- if (!isAuth) {
4931
- authLogger.warn("No valid authentication token, throwing error");
4932
- throw new Error("No valid authentication token");
4933
- }
4934
- if (accounts.profile && !accounts.isLoadingProfile) {
4935
- authLogger.debug("Profile already loaded in AccountsContext, skipping API call");
4936
- setInitialized(true);
4937
- return;
4938
- }
4939
- const refreshedProfile = await accounts.refreshProfile({ callerId: finalCallerId });
4940
- if (refreshedProfile) {
4941
- authLogger.info("Profile loaded successfully:", refreshedProfile.id);
4942
- } else {
4943
- authLogger.warn("Profile refresh returned undefined - but keeping tokens");
4944
- }
4945
- setInitialized(true);
4946
- } catch (error) {
4947
- authLogger.error("Failed to load profile:", error);
4948
- const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
4949
- if (isAuthError) {
4950
- authLogger.warn("Authentication error, clearing session");
4951
- clearAuthState("loadCurrentProfile:authError");
4952
- } else {
4953
- authLogger.warn("Profile load failed but keeping session (non-auth error)");
4954
- setInitialized(true);
4955
- }
4956
- } finally {
4957
- isLoadingProfileRef.current = false;
4505
+ }, [isAuthenticated, hasProfile]);
4506
+ const isLoading = !profileSettled;
4507
+ const expiredHandledRef = (0, import_react16.useRef)(false);
4508
+ (0, import_react16.useEffect)(() => {
4509
+ const unsubscribe = auth.onSessionExpired(() => {
4510
+ if (expiredHandledRef.current) return;
4511
+ expiredHandledRef.current = true;
4512
+ authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
4513
+ clearProfileCache();
4514
+ Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
4515
+ category: "auth" /* AUTH */
4516
+ });
4517
+ router.hardReplace(routesRef.current.defaultAuthCallback);
4518
+ });
4519
+ return unsubscribe;
4520
+ }, [router]);
4521
+ (0, import_react16.useEffect)(() => {
4522
+ if (!isAuthenticated || pendingManualNavRef.current) return;
4523
+ if (pathname === routes.auth && !queryParams.get("flow")) {
4524
+ const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
4525
+ if (saved) redirectManager.clearRedirect();
4526
+ router.push(saved || routes.defaultCallback);
4958
4527
  }
4959
- }, [clearAuthState, handleGlobalAuthError, accounts]);
4960
- (0, import_react17.useEffect)(() => {
4961
- if (initialized) return;
4962
- const initializeAuth = /* @__PURE__ */ __name(async () => {
4963
- authLogger.info("Initializing auth...");
4964
- const isInIframe = typeof window !== "undefined" && window.self !== window.top;
4965
- authLogger.info("Is in iframe:", isInIframe);
4966
- const token = CfgAccountsApi.getToken();
4967
- const refreshToken2 = CfgAccountsApi.getRefreshToken();
4968
- authLogger.info("Token from API:", token ? `${token.substring(0, 20)}...` : "null");
4969
- authLogger.info("Refresh token from API:", refreshToken2 ? `${refreshToken2.substring(0, 20)}...` : "null");
4970
- authLogger.info("localStorage keys:", Object.keys(localStorage).filter((k) => k.includes("token") || k.includes("auth")));
4971
- if (!isInIframe && isSessionDeadOnBootstrap(token, refreshToken2)) {
4972
- authLogger.warn("Bootstrap: dead session in storage (access + refresh both unusable) \u2014 clearing and showing login");
4973
- clearAuthState("initializeAuth:deadSession");
4974
- return;
4975
- }
4976
- const hasTokens = hasValidTokens();
4977
- authLogger.info("Has tokens:", hasTokens);
4978
- if (userRef.current) {
4979
- authLogger.info("Profile already loaded from AccountsContext cache, skipping API request");
4980
- setInitialized(true);
4981
- setIsLoading(false);
4982
- return;
4983
- }
4984
- const cachedProfile = getCachedProfile();
4985
- if (cachedProfile) {
4986
- authLogger.info("Profile found in localStorage cache, skipping API request");
4987
- setInitialized(true);
4988
- setIsLoading(false);
4989
- return;
4990
- }
4991
- if (isInIframe && !hasTokens) {
4992
- authLogger.info("Running in iframe without tokens - waiting for parent to send via postMessage");
4993
- authLogger.info("AdminLayout will handle auth initialization, skipping AuthContext init");
4994
- setInitialized(true);
4995
- setIsLoading(false);
4996
- return;
4997
- }
4998
- if (hasTokens) {
4999
- setIsLoading(true);
5000
- try {
5001
- authLogger.info("No cached profile found, loading from API...");
5002
- await loadCurrentProfile("AuthContext.initializeAuth");
5003
- } catch (error) {
5004
- authLogger.error("Failed to load profile during initialization:", error);
5005
- const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
5006
- if (isAuthError) {
5007
- clearAuthState("initializeAuth:authError");
5008
- } else {
5009
- authLogger.warn("Init profile load failed but keeping session");
5010
- setInitialized(true);
5011
- }
5012
- }
5013
- setIsLoading(false);
5014
- } else {
5015
- setInitialized(true);
5016
- setIsLoading(false);
5017
- }
5018
- }, "initializeAuth");
5019
- initializeAuth();
5020
- }, [initialized]);
5021
- (0, import_react17.useEffect)(() => {
5022
- if (!initialized) return;
5023
- const isAuthenticated = CfgAccountsApi.isAuthenticated();
5024
- const authRoute = config?.routes?.auth || defaultRoutes.auth;
5025
- const isAuthPage = pathname === authRoute;
5026
- const flowParam = queryParams.get("flow");
5027
- if (isAuthenticated && isAuthPage && !flowParam) {
5028
- const callbackUrl = config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
5029
- router.push(callbackUrl);
4528
+ }, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
4529
+ const loadCurrentProfile = (0, import_react16.useCallback)(async (callerId) => {
4530
+ if (!auth.isAuthenticated()) {
4531
+ throw new Error("No valid authentication token");
5030
4532
  }
5031
- }, [initialized, pathname, queryParams, config?.routes]);
5032
- const pushToDefaultCallbackUrl = (0, import_react17.useCallback)(() => {
5033
- const callbackUrl = config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
5034
- router.push(callbackUrl);
5035
- }, [config?.routes, router]);
5036
- const pushToDefaultAuthCallbackUrl = (0, import_react17.useCallback)(() => {
5037
- const authCallbackUrl = config?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
5038
- router.push(authCallbackUrl);
5039
- }, [config?.routes, router]);
5040
- const checkAuthAndRedirect = (0, import_react17.useCallback)(async () => {
5041
- try {
5042
- setIsLoading(true);
5043
- const isAuthenticated = CfgAccountsApi.isAuthenticated();
5044
- if (isAuthenticated) {
5045
- await loadCurrentProfile();
5046
- if (userRef.current) {
5047
- pushToDefaultCallbackUrl();
5048
- }
5049
- } else {
5050
- pushToDefaultAuthCallbackUrl();
5051
- }
5052
- } catch (error) {
5053
- authLogger.error("Failed to check authentication:", error);
5054
- if (!handleGlobalAuthError(error, "checkAuthAndRedirect")) {
5055
- clearAuthState("checkAuthAndRedirect");
4533
+ await accountsRef.current.refreshProfile({
4534
+ callerId: callerId || "AuthContext.loadCurrentProfile"
4535
+ });
4536
+ }, []);
4537
+ const checkAuthAndRedirect = (0, import_react16.useCallback)(async () => {
4538
+ if (auth.isAuthenticated()) {
4539
+ try {
4540
+ await loadCurrentProfile("checkAuthAndRedirect");
4541
+ } catch (error) {
4542
+ authLogger.warn("checkAuthAndRedirect: profile load failed", error);
5056
4543
  }
5057
- pushToDefaultAuthCallbackUrl();
5058
- } finally {
5059
- setIsLoading(false);
4544
+ router.push(routesRef.current.defaultCallback);
4545
+ } else {
4546
+ router.push(routesRef.current.defaultAuthCallback);
5060
4547
  }
5061
- }, [loadCurrentProfile, clearAuthState, pushToDefaultCallbackUrl, pushToDefaultAuthCallbackUrl, handleGlobalAuthError]);
5062
- const requestOTP = (0, import_react17.useCallback)(
4548
+ }, [loadCurrentProfile, router]);
4549
+ const requestOTP = (0, import_react16.useCallback)(
5063
4550
  async (identifier, sourceUrl) => {
5064
- CfgAccountsApi.clearToken();
4551
+ auth.clearSession();
5065
4552
  try {
5066
- const result = await accounts.requestOTP({
4553
+ const result = await accountsRef.current.requestOTP({
5067
4554
  identifier,
5068
4555
  source_url: sourceUrl
5069
4556
  });
@@ -5073,33 +4560,31 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5073
4560
  });
5074
4561
  return {
5075
4562
  success: true,
5076
- message: result.message || `OTP code sent to your email address`,
4563
+ message: result.message || "OTP code sent to your email address",
5077
4564
  webmail: result.webmail ?? null
5078
4565
  };
5079
4566
  } catch (error) {
5080
4567
  authLogger.error("Request OTP error:", error);
5081
4568
  if (error instanceof APIError) {
5082
- const retryAfter = error.response?.retry_after ?? error.response?.retryAfter;
5083
- const message = error.response?.error || error.response?.detail || error.response?.message || error.errorMessage;
4569
+ const body = error.response;
4570
+ const retryAfter = body?.retry_after ?? body?.retryAfter;
5084
4571
  return {
5085
4572
  success: false,
5086
4573
  statusCode: error.statusCode,
5087
- message,
4574
+ message: apiErrorMessage(error),
5088
4575
  retryAfter: typeof retryAfter === "number" ? retryAfter : void 0
5089
4576
  };
5090
4577
  }
5091
- return {
5092
- success: false,
5093
- message: "Failed to send OTP"
5094
- };
4578
+ return { success: false, message: "Failed to send OTP" };
5095
4579
  }
5096
4580
  },
5097
- [accounts]
4581
+ []
5098
4582
  );
5099
- const verifyOTP = (0, import_react17.useCallback)(
4583
+ const verifyOTP = (0, import_react16.useCallback)(
5100
4584
  async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
5101
4585
  try {
5102
- const result = await accounts.verifyOTP({
4586
+ pendingManualNavRef.current = Boolean(skipRedirect);
4587
+ const result = await accountsRef.current.verifyOTP({
5103
4588
  identifier,
5104
4589
  otp: otpCode,
5105
4590
  source_url: sourceUrl
@@ -5116,15 +4601,11 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5116
4601
  }
5117
4602
  if (!result.access || !result.refresh) {
5118
4603
  authLogger.error("Verify OTP returned invalid response:", result);
5119
- return {
5120
- success: false,
5121
- message: "Invalid OTP verification response"
5122
- };
4604
+ return { success: false, message: "Invalid OTP verification response" };
5123
4605
  }
5124
4606
  if (identifier.includes("@")) {
5125
4607
  setStoredEmail(identifier);
5126
4608
  }
5127
- await new Promise((resolve) => setTimeout(resolve, 200));
5128
4609
  Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
5129
4610
  category: "auth" /* AUTH */,
5130
4611
  label: "email"
@@ -5133,8 +4614,9 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5133
4614
  Analytics.setUser(String(result.user.id));
5134
4615
  }
5135
4616
  if (!skipRedirect) {
5136
- const savedRedirect = redirectManager.useAndClearRedirect();
5137
- const finalRedirectUrl = redirectUrl || savedRedirect || config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
4617
+ const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
4618
+ if (saved) redirectManager.clearRedirect();
4619
+ const finalRedirectUrl = saved || redirectUrl || routesRef.current.defaultCallback;
5138
4620
  authLogger.info("Redirecting after auth to:", finalRedirectUrl);
5139
4621
  router.hardPush(finalRedirectUrl);
5140
4622
  }
@@ -5151,96 +4633,62 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5151
4633
  label: "email"
5152
4634
  });
5153
4635
  if (error instanceof APIError) {
5154
- const message = error.response?.error || error.response?.detail || error.response?.message || error.errorMessage;
5155
- return { success: false, message };
4636
+ return { success: false, message: apiErrorMessage(error) };
5156
4637
  }
5157
- return {
5158
- success: false,
5159
- message: "Failed to verify OTP"
5160
- };
4638
+ return { success: false, message: "Failed to verify OTP" };
5161
4639
  }
5162
4640
  },
5163
- [setStoredEmail, config?.routes?.defaultCallback, accounts, router]
4641
+ [setStoredEmail, redirectManager, router]
5164
4642
  );
5165
- const refreshToken = (0, import_react17.useCallback)(async () => {
5166
- try {
5167
- const refreshTokenValue = CfgAccountsApi.getRefreshToken();
5168
- if (!refreshTokenValue) {
5169
- clearAuthState("refreshToken:noToken");
5170
- Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
5171
- category: "auth" /* AUTH */
5172
- });
5173
- return {
5174
- success: false,
5175
- message: "No refresh token available"
5176
- };
5177
- }
5178
- await accounts.refreshToken(refreshTokenValue);
4643
+ const refreshToken = (0, import_react16.useCallback)(async () => {
4644
+ const access = await auth.refreshNow();
4645
+ if (access) {
5179
4646
  Analytics.event("auth_token_refresh" /* AUTH_TOKEN_REFRESH */, {
5180
4647
  category: "auth" /* AUTH */
5181
4648
  });
5182
- return {
5183
- success: true,
5184
- message: "Token refreshed"
5185
- };
5186
- } catch (error) {
5187
- authLogger.error("Refresh token error:", error);
5188
- clearAuthState("refreshToken:error");
5189
- Analytics.event("auth_token_refresh_fail" /* AUTH_TOKEN_REFRESH_FAIL */, {
5190
- category: "auth" /* AUTH */
5191
- });
5192
- return {
5193
- success: false,
5194
- message: "Error refreshing token"
5195
- };
4649
+ return { success: true, message: "Token refreshed" };
5196
4650
  }
5197
- }, [clearAuthState, accounts]);
5198
- const logout = (0, import_react17.useCallback)(() => {
4651
+ Analytics.event("auth_token_refresh_fail" /* AUTH_TOKEN_REFRESH_FAIL */, {
4652
+ category: "auth" /* AUTH */
4653
+ });
4654
+ return { success: false, message: "Error refreshing token" };
4655
+ }, []);
4656
+ const logout = (0, import_react16.useCallback)(() => {
5199
4657
  Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
5200
4658
  category: "auth" /* AUTH */
5201
4659
  });
5202
- accounts.logout();
5203
- setInitialized(true);
5204
- setIsLoading(false);
5205
- const authCallbackUrl = config?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
5206
- router.hardReplace(authCallbackUrl);
5207
- }, [accounts, config?.routes?.defaultAuthCallback, router]);
5208
- const isAdminUser = (0, import_react17.useMemo)(() => {
5209
- return Boolean(user?.is_staff || user?.is_superuser);
5210
- }, [user]);
5211
- (0, import_react17.useEffect)(() => {
4660
+ accountsRef.current.logout();
4661
+ router.hardReplace(routesRef.current.defaultAuthCallback);
4662
+ }, [router]);
4663
+ const user = accounts.profile ?? null;
4664
+ const isAdminUser = Boolean(user?.is_staff || user?.is_superuser);
4665
+ (0, import_react16.useEffect)(() => {
5212
4666
  applyRoleLogPolicy({ isAdmin: isAdminUser });
5213
4667
  }, [isAdminUser]);
5214
- const updateProfile = (0, import_react17.useCallback)(
4668
+ const updateProfile = (0, import_react16.useCallback)(
5215
4669
  async (data) => {
5216
- const result = await accounts.partialUpdateProfile(data);
5217
- return result;
5218
- },
5219
- [accounts]
5220
- );
5221
- const uploadAvatar = (0, import_react17.useCallback)(
5222
- async (avatar) => {
5223
- const result = await accounts.uploadAvatar(avatar);
4670
+ const result = await accountsRef.current.partialUpdateProfile(data);
5224
4671
  return result;
5225
4672
  },
5226
- [accounts]
4673
+ []
5227
4674
  );
5228
- const value = (0, import_react17.useMemo)(
4675
+ const uploadAvatar = (0, import_react16.useCallback)(async (avatar) => {
4676
+ const result = await accountsRef.current.uploadAvatar(avatar);
4677
+ return result;
4678
+ }, []);
4679
+ const value = (0, import_react16.useMemo)(
5229
4680
  () => ({
5230
4681
  user,
5231
4682
  isLoading,
5232
- // Authenticated iff the session is locally alive (access valid OR a usable
5233
- // refresh token) — validated by `exp`, NOT by mere token presence. This is
5234
- // what makes the guard self-sufficient: an expired session reads as
5235
- // unauthenticated WITHOUT waiting for a server 401 (which CSP / a hung
5236
- // request / offline can swallow). `authTick` (in deps) forces recompute
5237
- // after tokens are cleared/set, since token state lives in storage.
5238
- isAuthenticated: isSessionAlive(),
4683
+ // Reactive: flips via the store snapshot (token writes, refreshes,
4684
+ // expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
4685
+ isAuthenticated,
5239
4686
  isAdminUser,
4687
+ routes,
5240
4688
  loadCurrentProfile,
5241
4689
  checkAuthAndRedirect,
5242
- getToken: /* @__PURE__ */ __name(() => CfgAccountsApi.getToken(), "getToken"),
5243
- getRefreshToken: /* @__PURE__ */ __name(() => CfgAccountsApi.getRefreshToken(), "getRefreshToken"),
4690
+ getToken: /* @__PURE__ */ __name(() => auth.getToken(), "getToken"),
4691
+ getRefreshToken: /* @__PURE__ */ __name(() => auth.getRefreshToken(), "getRefreshToken"),
5244
4692
  getSavedEmail: /* @__PURE__ */ __name(() => storedEmail, "getSavedEmail"),
5245
4693
  saveEmail: setStoredEmail,
5246
4694
  clearSavedEmail: clearStoredEmail,
@@ -5248,21 +4696,19 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5248
4696
  verifyOTP,
5249
4697
  refreshToken,
5250
4698
  logout,
5251
- // Redirect URL methods
5252
4699
  saveRedirectUrl: redirectManager.setRedirect,
5253
4700
  getRedirectUrl: redirectManager.getFinalRedirectUrl,
5254
4701
  clearRedirectUrl: redirectManager.clearRedirect,
5255
4702
  hasRedirectUrl: redirectManager.hasRedirect,
5256
- // Profile management
5257
4703
  updateProfile,
5258
4704
  uploadAvatar
5259
4705
  }),
5260
4706
  [
5261
4707
  user,
5262
4708
  isLoading,
5263
- authTick,
5264
- // recompute isAuthenticated when tokens are cleared/set
4709
+ isAuthenticated,
5265
4710
  isAdminUser,
4711
+ routes,
5266
4712
  loadCurrentProfile,
5267
4713
  checkAuthAndRedirect,
5268
4714
  storedEmail,
@@ -5277,19 +4723,20 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5277
4723
  uploadAvatar
5278
4724
  ]
5279
4725
  );
5280
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_swr5.SWRConfig, { value: swrConfig, children }) });
4726
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthContext.Provider, { value, children });
5281
4727
  }, "AuthProviderInternal");
5282
4728
  function AuthProviderRaw({ children, config, enabled = true }) {
5283
4729
  if (!enabled) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
5284
4730
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AccountsProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthProviderInternal, { config, children }) });
5285
4731
  }
5286
4732
  __name(AuthProviderRaw, "AuthProviderRaw");
5287
- var AuthProvider = (0, import_react17.memo)(AuthProviderRaw);
4733
+ var AuthProvider = (0, import_react16.memo)(AuthProviderRaw);
5288
4734
  var defaultAuthState = {
5289
4735
  user: null,
5290
4736
  isLoading: false,
5291
4737
  isAuthenticated: false,
5292
4738
  isAdminUser: false,
4739
+ routes: resolveAuthRoutes(),
5293
4740
  loadCurrentProfile: /* @__PURE__ */ __name(async () => {
5294
4741
  authLogger.warn("useAuth: loadCurrentProfile called outside AuthProvider");
5295
4742
  }, "loadCurrentProfile"),
@@ -5316,12 +4763,12 @@ var defaultAuthState = {
5316
4763
  authLogger.warn("useAuth: refreshToken called outside AuthProvider");
5317
4764
  return { success: false, message: "AuthProvider not available" };
5318
4765
  }, "refreshToken"),
5319
- logout: /* @__PURE__ */ __name(async (_options) => {
4766
+ logout: /* @__PURE__ */ __name(() => {
5320
4767
  authLogger.warn("useAuth: logout called outside AuthProvider");
5321
4768
  }, "logout"),
5322
4769
  saveRedirectUrl: /* @__PURE__ */ __name(() => {
5323
4770
  }, "saveRedirectUrl"),
5324
- getRedirectUrl: /* @__PURE__ */ __name(() => null, "getRedirectUrl"),
4771
+ getRedirectUrl: /* @__PURE__ */ __name(() => "", "getRedirectUrl"),
5325
4772
  clearRedirectUrl: /* @__PURE__ */ __name(() => {
5326
4773
  }, "clearRedirectUrl"),
5327
4774
  hasRedirectUrl: /* @__PURE__ */ __name(() => false, "hasRedirectUrl"),
@@ -5335,9 +4782,9 @@ var defaultAuthState = {
5335
4782
  }, "uploadAvatar")
5336
4783
  };
5337
4784
  var useAuth = /* @__PURE__ */ __name(() => {
5338
- const context = (0, import_react17.useContext)(AuthContext);
4785
+ const context = (0, import_react16.useContext)(AuthContext);
5339
4786
  if (context === void 0) {
5340
- if (isBrowser && isDev) {
4787
+ if (isBrowser2 && isDev) {
5341
4788
  authLogger.debug("useAuth called outside AuthProvider, returning default state");
5342
4789
  }
5343
4790
  return defaultAuthState;
@@ -5368,4 +4815,20 @@ var formatAuthError = /* @__PURE__ */ __name((error) => {
5368
4815
  }
5369
4816
  return "An unexpected error occurred";
5370
4817
  }, "formatAuthError");
4818
+
4819
+ // src/auth/utils/guard.ts
4820
+ function resolveGuardIsLoading(s) {
4821
+ if (!s.mounted) return true;
4822
+ if (!s.requireAuth) return false;
4823
+ return s.authLoading || s.isRedirecting || !s.isAuthenticated;
4824
+ }
4825
+ __name(resolveGuardIsLoading, "resolveGuardIsLoading");
4826
+ function shouldRedirectToAuth(s) {
4827
+ return s.mounted && s.requireAuth && !s.authLoading && !s.isAuthenticated && !s.isRedirecting;
4828
+ }
4829
+ __name(shouldRedirectToAuth, "shouldRedirectToAuth");
4830
+ function resolveGuardIsAuthenticated(s) {
4831
+ return !s.requireAuth || s.isAuthenticated;
4832
+ }
4833
+ __name(resolveGuardIsAuthenticated, "resolveGuardIsAuthenticated");
5371
4834
  //# sourceMappingURL=auth.cjs.map