@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.cjs +441 -106
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +80 -2
- package/dist/auth.d.ts +80 -2
- package/dist/auth.mjs +396 -63
- 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 +264 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +264 -40
- 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/__tests__/sharedContext.test.ts +16 -0
- package/src/auth/__tests__/trace.test.ts +38 -0
- package/src/auth/context/AccountsContext.tsx +6 -2
- package/src/auth/context/AuthContext.tsx +63 -10
- package/src/auth/context/sharedContext.ts +16 -0
- 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 +253 -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,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
|
|
325
|
+
var hasWindow2 = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
|
|
135
326
|
var domainByHost = /* @__PURE__ */ new Map();
|
|
136
327
|
function detectCookieDomain() {
|
|
137
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
415
|
+
return memory2[store].get(key) ?? null;
|
|
225
416
|
}
|
|
226
417
|
__name(rawGet, "rawGet");
|
|
227
418
|
function rawSet(store, key, value, maxAgeSeconds) {
|
|
228
|
-
if (!
|
|
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 && !
|
|
237
|
-
|
|
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
|
-
|
|
434
|
+
memory2[store].delete(key);
|
|
244
435
|
return true;
|
|
245
436
|
} catch {
|
|
246
|
-
|
|
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
|
-
|
|
253
|
-
if (!
|
|
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
|
|
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 (!
|
|
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
|
|
409
|
-
if (access === null &&
|
|
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 (
|
|
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
|
|
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 =
|
|
501
|
-
const refreshAlive =
|
|
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
|
|
738
|
+
const refresh2 = _storage.get(REFRESH_KEY);
|
|
525
739
|
const accessExp = access ? jwtExpMs(access) : null;
|
|
526
|
-
const refreshExp =
|
|
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)
|
|
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
|
|
633
|
-
|
|
634
|
-
|
|
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
|
|
789
|
-
if (!
|
|
790
|
-
const result = await _refreshHandler(
|
|
791
|
-
if (!result?.access)
|
|
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 (
|
|
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 ??
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
4896
|
-
if (
|
|
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
|
|
4936
|
-
if (!
|
|
5212
|
+
const context2 = useContext(AccountsContext);
|
|
5213
|
+
if (!context2) {
|
|
4937
5214
|
throw new Error("useAccountsContext must be used within AccountsProvider");
|
|
4938
5215
|
}
|
|
4939
|
-
return
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
5380
|
-
if (
|
|
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
|
|
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,
|