@djangocfg/api 2.1.618 → 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 +311 -39
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +75 -2
- package/dist/auth.d.ts +75 -2
- package/dist/auth.mjs +311 -39
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.cjs +46 -6
- package/dist/clients.cjs.map +1 -1
- package/dist/clients.mjs +46 -6
- package/dist/clients.mjs.map +1 -1
- package/dist/hooks.cjs +46 -6
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.mjs +46 -6
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.cjs +201 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +201 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_api/generated/helpers/auth.ts +39 -6
- package/src/auth/__tests__/authState.test.ts +46 -0
- package/src/auth/__tests__/formatAuthError.test.ts +7 -0
- package/src/auth/__tests__/signInRefusal.test.ts +52 -0
- package/src/auth/context/AccountsContext.tsx +3 -0
- package/src/auth/context/AuthContext.tsx +61 -4
- package/src/auth/context/types.ts +2 -0
- package/src/auth/hooks/useCfgRouter.ts +31 -0
- package/src/auth/hooks/useOAuthLogin.ts +3 -0
- package/src/auth/hooks/useTwoFactorVerify.ts +2 -0
- package/src/auth/index.ts +3 -0
- package/src/auth/trace.ts +183 -0
- package/src/auth/utils/authState.ts +39 -0
- package/src/auth/utils/errors.ts +5 -2
- package/src/auth/utils/index.ts +1 -0
- package/src/index.ts +3 -0
package/dist/auth.mjs
CHANGED
|
@@ -15,6 +15,141 @@ 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 = Math.random().toString(36).slice(2, 6);
|
|
24
|
+
var hasWindow = typeof window !== "undefined";
|
|
25
|
+
var memory = [];
|
|
26
|
+
var loaded = false;
|
|
27
|
+
function backend() {
|
|
28
|
+
if (!hasWindow) return null;
|
|
29
|
+
for (const name of ["localStorage", "sessionStorage"]) {
|
|
30
|
+
try {
|
|
31
|
+
const s = window[name];
|
|
32
|
+
s.setItem("__cfg_probe", "1");
|
|
33
|
+
s.removeItem("__cfg_probe");
|
|
34
|
+
return s;
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
__name(backend, "backend");
|
|
41
|
+
function load() {
|
|
42
|
+
if (loaded) return memory;
|
|
43
|
+
loaded = true;
|
|
44
|
+
try {
|
|
45
|
+
const raw = backend()?.getItem(KEY);
|
|
46
|
+
const parsed = raw ? JSON.parse(raw) : [];
|
|
47
|
+
if (Array.isArray(parsed)) memory = parsed.filter((e) => typeof e === "object" && e !== null);
|
|
48
|
+
} catch {
|
|
49
|
+
memory = [];
|
|
50
|
+
}
|
|
51
|
+
return memory;
|
|
52
|
+
}
|
|
53
|
+
__name(load, "load");
|
|
54
|
+
function persist() {
|
|
55
|
+
try {
|
|
56
|
+
backend()?.setItem(KEY, JSON.stringify(memory));
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
__name(persist, "persist");
|
|
61
|
+
function clean(fields) {
|
|
62
|
+
if (!fields) return void 0;
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [k2, v] of Object.entries(fields)) {
|
|
65
|
+
if (SENSITIVE.test(k2)) continue;
|
|
66
|
+
if (typeof v === "string") out[k2] = v.length > MAX_STRING ? `${v.slice(0, MAX_STRING)}\u2026` : v;
|
|
67
|
+
else if (typeof v === "number" || typeof v === "boolean" || v === null) out[k2] = v;
|
|
68
|
+
}
|
|
69
|
+
return Object.keys(out).length ? out : void 0;
|
|
70
|
+
}
|
|
71
|
+
__name(clean, "clean");
|
|
72
|
+
function record(at, ev, fields) {
|
|
73
|
+
const entries = load();
|
|
74
|
+
const p = hasWindow && typeof performance !== "undefined" ? Math.round(performance.now()) : 0;
|
|
75
|
+
entries.push({ t: at, p, tab: TAB, ev, f: clean(fields) });
|
|
76
|
+
if (entries.length > MAX) entries.splice(0, entries.length - MAX);
|
|
77
|
+
persist();
|
|
78
|
+
}
|
|
79
|
+
__name(record, "record");
|
|
80
|
+
function authTrace(ev, fields) {
|
|
81
|
+
if (!hasWindow) return;
|
|
82
|
+
try {
|
|
83
|
+
record(Date.now(), ev, fields);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
__name(authTrace, "authTrace");
|
|
88
|
+
function readAuthTrace() {
|
|
89
|
+
return load().slice();
|
|
90
|
+
}
|
|
91
|
+
__name(readAuthTrace, "readAuthTrace");
|
|
92
|
+
function clearAuthTrace() {
|
|
93
|
+
memory = [];
|
|
94
|
+
loaded = true;
|
|
95
|
+
try {
|
|
96
|
+
backend()?.removeItem(KEY);
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
__name(clearAuthTrace, "clearAuthTrace");
|
|
101
|
+
function context() {
|
|
102
|
+
if (!hasWindow) return [];
|
|
103
|
+
const lines = [`url: ${window.location.href.split("?")[0]}`, `online: ${navigator.onLine}`, `ua: ${navigator.userAgent}`];
|
|
104
|
+
try {
|
|
105
|
+
const auth2 = globalThis.__cfgAuthNow;
|
|
106
|
+
const now = auth2?.();
|
|
107
|
+
if (now) lines.push(`now: ${Object.entries(now).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}`);
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
|
112
|
+
__name(context, "context");
|
|
113
|
+
function dumpAuthTrace() {
|
|
114
|
+
const entries = load();
|
|
115
|
+
const first = entries[0]?.t ?? Date.now();
|
|
116
|
+
const rows = entries.map((e) => {
|
|
117
|
+
const fields = e.f ? ` ${Object.entries(e.f).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}` : "";
|
|
118
|
+
const at = new Date(e.t).toISOString().slice(11, 23);
|
|
119
|
+
return `${at} +${String(e.t - first).padStart(6)}ms [${e.tab} p${e.p}] ${e.ev}${fields}`;
|
|
120
|
+
});
|
|
121
|
+
return [`# cfg auth trace, ${entries.length} events`, ...context(), "", ...rows].join("\n");
|
|
122
|
+
}
|
|
123
|
+
__name(dumpAuthTrace, "dumpAuthTrace");
|
|
124
|
+
function installAuthTrace() {
|
|
125
|
+
if (!hasWindow) return;
|
|
126
|
+
const g = globalThis;
|
|
127
|
+
g.__cfgAuthTrace = authTrace;
|
|
128
|
+
g.__cfgAuth = {
|
|
129
|
+
trace: readAuthTrace,
|
|
130
|
+
dump: /* @__PURE__ */ __name(() => {
|
|
131
|
+
const text = dumpAuthTrace();
|
|
132
|
+
console.log(text);
|
|
133
|
+
return text;
|
|
134
|
+
}, "dump"),
|
|
135
|
+
clear: clearAuthTrace
|
|
136
|
+
};
|
|
137
|
+
const queued = g.__cfgAuthTraceQ;
|
|
138
|
+
if (Array.isArray(queued)) {
|
|
139
|
+
g.__cfgAuthTraceQ = [];
|
|
140
|
+
for (const item of queued) {
|
|
141
|
+
if (Array.isArray(item) && typeof item[1] === "string") {
|
|
142
|
+
try {
|
|
143
|
+
record(Number(item[0]) || Date.now(), item[1], item[2]);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
__name(installAuthTrace, "installAuthTrace");
|
|
151
|
+
installAuthTrace();
|
|
152
|
+
|
|
18
153
|
// src/auth/context/AuthContext.tsx
|
|
19
154
|
import { usePathname as usePathname3 } from "next/navigation";
|
|
20
155
|
import {
|
|
@@ -131,10 +266,10 @@ function normalizeSegment(raw) {
|
|
|
131
266
|
return raw.trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
|
|
132
267
|
}
|
|
133
268
|
__name(normalizeSegment, "normalizeSegment");
|
|
134
|
-
var
|
|
269
|
+
var hasWindow2 = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
|
|
135
270
|
var domainByHost = /* @__PURE__ */ new Map();
|
|
136
271
|
function detectCookieDomain() {
|
|
137
|
-
if (!
|
|
272
|
+
if (!hasWindow2() || typeof document === "undefined") return null;
|
|
138
273
|
const host = window.location.hostname.toLowerCase();
|
|
139
274
|
if (!host || !host.includes(".") || host.includes(":") || /^[0-9.]+$/.test(host)) return null;
|
|
140
275
|
const cached = domainByHost.get(host);
|
|
@@ -158,7 +293,7 @@ function detectCookieDomain() {
|
|
|
158
293
|
}
|
|
159
294
|
__name(detectCookieDomain, "detectCookieDomain");
|
|
160
295
|
function nativeStorage(store) {
|
|
161
|
-
if (!
|
|
296
|
+
if (!hasWindow2()) return null;
|
|
162
297
|
try {
|
|
163
298
|
return store === "session" ? window.sessionStorage : window.localStorage;
|
|
164
299
|
} catch {
|
|
@@ -167,7 +302,7 @@ function nativeStorage(store) {
|
|
|
167
302
|
}
|
|
168
303
|
__name(nativeStorage, "nativeStorage");
|
|
169
304
|
function cookieNames() {
|
|
170
|
-
if (!
|
|
305
|
+
if (!hasWindow2()) return [];
|
|
171
306
|
try {
|
|
172
307
|
return document.cookie.split("; ").map((part) => decodeURIComponent(part.split("=")[0] ?? "")).filter(Boolean);
|
|
173
308
|
} catch {
|
|
@@ -192,7 +327,7 @@ function createStorage(owner, options = {}) {
|
|
|
192
327
|
const families = Object.values(options.families ?? {});
|
|
193
328
|
const specByKey = /* @__PURE__ */ new Map();
|
|
194
329
|
for (const spec of specs) specByKey.set(spec.key, [...specByKey.get(spec.key) ?? [], spec]);
|
|
195
|
-
const
|
|
330
|
+
const memory2 = {
|
|
196
331
|
local: /* @__PURE__ */ new Map(),
|
|
197
332
|
session: /* @__PURE__ */ new Map(),
|
|
198
333
|
cookie: /* @__PURE__ */ new Map()
|
|
@@ -206,7 +341,7 @@ function createStorage(owner, options = {}) {
|
|
|
206
341
|
if (!key.startsWith(prefix)) throw new TypeError(`storage key "${key}" is not owned by "${owner}"`);
|
|
207
342
|
}, "assertOwned");
|
|
208
343
|
function rawGet(store, key) {
|
|
209
|
-
if (!
|
|
344
|
+
if (!hasWindow2()) return null;
|
|
210
345
|
if (store === "cookie") {
|
|
211
346
|
try {
|
|
212
347
|
const escaped = encodeURIComponent(key).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -214,18 +349,18 @@ function createStorage(owner, options = {}) {
|
|
|
214
349
|
if (match?.[1] !== void 0) return decodeURIComponent(match[1]);
|
|
215
350
|
} catch {
|
|
216
351
|
}
|
|
217
|
-
return
|
|
352
|
+
return memory2.cookie.get(key) ?? null;
|
|
218
353
|
}
|
|
219
354
|
try {
|
|
220
355
|
const value = nativeStorage(store)?.getItem(key) ?? null;
|
|
221
356
|
if (value !== null) return value;
|
|
222
357
|
} catch {
|
|
223
358
|
}
|
|
224
|
-
return
|
|
359
|
+
return memory2[store].get(key) ?? null;
|
|
225
360
|
}
|
|
226
361
|
__name(rawGet, "rawGet");
|
|
227
362
|
function rawSet(store, key, value, maxAgeSeconds) {
|
|
228
|
-
if (!
|
|
363
|
+
if (!hasWindow2()) return false;
|
|
229
364
|
try {
|
|
230
365
|
if (store === "cookie") {
|
|
231
366
|
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
@@ -233,24 +368,24 @@ function createStorage(owner, options = {}) {
|
|
|
233
368
|
const domainPart = domain ? `; Domain=${domain}` : "";
|
|
234
369
|
document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; Path=/; Max-Age=${Math.max(1, Math.floor(maxAgeSeconds ?? cookieMaxAge))}; SameSite=Lax${domainPart}${secure}`;
|
|
235
370
|
if (domain) expireCookie(key, null);
|
|
236
|
-
if (rawGet("cookie", key) === value && !
|
|
237
|
-
|
|
371
|
+
if (rawGet("cookie", key) === value && !memory2.cookie.has(key)) return true;
|
|
372
|
+
memory2.cookie.set(key, value);
|
|
238
373
|
return false;
|
|
239
374
|
}
|
|
240
375
|
const native = nativeStorage(store);
|
|
241
376
|
if (!native) throw new Error("storage unavailable");
|
|
242
377
|
native.setItem(key, value);
|
|
243
|
-
|
|
378
|
+
memory2[store].delete(key);
|
|
244
379
|
return true;
|
|
245
380
|
} catch {
|
|
246
|
-
|
|
381
|
+
memory2[store].set(key, value);
|
|
247
382
|
return false;
|
|
248
383
|
}
|
|
249
384
|
}
|
|
250
385
|
__name(rawSet, "rawSet");
|
|
251
386
|
function rawRemove(store, key) {
|
|
252
|
-
|
|
253
|
-
if (!
|
|
387
|
+
memory2[store].delete(key);
|
|
388
|
+
if (!hasWindow2()) return;
|
|
254
389
|
try {
|
|
255
390
|
if (store === "cookie") {
|
|
256
391
|
expireCookie(key, null);
|
|
@@ -279,7 +414,7 @@ function createStorage(owner, options = {}) {
|
|
|
279
414
|
}
|
|
280
415
|
} catch {
|
|
281
416
|
}
|
|
282
|
-
for (const name of
|
|
417
|
+
for (const name of memory2[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
|
|
283
418
|
return out;
|
|
284
419
|
}
|
|
285
420
|
__name(listKeys, "listKeys");
|
|
@@ -351,7 +486,7 @@ function createStorage(owner, options = {}) {
|
|
|
351
486
|
cookieDomain: resolveCookieDomain,
|
|
352
487
|
onChange(key, listener, store = "local") {
|
|
353
488
|
assertValid(key);
|
|
354
|
-
if (!
|
|
489
|
+
if (!hasWindow2()) return () => {
|
|
355
490
|
};
|
|
356
491
|
const handler = /* @__PURE__ */ __name((event) => {
|
|
357
492
|
if (event.storageArea !== nativeStorage(store)) return;
|
|
@@ -375,6 +510,29 @@ var REFRESH_KEY = "cfg.auth.refresh_token";
|
|
|
375
510
|
var API_KEY_KEY = "cfg.auth.api_key";
|
|
376
511
|
var storage = true ? cfgStorage : createStorage2("cfg");
|
|
377
512
|
var isBrowser = typeof window !== "undefined";
|
|
513
|
+
function trace(ev, fields) {
|
|
514
|
+
try {
|
|
515
|
+
const g = globalThis;
|
|
516
|
+
const sink2 = g.__cfgAuthTrace;
|
|
517
|
+
if (sink2) {
|
|
518
|
+
sink2(ev, fields);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const queue = g.__cfgAuthTraceQ ?? [];
|
|
522
|
+
g.__cfgAuthTraceQ = queue;
|
|
523
|
+
if (queue.length < 100) queue.push([Date.now(), ev, fields]);
|
|
524
|
+
} catch {
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
__name(trace, "trace");
|
|
528
|
+
function pathOf(url) {
|
|
529
|
+
try {
|
|
530
|
+
return new URL(url, "http://x").pathname;
|
|
531
|
+
} catch {
|
|
532
|
+
return "";
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
__name(pathOf, "pathOf");
|
|
378
536
|
function secondsUntilExp(token) {
|
|
379
537
|
const exp = jwtExpMs(token);
|
|
380
538
|
return exp === null ? void 0 : Math.max(1, Math.floor((exp - Date.now()) / 1e3));
|
|
@@ -534,7 +692,10 @@ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
|
|
|
534
692
|
function notifySessionChanged() {
|
|
535
693
|
const next = computeSnapshot();
|
|
536
694
|
const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
|
|
537
|
-
if (changed)
|
|
695
|
+
if (changed) {
|
|
696
|
+
_snapshot = next;
|
|
697
|
+
trace("store.snapshot", { status: next.status });
|
|
698
|
+
}
|
|
538
699
|
scheduleExpiryFlip();
|
|
539
700
|
if (!changed) return;
|
|
540
701
|
for (const listener of Array.from(_sessionListeners)) {
|
|
@@ -617,6 +778,7 @@ var auth = {
|
|
|
617
778
|
return _storage.get(ACCESS_KEY);
|
|
618
779
|
},
|
|
619
780
|
setToken(token) {
|
|
781
|
+
trace("store.setToken", { present: token !== null });
|
|
620
782
|
_storage.set(ACCESS_KEY, token);
|
|
621
783
|
notifySessionChanged();
|
|
622
784
|
},
|
|
@@ -624,14 +786,16 @@ var auth = {
|
|
|
624
786
|
return _storage.get(REFRESH_KEY);
|
|
625
787
|
},
|
|
626
788
|
setRefreshToken(token) {
|
|
789
|
+
trace("store.setRefreshToken", { present: token !== null });
|
|
627
790
|
_storage.set(REFRESH_KEY, token);
|
|
628
791
|
notifySessionChanged();
|
|
629
792
|
},
|
|
630
793
|
clearTokens() {
|
|
794
|
+
trace("store.clearTokens");
|
|
631
795
|
for (const mode of ["localStorage", "sessionStorage", "cookie"]) {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
796
|
+
const backend2 = backendFor(mode);
|
|
797
|
+
backend2.set(ACCESS_KEY, null);
|
|
798
|
+
backend2.set(REFRESH_KEY, null);
|
|
635
799
|
}
|
|
636
800
|
storage.clearAll();
|
|
637
801
|
notifySessionChanged();
|
|
@@ -650,6 +814,7 @@ var auth = {
|
|
|
650
814
|
* rotation); `refresh: null` explicitly drops it.
|
|
651
815
|
*/
|
|
652
816
|
setSession(tokens) {
|
|
817
|
+
trace("store.setSession", { refresh: tokens.refresh === void 0 ? "kept" : tokens.refresh === null ? "dropped" : "set" });
|
|
653
818
|
_storage.set(ACCESS_KEY, tokens.access);
|
|
654
819
|
if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
|
|
655
820
|
notifySessionChanged();
|
|
@@ -788,7 +953,11 @@ async function tryRefresh() {
|
|
|
788
953
|
const refresh = auth.getRefreshToken();
|
|
789
954
|
if (!refresh) return null;
|
|
790
955
|
const result = await _refreshHandler(refresh);
|
|
791
|
-
if (!result?.access)
|
|
956
|
+
if (!result?.access) {
|
|
957
|
+
trace("refresh", { ok: false, why: "no-access" });
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
960
|
+
trace("refresh", { ok: true, rotated: Boolean(result.refresh) });
|
|
792
961
|
auth.setToken(result.access);
|
|
793
962
|
if (result.refresh) auth.setRefreshToken(result.refresh);
|
|
794
963
|
return result.access;
|
|
@@ -801,6 +970,7 @@ async function tryRefresh() {
|
|
|
801
970
|
}
|
|
802
971
|
return await runRefresh();
|
|
803
972
|
} catch {
|
|
973
|
+
trace("refresh", { ok: false, why: "threw" });
|
|
804
974
|
return null;
|
|
805
975
|
} finally {
|
|
806
976
|
_refreshInflight = null;
|
|
@@ -809,7 +979,8 @@ async function tryRefresh() {
|
|
|
809
979
|
return _refreshInflight;
|
|
810
980
|
}
|
|
811
981
|
__name(tryRefresh, "tryRefresh");
|
|
812
|
-
function expireSession(response) {
|
|
982
|
+
function expireSession(response, why) {
|
|
983
|
+
trace("store.expire", { why, status: response.status, path: pathOf(response.url), handlers: _sessionExpiredHandlers.size });
|
|
813
984
|
auth.clearTokens();
|
|
814
985
|
for (const cb of Array.from(_sessionExpiredHandlers)) {
|
|
815
986
|
try {
|
|
@@ -951,13 +1122,17 @@ function installAuthOnClient(client2) {
|
|
|
951
1122
|
});
|
|
952
1123
|
client2.interceptors.response.use(async (response, request) => {
|
|
953
1124
|
if (response.status !== 401) return response;
|
|
1125
|
+
if (/\/accounts\/(otp|oauth|login|register|token)\b/.test(request.url)) {
|
|
1126
|
+
trace("401.passthrough", { path: pathOf(request.url) });
|
|
1127
|
+
return response;
|
|
1128
|
+
}
|
|
954
1129
|
if (request.headers.get(RETRY_MARKER)) {
|
|
955
|
-
expireSession(response);
|
|
1130
|
+
expireSession(response, "retry-marker");
|
|
956
1131
|
return response;
|
|
957
1132
|
}
|
|
958
1133
|
const newToken = await tryRefresh();
|
|
959
1134
|
if (!newToken) {
|
|
960
|
-
expireSession(response);
|
|
1135
|
+
expireSession(response, "refresh-failed");
|
|
961
1136
|
return response;
|
|
962
1137
|
}
|
|
963
1138
|
const retry = request.clone();
|
|
@@ -969,7 +1144,7 @@ function installAuthOnClient(client2) {
|
|
|
969
1144
|
}
|
|
970
1145
|
try {
|
|
971
1146
|
const retried = await fetch(retry);
|
|
972
|
-
if (retried.status === 401) expireSession(retried);
|
|
1147
|
+
if (retried.status === 401) expireSession(retried, "retry-401");
|
|
973
1148
|
return retried;
|
|
974
1149
|
} catch {
|
|
975
1150
|
return response;
|
|
@@ -1193,6 +1368,16 @@ __name(useTimezone, "useTimezone");
|
|
|
1193
1368
|
// src/auth/hooks/useCfgRouter.ts
|
|
1194
1369
|
import { useRouter as useNextRouter } from "next/navigation";
|
|
1195
1370
|
import { useCallback as useCallback2, useMemo } from "react";
|
|
1371
|
+
var hardNavigating = false;
|
|
1372
|
+
if (typeof window !== "undefined") {
|
|
1373
|
+
window.addEventListener("pageshow", (e) => {
|
|
1374
|
+
if (e.persisted) hardNavigating = false;
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
function fromPath() {
|
|
1378
|
+
return typeof window === "undefined" ? "" : window.location.pathname;
|
|
1379
|
+
}
|
|
1380
|
+
__name(fromPath, "fromPath");
|
|
1196
1381
|
function getBasePath() {
|
|
1197
1382
|
if (typeof process === "undefined") {
|
|
1198
1383
|
return "";
|
|
@@ -1213,6 +1398,7 @@ function useCfgRouter() {
|
|
|
1213
1398
|
const router = useNextRouter();
|
|
1214
1399
|
const basePath = useMemo(() => getBasePath(), []);
|
|
1215
1400
|
const push = useCallback2((href, options) => {
|
|
1401
|
+
authTrace("nav.push", { from: fromPath(), to: href });
|
|
1216
1402
|
if (basePath) {
|
|
1217
1403
|
window.location.href = withBasePath(href, basePath);
|
|
1218
1404
|
} else {
|
|
@@ -1220,6 +1406,7 @@ function useCfgRouter() {
|
|
|
1220
1406
|
}
|
|
1221
1407
|
}, [router, basePath]);
|
|
1222
1408
|
const replace = useCallback2((href, options) => {
|
|
1409
|
+
authTrace("nav.replace", { from: fromPath(), to: href });
|
|
1223
1410
|
if (basePath) {
|
|
1224
1411
|
window.location.replace(withBasePath(href, basePath));
|
|
1225
1412
|
} else {
|
|
@@ -1227,9 +1414,21 @@ function useCfgRouter() {
|
|
|
1227
1414
|
}
|
|
1228
1415
|
}, [router, basePath]);
|
|
1229
1416
|
const hardPush = useCallback2((href) => {
|
|
1417
|
+
if (hardNavigating) {
|
|
1418
|
+
authTrace("nav.hardPush.ignored", { from: fromPath(), to: href });
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
hardNavigating = true;
|
|
1422
|
+
authTrace("nav.hardPush", { from: fromPath(), to: href });
|
|
1230
1423
|
window.location.href = withBasePath(href, basePath);
|
|
1231
1424
|
}, [basePath]);
|
|
1232
1425
|
const hardReplace = useCallback2((href) => {
|
|
1426
|
+
if (hardNavigating) {
|
|
1427
|
+
authTrace("nav.hardReplace.ignored", { from: fromPath(), to: href });
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
hardNavigating = true;
|
|
1431
|
+
authTrace("nav.hardReplace", { from: fromPath(), to: href });
|
|
1233
1432
|
window.location.replace(withBasePath(href, basePath));
|
|
1234
1433
|
}, [basePath]);
|
|
1235
1434
|
const prefetch = useCallback2((href) => {
|
|
@@ -3195,7 +3394,7 @@ var OAuthErrorCodeEnum = /* @__PURE__ */ ((OAuthErrorCodeEnum2) => {
|
|
|
3195
3394
|
|
|
3196
3395
|
// src/auth/utils/errors.ts
|
|
3197
3396
|
var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
|
|
3198
|
-
var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
|
|
3397
|
+
var TRANSPORT_FALLBACK = /^HTTP \d{1,3}:\s*/;
|
|
3199
3398
|
var AUTH_ERROR_KEYS = {
|
|
3200
3399
|
invalidCredentials: "ui.errors.unauthorized",
|
|
3201
3400
|
rateLimited: "api.errors.rateLimited",
|
|
@@ -3212,6 +3411,7 @@ var FALLBACK_COPY = {
|
|
|
3212
3411
|
};
|
|
3213
3412
|
var keyForStatus = /* @__PURE__ */ __name((status) => {
|
|
3214
3413
|
if (status === void 0) return AUTH_ERROR_KEYS.generic;
|
|
3414
|
+
if (status === 0) return AUTH_ERROR_KEYS.networkError;
|
|
3215
3415
|
if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
|
|
3216
3416
|
if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
|
|
3217
3417
|
if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
|
|
@@ -3256,9 +3456,9 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
|
|
|
3256
3456
|
|
|
3257
3457
|
// src/auth/utils/oauthErrors.ts
|
|
3258
3458
|
var SERVER_CODES = new Set(Object.values(OAuthErrorCodeEnum));
|
|
3259
|
-
var
|
|
3459
|
+
var record2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
|
|
3260
3460
|
var readOAuthFailure = /* @__PURE__ */ __name((err, fallback) => {
|
|
3261
|
-
const body =
|
|
3461
|
+
const body = record2(record2(err)?.response);
|
|
3262
3462
|
const code = body?.error;
|
|
3263
3463
|
if (typeof code === "string" && SERVER_CODES.has(code)) {
|
|
3264
3464
|
const description = body?.error_description;
|
|
@@ -3537,6 +3737,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3537
3737
|
setLoadingProvider(provider);
|
|
3538
3738
|
setError(null);
|
|
3539
3739
|
setErrorCode(null);
|
|
3740
|
+
authTrace("oauth.start");
|
|
3540
3741
|
auth.clearSession();
|
|
3541
3742
|
try {
|
|
3542
3743
|
Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
|
|
@@ -3584,6 +3785,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3584
3785
|
if (!response.access || !response.refresh) {
|
|
3585
3786
|
throw new Error("Invalid response from OAuth callback");
|
|
3586
3787
|
}
|
|
3788
|
+
authTrace("oauth.session");
|
|
3587
3789
|
auth.setSession({ access: response.access, refresh: response.refresh });
|
|
3588
3790
|
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
3589
3791
|
category: "auth" /* AUTH */,
|
|
@@ -3764,6 +3966,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3764
3966
|
setError(null);
|
|
3765
3967
|
}, []);
|
|
3766
3968
|
const handleSuccess = useCallback8((response) => {
|
|
3969
|
+
authTrace("2fa.session");
|
|
3767
3970
|
auth.setSession({ access: response.access_token, refresh: response.refresh_token });
|
|
3768
3971
|
if (response.warning) {
|
|
3769
3972
|
setWarning(response.warning);
|
|
@@ -4415,6 +4618,17 @@ function purgeLegacyTokenStorage() {
|
|
|
4415
4618
|
}
|
|
4416
4619
|
__name(purgeLegacyTokenStorage, "purgeLegacyTokenStorage");
|
|
4417
4620
|
|
|
4621
|
+
// src/auth/utils/authState.ts
|
|
4622
|
+
function resolveAuthStatus(s) {
|
|
4623
|
+
if (!s.clientReady) return "resolving";
|
|
4624
|
+
if (s.snapshotAuthenticated) {
|
|
4625
|
+
return s.profileSettled || s.timedOut ? "authenticated" : "resolving";
|
|
4626
|
+
}
|
|
4627
|
+
if (s.storeAuthenticated && !s.timedOut) return "resolving";
|
|
4628
|
+
return "anonymous";
|
|
4629
|
+
}
|
|
4630
|
+
__name(resolveAuthStatus, "resolveAuthStatus");
|
|
4631
|
+
|
|
4418
4632
|
// src/auth/context/AccountsContext.tsx
|
|
4419
4633
|
import {
|
|
4420
4634
|
createContext,
|
|
@@ -4870,6 +5084,7 @@ function AccountsProvider({ children }) {
|
|
|
4870
5084
|
return;
|
|
4871
5085
|
}
|
|
4872
5086
|
if (result.access && result.refresh) {
|
|
5087
|
+
authTrace("accounts.otp.session");
|
|
4873
5088
|
auth.setSession({ access: result.access, refresh: result.refresh });
|
|
4874
5089
|
try {
|
|
4875
5090
|
await refreshProfile({ callerId, force: true });
|
|
@@ -4895,6 +5110,7 @@ function AccountsProvider({ children }) {
|
|
|
4895
5110
|
authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
|
|
4896
5111
|
});
|
|
4897
5112
|
}
|
|
5113
|
+
authTrace("accounts.logout");
|
|
4898
5114
|
auth.clearSession();
|
|
4899
5115
|
setProfile(void 0);
|
|
4900
5116
|
setProfileError(null);
|
|
@@ -4929,11 +5145,11 @@ function AccountsProvider({ children }) {
|
|
|
4929
5145
|
}
|
|
4930
5146
|
__name(AccountsProvider, "AccountsProvider");
|
|
4931
5147
|
function useAccountsContext() {
|
|
4932
|
-
const
|
|
4933
|
-
if (!
|
|
5148
|
+
const context2 = useContext(AccountsContext);
|
|
5149
|
+
if (!context2) {
|
|
4934
5150
|
throw new Error("useAccountsContext must be used within AccountsProvider");
|
|
4935
5151
|
}
|
|
4936
|
-
return
|
|
5152
|
+
return context2;
|
|
4937
5153
|
}
|
|
4938
5154
|
__name(useAccountsContext, "useAccountsContext");
|
|
4939
5155
|
|
|
@@ -4989,9 +5205,11 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4989
5205
|
const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
|
|
4990
5206
|
if (hasTokens && !auth.isAuthenticated()) {
|
|
4991
5207
|
authLogger.warn("Dead session in storage \u2014 clearing");
|
|
5208
|
+
authTrace("bootstrap.purge", { why: "dead-tokens" });
|
|
4992
5209
|
auth.clearSession();
|
|
4993
5210
|
clearProfileCache();
|
|
4994
5211
|
} else if (!hasTokens && !auth.isAuthenticated()) {
|
|
5212
|
+
authTrace("bootstrap.purge", { why: "no-session" });
|
|
4995
5213
|
clearProfileCache();
|
|
4996
5214
|
purgeLegacyTokenStorage();
|
|
4997
5215
|
if (accountsRef.current.profile) accountsRef.current.logout();
|
|
@@ -5004,9 +5222,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5004
5222
|
return;
|
|
5005
5223
|
}
|
|
5006
5224
|
profileAttemptedRef.current = true;
|
|
5225
|
+
authTrace("bootstrap.profile.start");
|
|
5007
5226
|
let cancelled = false;
|
|
5008
5227
|
accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
|
|
5009
5228
|
authLogger.warn("Profile load failed (session kept):", error);
|
|
5229
|
+
authTrace("bootstrap.profile.failed", {
|
|
5230
|
+
status: error instanceof APIError ? error.statusCode : 0
|
|
5231
|
+
});
|
|
5010
5232
|
}).finally(() => {
|
|
5011
5233
|
if (!cancelled) setProfileSettled(true);
|
|
5012
5234
|
});
|
|
@@ -5015,11 +5237,47 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5015
5237
|
};
|
|
5016
5238
|
}, [isAuthenticated, hasProfile]);
|
|
5017
5239
|
const isLoading = !profileSettled;
|
|
5018
|
-
const
|
|
5240
|
+
const [clientReady, setClientReady] = useState12(false);
|
|
5241
|
+
const [timedOut, setTimedOut] = useState12(false);
|
|
5242
|
+
useEffect7(() => {
|
|
5243
|
+
setClientReady(true);
|
|
5244
|
+
const timer = setTimeout(() => setTimedOut(true), 8e3);
|
|
5245
|
+
return () => clearTimeout(timer);
|
|
5246
|
+
}, []);
|
|
5247
|
+
const status = resolveAuthStatus({
|
|
5248
|
+
clientReady,
|
|
5249
|
+
snapshotAuthenticated: isAuthenticated,
|
|
5250
|
+
storeAuthenticated: isBrowser2 ? auth.isAuthenticated() : false,
|
|
5251
|
+
profileSettled,
|
|
5252
|
+
timedOut
|
|
5253
|
+
});
|
|
5254
|
+
const lastStatusRef = useRef7("");
|
|
5255
|
+
useEffect7(() => {
|
|
5256
|
+
if (lastStatusRef.current === status) return;
|
|
5257
|
+
lastStatusRef.current = status;
|
|
5258
|
+
authTrace("status", {
|
|
5259
|
+
status,
|
|
5260
|
+
snapshot: isAuthenticated,
|
|
5261
|
+
store: auth.isAuthenticated(),
|
|
5262
|
+
profileSettled,
|
|
5263
|
+
timedOut,
|
|
5264
|
+
path: pathname ?? ""
|
|
5265
|
+
});
|
|
5266
|
+
}, [status, isAuthenticated, profileSettled, timedOut, pathname]);
|
|
5267
|
+
useEffect7(() => {
|
|
5268
|
+
globalThis.__cfgAuthNow = () => ({
|
|
5269
|
+
status,
|
|
5270
|
+
snapshot: isAuthenticated,
|
|
5271
|
+
store: auth.isAuthenticated(),
|
|
5272
|
+
hasToken: Boolean(auth.getToken()),
|
|
5273
|
+
hasRefresh: Boolean(auth.getRefreshToken()),
|
|
5274
|
+
profileSettled,
|
|
5275
|
+
timedOut
|
|
5276
|
+
});
|
|
5277
|
+
}, [status, isAuthenticated, profileSettled, timedOut]);
|
|
5019
5278
|
useEffect7(() => {
|
|
5020
5279
|
const unsubscribe = auth.onSessionExpired(() => {
|
|
5021
|
-
|
|
5022
|
-
expiredHandledRef.current = true;
|
|
5280
|
+
authTrace("expired.handler", { path: pathname ?? "" });
|
|
5023
5281
|
authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
|
|
5024
5282
|
clearProfileCache();
|
|
5025
5283
|
Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
|
|
@@ -5028,12 +5286,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5028
5286
|
router.hardReplace(routesRef.current.defaultAuthCallback);
|
|
5029
5287
|
});
|
|
5030
5288
|
return unsubscribe;
|
|
5031
|
-
}, [router]);
|
|
5289
|
+
}, [router, pathname]);
|
|
5032
5290
|
useEffect7(() => {
|
|
5033
5291
|
if (!isAuthenticated || pendingManualNavRef.current) return;
|
|
5034
5292
|
if (pathname === routes.auth && !queryParams.get("flow")) {
|
|
5035
5293
|
const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
|
|
5036
5294
|
if (saved) redirectManager.clearRedirect();
|
|
5295
|
+
authTrace("leave-auth", { to: saved || routes.defaultCallback, saved: Boolean(saved) });
|
|
5037
5296
|
router.push(saved || routes.defaultCallback);
|
|
5038
5297
|
}
|
|
5039
5298
|
}, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
|
|
@@ -5059,6 +5318,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5059
5318
|
}, [loadCurrentProfile, router]);
|
|
5060
5319
|
const requestOTP = useCallback13(
|
|
5061
5320
|
async (identifier, sourceUrl, consent) => {
|
|
5321
|
+
authTrace("otp.request");
|
|
5062
5322
|
auth.clearSession();
|
|
5063
5323
|
try {
|
|
5064
5324
|
const body = {
|
|
@@ -5103,6 +5363,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5103
5363
|
async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
|
|
5104
5364
|
try {
|
|
5105
5365
|
pendingManualNavRef.current = Boolean(skipRedirect);
|
|
5366
|
+
authTrace("otp.verify", { skipRedirect: Boolean(skipRedirect) });
|
|
5106
5367
|
const result = await accountsRef.current.verifyOTP({
|
|
5107
5368
|
identifier,
|
|
5108
5369
|
otp: otpCode,
|
|
@@ -5164,6 +5425,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5164
5425
|
const { redirectUrl, skipRedirect } = options || {};
|
|
5165
5426
|
try {
|
|
5166
5427
|
pendingManualNavRef.current = Boolean(skipRedirect);
|
|
5428
|
+
authTrace("password.login", { skipRedirect: Boolean(skipRedirect) });
|
|
5167
5429
|
const result = await accountsRef.current.loginWithPassword({
|
|
5168
5430
|
email,
|
|
5169
5431
|
password
|
|
@@ -5238,6 +5500,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5238
5500
|
return { success: false, message: "Error refreshing token" };
|
|
5239
5501
|
}, []);
|
|
5240
5502
|
const logout = useCallback13(() => {
|
|
5503
|
+
authTrace("logout");
|
|
5241
5504
|
Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
|
|
5242
5505
|
category: "auth" /* AUTH */
|
|
5243
5506
|
});
|
|
@@ -5264,6 +5527,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5264
5527
|
() => ({
|
|
5265
5528
|
user,
|
|
5266
5529
|
isLoading,
|
|
5530
|
+
status,
|
|
5267
5531
|
// Reactive: flips via the store snapshot (token writes, refreshes,
|
|
5268
5532
|
// expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
|
|
5269
5533
|
isAuthenticated,
|
|
@@ -5291,6 +5555,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5291
5555
|
[
|
|
5292
5556
|
user,
|
|
5293
5557
|
isLoading,
|
|
5558
|
+
status,
|
|
5294
5559
|
isAuthenticated,
|
|
5295
5560
|
isAdminUser,
|
|
5296
5561
|
routes,
|
|
@@ -5320,6 +5585,7 @@ var AuthProvider = memo(AuthProviderRaw);
|
|
|
5320
5585
|
var defaultAuthState = {
|
|
5321
5586
|
user: null,
|
|
5322
5587
|
isLoading: false,
|
|
5588
|
+
status: "anonymous",
|
|
5323
5589
|
isAuthenticated: false,
|
|
5324
5590
|
isAdminUser: false,
|
|
5325
5591
|
routes: resolveAuthRoutes(),
|
|
@@ -5372,14 +5638,14 @@ var defaultAuthState = {
|
|
|
5372
5638
|
}, "uploadAvatar")
|
|
5373
5639
|
};
|
|
5374
5640
|
var useAuth = /* @__PURE__ */ __name(() => {
|
|
5375
|
-
const
|
|
5376
|
-
if (
|
|
5641
|
+
const context2 = useContext2(AuthContext);
|
|
5642
|
+
if (context2 === void 0) {
|
|
5377
5643
|
if (isBrowser2 && isDev) {
|
|
5378
5644
|
authLogger.debug("useAuth called outside AuthProvider, returning default state");
|
|
5379
5645
|
}
|
|
5380
5646
|
return defaultAuthState;
|
|
5381
5647
|
}
|
|
5382
|
-
return
|
|
5648
|
+
return context2;
|
|
5383
5649
|
}, "useAuth");
|
|
5384
5650
|
var AuthContext_default = AuthContext;
|
|
5385
5651
|
|
|
@@ -5422,17 +5688,21 @@ export {
|
|
|
5422
5688
|
AuthProvider,
|
|
5423
5689
|
OAUTH_PROVIDER_IDS,
|
|
5424
5690
|
authLogger,
|
|
5691
|
+
authTrace,
|
|
5425
5692
|
beginTwoFactorChallenge,
|
|
5693
|
+
clearAuthTrace,
|
|
5426
5694
|
clearOAuthAttempt,
|
|
5427
5695
|
clearProfileCache,
|
|
5428
5696
|
consumeSavedRedirect,
|
|
5429
5697
|
decodeBase64,
|
|
5698
|
+
dumpAuthTrace,
|
|
5430
5699
|
encodeBase64,
|
|
5431
5700
|
endTwoFactorChallenge,
|
|
5432
5701
|
formatAuthError,
|
|
5433
5702
|
getCacheMetadata,
|
|
5434
5703
|
getCachedProfile,
|
|
5435
5704
|
hasValidCache,
|
|
5705
|
+
installAuthTrace,
|
|
5436
5706
|
isAllowedAuthPath,
|
|
5437
5707
|
isForbidden,
|
|
5438
5708
|
isOAuthProviderId,
|
|
@@ -5440,8 +5710,10 @@ export {
|
|
|
5440
5710
|
logger,
|
|
5441
5711
|
normalizePath,
|
|
5442
5712
|
peekSavedRedirect,
|
|
5713
|
+
readAuthTrace,
|
|
5443
5714
|
readOAuthFailure,
|
|
5444
5715
|
resolveAuthError,
|
|
5716
|
+
resolveAuthStatus,
|
|
5445
5717
|
resolveGuardIsAuthenticated,
|
|
5446
5718
|
resolveGuardIsLoading,
|
|
5447
5719
|
setAnalyticsSink,
|