@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 +308 -40
- 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 +308 -40
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.cjs +43 -6
- package/dist/clients.cjs.map +1 -1
- package/dist/clients.mjs +43 -6
- package/dist/clients.mjs.map +1 -1
- package/dist/hooks.cjs +43 -6
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.mjs +43 -6
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.cjs +198 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +198 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_api/generated/helpers/auth.ts +33 -6
- package/src/auth/__tests__/authState.test.ts +46 -0
- package/src/auth/__tests__/formatAuthError.test.ts +7 -0
- package/src/auth/context/AccountsContext.tsx +3 -0
- package/src/auth/context/AuthContext.tsx +61 -8
- 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 {
|
|
@@ -952,15 +1123,16 @@ function installAuthOnClient(client2) {
|
|
|
952
1123
|
client2.interceptors.response.use(async (response, request) => {
|
|
953
1124
|
if (response.status !== 401) return response;
|
|
954
1125
|
if (/\/accounts\/(otp|oauth|login|register|token)\b/.test(request.url)) {
|
|
1126
|
+
trace("401.passthrough", { path: pathOf(request.url) });
|
|
955
1127
|
return response;
|
|
956
1128
|
}
|
|
957
1129
|
if (request.headers.get(RETRY_MARKER)) {
|
|
958
|
-
expireSession(response);
|
|
1130
|
+
expireSession(response, "retry-marker");
|
|
959
1131
|
return response;
|
|
960
1132
|
}
|
|
961
1133
|
const newToken = await tryRefresh();
|
|
962
1134
|
if (!newToken) {
|
|
963
|
-
expireSession(response);
|
|
1135
|
+
expireSession(response, "refresh-failed");
|
|
964
1136
|
return response;
|
|
965
1137
|
}
|
|
966
1138
|
const retry = request.clone();
|
|
@@ -972,7 +1144,7 @@ function installAuthOnClient(client2) {
|
|
|
972
1144
|
}
|
|
973
1145
|
try {
|
|
974
1146
|
const retried = await fetch(retry);
|
|
975
|
-
if (retried.status === 401) expireSession(retried);
|
|
1147
|
+
if (retried.status === 401) expireSession(retried, "retry-401");
|
|
976
1148
|
return retried;
|
|
977
1149
|
} catch {
|
|
978
1150
|
return response;
|
|
@@ -1196,6 +1368,16 @@ __name(useTimezone, "useTimezone");
|
|
|
1196
1368
|
// src/auth/hooks/useCfgRouter.ts
|
|
1197
1369
|
import { useRouter as useNextRouter } from "next/navigation";
|
|
1198
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");
|
|
1199
1381
|
function getBasePath() {
|
|
1200
1382
|
if (typeof process === "undefined") {
|
|
1201
1383
|
return "";
|
|
@@ -1216,6 +1398,7 @@ function useCfgRouter() {
|
|
|
1216
1398
|
const router = useNextRouter();
|
|
1217
1399
|
const basePath = useMemo(() => getBasePath(), []);
|
|
1218
1400
|
const push = useCallback2((href, options) => {
|
|
1401
|
+
authTrace("nav.push", { from: fromPath(), to: href });
|
|
1219
1402
|
if (basePath) {
|
|
1220
1403
|
window.location.href = withBasePath(href, basePath);
|
|
1221
1404
|
} else {
|
|
@@ -1223,6 +1406,7 @@ function useCfgRouter() {
|
|
|
1223
1406
|
}
|
|
1224
1407
|
}, [router, basePath]);
|
|
1225
1408
|
const replace = useCallback2((href, options) => {
|
|
1409
|
+
authTrace("nav.replace", { from: fromPath(), to: href });
|
|
1226
1410
|
if (basePath) {
|
|
1227
1411
|
window.location.replace(withBasePath(href, basePath));
|
|
1228
1412
|
} else {
|
|
@@ -1230,9 +1414,21 @@ function useCfgRouter() {
|
|
|
1230
1414
|
}
|
|
1231
1415
|
}, [router, basePath]);
|
|
1232
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 });
|
|
1233
1423
|
window.location.href = withBasePath(href, basePath);
|
|
1234
1424
|
}, [basePath]);
|
|
1235
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 });
|
|
1236
1432
|
window.location.replace(withBasePath(href, basePath));
|
|
1237
1433
|
}, [basePath]);
|
|
1238
1434
|
const prefetch = useCallback2((href) => {
|
|
@@ -3198,7 +3394,7 @@ var OAuthErrorCodeEnum = /* @__PURE__ */ ((OAuthErrorCodeEnum2) => {
|
|
|
3198
3394
|
|
|
3199
3395
|
// src/auth/utils/errors.ts
|
|
3200
3396
|
var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
|
|
3201
|
-
var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
|
|
3397
|
+
var TRANSPORT_FALLBACK = /^HTTP \d{1,3}:\s*/;
|
|
3202
3398
|
var AUTH_ERROR_KEYS = {
|
|
3203
3399
|
invalidCredentials: "ui.errors.unauthorized",
|
|
3204
3400
|
rateLimited: "api.errors.rateLimited",
|
|
@@ -3215,6 +3411,7 @@ var FALLBACK_COPY = {
|
|
|
3215
3411
|
};
|
|
3216
3412
|
var keyForStatus = /* @__PURE__ */ __name((status) => {
|
|
3217
3413
|
if (status === void 0) return AUTH_ERROR_KEYS.generic;
|
|
3414
|
+
if (status === 0) return AUTH_ERROR_KEYS.networkError;
|
|
3218
3415
|
if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
|
|
3219
3416
|
if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
|
|
3220
3417
|
if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
|
|
@@ -3259,9 +3456,9 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
|
|
|
3259
3456
|
|
|
3260
3457
|
// src/auth/utils/oauthErrors.ts
|
|
3261
3458
|
var SERVER_CODES = new Set(Object.values(OAuthErrorCodeEnum));
|
|
3262
|
-
var
|
|
3459
|
+
var record2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
|
|
3263
3460
|
var readOAuthFailure = /* @__PURE__ */ __name((err, fallback) => {
|
|
3264
|
-
const body =
|
|
3461
|
+
const body = record2(record2(err)?.response);
|
|
3265
3462
|
const code = body?.error;
|
|
3266
3463
|
if (typeof code === "string" && SERVER_CODES.has(code)) {
|
|
3267
3464
|
const description = body?.error_description;
|
|
@@ -3540,6 +3737,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3540
3737
|
setLoadingProvider(provider);
|
|
3541
3738
|
setError(null);
|
|
3542
3739
|
setErrorCode(null);
|
|
3740
|
+
authTrace("oauth.start");
|
|
3543
3741
|
auth.clearSession();
|
|
3544
3742
|
try {
|
|
3545
3743
|
Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
|
|
@@ -3587,6 +3785,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3587
3785
|
if (!response.access || !response.refresh) {
|
|
3588
3786
|
throw new Error("Invalid response from OAuth callback");
|
|
3589
3787
|
}
|
|
3788
|
+
authTrace("oauth.session");
|
|
3590
3789
|
auth.setSession({ access: response.access, refresh: response.refresh });
|
|
3591
3790
|
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
3592
3791
|
category: "auth" /* AUTH */,
|
|
@@ -3767,6 +3966,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3767
3966
|
setError(null);
|
|
3768
3967
|
}, []);
|
|
3769
3968
|
const handleSuccess = useCallback8((response) => {
|
|
3969
|
+
authTrace("2fa.session");
|
|
3770
3970
|
auth.setSession({ access: response.access_token, refresh: response.refresh_token });
|
|
3771
3971
|
if (response.warning) {
|
|
3772
3972
|
setWarning(response.warning);
|
|
@@ -4418,6 +4618,17 @@ function purgeLegacyTokenStorage() {
|
|
|
4418
4618
|
}
|
|
4419
4619
|
__name(purgeLegacyTokenStorage, "purgeLegacyTokenStorage");
|
|
4420
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
|
+
|
|
4421
4632
|
// src/auth/context/AccountsContext.tsx
|
|
4422
4633
|
import {
|
|
4423
4634
|
createContext,
|
|
@@ -4873,6 +5084,7 @@ function AccountsProvider({ children }) {
|
|
|
4873
5084
|
return;
|
|
4874
5085
|
}
|
|
4875
5086
|
if (result.access && result.refresh) {
|
|
5087
|
+
authTrace("accounts.otp.session");
|
|
4876
5088
|
auth.setSession({ access: result.access, refresh: result.refresh });
|
|
4877
5089
|
try {
|
|
4878
5090
|
await refreshProfile({ callerId, force: true });
|
|
@@ -4898,6 +5110,7 @@ function AccountsProvider({ children }) {
|
|
|
4898
5110
|
authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
|
|
4899
5111
|
});
|
|
4900
5112
|
}
|
|
5113
|
+
authTrace("accounts.logout");
|
|
4901
5114
|
auth.clearSession();
|
|
4902
5115
|
setProfile(void 0);
|
|
4903
5116
|
setProfileError(null);
|
|
@@ -4932,11 +5145,11 @@ function AccountsProvider({ children }) {
|
|
|
4932
5145
|
}
|
|
4933
5146
|
__name(AccountsProvider, "AccountsProvider");
|
|
4934
5147
|
function useAccountsContext() {
|
|
4935
|
-
const
|
|
4936
|
-
if (!
|
|
5148
|
+
const context2 = useContext(AccountsContext);
|
|
5149
|
+
if (!context2) {
|
|
4937
5150
|
throw new Error("useAccountsContext must be used within AccountsProvider");
|
|
4938
5151
|
}
|
|
4939
|
-
return
|
|
5152
|
+
return context2;
|
|
4940
5153
|
}
|
|
4941
5154
|
__name(useAccountsContext, "useAccountsContext");
|
|
4942
5155
|
|
|
@@ -4992,14 +5205,15 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
4992
5205
|
const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
|
|
4993
5206
|
if (hasTokens && !auth.isAuthenticated()) {
|
|
4994
5207
|
authLogger.warn("Dead session in storage \u2014 clearing");
|
|
5208
|
+
authTrace("bootstrap.purge", { why: "dead-tokens" });
|
|
4995
5209
|
auth.clearSession();
|
|
4996
5210
|
clearProfileCache();
|
|
4997
5211
|
} else if (!hasTokens && !auth.isAuthenticated()) {
|
|
5212
|
+
authTrace("bootstrap.purge", { why: "no-session" });
|
|
4998
5213
|
clearProfileCache();
|
|
4999
5214
|
purgeLegacyTokenStorage();
|
|
5000
5215
|
if (accountsRef.current.profile) accountsRef.current.logout();
|
|
5001
5216
|
}
|
|
5002
|
-
if (auth.isAuthenticated()) return;
|
|
5003
5217
|
setProfileSettled(true);
|
|
5004
5218
|
return;
|
|
5005
5219
|
}
|
|
@@ -5008,9 +5222,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5008
5222
|
return;
|
|
5009
5223
|
}
|
|
5010
5224
|
profileAttemptedRef.current = true;
|
|
5225
|
+
authTrace("bootstrap.profile.start");
|
|
5011
5226
|
let cancelled = false;
|
|
5012
5227
|
accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
|
|
5013
5228
|
authLogger.warn("Profile load failed (session kept):", error);
|
|
5229
|
+
authTrace("bootstrap.profile.failed", {
|
|
5230
|
+
status: error instanceof APIError ? error.statusCode : 0
|
|
5231
|
+
});
|
|
5014
5232
|
}).finally(() => {
|
|
5015
5233
|
if (!cancelled) setProfileSettled(true);
|
|
5016
5234
|
});
|
|
@@ -5019,11 +5237,47 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5019
5237
|
};
|
|
5020
5238
|
}, [isAuthenticated, hasProfile]);
|
|
5021
5239
|
const isLoading = !profileSettled;
|
|
5022
|
-
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]);
|
|
5023
5278
|
useEffect7(() => {
|
|
5024
5279
|
const unsubscribe = auth.onSessionExpired(() => {
|
|
5025
|
-
|
|
5026
|
-
expiredHandledRef.current = true;
|
|
5280
|
+
authTrace("expired.handler", { path: pathname ?? "" });
|
|
5027
5281
|
authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
|
|
5028
5282
|
clearProfileCache();
|
|
5029
5283
|
Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
|
|
@@ -5032,12 +5286,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5032
5286
|
router.hardReplace(routesRef.current.defaultAuthCallback);
|
|
5033
5287
|
});
|
|
5034
5288
|
return unsubscribe;
|
|
5035
|
-
}, [router]);
|
|
5289
|
+
}, [router, pathname]);
|
|
5036
5290
|
useEffect7(() => {
|
|
5037
5291
|
if (!isAuthenticated || pendingManualNavRef.current) return;
|
|
5038
5292
|
if (pathname === routes.auth && !queryParams.get("flow")) {
|
|
5039
5293
|
const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
|
|
5040
5294
|
if (saved) redirectManager.clearRedirect();
|
|
5295
|
+
authTrace("leave-auth", { to: saved || routes.defaultCallback, saved: Boolean(saved) });
|
|
5041
5296
|
router.push(saved || routes.defaultCallback);
|
|
5042
5297
|
}
|
|
5043
5298
|
}, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
|
|
@@ -5063,6 +5318,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5063
5318
|
}, [loadCurrentProfile, router]);
|
|
5064
5319
|
const requestOTP = useCallback13(
|
|
5065
5320
|
async (identifier, sourceUrl, consent) => {
|
|
5321
|
+
authTrace("otp.request");
|
|
5066
5322
|
auth.clearSession();
|
|
5067
5323
|
try {
|
|
5068
5324
|
const body = {
|
|
@@ -5107,6 +5363,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5107
5363
|
async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
|
|
5108
5364
|
try {
|
|
5109
5365
|
pendingManualNavRef.current = Boolean(skipRedirect);
|
|
5366
|
+
authTrace("otp.verify", { skipRedirect: Boolean(skipRedirect) });
|
|
5110
5367
|
const result = await accountsRef.current.verifyOTP({
|
|
5111
5368
|
identifier,
|
|
5112
5369
|
otp: otpCode,
|
|
@@ -5168,6 +5425,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5168
5425
|
const { redirectUrl, skipRedirect } = options || {};
|
|
5169
5426
|
try {
|
|
5170
5427
|
pendingManualNavRef.current = Boolean(skipRedirect);
|
|
5428
|
+
authTrace("password.login", { skipRedirect: Boolean(skipRedirect) });
|
|
5171
5429
|
const result = await accountsRef.current.loginWithPassword({
|
|
5172
5430
|
email,
|
|
5173
5431
|
password
|
|
@@ -5242,6 +5500,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5242
5500
|
return { success: false, message: "Error refreshing token" };
|
|
5243
5501
|
}, []);
|
|
5244
5502
|
const logout = useCallback13(() => {
|
|
5503
|
+
authTrace("logout");
|
|
5245
5504
|
Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
|
|
5246
5505
|
category: "auth" /* AUTH */
|
|
5247
5506
|
});
|
|
@@ -5268,6 +5527,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5268
5527
|
() => ({
|
|
5269
5528
|
user,
|
|
5270
5529
|
isLoading,
|
|
5530
|
+
status,
|
|
5271
5531
|
// Reactive: flips via the store snapshot (token writes, refreshes,
|
|
5272
5532
|
// expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
|
|
5273
5533
|
isAuthenticated,
|
|
@@ -5295,6 +5555,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5295
5555
|
[
|
|
5296
5556
|
user,
|
|
5297
5557
|
isLoading,
|
|
5558
|
+
status,
|
|
5298
5559
|
isAuthenticated,
|
|
5299
5560
|
isAdminUser,
|
|
5300
5561
|
routes,
|
|
@@ -5324,6 +5585,7 @@ var AuthProvider = memo(AuthProviderRaw);
|
|
|
5324
5585
|
var defaultAuthState = {
|
|
5325
5586
|
user: null,
|
|
5326
5587
|
isLoading: false,
|
|
5588
|
+
status: "anonymous",
|
|
5327
5589
|
isAuthenticated: false,
|
|
5328
5590
|
isAdminUser: false,
|
|
5329
5591
|
routes: resolveAuthRoutes(),
|
|
@@ -5376,14 +5638,14 @@ var defaultAuthState = {
|
|
|
5376
5638
|
}, "uploadAvatar")
|
|
5377
5639
|
};
|
|
5378
5640
|
var useAuth = /* @__PURE__ */ __name(() => {
|
|
5379
|
-
const
|
|
5380
|
-
if (
|
|
5641
|
+
const context2 = useContext2(AuthContext);
|
|
5642
|
+
if (context2 === void 0) {
|
|
5381
5643
|
if (isBrowser2 && isDev) {
|
|
5382
5644
|
authLogger.debug("useAuth called outside AuthProvider, returning default state");
|
|
5383
5645
|
}
|
|
5384
5646
|
return defaultAuthState;
|
|
5385
5647
|
}
|
|
5386
|
-
return
|
|
5648
|
+
return context2;
|
|
5387
5649
|
}, "useAuth");
|
|
5388
5650
|
var AuthContext_default = AuthContext;
|
|
5389
5651
|
|
|
@@ -5426,17 +5688,21 @@ export {
|
|
|
5426
5688
|
AuthProvider,
|
|
5427
5689
|
OAUTH_PROVIDER_IDS,
|
|
5428
5690
|
authLogger,
|
|
5691
|
+
authTrace,
|
|
5429
5692
|
beginTwoFactorChallenge,
|
|
5693
|
+
clearAuthTrace,
|
|
5430
5694
|
clearOAuthAttempt,
|
|
5431
5695
|
clearProfileCache,
|
|
5432
5696
|
consumeSavedRedirect,
|
|
5433
5697
|
decodeBase64,
|
|
5698
|
+
dumpAuthTrace,
|
|
5434
5699
|
encodeBase64,
|
|
5435
5700
|
endTwoFactorChallenge,
|
|
5436
5701
|
formatAuthError,
|
|
5437
5702
|
getCacheMetadata,
|
|
5438
5703
|
getCachedProfile,
|
|
5439
5704
|
hasValidCache,
|
|
5705
|
+
installAuthTrace,
|
|
5440
5706
|
isAllowedAuthPath,
|
|
5441
5707
|
isForbidden,
|
|
5442
5708
|
isOAuthProviderId,
|
|
@@ -5444,8 +5710,10 @@ export {
|
|
|
5444
5710
|
logger,
|
|
5445
5711
|
normalizePath,
|
|
5446
5712
|
peekSavedRedirect,
|
|
5713
|
+
readAuthTrace,
|
|
5447
5714
|
readOAuthFailure,
|
|
5448
5715
|
resolveAuthError,
|
|
5716
|
+
resolveAuthStatus,
|
|
5449
5717
|
resolveGuardIsAuthenticated,
|
|
5450
5718
|
resolveGuardIsLoading,
|
|
5451
5719
|
setAnalyticsSink,
|