@djangocfg/api 2.1.619 → 2.1.621

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.cjs CHANGED
@@ -216,17 +216,21 @@ __export(auth_exports, {
216
216
  AuthProvider: () => AuthProvider,
217
217
  OAUTH_PROVIDER_IDS: () => OAUTH_PROVIDER_IDS,
218
218
  authLogger: () => authLogger,
219
+ authTrace: () => authTrace,
219
220
  beginTwoFactorChallenge: () => beginTwoFactorChallenge,
221
+ clearAuthTrace: () => clearAuthTrace,
220
222
  clearOAuthAttempt: () => clearOAuthAttempt,
221
223
  clearProfileCache: () => clearProfileCache,
222
224
  consumeSavedRedirect: () => consumeSavedRedirect,
223
225
  decodeBase64: () => decodeBase64,
226
+ dumpAuthTrace: () => dumpAuthTrace,
224
227
  encodeBase64: () => encodeBase64,
225
228
  endTwoFactorChallenge: () => endTwoFactorChallenge,
226
229
  formatAuthError: () => formatAuthError,
227
230
  getCacheMetadata: () => getCacheMetadata,
228
231
  getCachedProfile: () => getCachedProfile,
229
232
  hasValidCache: () => hasValidCache,
233
+ installAuthTrace: () => installAuthTrace,
230
234
  isAllowedAuthPath: () => isAllowedAuthPath,
231
235
  isForbidden: () => isForbidden,
232
236
  isOAuthProviderId: () => isOAuthProviderId,
@@ -234,8 +238,10 @@ __export(auth_exports, {
234
238
  logger: () => logger,
235
239
  normalizePath: () => normalizePath,
236
240
  peekSavedRedirect: () => peekSavedRedirect,
241
+ readAuthTrace: () => readAuthTrace,
237
242
  readOAuthFailure: () => readOAuthFailure,
238
243
  resolveAuthError: () => resolveAuthError,
244
+ resolveAuthStatus: () => resolveAuthStatus,
239
245
  resolveGuardIsAuthenticated: () => resolveGuardIsAuthenticated,
240
246
  resolveGuardIsLoading: () => resolveGuardIsLoading,
241
247
  setAnalyticsSink: () => setAnalyticsSink,
@@ -281,6 +287,141 @@ var AUTH_CONSTANTS = {
281
287
  RESEND_COOLDOWN_SECONDS: 30
282
288
  };
283
289
 
290
+ // src/auth/trace.ts
291
+ var KEY = "cfg.auth.trace";
292
+ var MAX = 200;
293
+ var MAX_STRING = 120;
294
+ var SENSITIVE = /token|otp|code|email|password|secret|cookie|authorization|key|identifier/i;
295
+ var TAB = Math.random().toString(36).slice(2, 6);
296
+ var hasWindow = typeof window !== "undefined";
297
+ var memory = [];
298
+ var loaded = false;
299
+ function backend() {
300
+ if (!hasWindow) return null;
301
+ for (const name of ["localStorage", "sessionStorage"]) {
302
+ try {
303
+ const s = window[name];
304
+ s.setItem("__cfg_probe", "1");
305
+ s.removeItem("__cfg_probe");
306
+ return s;
307
+ } catch {
308
+ }
309
+ }
310
+ return null;
311
+ }
312
+ __name(backend, "backend");
313
+ function load() {
314
+ if (loaded) return memory;
315
+ loaded = true;
316
+ try {
317
+ const raw = backend()?.getItem(KEY);
318
+ const parsed = raw ? JSON.parse(raw) : [];
319
+ if (Array.isArray(parsed)) memory = parsed.filter((e) => typeof e === "object" && e !== null);
320
+ } catch {
321
+ memory = [];
322
+ }
323
+ return memory;
324
+ }
325
+ __name(load, "load");
326
+ function persist() {
327
+ try {
328
+ backend()?.setItem(KEY, JSON.stringify(memory));
329
+ } catch {
330
+ }
331
+ }
332
+ __name(persist, "persist");
333
+ function clean(fields) {
334
+ if (!fields) return void 0;
335
+ const out = {};
336
+ for (const [k2, v] of Object.entries(fields)) {
337
+ if (SENSITIVE.test(k2)) continue;
338
+ if (typeof v === "string") out[k2] = v.length > MAX_STRING ? `${v.slice(0, MAX_STRING)}\u2026` : v;
339
+ else if (typeof v === "number" || typeof v === "boolean" || v === null) out[k2] = v;
340
+ }
341
+ return Object.keys(out).length ? out : void 0;
342
+ }
343
+ __name(clean, "clean");
344
+ function record(at, ev, fields) {
345
+ const entries = load();
346
+ const p = hasWindow && typeof performance !== "undefined" ? Math.round(performance.now()) : 0;
347
+ entries.push({ t: at, p, tab: TAB, ev, f: clean(fields) });
348
+ if (entries.length > MAX) entries.splice(0, entries.length - MAX);
349
+ persist();
350
+ }
351
+ __name(record, "record");
352
+ function authTrace(ev, fields) {
353
+ if (!hasWindow) return;
354
+ try {
355
+ record(Date.now(), ev, fields);
356
+ } catch {
357
+ }
358
+ }
359
+ __name(authTrace, "authTrace");
360
+ function readAuthTrace() {
361
+ return load().slice();
362
+ }
363
+ __name(readAuthTrace, "readAuthTrace");
364
+ function clearAuthTrace() {
365
+ memory = [];
366
+ loaded = true;
367
+ try {
368
+ backend()?.removeItem(KEY);
369
+ } catch {
370
+ }
371
+ }
372
+ __name(clearAuthTrace, "clearAuthTrace");
373
+ function context() {
374
+ if (!hasWindow) return [];
375
+ const lines = [`url: ${window.location.href.split("?")[0]}`, `online: ${navigator.onLine}`, `ua: ${navigator.userAgent}`];
376
+ try {
377
+ const auth2 = globalThis.__cfgAuthNow;
378
+ const now = auth2?.();
379
+ if (now) lines.push(`now: ${Object.entries(now).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}`);
380
+ } catch {
381
+ }
382
+ return lines;
383
+ }
384
+ __name(context, "context");
385
+ function dumpAuthTrace() {
386
+ const entries = load();
387
+ const first = entries[0]?.t ?? Date.now();
388
+ const rows = entries.map((e) => {
389
+ const fields = e.f ? ` ${Object.entries(e.f).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}` : "";
390
+ const at = new Date(e.t).toISOString().slice(11, 23);
391
+ return `${at} +${String(e.t - first).padStart(6)}ms [${e.tab} p${e.p}] ${e.ev}${fields}`;
392
+ });
393
+ return [`# cfg auth trace, ${entries.length} events`, ...context(), "", ...rows].join("\n");
394
+ }
395
+ __name(dumpAuthTrace, "dumpAuthTrace");
396
+ function installAuthTrace() {
397
+ if (!hasWindow) return;
398
+ const g = globalThis;
399
+ g.__cfgAuthTrace = authTrace;
400
+ g.__cfgAuth = {
401
+ trace: readAuthTrace,
402
+ dump: /* @__PURE__ */ __name(() => {
403
+ const text = dumpAuthTrace();
404
+ console.log(text);
405
+ return text;
406
+ }, "dump"),
407
+ clear: clearAuthTrace
408
+ };
409
+ const queued = g.__cfgAuthTraceQ;
410
+ if (Array.isArray(queued)) {
411
+ g.__cfgAuthTraceQ = [];
412
+ for (const item of queued) {
413
+ if (Array.isArray(item) && typeof item[1] === "string") {
414
+ try {
415
+ record(Number(item[0]) || Date.now(), item[1], item[2]);
416
+ } catch {
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ __name(installAuthTrace, "installAuthTrace");
423
+ installAuthTrace();
424
+
284
425
  // src/auth/context/AuthContext.tsx
285
426
  var import_navigation4 = require("next/navigation");
286
427
  var import_react20 = require("react");
@@ -388,10 +529,10 @@ function normalizeSegment(raw) {
388
529
  return raw.trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
389
530
  }
390
531
  __name(normalizeSegment, "normalizeSegment");
391
- var hasWindow = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
532
+ var hasWindow2 = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
392
533
  var domainByHost = /* @__PURE__ */ new Map();
393
534
  function detectCookieDomain() {
394
- if (!hasWindow() || typeof document === "undefined") return null;
535
+ if (!hasWindow2() || typeof document === "undefined") return null;
395
536
  const host = window.location.hostname.toLowerCase();
396
537
  if (!host || !host.includes(".") || host.includes(":") || /^[0-9.]+$/.test(host)) return null;
397
538
  const cached = domainByHost.get(host);
@@ -415,7 +556,7 @@ function detectCookieDomain() {
415
556
  }
416
557
  __name(detectCookieDomain, "detectCookieDomain");
417
558
  function nativeStorage(store) {
418
- if (!hasWindow()) return null;
559
+ if (!hasWindow2()) return null;
419
560
  try {
420
561
  return store === "session" ? window.sessionStorage : window.localStorage;
421
562
  } catch {
@@ -424,7 +565,7 @@ function nativeStorage(store) {
424
565
  }
425
566
  __name(nativeStorage, "nativeStorage");
426
567
  function cookieNames() {
427
- if (!hasWindow()) return [];
568
+ if (!hasWindow2()) return [];
428
569
  try {
429
570
  return document.cookie.split("; ").map((part) => decodeURIComponent(part.split("=")[0] ?? "")).filter(Boolean);
430
571
  } catch {
@@ -449,7 +590,7 @@ function createStorage(owner, options = {}) {
449
590
  const families = Object.values(options.families ?? {});
450
591
  const specByKey = /* @__PURE__ */ new Map();
451
592
  for (const spec of specs) specByKey.set(spec.key, [...specByKey.get(spec.key) ?? [], spec]);
452
- const memory = {
593
+ const memory2 = {
453
594
  local: /* @__PURE__ */ new Map(),
454
595
  session: /* @__PURE__ */ new Map(),
455
596
  cookie: /* @__PURE__ */ new Map()
@@ -463,7 +604,7 @@ function createStorage(owner, options = {}) {
463
604
  if (!key.startsWith(prefix)) throw new TypeError(`storage key "${key}" is not owned by "${owner}"`);
464
605
  }, "assertOwned");
465
606
  function rawGet(store, key) {
466
- if (!hasWindow()) return null;
607
+ if (!hasWindow2()) return null;
467
608
  if (store === "cookie") {
468
609
  try {
469
610
  const escaped = encodeURIComponent(key).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -471,18 +612,18 @@ function createStorage(owner, options = {}) {
471
612
  if (match?.[1] !== void 0) return decodeURIComponent(match[1]);
472
613
  } catch {
473
614
  }
474
- return memory.cookie.get(key) ?? null;
615
+ return memory2.cookie.get(key) ?? null;
475
616
  }
476
617
  try {
477
618
  const value = nativeStorage(store)?.getItem(key) ?? null;
478
619
  if (value !== null) return value;
479
620
  } catch {
480
621
  }
481
- return memory[store].get(key) ?? null;
622
+ return memory2[store].get(key) ?? null;
482
623
  }
483
624
  __name(rawGet, "rawGet");
484
625
  function rawSet(store, key, value, maxAgeSeconds) {
485
- if (!hasWindow()) return false;
626
+ if (!hasWindow2()) return false;
486
627
  try {
487
628
  if (store === "cookie") {
488
629
  const secure = window.location.protocol === "https:" ? "; Secure" : "";
@@ -490,24 +631,24 @@ function createStorage(owner, options = {}) {
490
631
  const domainPart = domain ? `; Domain=${domain}` : "";
491
632
  document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; Path=/; Max-Age=${Math.max(1, Math.floor(maxAgeSeconds ?? cookieMaxAge))}; SameSite=Lax${domainPart}${secure}`;
492
633
  if (domain) expireCookie(key, null);
493
- if (rawGet("cookie", key) === value && !memory.cookie.has(key)) return true;
494
- memory.cookie.set(key, value);
634
+ if (rawGet("cookie", key) === value && !memory2.cookie.has(key)) return true;
635
+ memory2.cookie.set(key, value);
495
636
  return false;
496
637
  }
497
638
  const native = nativeStorage(store);
498
639
  if (!native) throw new Error("storage unavailable");
499
640
  native.setItem(key, value);
500
- memory[store].delete(key);
641
+ memory2[store].delete(key);
501
642
  return true;
502
643
  } catch {
503
- memory[store].set(key, value);
644
+ memory2[store].set(key, value);
504
645
  return false;
505
646
  }
506
647
  }
507
648
  __name(rawSet, "rawSet");
508
649
  function rawRemove(store, key) {
509
- memory[store].delete(key);
510
- if (!hasWindow()) return;
650
+ memory2[store].delete(key);
651
+ if (!hasWindow2()) return;
511
652
  try {
512
653
  if (store === "cookie") {
513
654
  expireCookie(key, null);
@@ -536,7 +677,7 @@ function createStorage(owner, options = {}) {
536
677
  }
537
678
  } catch {
538
679
  }
539
- for (const name of memory[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
680
+ for (const name of memory2[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
540
681
  return out;
541
682
  }
542
683
  __name(listKeys, "listKeys");
@@ -608,7 +749,7 @@ function createStorage(owner, options = {}) {
608
749
  cookieDomain: resolveCookieDomain,
609
750
  onChange(key, listener, store = "local") {
610
751
  assertValid(key);
611
- if (!hasWindow()) return () => {
752
+ if (!hasWindow2()) return () => {
612
753
  };
613
754
  const handler = /* @__PURE__ */ __name((event) => {
614
755
  if (event.storageArea !== nativeStorage(store)) return;
@@ -632,6 +773,29 @@ var REFRESH_KEY = "cfg.auth.refresh_token";
632
773
  var API_KEY_KEY = "cfg.auth.api_key";
633
774
  var storage = true ? cfgStorage : createStorage2("cfg");
634
775
  var isBrowser = typeof window !== "undefined";
776
+ function trace(ev, fields) {
777
+ try {
778
+ const g = globalThis;
779
+ const sink2 = g.__cfgAuthTrace;
780
+ if (sink2) {
781
+ sink2(ev, fields);
782
+ return;
783
+ }
784
+ const queue = g.__cfgAuthTraceQ ?? [];
785
+ g.__cfgAuthTraceQ = queue;
786
+ if (queue.length < 100) queue.push([Date.now(), ev, fields]);
787
+ } catch {
788
+ }
789
+ }
790
+ __name(trace, "trace");
791
+ function pathOf(url) {
792
+ try {
793
+ return new URL(url, "http://x").pathname;
794
+ } catch {
795
+ return "";
796
+ }
797
+ }
798
+ __name(pathOf, "pathOf");
635
799
  function secondsUntilExp(token) {
636
800
  const exp = jwtExpMs(token);
637
801
  return exp === null ? void 0 : Math.max(1, Math.floor((exp - Date.now()) / 1e3));
@@ -791,7 +955,10 @@ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
791
955
  function notifySessionChanged() {
792
956
  const next = computeSnapshot();
793
957
  const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
794
- if (changed) _snapshot = next;
958
+ if (changed) {
959
+ _snapshot = next;
960
+ trace("store.snapshot", { status: next.status });
961
+ }
795
962
  scheduleExpiryFlip();
796
963
  if (!changed) return;
797
964
  for (const listener of Array.from(_sessionListeners)) {
@@ -874,6 +1041,7 @@ var auth = {
874
1041
  return _storage.get(ACCESS_KEY);
875
1042
  },
876
1043
  setToken(token) {
1044
+ trace("store.setToken", { present: token !== null });
877
1045
  _storage.set(ACCESS_KEY, token);
878
1046
  notifySessionChanged();
879
1047
  },
@@ -881,14 +1049,16 @@ var auth = {
881
1049
  return _storage.get(REFRESH_KEY);
882
1050
  },
883
1051
  setRefreshToken(token) {
1052
+ trace("store.setRefreshToken", { present: token !== null });
884
1053
  _storage.set(REFRESH_KEY, token);
885
1054
  notifySessionChanged();
886
1055
  },
887
1056
  clearTokens() {
1057
+ trace("store.clearTokens");
888
1058
  for (const mode of ["localStorage", "sessionStorage", "cookie"]) {
889
- const backend = backendFor(mode);
890
- backend.set(ACCESS_KEY, null);
891
- backend.set(REFRESH_KEY, null);
1059
+ const backend2 = backendFor(mode);
1060
+ backend2.set(ACCESS_KEY, null);
1061
+ backend2.set(REFRESH_KEY, null);
892
1062
  }
893
1063
  storage.clearAll();
894
1064
  notifySessionChanged();
@@ -907,6 +1077,7 @@ var auth = {
907
1077
  * rotation); `refresh: null` explicitly drops it.
908
1078
  */
909
1079
  setSession(tokens) {
1080
+ trace("store.setSession", { refresh: tokens.refresh === void 0 ? "kept" : tokens.refresh === null ? "dropped" : "set" });
910
1081
  _storage.set(ACCESS_KEY, tokens.access);
911
1082
  if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
912
1083
  notifySessionChanged();
@@ -1045,7 +1216,11 @@ async function tryRefresh() {
1045
1216
  const refresh = auth.getRefreshToken();
1046
1217
  if (!refresh) return null;
1047
1218
  const result = await _refreshHandler(refresh);
1048
- if (!result?.access) return null;
1219
+ if (!result?.access) {
1220
+ trace("refresh", { ok: false, why: "no-access" });
1221
+ return null;
1222
+ }
1223
+ trace("refresh", { ok: true, rotated: Boolean(result.refresh) });
1049
1224
  auth.setToken(result.access);
1050
1225
  if (result.refresh) auth.setRefreshToken(result.refresh);
1051
1226
  return result.access;
@@ -1058,6 +1233,7 @@ async function tryRefresh() {
1058
1233
  }
1059
1234
  return await runRefresh();
1060
1235
  } catch {
1236
+ trace("refresh", { ok: false, why: "threw" });
1061
1237
  return null;
1062
1238
  } finally {
1063
1239
  _refreshInflight = null;
@@ -1066,7 +1242,8 @@ async function tryRefresh() {
1066
1242
  return _refreshInflight;
1067
1243
  }
1068
1244
  __name(tryRefresh, "tryRefresh");
1069
- function expireSession(response) {
1245
+ function expireSession(response, why) {
1246
+ trace("store.expire", { why, status: response.status, path: pathOf(response.url), handlers: _sessionExpiredHandlers.size });
1070
1247
  auth.clearTokens();
1071
1248
  for (const cb of Array.from(_sessionExpiredHandlers)) {
1072
1249
  try {
@@ -1209,15 +1386,16 @@ function installAuthOnClient(client2) {
1209
1386
  client2.interceptors.response.use(async (response, request) => {
1210
1387
  if (response.status !== 401) return response;
1211
1388
  if (/\/accounts\/(otp|oauth|login|register|token)\b/.test(request.url)) {
1389
+ trace("401.passthrough", { path: pathOf(request.url) });
1212
1390
  return response;
1213
1391
  }
1214
1392
  if (request.headers.get(RETRY_MARKER)) {
1215
- expireSession(response);
1393
+ expireSession(response, "retry-marker");
1216
1394
  return response;
1217
1395
  }
1218
1396
  const newToken = await tryRefresh();
1219
1397
  if (!newToken) {
1220
- expireSession(response);
1398
+ expireSession(response, "refresh-failed");
1221
1399
  return response;
1222
1400
  }
1223
1401
  const retry = request.clone();
@@ -1229,7 +1407,7 @@ function installAuthOnClient(client2) {
1229
1407
  }
1230
1408
  try {
1231
1409
  const retried = await fetch(retry);
1232
- if (retried.status === 401) expireSession(retried);
1410
+ if (retried.status === 401) expireSession(retried, "retry-401");
1233
1411
  return retried;
1234
1412
  } catch {
1235
1413
  return response;
@@ -1453,6 +1631,16 @@ __name(useTimezone, "useTimezone");
1453
1631
  // src/auth/hooks/useCfgRouter.ts
1454
1632
  var import_navigation = require("next/navigation");
1455
1633
  var import_react3 = require("react");
1634
+ var hardNavigating = false;
1635
+ if (typeof window !== "undefined") {
1636
+ window.addEventListener("pageshow", (e) => {
1637
+ if (e.persisted) hardNavigating = false;
1638
+ });
1639
+ }
1640
+ function fromPath() {
1641
+ return typeof window === "undefined" ? "" : window.location.pathname;
1642
+ }
1643
+ __name(fromPath, "fromPath");
1456
1644
  function getBasePath() {
1457
1645
  if (typeof process === "undefined") {
1458
1646
  return "";
@@ -1473,6 +1661,7 @@ function useCfgRouter() {
1473
1661
  const router = (0, import_navigation.useRouter)();
1474
1662
  const basePath = (0, import_react3.useMemo)(() => getBasePath(), []);
1475
1663
  const push = (0, import_react3.useCallback)((href, options) => {
1664
+ authTrace("nav.push", { from: fromPath(), to: href });
1476
1665
  if (basePath) {
1477
1666
  window.location.href = withBasePath(href, basePath);
1478
1667
  } else {
@@ -1480,6 +1669,7 @@ function useCfgRouter() {
1480
1669
  }
1481
1670
  }, [router, basePath]);
1482
1671
  const replace = (0, import_react3.useCallback)((href, options) => {
1672
+ authTrace("nav.replace", { from: fromPath(), to: href });
1483
1673
  if (basePath) {
1484
1674
  window.location.replace(withBasePath(href, basePath));
1485
1675
  } else {
@@ -1487,9 +1677,21 @@ function useCfgRouter() {
1487
1677
  }
1488
1678
  }, [router, basePath]);
1489
1679
  const hardPush = (0, import_react3.useCallback)((href) => {
1680
+ if (hardNavigating) {
1681
+ authTrace("nav.hardPush.ignored", { from: fromPath(), to: href });
1682
+ return;
1683
+ }
1684
+ hardNavigating = true;
1685
+ authTrace("nav.hardPush", { from: fromPath(), to: href });
1490
1686
  window.location.href = withBasePath(href, basePath);
1491
1687
  }, [basePath]);
1492
1688
  const hardReplace = (0, import_react3.useCallback)((href) => {
1689
+ if (hardNavigating) {
1690
+ authTrace("nav.hardReplace.ignored", { from: fromPath(), to: href });
1691
+ return;
1692
+ }
1693
+ hardNavigating = true;
1694
+ authTrace("nav.hardReplace", { from: fromPath(), to: href });
1493
1695
  window.location.replace(withBasePath(href, basePath));
1494
1696
  }, [basePath]);
1495
1697
  const prefetch = (0, import_react3.useCallback)((href) => {
@@ -3455,7 +3657,7 @@ var OAuthErrorCodeEnum = /* @__PURE__ */ ((OAuthErrorCodeEnum2) => {
3455
3657
 
3456
3658
  // src/auth/utils/errors.ts
3457
3659
  var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
3458
- var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
3660
+ var TRANSPORT_FALLBACK = /^HTTP \d{1,3}:\s*/;
3459
3661
  var AUTH_ERROR_KEYS = {
3460
3662
  invalidCredentials: "ui.errors.unauthorized",
3461
3663
  rateLimited: "api.errors.rateLimited",
@@ -3472,6 +3674,7 @@ var FALLBACK_COPY = {
3472
3674
  };
3473
3675
  var keyForStatus = /* @__PURE__ */ __name((status) => {
3474
3676
  if (status === void 0) return AUTH_ERROR_KEYS.generic;
3677
+ if (status === 0) return AUTH_ERROR_KEYS.networkError;
3475
3678
  if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
3476
3679
  if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
3477
3680
  if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
@@ -3516,9 +3719,9 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
3516
3719
 
3517
3720
  // src/auth/utils/oauthErrors.ts
3518
3721
  var SERVER_CODES = new Set(Object.values(OAuthErrorCodeEnum));
3519
- var record = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3722
+ var record2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3520
3723
  var readOAuthFailure = /* @__PURE__ */ __name((err, fallback) => {
3521
- const body = record(record(err)?.response);
3724
+ const body = record2(record2(err)?.response);
3522
3725
  const code = body?.error;
3523
3726
  if (typeof code === "string" && SERVER_CODES.has(code)) {
3524
3727
  const description = body?.error_description;
@@ -3797,6 +4000,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3797
4000
  setLoadingProvider(provider);
3798
4001
  setError(null);
3799
4002
  setErrorCode(null);
4003
+ authTrace("oauth.start");
3800
4004
  auth.clearSession();
3801
4005
  try {
3802
4006
  Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
@@ -3844,6 +4048,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3844
4048
  if (!response.access || !response.refresh) {
3845
4049
  throw new Error("Invalid response from OAuth callback");
3846
4050
  }
4051
+ authTrace("oauth.session");
3847
4052
  auth.setSession({ access: response.access, refresh: response.refresh });
3848
4053
  Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
3849
4054
  category: "auth" /* AUTH */,
@@ -4024,6 +4229,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
4024
4229
  setError(null);
4025
4230
  }, []);
4026
4231
  const handleSuccess = (0, import_react14.useCallback)((response) => {
4232
+ authTrace("2fa.session");
4027
4233
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
4028
4234
  if (response.warning) {
4029
4235
  setWarning(response.warning);
@@ -4675,6 +4881,17 @@ function purgeLegacyTokenStorage() {
4675
4881
  }
4676
4882
  __name(purgeLegacyTokenStorage, "purgeLegacyTokenStorage");
4677
4883
 
4884
+ // src/auth/utils/authState.ts
4885
+ function resolveAuthStatus(s) {
4886
+ if (!s.clientReady) return "resolving";
4887
+ if (s.snapshotAuthenticated) {
4888
+ return s.profileSettled || s.timedOut ? "authenticated" : "resolving";
4889
+ }
4890
+ if (s.storeAuthenticated && !s.timedOut) return "resolving";
4891
+ return "anonymous";
4892
+ }
4893
+ __name(resolveAuthStatus, "resolveAuthStatus");
4894
+
4678
4895
  // src/auth/context/AccountsContext.tsx
4679
4896
  var import_react19 = require("react");
4680
4897
 
@@ -5122,6 +5339,7 @@ function AccountsProvider({ children }) {
5122
5339
  return;
5123
5340
  }
5124
5341
  if (result.access && result.refresh) {
5342
+ authTrace("accounts.otp.session");
5125
5343
  auth.setSession({ access: result.access, refresh: result.refresh });
5126
5344
  try {
5127
5345
  await refreshProfile({ callerId, force: true });
@@ -5147,6 +5365,7 @@ function AccountsProvider({ children }) {
5147
5365
  authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
5148
5366
  });
5149
5367
  }
5368
+ authTrace("accounts.logout");
5150
5369
  auth.clearSession();
5151
5370
  setProfile(void 0);
5152
5371
  setProfileError(null);
@@ -5181,11 +5400,11 @@ function AccountsProvider({ children }) {
5181
5400
  }
5182
5401
  __name(AccountsProvider, "AccountsProvider");
5183
5402
  function useAccountsContext() {
5184
- const context = (0, import_react19.useContext)(AccountsContext);
5185
- if (!context) {
5403
+ const context2 = (0, import_react19.useContext)(AccountsContext);
5404
+ if (!context2) {
5186
5405
  throw new Error("useAccountsContext must be used within AccountsProvider");
5187
5406
  }
5188
- return context;
5407
+ return context2;
5189
5408
  }
5190
5409
  __name(useAccountsContext, "useAccountsContext");
5191
5410
 
@@ -5241,14 +5460,15 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5241
5460
  const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
5242
5461
  if (hasTokens && !auth.isAuthenticated()) {
5243
5462
  authLogger.warn("Dead session in storage \u2014 clearing");
5463
+ authTrace("bootstrap.purge", { why: "dead-tokens" });
5244
5464
  auth.clearSession();
5245
5465
  clearProfileCache();
5246
5466
  } else if (!hasTokens && !auth.isAuthenticated()) {
5467
+ authTrace("bootstrap.purge", { why: "no-session" });
5247
5468
  clearProfileCache();
5248
5469
  purgeLegacyTokenStorage();
5249
5470
  if (accountsRef.current.profile) accountsRef.current.logout();
5250
5471
  }
5251
- if (auth.isAuthenticated()) return;
5252
5472
  setProfileSettled(true);
5253
5473
  return;
5254
5474
  }
@@ -5257,9 +5477,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5257
5477
  return;
5258
5478
  }
5259
5479
  profileAttemptedRef.current = true;
5480
+ authTrace("bootstrap.profile.start");
5260
5481
  let cancelled = false;
5261
5482
  accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
5262
5483
  authLogger.warn("Profile load failed (session kept):", error);
5484
+ authTrace("bootstrap.profile.failed", {
5485
+ status: error instanceof APIError ? error.statusCode : 0
5486
+ });
5263
5487
  }).finally(() => {
5264
5488
  if (!cancelled) setProfileSettled(true);
5265
5489
  });
@@ -5268,11 +5492,47 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5268
5492
  };
5269
5493
  }, [isAuthenticated, hasProfile]);
5270
5494
  const isLoading = !profileSettled;
5271
- const expiredHandledRef = (0, import_react20.useRef)(false);
5495
+ const [clientReady, setClientReady] = (0, import_react20.useState)(false);
5496
+ const [timedOut, setTimedOut] = (0, import_react20.useState)(false);
5497
+ (0, import_react20.useEffect)(() => {
5498
+ setClientReady(true);
5499
+ const timer = setTimeout(() => setTimedOut(true), 8e3);
5500
+ return () => clearTimeout(timer);
5501
+ }, []);
5502
+ const status = resolveAuthStatus({
5503
+ clientReady,
5504
+ snapshotAuthenticated: isAuthenticated,
5505
+ storeAuthenticated: isBrowser2 ? auth.isAuthenticated() : false,
5506
+ profileSettled,
5507
+ timedOut
5508
+ });
5509
+ const lastStatusRef = (0, import_react20.useRef)("");
5510
+ (0, import_react20.useEffect)(() => {
5511
+ if (lastStatusRef.current === status) return;
5512
+ lastStatusRef.current = status;
5513
+ authTrace("status", {
5514
+ status,
5515
+ snapshot: isAuthenticated,
5516
+ store: auth.isAuthenticated(),
5517
+ profileSettled,
5518
+ timedOut,
5519
+ path: pathname ?? ""
5520
+ });
5521
+ }, [status, isAuthenticated, profileSettled, timedOut, pathname]);
5522
+ (0, import_react20.useEffect)(() => {
5523
+ globalThis.__cfgAuthNow = () => ({
5524
+ status,
5525
+ snapshot: isAuthenticated,
5526
+ store: auth.isAuthenticated(),
5527
+ hasToken: Boolean(auth.getToken()),
5528
+ hasRefresh: Boolean(auth.getRefreshToken()),
5529
+ profileSettled,
5530
+ timedOut
5531
+ });
5532
+ }, [status, isAuthenticated, profileSettled, timedOut]);
5272
5533
  (0, import_react20.useEffect)(() => {
5273
5534
  const unsubscribe = auth.onSessionExpired(() => {
5274
- if (expiredHandledRef.current) return;
5275
- expiredHandledRef.current = true;
5535
+ authTrace("expired.handler", { path: pathname ?? "" });
5276
5536
  authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
5277
5537
  clearProfileCache();
5278
5538
  Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
@@ -5281,12 +5541,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5281
5541
  router.hardReplace(routesRef.current.defaultAuthCallback);
5282
5542
  });
5283
5543
  return unsubscribe;
5284
- }, [router]);
5544
+ }, [router, pathname]);
5285
5545
  (0, import_react20.useEffect)(() => {
5286
5546
  if (!isAuthenticated || pendingManualNavRef.current) return;
5287
5547
  if (pathname === routes.auth && !queryParams.get("flow")) {
5288
5548
  const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
5289
5549
  if (saved) redirectManager.clearRedirect();
5550
+ authTrace("leave-auth", { to: saved || routes.defaultCallback, saved: Boolean(saved) });
5290
5551
  router.push(saved || routes.defaultCallback);
5291
5552
  }
5292
5553
  }, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
@@ -5312,6 +5573,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5312
5573
  }, [loadCurrentProfile, router]);
5313
5574
  const requestOTP = (0, import_react20.useCallback)(
5314
5575
  async (identifier, sourceUrl, consent) => {
5576
+ authTrace("otp.request");
5315
5577
  auth.clearSession();
5316
5578
  try {
5317
5579
  const body = {
@@ -5356,6 +5618,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5356
5618
  async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
5357
5619
  try {
5358
5620
  pendingManualNavRef.current = Boolean(skipRedirect);
5621
+ authTrace("otp.verify", { skipRedirect: Boolean(skipRedirect) });
5359
5622
  const result = await accountsRef.current.verifyOTP({
5360
5623
  identifier,
5361
5624
  otp: otpCode,
@@ -5417,6 +5680,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5417
5680
  const { redirectUrl, skipRedirect } = options || {};
5418
5681
  try {
5419
5682
  pendingManualNavRef.current = Boolean(skipRedirect);
5683
+ authTrace("password.login", { skipRedirect: Boolean(skipRedirect) });
5420
5684
  const result = await accountsRef.current.loginWithPassword({
5421
5685
  email,
5422
5686
  password
@@ -5491,6 +5755,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5491
5755
  return { success: false, message: "Error refreshing token" };
5492
5756
  }, []);
5493
5757
  const logout = (0, import_react20.useCallback)(() => {
5758
+ authTrace("logout");
5494
5759
  Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
5495
5760
  category: "auth" /* AUTH */
5496
5761
  });
@@ -5517,6 +5782,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5517
5782
  () => ({
5518
5783
  user,
5519
5784
  isLoading,
5785
+ status,
5520
5786
  // Reactive: flips via the store snapshot (token writes, refreshes,
5521
5787
  // expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
5522
5788
  isAuthenticated,
@@ -5544,6 +5810,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5544
5810
  [
5545
5811
  user,
5546
5812
  isLoading,
5813
+ status,
5547
5814
  isAuthenticated,
5548
5815
  isAdminUser,
5549
5816
  routes,
@@ -5573,6 +5840,7 @@ var AuthProvider = (0, import_react20.memo)(AuthProviderRaw);
5573
5840
  var defaultAuthState = {
5574
5841
  user: null,
5575
5842
  isLoading: false,
5843
+ status: "anonymous",
5576
5844
  isAuthenticated: false,
5577
5845
  isAdminUser: false,
5578
5846
  routes: resolveAuthRoutes(),
@@ -5625,14 +5893,14 @@ var defaultAuthState = {
5625
5893
  }, "uploadAvatar")
5626
5894
  };
5627
5895
  var useAuth = /* @__PURE__ */ __name(() => {
5628
- const context = (0, import_react20.useContext)(AuthContext);
5629
- if (context === void 0) {
5896
+ const context2 = (0, import_react20.useContext)(AuthContext);
5897
+ if (context2 === void 0) {
5630
5898
  if (isBrowser2 && isDev) {
5631
5899
  authLogger.debug("useAuth called outside AuthProvider, returning default state");
5632
5900
  }
5633
5901
  return defaultAuthState;
5634
5902
  }
5635
- return context;
5903
+ return context2;
5636
5904
  }, "useAuth");
5637
5905
  var AuthContext_default = AuthContext;
5638
5906