@djangocfg/api 2.1.619 → 2.1.622

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.mjs CHANGED
@@ -15,10 +15,201 @@ var AUTH_CONSTANTS = {
15
15
  RESEND_COOLDOWN_SECONDS: 30
16
16
  };
17
17
 
18
+ // src/auth/trace.ts
19
+ var KEY = "cfg.auth.trace";
20
+ var MAX = 200;
21
+ var MAX_STRING = 120;
22
+ var SENSITIVE = /token|otp|code|email|password|secret|cookie|authorization|key|identifier/i;
23
+ var TAB = (() => {
24
+ const g = globalThis;
25
+ return g.__cfgAuthTab ??= Math.random().toString(36).slice(2, 6);
26
+ })();
27
+ var hasWindow = typeof window !== "undefined";
28
+ var memory = [];
29
+ var loaded = false;
30
+ function backend() {
31
+ if (!hasWindow) return null;
32
+ for (const name of ["localStorage", "sessionStorage"]) {
33
+ try {
34
+ const s = window[name];
35
+ s.setItem("__cfg_probe", "1");
36
+ s.removeItem("__cfg_probe");
37
+ return s;
38
+ } catch {
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ __name(backend, "backend");
44
+ function load() {
45
+ if (loaded) return memory;
46
+ loaded = true;
47
+ try {
48
+ const raw = backend()?.getItem(KEY);
49
+ const parsed = raw ? JSON.parse(raw) : [];
50
+ if (Array.isArray(parsed)) memory = parsed.filter((e) => typeof e === "object" && e !== null);
51
+ } catch {
52
+ memory = [];
53
+ }
54
+ return memory;
55
+ }
56
+ __name(load, "load");
57
+ function persist() {
58
+ try {
59
+ backend()?.setItem(KEY, JSON.stringify(memory));
60
+ } catch {
61
+ }
62
+ }
63
+ __name(persist, "persist");
64
+ function clean(fields) {
65
+ if (!fields) return void 0;
66
+ const out = {};
67
+ for (const [k2, v] of Object.entries(fields)) {
68
+ if (SENSITIVE.test(k2)) continue;
69
+ if (typeof v === "string") out[k2] = v.length > MAX_STRING ? `${v.slice(0, MAX_STRING)}\u2026` : v;
70
+ else if (typeof v === "number" || typeof v === "boolean" || v === null) out[k2] = v;
71
+ }
72
+ return Object.keys(out).length ? out : void 0;
73
+ }
74
+ __name(clean, "clean");
75
+ function refresh() {
76
+ try {
77
+ const raw = backend()?.getItem(KEY);
78
+ const parsed = raw ? JSON.parse(raw) : null;
79
+ if (Array.isArray(parsed)) memory = parsed.filter((e) => typeof e === "object" && e !== null);
80
+ } catch {
81
+ }
82
+ }
83
+ __name(refresh, "refresh");
84
+ function record(at, ev, fields) {
85
+ load();
86
+ refresh();
87
+ const entries = memory;
88
+ const p = hasWindow && typeof performance !== "undefined" ? Math.round(performance.now()) : 0;
89
+ const f2 = clean(fields);
90
+ const last = entries[entries.length - 1];
91
+ if (last && last.tab === TAB && last.ev === ev && JSON.stringify(last.f) === JSON.stringify(f2)) {
92
+ last.n = (last.n ?? 1) + 1;
93
+ last.t = at;
94
+ last.p = p;
95
+ persist();
96
+ return;
97
+ }
98
+ entries.push({ t: at, p, tab: TAB, ev, f: f2 });
99
+ if (entries.length > MAX) entries.splice(0, entries.length - MAX);
100
+ persist();
101
+ }
102
+ __name(record, "record");
103
+ function authTrace(ev, fields) {
104
+ if (!hasWindow) return;
105
+ try {
106
+ record(Date.now(), ev, fields);
107
+ } catch {
108
+ }
109
+ }
110
+ __name(authTrace, "authTrace");
111
+ function resetAuthTraceForTests() {
112
+ memory = [];
113
+ loaded = true;
114
+ }
115
+ __name(resetAuthTraceForTests, "resetAuthTraceForTests");
116
+ function readAuthTrace() {
117
+ return load().slice();
118
+ }
119
+ __name(readAuthTrace, "readAuthTrace");
120
+ function clearAuthTrace() {
121
+ memory = [];
122
+ loaded = true;
123
+ try {
124
+ backend()?.removeItem(KEY);
125
+ } catch {
126
+ }
127
+ }
128
+ __name(clearAuthTrace, "clearAuthTrace");
129
+ function context() {
130
+ if (!hasWindow) return [];
131
+ const copies = globalThis.__cfgAuthCopies ?? 1;
132
+ const lines = [`url: ${window.location.href.split("?")[0]}`, `copies: ${String(copies)}`, `online: ${navigator.onLine}`, `ua: ${navigator.userAgent}`];
133
+ try {
134
+ const auth2 = globalThis.__cfgAuthNow;
135
+ const now = auth2?.();
136
+ if (now) lines.push(`now: ${Object.entries(now).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}`);
137
+ } catch {
138
+ }
139
+ return lines;
140
+ }
141
+ __name(context, "context");
142
+ function dumpAuthTrace() {
143
+ const entries = load();
144
+ const first = entries[0]?.t ?? Date.now();
145
+ const rows = entries.map((e) => {
146
+ const fields = e.f ? ` ${Object.entries(e.f).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}` : "";
147
+ const at = new Date(e.t).toISOString().slice(11, 23);
148
+ return `${at} +${String(e.t - first).padStart(6)}ms [${e.tab} p${e.p}] ${e.ev}${fields}${e.n && e.n > 1 ? ` x${e.n}` : ""}`;
149
+ });
150
+ return [`# cfg auth trace, ${entries.length} events`, ...context(), "", ...rows].join("\n");
151
+ }
152
+ __name(dumpAuthTrace, "dumpAuthTrace");
153
+ function installAuthTrace() {
154
+ if (!hasWindow) return;
155
+ const g = globalThis;
156
+ g.__cfgAuthTrace = authTrace;
157
+ g.__cfgAuth = {
158
+ trace: readAuthTrace,
159
+ dump: /* @__PURE__ */ __name(() => {
160
+ const text = dumpAuthTrace();
161
+ console.log(text);
162
+ return text;
163
+ }, "dump"),
164
+ clear: clearAuthTrace,
165
+ /** Put the dump on the clipboard (works when the console output is blocked or too long to select). */
166
+ copy: /* @__PURE__ */ __name(() => {
167
+ const text = dumpAuthTrace();
168
+ void navigator.clipboard?.writeText(text);
169
+ return `${text.split("\n").length} lines copied`;
170
+ }, "copy"),
171
+ /** Download the raw trace as a JSON file. */
172
+ download: /* @__PURE__ */ __name(() => {
173
+ const blob = new Blob([JSON.stringify(readAuthTrace(), null, 1)], { type: "application/json" });
174
+ const a = document.createElement("a");
175
+ a.href = URL.createObjectURL(blob);
176
+ a.download = `auth-trace-${Date.now()}.json`;
177
+ a.click();
178
+ }, "download")
179
+ };
180
+ const queued = g.__cfgAuthTraceQ;
181
+ if (Array.isArray(queued)) {
182
+ g.__cfgAuthTraceQ = [];
183
+ for (const item of queued) {
184
+ if (Array.isArray(item) && typeof item[1] === "string") {
185
+ try {
186
+ record(Number(item[0]) || Date.now(), item[1], item[2]);
187
+ } catch {
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ __name(installAuthTrace, "installAuthTrace");
194
+ function registerCopy() {
195
+ if (!hasWindow) return;
196
+ const g = globalThis;
197
+ const n = (g.__cfgAuthCopies ?? 0) + 1;
198
+ g.__cfgAuthCopies = n;
199
+ if (n > 1) {
200
+ authTrace("dup.copy", { copies: n });
201
+ if (n === 2) {
202
+ console.warn("[@djangocfg/api] more than one copy of the auth package is loaded; see __cfgAuth.dump() (dup.copy). Align the peer set so pnpm installs one instance.");
203
+ }
204
+ }
205
+ }
206
+ __name(registerCopy, "registerCopy");
207
+ registerCopy();
208
+ installAuthTrace();
209
+
18
210
  // src/auth/context/AuthContext.tsx
19
211
  import { usePathname as usePathname3 } from "next/navigation";
20
212
  import {
21
- createContext as createContext2,
22
213
  memo,
23
214
  useCallback as useCallback13,
24
215
  useContext as useContext2,
@@ -131,10 +322,10 @@ function normalizeSegment(raw) {
131
322
  return raw.trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
132
323
  }
133
324
  __name(normalizeSegment, "normalizeSegment");
134
- var hasWindow = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
325
+ var hasWindow2 = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
135
326
  var domainByHost = /* @__PURE__ */ new Map();
136
327
  function detectCookieDomain() {
137
- if (!hasWindow() || typeof document === "undefined") return null;
328
+ if (!hasWindow2() || typeof document === "undefined") return null;
138
329
  const host = window.location.hostname.toLowerCase();
139
330
  if (!host || !host.includes(".") || host.includes(":") || /^[0-9.]+$/.test(host)) return null;
140
331
  const cached = domainByHost.get(host);
@@ -158,7 +349,7 @@ function detectCookieDomain() {
158
349
  }
159
350
  __name(detectCookieDomain, "detectCookieDomain");
160
351
  function nativeStorage(store) {
161
- if (!hasWindow()) return null;
352
+ if (!hasWindow2()) return null;
162
353
  try {
163
354
  return store === "session" ? window.sessionStorage : window.localStorage;
164
355
  } catch {
@@ -167,7 +358,7 @@ function nativeStorage(store) {
167
358
  }
168
359
  __name(nativeStorage, "nativeStorage");
169
360
  function cookieNames() {
170
- if (!hasWindow()) return [];
361
+ if (!hasWindow2()) return [];
171
362
  try {
172
363
  return document.cookie.split("; ").map((part) => decodeURIComponent(part.split("=")[0] ?? "")).filter(Boolean);
173
364
  } catch {
@@ -192,7 +383,7 @@ function createStorage(owner, options = {}) {
192
383
  const families = Object.values(options.families ?? {});
193
384
  const specByKey = /* @__PURE__ */ new Map();
194
385
  for (const spec of specs) specByKey.set(spec.key, [...specByKey.get(spec.key) ?? [], spec]);
195
- const memory = {
386
+ const memory2 = {
196
387
  local: /* @__PURE__ */ new Map(),
197
388
  session: /* @__PURE__ */ new Map(),
198
389
  cookie: /* @__PURE__ */ new Map()
@@ -206,7 +397,7 @@ function createStorage(owner, options = {}) {
206
397
  if (!key.startsWith(prefix)) throw new TypeError(`storage key "${key}" is not owned by "${owner}"`);
207
398
  }, "assertOwned");
208
399
  function rawGet(store, key) {
209
- if (!hasWindow()) return null;
400
+ if (!hasWindow2()) return null;
210
401
  if (store === "cookie") {
211
402
  try {
212
403
  const escaped = encodeURIComponent(key).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -214,18 +405,18 @@ function createStorage(owner, options = {}) {
214
405
  if (match?.[1] !== void 0) return decodeURIComponent(match[1]);
215
406
  } catch {
216
407
  }
217
- return memory.cookie.get(key) ?? null;
408
+ return memory2.cookie.get(key) ?? null;
218
409
  }
219
410
  try {
220
411
  const value = nativeStorage(store)?.getItem(key) ?? null;
221
412
  if (value !== null) return value;
222
413
  } catch {
223
414
  }
224
- return memory[store].get(key) ?? null;
415
+ return memory2[store].get(key) ?? null;
225
416
  }
226
417
  __name(rawGet, "rawGet");
227
418
  function rawSet(store, key, value, maxAgeSeconds) {
228
- if (!hasWindow()) return false;
419
+ if (!hasWindow2()) return false;
229
420
  try {
230
421
  if (store === "cookie") {
231
422
  const secure = window.location.protocol === "https:" ? "; Secure" : "";
@@ -233,24 +424,24 @@ function createStorage(owner, options = {}) {
233
424
  const domainPart = domain ? `; Domain=${domain}` : "";
234
425
  document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; Path=/; Max-Age=${Math.max(1, Math.floor(maxAgeSeconds ?? cookieMaxAge))}; SameSite=Lax${domainPart}${secure}`;
235
426
  if (domain) expireCookie(key, null);
236
- if (rawGet("cookie", key) === value && !memory.cookie.has(key)) return true;
237
- memory.cookie.set(key, value);
427
+ if (rawGet("cookie", key) === value && !memory2.cookie.has(key)) return true;
428
+ memory2.cookie.set(key, value);
238
429
  return false;
239
430
  }
240
431
  const native = nativeStorage(store);
241
432
  if (!native) throw new Error("storage unavailable");
242
433
  native.setItem(key, value);
243
- memory[store].delete(key);
434
+ memory2[store].delete(key);
244
435
  return true;
245
436
  } catch {
246
- memory[store].set(key, value);
437
+ memory2[store].set(key, value);
247
438
  return false;
248
439
  }
249
440
  }
250
441
  __name(rawSet, "rawSet");
251
442
  function rawRemove(store, key) {
252
- memory[store].delete(key);
253
- if (!hasWindow()) return;
443
+ memory2[store].delete(key);
444
+ if (!hasWindow2()) return;
254
445
  try {
255
446
  if (store === "cookie") {
256
447
  expireCookie(key, null);
@@ -279,7 +470,7 @@ function createStorage(owner, options = {}) {
279
470
  }
280
471
  } catch {
281
472
  }
282
- for (const name of memory[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
473
+ for (const name of memory2[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
283
474
  return out;
284
475
  }
285
476
  __name(listKeys, "listKeys");
@@ -351,7 +542,7 @@ function createStorage(owner, options = {}) {
351
542
  cookieDomain: resolveCookieDomain,
352
543
  onChange(key, listener, store = "local") {
353
544
  assertValid(key);
354
- if (!hasWindow()) return () => {
545
+ if (!hasWindow2()) return () => {
355
546
  };
356
547
  const handler = /* @__PURE__ */ __name((event) => {
357
548
  if (event.storageArea !== nativeStorage(store)) return;
@@ -375,6 +566,29 @@ var REFRESH_KEY = "cfg.auth.refresh_token";
375
566
  var API_KEY_KEY = "cfg.auth.api_key";
376
567
  var storage = true ? cfgStorage : createStorage2("cfg");
377
568
  var isBrowser = typeof window !== "undefined";
569
+ function trace(ev, fields) {
570
+ try {
571
+ const g = globalThis;
572
+ const sink2 = g.__cfgAuthTrace;
573
+ if (sink2) {
574
+ sink2(ev, fields);
575
+ return;
576
+ }
577
+ const queue = g.__cfgAuthTraceQ ?? [];
578
+ g.__cfgAuthTraceQ = queue;
579
+ if (queue.length < 100) queue.push([Date.now(), ev, fields]);
580
+ } catch {
581
+ }
582
+ }
583
+ __name(trace, "trace");
584
+ function pathOf(url) {
585
+ try {
586
+ return new URL(url, "http://x").pathname;
587
+ } catch {
588
+ return "";
589
+ }
590
+ }
591
+ __name(pathOf, "pathOf");
378
592
  function secondsUntilExp(token) {
379
593
  const exp = jwtExpMs(token);
380
594
  return exp === null ? void 0 : Math.max(1, Math.floor((exp - Date.now()) / 1e3));
@@ -405,10 +619,10 @@ function adoptLegacySession() {
405
619
  if (_storage.get(ACCESS_KEY) !== null || _storage.get(REFRESH_KEY) !== null) return;
406
620
  const legacy = backendFor("localStorage");
407
621
  const access = legacy.get(ACCESS_KEY);
408
- const refresh = legacy.get(REFRESH_KEY);
409
- if (access === null && refresh === null) return;
622
+ const refresh2 = legacy.get(REFRESH_KEY);
623
+ if (access === null && refresh2 === null) return;
410
624
  if (access !== null) _storage.set(ACCESS_KEY, access);
411
- if (refresh !== null) _storage.set(REFRESH_KEY, refresh);
625
+ if (refresh2 !== null) _storage.set(REFRESH_KEY, refresh2);
412
626
  legacy.set(ACCESS_KEY, null);
413
627
  legacy.set(REFRESH_KEY, null);
414
628
  } catch {
@@ -493,12 +707,12 @@ function jwtExpMs(token) {
493
707
  __name(jwtExpMs, "jwtExpMs");
494
708
  function computeSnapshot() {
495
709
  const access = _storage.get(ACCESS_KEY);
496
- const refresh = _storage.get(REFRESH_KEY);
710
+ const refresh2 = _storage.get(REFRESH_KEY);
497
711
  const now = Date.now();
498
712
  const accessExp = access ? jwtExpMs(access) : null;
499
713
  const accessAlive = access !== null && (accessExp === null || accessExp > now);
500
- const refreshExp = refresh ? jwtExpMs(refresh) : null;
501
- const refreshAlive = refresh !== null && (refreshExp === null || refreshExp > now);
714
+ const refreshExp = refresh2 ? jwtExpMs(refresh2) : null;
715
+ const refreshAlive = refresh2 !== null && (refreshExp === null || refreshExp > now);
502
716
  return {
503
717
  status: accessAlive || refreshAlive ? "authenticated" : "anonymous",
504
718
  accessExpiresAt: accessExp
@@ -521,9 +735,9 @@ function scheduleExpiryFlip() {
521
735
  const now = Date.now();
522
736
  const exps = [];
523
737
  const access = _storage.get(ACCESS_KEY);
524
- const refresh = _storage.get(REFRESH_KEY);
738
+ const refresh2 = _storage.get(REFRESH_KEY);
525
739
  const accessExp = access ? jwtExpMs(access) : null;
526
- const refreshExp = refresh ? jwtExpMs(refresh) : null;
740
+ const refreshExp = refresh2 ? jwtExpMs(refresh2) : null;
527
741
  if (accessExp !== null && accessExp > now) exps.push(accessExp);
528
742
  if (refreshExp !== null && refreshExp > now) exps.push(refreshExp);
529
743
  if (!exps.length) return;
@@ -534,7 +748,10 @@ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
534
748
  function notifySessionChanged() {
535
749
  const next = computeSnapshot();
536
750
  const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
537
- if (changed) _snapshot = next;
751
+ if (changed) {
752
+ _snapshot = next;
753
+ trace("store.snapshot", { status: next.status });
754
+ }
538
755
  scheduleExpiryFlip();
539
756
  if (!changed) return;
540
757
  for (const listener of Array.from(_sessionListeners)) {
@@ -617,6 +834,7 @@ var auth = {
617
834
  return _storage.get(ACCESS_KEY);
618
835
  },
619
836
  setToken(token) {
837
+ trace("store.setToken", { present: token !== null });
620
838
  _storage.set(ACCESS_KEY, token);
621
839
  notifySessionChanged();
622
840
  },
@@ -624,14 +842,16 @@ var auth = {
624
842
  return _storage.get(REFRESH_KEY);
625
843
  },
626
844
  setRefreshToken(token) {
845
+ trace("store.setRefreshToken", { present: token !== null });
627
846
  _storage.set(REFRESH_KEY, token);
628
847
  notifySessionChanged();
629
848
  },
630
849
  clearTokens() {
850
+ trace("store.clearTokens");
631
851
  for (const mode of ["localStorage", "sessionStorage", "cookie"]) {
632
- const backend = backendFor(mode);
633
- backend.set(ACCESS_KEY, null);
634
- backend.set(REFRESH_KEY, null);
852
+ const backend2 = backendFor(mode);
853
+ backend2.set(ACCESS_KEY, null);
854
+ backend2.set(REFRESH_KEY, null);
635
855
  }
636
856
  storage.clearAll();
637
857
  notifySessionChanged();
@@ -650,6 +870,7 @@ var auth = {
650
870
  * rotation); `refresh: null` explicitly drops it.
651
871
  */
652
872
  setSession(tokens) {
873
+ trace("store.setSession", { refresh: tokens.refresh === void 0 ? "kept" : tokens.refresh === null ? "dropped" : "set" });
653
874
  _storage.set(ACCESS_KEY, tokens.access);
654
875
  if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
655
876
  notifySessionChanged();
@@ -785,10 +1006,14 @@ async function tryRefresh() {
785
1006
  if (_refreshInflight) return _refreshInflight;
786
1007
  if (!_refreshHandler) return null;
787
1008
  const runRefresh = /* @__PURE__ */ __name(async () => {
788
- const refresh = auth.getRefreshToken();
789
- if (!refresh) return null;
790
- const result = await _refreshHandler(refresh);
791
- if (!result?.access) return null;
1009
+ const refresh2 = auth.getRefreshToken();
1010
+ if (!refresh2) return null;
1011
+ const result = await _refreshHandler(refresh2);
1012
+ if (!result?.access) {
1013
+ trace("refresh", { ok: false, why: "no-access" });
1014
+ return null;
1015
+ }
1016
+ trace("refresh", { ok: true, rotated: Boolean(result.refresh) });
792
1017
  auth.setToken(result.access);
793
1018
  if (result.refresh) auth.setRefreshToken(result.refresh);
794
1019
  return result.access;
@@ -801,6 +1026,7 @@ async function tryRefresh() {
801
1026
  }
802
1027
  return await runRefresh();
803
1028
  } catch {
1029
+ trace("refresh", { ok: false, why: "threw" });
804
1030
  return null;
805
1031
  } finally {
806
1032
  _refreshInflight = null;
@@ -809,7 +1035,8 @@ async function tryRefresh() {
809
1035
  return _refreshInflight;
810
1036
  }
811
1037
  __name(tryRefresh, "tryRefresh");
812
- function expireSession(response) {
1038
+ function expireSession(response, why) {
1039
+ trace("store.expire", { why, status: response.status, path: pathOf(response.url), handlers: _sessionExpiredHandlers.size });
813
1040
  auth.clearTokens();
814
1041
  for (const cb of Array.from(_sessionExpiredHandlers)) {
815
1042
  try {
@@ -952,15 +1179,16 @@ function installAuthOnClient(client2) {
952
1179
  client2.interceptors.response.use(async (response, request) => {
953
1180
  if (response.status !== 401) return response;
954
1181
  if (/\/accounts\/(otp|oauth|login|register|token)\b/.test(request.url)) {
1182
+ trace("401.passthrough", { path: pathOf(request.url) });
955
1183
  return response;
956
1184
  }
957
1185
  if (request.headers.get(RETRY_MARKER)) {
958
- expireSession(response);
1186
+ expireSession(response, "retry-marker");
959
1187
  return response;
960
1188
  }
961
1189
  const newToken = await tryRefresh();
962
1190
  if (!newToken) {
963
- expireSession(response);
1191
+ expireSession(response, "refresh-failed");
964
1192
  return response;
965
1193
  }
966
1194
  const retry = request.clone();
@@ -972,7 +1200,7 @@ function installAuthOnClient(client2) {
972
1200
  }
973
1201
  try {
974
1202
  const retried = await fetch(retry);
975
- if (retried.status === 401) expireSession(retried);
1203
+ if (retried.status === 401) expireSession(retried, "retry-401");
976
1204
  return retried;
977
1205
  } catch {
978
1206
  return response;
@@ -981,7 +1209,7 @@ function installAuthOnClient(client2) {
981
1209
  }
982
1210
  __name(installAuthOnClient, "installAuthOnClient");
983
1211
  var REFRESH_ENDPOINT = "/cfg/accounts/token/refresh/";
984
- auth.setRefreshHandler(async (refresh) => {
1212
+ auth.setRefreshHandler(async (refresh2) => {
985
1213
  try {
986
1214
  const url = `${auth.getBaseUrl()}${REFRESH_ENDPOINT}`;
987
1215
  const headers = { "Content-Type": "application/json" };
@@ -993,11 +1221,11 @@ auth.setRefreshHandler(async (refresh) => {
993
1221
  method: "POST",
994
1222
  headers,
995
1223
  credentials: auth.getWithCredentials() ? "include" : "same-origin",
996
- body: JSON.stringify({ refresh })
1224
+ body: JSON.stringify({ refresh: refresh2 })
997
1225
  });
998
1226
  if (!res.ok) return null;
999
1227
  const data = await res.json();
1000
- return data?.access ? { access: data.access, refresh: data.refresh ?? refresh } : null;
1228
+ return data?.access ? { access: data.access, refresh: data.refresh ?? refresh2 } : null;
1001
1229
  } catch {
1002
1230
  return null;
1003
1231
  }
@@ -1196,6 +1424,16 @@ __name(useTimezone, "useTimezone");
1196
1424
  // src/auth/hooks/useCfgRouter.ts
1197
1425
  import { useRouter as useNextRouter } from "next/navigation";
1198
1426
  import { useCallback as useCallback2, useMemo } from "react";
1427
+ var hardNavigating = false;
1428
+ if (typeof window !== "undefined") {
1429
+ window.addEventListener("pageshow", (e) => {
1430
+ if (e.persisted) hardNavigating = false;
1431
+ });
1432
+ }
1433
+ function fromPath() {
1434
+ return typeof window === "undefined" ? "" : window.location.pathname;
1435
+ }
1436
+ __name(fromPath, "fromPath");
1199
1437
  function getBasePath() {
1200
1438
  if (typeof process === "undefined") {
1201
1439
  return "";
@@ -1216,6 +1454,7 @@ function useCfgRouter() {
1216
1454
  const router = useNextRouter();
1217
1455
  const basePath = useMemo(() => getBasePath(), []);
1218
1456
  const push = useCallback2((href, options) => {
1457
+ authTrace("nav.push", { from: fromPath(), to: href });
1219
1458
  if (basePath) {
1220
1459
  window.location.href = withBasePath(href, basePath);
1221
1460
  } else {
@@ -1223,6 +1462,7 @@ function useCfgRouter() {
1223
1462
  }
1224
1463
  }, [router, basePath]);
1225
1464
  const replace = useCallback2((href, options) => {
1465
+ authTrace("nav.replace", { from: fromPath(), to: href });
1226
1466
  if (basePath) {
1227
1467
  window.location.replace(withBasePath(href, basePath));
1228
1468
  } else {
@@ -1230,9 +1470,21 @@ function useCfgRouter() {
1230
1470
  }
1231
1471
  }, [router, basePath]);
1232
1472
  const hardPush = useCallback2((href) => {
1473
+ if (hardNavigating) {
1474
+ authTrace("nav.hardPush.ignored", { from: fromPath(), to: href });
1475
+ return;
1476
+ }
1477
+ hardNavigating = true;
1478
+ authTrace("nav.hardPush", { from: fromPath(), to: href });
1233
1479
  window.location.href = withBasePath(href, basePath);
1234
1480
  }, [basePath]);
1235
1481
  const hardReplace = useCallback2((href) => {
1482
+ if (hardNavigating) {
1483
+ authTrace("nav.hardReplace.ignored", { from: fromPath(), to: href });
1484
+ return;
1485
+ }
1486
+ hardNavigating = true;
1487
+ authTrace("nav.hardReplace", { from: fromPath(), to: href });
1236
1488
  window.location.replace(withBasePath(href, basePath));
1237
1489
  }, [basePath]);
1238
1490
  const prefetch = useCallback2((href) => {
@@ -1244,7 +1496,7 @@ function useCfgRouter() {
1244
1496
  const forward = useCallback2(() => {
1245
1497
  router.forward();
1246
1498
  }, [router]);
1247
- const refresh = useCallback2(() => {
1499
+ const refresh2 = useCallback2(() => {
1248
1500
  router.refresh();
1249
1501
  }, [router]);
1250
1502
  return {
@@ -1255,7 +1507,7 @@ function useCfgRouter() {
1255
1507
  prefetch,
1256
1508
  back,
1257
1509
  forward,
1258
- refresh
1510
+ refresh: refresh2
1259
1511
  };
1260
1512
  }
1261
1513
  __name(useCfgRouter, "useCfgRouter");
@@ -3198,7 +3450,7 @@ var OAuthErrorCodeEnum = /* @__PURE__ */ ((OAuthErrorCodeEnum2) => {
3198
3450
 
3199
3451
  // src/auth/utils/errors.ts
3200
3452
  var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
3201
- var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
3453
+ var TRANSPORT_FALLBACK = /^HTTP \d{1,3}:\s*/;
3202
3454
  var AUTH_ERROR_KEYS = {
3203
3455
  invalidCredentials: "ui.errors.unauthorized",
3204
3456
  rateLimited: "api.errors.rateLimited",
@@ -3215,6 +3467,7 @@ var FALLBACK_COPY = {
3215
3467
  };
3216
3468
  var keyForStatus = /* @__PURE__ */ __name((status) => {
3217
3469
  if (status === void 0) return AUTH_ERROR_KEYS.generic;
3470
+ if (status === 0) return AUTH_ERROR_KEYS.networkError;
3218
3471
  if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
3219
3472
  if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
3220
3473
  if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
@@ -3259,9 +3512,9 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
3259
3512
 
3260
3513
  // src/auth/utils/oauthErrors.ts
3261
3514
  var SERVER_CODES = new Set(Object.values(OAuthErrorCodeEnum));
3262
- var record = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3515
+ var record2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3263
3516
  var readOAuthFailure = /* @__PURE__ */ __name((err, fallback) => {
3264
- const body = record(record(err)?.response);
3517
+ const body = record2(record2(err)?.response);
3265
3518
  const code = body?.error;
3266
3519
  if (typeof code === "string" && SERVER_CODES.has(code)) {
3267
3520
  const description = body?.error_description;
@@ -3540,6 +3793,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3540
3793
  setLoadingProvider(provider);
3541
3794
  setError(null);
3542
3795
  setErrorCode(null);
3796
+ authTrace("oauth.start");
3543
3797
  auth.clearSession();
3544
3798
  try {
3545
3799
  Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
@@ -3587,6 +3841,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3587
3841
  if (!response.access || !response.refresh) {
3588
3842
  throw new Error("Invalid response from OAuth callback");
3589
3843
  }
3844
+ authTrace("oauth.session");
3590
3845
  auth.setSession({ access: response.access, refresh: response.refresh });
3591
3846
  Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
3592
3847
  category: "auth" /* AUTH */,
@@ -3767,6 +4022,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
3767
4022
  setError(null);
3768
4023
  }, []);
3769
4024
  const handleSuccess = useCallback8((response) => {
4025
+ authTrace("2fa.session");
3770
4026
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
3771
4027
  if (response.warning) {
3772
4028
  setWarning(response.warning);
@@ -4418,9 +4674,19 @@ function purgeLegacyTokenStorage() {
4418
4674
  }
4419
4675
  __name(purgeLegacyTokenStorage, "purgeLegacyTokenStorage");
4420
4676
 
4677
+ // src/auth/utils/authState.ts
4678
+ function resolveAuthStatus(s) {
4679
+ if (!s.clientReady) return "resolving";
4680
+ if (s.snapshotAuthenticated) {
4681
+ return s.profileSettled || s.timedOut ? "authenticated" : "resolving";
4682
+ }
4683
+ if (s.storeAuthenticated && !s.timedOut) return "resolving";
4684
+ return "anonymous";
4685
+ }
4686
+ __name(resolveAuthStatus, "resolveAuthStatus");
4687
+
4421
4688
  // src/auth/context/AccountsContext.tsx
4422
4689
  import {
4423
- createContext,
4424
4690
  useCallback as useCallback12,
4425
4691
  useContext,
4426
4692
  useEffect as useEffect6,
@@ -4774,6 +5040,15 @@ __name(useCfgAccountsTokenCreate, "useCfgAccountsTokenCreate");
4774
5040
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
4775
5041
  import useSWRMutation19 from "swr/mutation";
4776
5042
 
5043
+ // src/auth/context/sharedContext.ts
5044
+ import { createContext } from "react";
5045
+ function sharedContext(key, initial) {
5046
+ const g = globalThis;
5047
+ const registry = g.__cfgAuthContexts ??= {};
5048
+ return registry[key] ??= createContext(initial);
5049
+ }
5050
+ __name(sharedContext, "sharedContext");
5051
+
4777
5052
  // src/auth/context/AccountsContext.tsx
4778
5053
  import { jsx } from "react/jsx-runtime";
4779
5054
  function hasApiOrigin() {
@@ -4787,7 +5062,7 @@ function hasApiOrigin() {
4787
5062
  }
4788
5063
  }
4789
5064
  __name(hasApiOrigin, "hasApiOrigin");
4790
- var AccountsContext = createContext(void 0);
5065
+ var AccountsContext = sharedContext("accounts", void 0);
4791
5066
  function AccountsProvider({ children }) {
4792
5067
  const [profile, setProfile] = useState11(() => {
4793
5068
  if (!auth.isAuthenticated()) {
@@ -4873,6 +5148,7 @@ function AccountsProvider({ children }) {
4873
5148
  return;
4874
5149
  }
4875
5150
  if (result.access && result.refresh) {
5151
+ authTrace("accounts.otp.session");
4876
5152
  auth.setSession({ access: result.access, refresh: result.refresh });
4877
5153
  try {
4878
5154
  await refreshProfile({ callerId, force: true });
@@ -4892,12 +5168,13 @@ function AccountsProvider({ children }) {
4892
5168
  return result;
4893
5169
  }, [triggerPasswordLogin, settleLoginEnvelope]);
4894
5170
  const logout = useCallback12(() => {
4895
- const refresh = auth.getRefreshToken();
4896
- if (refresh) {
4897
- CfgAccountsAuth.cfgAccountsTokenBlacklistCreate({ body: { refresh } }).catch(() => {
5171
+ const refresh2 = auth.getRefreshToken();
5172
+ if (refresh2) {
5173
+ CfgAccountsAuth.cfgAccountsTokenBlacklistCreate({ body: { refresh: refresh2 } }).catch(() => {
4898
5174
  authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
4899
5175
  });
4900
5176
  }
5177
+ authTrace("accounts.logout");
4901
5178
  auth.clearSession();
4902
5179
  setProfile(void 0);
4903
5180
  setProfileError(null);
@@ -4932,11 +5209,11 @@ function AccountsProvider({ children }) {
4932
5209
  }
4933
5210
  __name(AccountsProvider, "AccountsProvider");
4934
5211
  function useAccountsContext() {
4935
- const context = useContext(AccountsContext);
4936
- if (!context) {
5212
+ const context2 = useContext(AccountsContext);
5213
+ if (!context2) {
4937
5214
  throw new Error("useAccountsContext must be used within AccountsProvider");
4938
5215
  }
4939
- return context;
5216
+ return context2;
4940
5217
  }
4941
5218
  __name(useAccountsContext, "useAccountsContext");
4942
5219
 
@@ -4953,7 +5230,7 @@ var resolveAuthRoutes = /* @__PURE__ */ __name((routes) => ({
4953
5230
  defaultAuthCallback: routes?.defaultAuthCallback || routes?.auth || defaultRoutes.defaultAuthCallback
4954
5231
  }), "resolveAuthRoutes");
4955
5232
  var EMAIL_STORAGE_KEY = CFG_KEYS.email.key;
4956
- var AuthContext = createContext2(void 0);
5233
+ var AuthContext = sharedContext("auth", void 0);
4957
5234
  var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4958
5235
  const accounts = useAccountsContext();
4959
5236
  const session = useSession();
@@ -4992,14 +5269,15 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4992
5269
  const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
4993
5270
  if (hasTokens && !auth.isAuthenticated()) {
4994
5271
  authLogger.warn("Dead session in storage \u2014 clearing");
5272
+ authTrace("bootstrap.purge", { why: "dead-tokens" });
4995
5273
  auth.clearSession();
4996
5274
  clearProfileCache();
4997
5275
  } else if (!hasTokens && !auth.isAuthenticated()) {
5276
+ authTrace("bootstrap.purge", { why: "no-session" });
4998
5277
  clearProfileCache();
4999
5278
  purgeLegacyTokenStorage();
5000
5279
  if (accountsRef.current.profile) accountsRef.current.logout();
5001
5280
  }
5002
- if (auth.isAuthenticated()) return;
5003
5281
  setProfileSettled(true);
5004
5282
  return;
5005
5283
  }
@@ -5008,9 +5286,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5008
5286
  return;
5009
5287
  }
5010
5288
  profileAttemptedRef.current = true;
5289
+ authTrace("bootstrap.profile.start");
5011
5290
  let cancelled = false;
5012
5291
  accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
5013
5292
  authLogger.warn("Profile load failed (session kept):", error);
5293
+ authTrace("bootstrap.profile.failed", {
5294
+ status: error instanceof APIError ? error.statusCode : 0
5295
+ });
5014
5296
  }).finally(() => {
5015
5297
  if (!cancelled) setProfileSettled(true);
5016
5298
  });
@@ -5019,11 +5301,47 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5019
5301
  };
5020
5302
  }, [isAuthenticated, hasProfile]);
5021
5303
  const isLoading = !profileSettled;
5022
- const expiredHandledRef = useRef7(false);
5304
+ const [clientReady, setClientReady] = useState12(false);
5305
+ const [timedOut, setTimedOut] = useState12(false);
5306
+ useEffect7(() => {
5307
+ setClientReady(true);
5308
+ const timer = setTimeout(() => setTimedOut(true), 8e3);
5309
+ return () => clearTimeout(timer);
5310
+ }, []);
5311
+ const status = resolveAuthStatus({
5312
+ clientReady,
5313
+ snapshotAuthenticated: isAuthenticated,
5314
+ storeAuthenticated: isBrowser2 ? auth.isAuthenticated() : false,
5315
+ profileSettled,
5316
+ timedOut
5317
+ });
5318
+ const lastStatusRef = useRef7("");
5319
+ useEffect7(() => {
5320
+ if (lastStatusRef.current === status) return;
5321
+ lastStatusRef.current = status;
5322
+ authTrace("status", {
5323
+ status,
5324
+ snapshot: isAuthenticated,
5325
+ store: auth.isAuthenticated(),
5326
+ profileSettled,
5327
+ timedOut,
5328
+ path: pathname ?? ""
5329
+ });
5330
+ }, [status, isAuthenticated, profileSettled, timedOut, pathname]);
5331
+ useEffect7(() => {
5332
+ globalThis.__cfgAuthNow = () => ({
5333
+ status,
5334
+ snapshot: isAuthenticated,
5335
+ store: auth.isAuthenticated(),
5336
+ hasToken: Boolean(auth.getToken()),
5337
+ hasRefresh: Boolean(auth.getRefreshToken()),
5338
+ profileSettled,
5339
+ timedOut
5340
+ });
5341
+ }, [status, isAuthenticated, profileSettled, timedOut]);
5023
5342
  useEffect7(() => {
5024
5343
  const unsubscribe = auth.onSessionExpired(() => {
5025
- if (expiredHandledRef.current) return;
5026
- expiredHandledRef.current = true;
5344
+ authTrace("expired.handler", { path: pathname ?? "" });
5027
5345
  authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
5028
5346
  clearProfileCache();
5029
5347
  Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
@@ -5032,12 +5350,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5032
5350
  router.hardReplace(routesRef.current.defaultAuthCallback);
5033
5351
  });
5034
5352
  return unsubscribe;
5035
- }, [router]);
5353
+ }, [router, pathname]);
5036
5354
  useEffect7(() => {
5037
5355
  if (!isAuthenticated || pendingManualNavRef.current) return;
5038
5356
  if (pathname === routes.auth && !queryParams.get("flow")) {
5039
5357
  const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
5040
5358
  if (saved) redirectManager.clearRedirect();
5359
+ authTrace("leave-auth", { to: saved || routes.defaultCallback, saved: Boolean(saved) });
5041
5360
  router.push(saved || routes.defaultCallback);
5042
5361
  }
5043
5362
  }, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
@@ -5063,6 +5382,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5063
5382
  }, [loadCurrentProfile, router]);
5064
5383
  const requestOTP = useCallback13(
5065
5384
  async (identifier, sourceUrl, consent) => {
5385
+ authTrace("otp.request");
5066
5386
  auth.clearSession();
5067
5387
  try {
5068
5388
  const body = {
@@ -5107,6 +5427,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5107
5427
  async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
5108
5428
  try {
5109
5429
  pendingManualNavRef.current = Boolean(skipRedirect);
5430
+ authTrace("otp.verify", { skipRedirect: Boolean(skipRedirect) });
5110
5431
  const result = await accountsRef.current.verifyOTP({
5111
5432
  identifier,
5112
5433
  otp: otpCode,
@@ -5168,6 +5489,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5168
5489
  const { redirectUrl, skipRedirect } = options || {};
5169
5490
  try {
5170
5491
  pendingManualNavRef.current = Boolean(skipRedirect);
5492
+ authTrace("password.login", { skipRedirect: Boolean(skipRedirect) });
5171
5493
  const result = await accountsRef.current.loginWithPassword({
5172
5494
  email,
5173
5495
  password
@@ -5242,6 +5564,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5242
5564
  return { success: false, message: "Error refreshing token" };
5243
5565
  }, []);
5244
5566
  const logout = useCallback13(() => {
5567
+ authTrace("logout");
5245
5568
  Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
5246
5569
  category: "auth" /* AUTH */
5247
5570
  });
@@ -5268,6 +5591,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5268
5591
  () => ({
5269
5592
  user,
5270
5593
  isLoading,
5594
+ status,
5271
5595
  // Reactive: flips via the store snapshot (token writes, refreshes,
5272
5596
  // expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
5273
5597
  isAuthenticated,
@@ -5295,6 +5619,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5295
5619
  [
5296
5620
  user,
5297
5621
  isLoading,
5622
+ status,
5298
5623
  isAuthenticated,
5299
5624
  isAdminUser,
5300
5625
  routes,
@@ -5324,6 +5649,7 @@ var AuthProvider = memo(AuthProviderRaw);
5324
5649
  var defaultAuthState = {
5325
5650
  user: null,
5326
5651
  isLoading: false,
5652
+ status: "anonymous",
5327
5653
  isAuthenticated: false,
5328
5654
  isAdminUser: false,
5329
5655
  routes: resolveAuthRoutes(),
@@ -5376,14 +5702,14 @@ var defaultAuthState = {
5376
5702
  }, "uploadAvatar")
5377
5703
  };
5378
5704
  var useAuth = /* @__PURE__ */ __name(() => {
5379
- const context = useContext2(AuthContext);
5380
- if (context === void 0) {
5705
+ const context2 = useContext2(AuthContext);
5706
+ if (context2 === void 0) {
5381
5707
  if (isBrowser2 && isDev) {
5382
5708
  authLogger.debug("useAuth called outside AuthProvider, returning default state");
5383
5709
  }
5384
5710
  return defaultAuthState;
5385
5711
  }
5386
- return context;
5712
+ return context2;
5387
5713
  }, "useAuth");
5388
5714
  var AuthContext_default = AuthContext;
5389
5715
 
@@ -5426,17 +5752,21 @@ export {
5426
5752
  AuthProvider,
5427
5753
  OAUTH_PROVIDER_IDS,
5428
5754
  authLogger,
5755
+ authTrace,
5429
5756
  beginTwoFactorChallenge,
5757
+ clearAuthTrace,
5430
5758
  clearOAuthAttempt,
5431
5759
  clearProfileCache,
5432
5760
  consumeSavedRedirect,
5433
5761
  decodeBase64,
5762
+ dumpAuthTrace,
5434
5763
  encodeBase64,
5435
5764
  endTwoFactorChallenge,
5436
5765
  formatAuthError,
5437
5766
  getCacheMetadata,
5438
5767
  getCachedProfile,
5439
5768
  hasValidCache,
5769
+ installAuthTrace,
5440
5770
  isAllowedAuthPath,
5441
5771
  isForbidden,
5442
5772
  isOAuthProviderId,
@@ -5444,8 +5774,11 @@ export {
5444
5774
  logger,
5445
5775
  normalizePath,
5446
5776
  peekSavedRedirect,
5777
+ readAuthTrace,
5447
5778
  readOAuthFailure,
5779
+ resetAuthTraceForTests,
5448
5780
  resolveAuthError,
5781
+ resolveAuthStatus,
5449
5782
  resolveGuardIsAuthenticated,
5450
5783
  resolveGuardIsLoading,
5451
5784
  setAnalyticsSink,