@djangocfg/api 2.1.455 → 2.1.457
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 +1611 -2148
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +37 -82
- package/dist/auth.d.ts +37 -82
- package/dist/auth.mjs +1581 -2118
- package/dist/auth.mjs.map +1 -1
- package/dist/chunk-2G67QRNU.mjs +2096 -0
- package/dist/chunk-2G67QRNU.mjs.map +1 -0
- package/dist/chunk-32SRQGAC.mjs +2109 -0
- package/dist/chunk-32SRQGAC.mjs.map +1 -0
- package/dist/chunk-4BPRCONN.mjs +2098 -0
- package/dist/chunk-4BPRCONN.mjs.map +1 -0
- package/dist/chunk-5UZ2Z323.mjs +2021 -0
- package/dist/chunk-5UZ2Z323.mjs.map +1 -0
- package/dist/chunk-7KFTJXNM.mjs +2097 -0
- package/dist/chunk-7KFTJXNM.mjs.map +1 -0
- package/dist/chunk-BK4K5CVT.mjs +2019 -0
- package/dist/chunk-BK4K5CVT.mjs.map +1 -0
- package/dist/chunk-JLPJZ6WB.mjs +2020 -0
- package/dist/chunk-JLPJZ6WB.mjs.map +1 -0
- package/dist/chunk-TVU6PYJH.mjs +2032 -0
- package/dist/chunk-TVU6PYJH.mjs.map +1 -0
- package/dist/clients.cjs +292 -47
- package/dist/clients.cjs.map +1 -1
- package/dist/clients.d.cts +23 -0
- package/dist/clients.d.ts +23 -0
- package/dist/clients.mjs +292 -47
- package/dist/clients.mjs.map +1 -1
- package/dist/hooks.cjs +200 -32
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.mjs +200 -32
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.cjs +292 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -4
- package/dist/index.d.ts +81 -4
- package/dist/index.mjs +292 -47
- package/dist/index.mjs.map +1 -1
- package/dist/sdk.gen-2SQOPTWJ.mjs +26 -0
- package/dist/sdk.gen-2SQOPTWJ.mjs.map +1 -0
- package/dist/sdk.gen-HSGK4C5S.mjs +26 -0
- package/dist/sdk.gen-HSGK4C5S.mjs.map +1 -0
- package/dist/sdk.gen-NUK2VGHO.mjs +25 -0
- package/dist/sdk.gen-NUK2VGHO.mjs.map +1 -0
- package/dist/sdk.gen-O4KAQUZB.mjs +26 -0
- package/dist/sdk.gen-O4KAQUZB.mjs.map +1 -0
- package/dist/sdk.gen-PPAVSBNT.mjs +26 -0
- package/dist/sdk.gen-PPAVSBNT.mjs.map +1 -0
- package/dist/sdk.gen-XLHLOCJ2.mjs +25 -0
- package/dist/sdk.gen-XLHLOCJ2.mjs.map +1 -0
- package/dist/sdk.gen-ZOE6NQAL.mjs +26 -0
- package/dist/sdk.gen-ZOE6NQAL.mjs.map +1 -0
- package/dist/sdk.gen-ZT7LGJVO.mjs +26 -0
- package/dist/sdk.gen-ZT7LGJVO.mjs.map +1 -0
- package/package.json +2 -2
- package/src/_api/generated/_cfg_accounts/hooks/index.ts +1 -0
- package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenBlacklistCreate.ts +28 -0
- package/src/_api/generated/_cfg_accounts/openapi.json +99 -0
- package/src/_api/generated/_cfg_accounts/schemas/TokenBlacklistRequest.ts +11 -0
- package/src/_api/generated/_cfg_accounts/schemas/index.ts +1 -0
- package/src/_api/generated/_cfg_centrifugo/openapi.json +9 -0
- package/src/_api/generated/_cfg_totp/openapi.json +33 -0
- package/src/_api/generated/helpers/auth.ts +288 -31
- package/src/_api/generated/openapi.json +129 -0
- package/src/_api/generated/sdk.gen.ts +94 -16
- package/src/_api/generated/types.gen.ts +18 -0
- package/src/auth/__tests__/guard.test.ts +0 -31
- package/src/auth/constants.ts +6 -0
- package/src/auth/context/AccountsContext.tsx +14 -24
- package/src/auth/context/AuthContext.tsx +263 -607
- package/src/auth/context/types.ts +13 -0
- package/src/auth/hooks/index.ts +8 -5
- package/src/auth/hooks/useAuthRedirect.ts +17 -0
- package/src/auth/hooks/useGithubAuth.ts +3 -3
- package/src/auth/hooks/useSession.ts +22 -0
- package/src/auth/hooks/useTwoFactor.ts +8 -6
- package/src/auth/middlewares/index.ts +5 -6
- package/src/auth/utils/guard.ts +0 -22
- package/src/auth/utils/index.ts +0 -2
- package/src/_api/generated/_cfg_accounts/events.ts +0 -198
- package/src/_api/generated/_cfg_centrifugo/events.ts +0 -198
- package/src/_api/generated/_cfg_totp/events.ts +0 -198
- package/src/auth/__tests__/jwt.test.ts +0 -119
- package/src/auth/__tests__/sessionBootstrap.test.ts +0 -111
- package/src/auth/__tests__/useTokenRefresh.dom.test.tsx +0 -167
- package/src/auth/hooks/useAuthGuard.ts +0 -35
- package/src/auth/hooks/useTokenRefresh.ts +0 -131
- package/src/auth/refreshHandler.ts +0 -79
- package/src/auth/utils/jwt.ts +0 -66
package/dist/auth.mjs
CHANGED
|
@@ -12,905 +12,1077 @@ var AUTH_CONSTANTS = {
|
|
|
12
12
|
BACKUP_CODE_MAX_LENGTH: 12
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
// src/auth/utils/env.ts
|
|
16
|
-
var isDev = process.env.NODE_ENV === "development";
|
|
17
|
-
var isBrowser = typeof window !== "undefined";
|
|
18
|
-
var isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
19
|
-
var dpopEnabled = process.env.NEXT_PUBLIC_DPOP_ENABLED === "true";
|
|
20
|
-
|
|
21
15
|
// src/auth/context/AuthContext.tsx
|
|
22
16
|
import { usePathname as usePathname3 } from "next/navigation";
|
|
23
17
|
import {
|
|
24
18
|
createContext as createContext2,
|
|
25
19
|
memo as memo2,
|
|
26
|
-
useCallback as
|
|
20
|
+
useCallback as useCallback11,
|
|
27
21
|
useContext as useContext2,
|
|
28
|
-
useEffect as
|
|
22
|
+
useEffect as useEffect7,
|
|
29
23
|
useMemo as useMemo3,
|
|
30
24
|
useRef as useRef6,
|
|
31
|
-
useState as
|
|
25
|
+
useState as useState11
|
|
32
26
|
} from "react";
|
|
33
|
-
import { SWRConfig } from "swr";
|
|
34
27
|
|
|
35
|
-
// src/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
// src/_api/generated/helpers/errors.ts
|
|
29
|
+
var APIError = class extends Error {
|
|
30
|
+
constructor(statusCode, statusText, response, url, message) {
|
|
31
|
+
super(message || `HTTP ${statusCode}: ${statusText}`);
|
|
32
|
+
this.statusCode = statusCode;
|
|
33
|
+
this.statusText = statusText;
|
|
34
|
+
this.response = response;
|
|
35
|
+
this.url = url;
|
|
36
|
+
this.name = "APIError";
|
|
41
37
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
__name(getBasePath, "getBasePath");
|
|
45
|
-
function withBasePath(path, basePath) {
|
|
46
|
-
if (!basePath) {
|
|
47
|
-
return path;
|
|
38
|
+
static {
|
|
39
|
+
__name(this, "APIError");
|
|
48
40
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
get details() {
|
|
42
|
+
if (typeof this.response === "object" && this.response !== null) {
|
|
43
|
+
return this.response;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
get fieldErrors() {
|
|
48
|
+
const details = this.details;
|
|
49
|
+
if (!details) return null;
|
|
50
|
+
const fieldErrors = {};
|
|
51
|
+
for (const [key, value] of Object.entries(details)) {
|
|
52
|
+
if (Array.isArray(value)) fieldErrors[key] = value;
|
|
53
|
+
}
|
|
54
|
+
return Object.keys(fieldErrors).length > 0 ? fieldErrors : null;
|
|
55
|
+
}
|
|
56
|
+
get errorMessage() {
|
|
57
|
+
const details = this.details;
|
|
58
|
+
if (!details) return this.message;
|
|
59
|
+
if (details.detail) {
|
|
60
|
+
return Array.isArray(details.detail) ? details.detail.join(", ") : String(details.detail);
|
|
61
|
+
}
|
|
62
|
+
if (details.error) return String(details.error);
|
|
63
|
+
if (details.message) return String(details.message);
|
|
64
|
+
const fieldErrors = this.fieldErrors;
|
|
65
|
+
if (fieldErrors) {
|
|
66
|
+
const firstField = Object.keys(fieldErrors)[0];
|
|
67
|
+
if (firstField) return `${firstField}: ${fieldErrors[firstField]?.join(", ")}`;
|
|
68
|
+
}
|
|
69
|
+
return this.message;
|
|
70
|
+
}
|
|
71
|
+
get isValidationError() {
|
|
72
|
+
return this.statusCode === 400;
|
|
73
|
+
}
|
|
74
|
+
get isAuthError() {
|
|
75
|
+
return this.statusCode === 401;
|
|
76
|
+
}
|
|
77
|
+
get isPermissionError() {
|
|
78
|
+
return this.statusCode === 403;
|
|
79
|
+
}
|
|
80
|
+
get isNotFoundError() {
|
|
81
|
+
return this.statusCode === 404;
|
|
82
|
+
}
|
|
83
|
+
get isServerError() {
|
|
84
|
+
return this.statusCode >= 500 && this.statusCode < 600;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/_api/generated/helpers/auth.ts
|
|
89
|
+
var ACCESS_KEY = "cfg.access_token";
|
|
90
|
+
var REFRESH_KEY = "cfg.refresh_token";
|
|
91
|
+
var API_KEY_KEY = "cfg.api_key";
|
|
92
|
+
var isBrowser = typeof window !== "undefined";
|
|
93
|
+
var localStorageBackend = {
|
|
94
|
+
get(key) {
|
|
95
|
+
if (!isBrowser) return null;
|
|
96
|
+
try {
|
|
97
|
+
return window.localStorage.getItem(key);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
set(key, value) {
|
|
103
|
+
if (!isBrowser) return;
|
|
104
|
+
try {
|
|
105
|
+
if (value === null) window.localStorage.removeItem(key);
|
|
106
|
+
else window.localStorage.setItem(key, value);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
|
112
|
+
var cookieBackend = {
|
|
113
|
+
get(key) {
|
|
114
|
+
if (!isBrowser) return null;
|
|
115
|
+
try {
|
|
116
|
+
const re = new RegExp(`(?:^|;\\s*)${encodeURIComponent(key)}=([^;]*)`);
|
|
117
|
+
const m = document.cookie.match(re);
|
|
118
|
+
return m ? decodeURIComponent(m[1]) : null;
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
set(key, value) {
|
|
124
|
+
if (!isBrowser) return;
|
|
125
|
+
try {
|
|
126
|
+
const k = encodeURIComponent(key);
|
|
127
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
128
|
+
if (value === null) {
|
|
129
|
+
document.cookie = `${k}=; Path=/; Max-Age=0; SameSite=Lax${secure}`;
|
|
130
|
+
} else {
|
|
131
|
+
const v = encodeURIComponent(value);
|
|
132
|
+
document.cookie = `${k}=${v}; Path=/; Max-Age=${COOKIE_MAX_AGE}; SameSite=Lax${secure}`;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
var _storage = localStorageBackend;
|
|
139
|
+
var _storageMode = "localStorage";
|
|
140
|
+
function detectLocale() {
|
|
141
|
+
try {
|
|
142
|
+
if (typeof document !== "undefined") {
|
|
143
|
+
const m = document.cookie.match(/(?:^|;\s*)NEXT_LOCALE=([^;]*)/);
|
|
144
|
+
if (m) return decodeURIComponent(m[1]);
|
|
145
|
+
}
|
|
146
|
+
if (typeof navigator !== "undefined" && navigator.language) {
|
|
147
|
+
return navigator.language;
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
52
152
|
}
|
|
53
|
-
__name(
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
router.push(href, options);
|
|
153
|
+
__name(detectLocale, "detectLocale");
|
|
154
|
+
function defaultBaseUrl() {
|
|
155
|
+
if (typeof window !== "undefined") {
|
|
156
|
+
try {
|
|
157
|
+
if (typeof process !== "undefined" && process.env) {
|
|
158
|
+
if (process.env.NEXT_PUBLIC_STATIC_BUILD === "true") return "";
|
|
159
|
+
if (process.env.NEXT_PUBLIC_API_PROXY_URL !== void 0)
|
|
160
|
+
return process.env.NEXT_PUBLIC_API_PROXY_URL;
|
|
161
|
+
return process.env.NEXT_PUBLIC_API_URL || "";
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
65
164
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
if (typeof process !== "undefined" && process.env) {
|
|
169
|
+
if (process.env.NEXT_PUBLIC_STATIC_BUILD === "true") return "";
|
|
170
|
+
return process.env.NEXT_PUBLIC_API_URL || "";
|
|
72
171
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
172
|
+
} catch {
|
|
173
|
+
}
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
__name(defaultBaseUrl, "defaultBaseUrl");
|
|
177
|
+
function defaultApiKey() {
|
|
178
|
+
try {
|
|
179
|
+
if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_API_KEY) {
|
|
180
|
+
return process.env.NEXT_PUBLIC_API_KEY;
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
__name(defaultApiKey, "defaultApiKey");
|
|
187
|
+
var _localeOverride = null;
|
|
188
|
+
var _apiKeyOverride = null;
|
|
189
|
+
var _baseUrlOverride = null;
|
|
190
|
+
var _withCredentials = true;
|
|
191
|
+
var _onUnauthorized = null;
|
|
192
|
+
var _refreshHandler = null;
|
|
193
|
+
var _refreshInflight = null;
|
|
194
|
+
var RETRY_MARKER = "X-Auth-Retry";
|
|
195
|
+
function jwtExpMs(token) {
|
|
196
|
+
try {
|
|
197
|
+
const payload = token.split(".")[1];
|
|
198
|
+
if (!payload) return null;
|
|
199
|
+
const json = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
|
|
200
|
+
return typeof json.exp === "number" ? json.exp * 1e3 : null;
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
__name(jwtExpMs, "jwtExpMs");
|
|
206
|
+
function computeSnapshot() {
|
|
207
|
+
const access = _storage.get(ACCESS_KEY);
|
|
208
|
+
const refresh = _storage.get(REFRESH_KEY);
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
const accessExp = access ? jwtExpMs(access) : null;
|
|
211
|
+
const accessAlive = access !== null && (accessExp === null || accessExp > now);
|
|
212
|
+
const refreshExp = refresh ? jwtExpMs(refresh) : null;
|
|
213
|
+
const refreshAlive = refresh !== null && (refreshExp === null || refreshExp > now);
|
|
92
214
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
hardPush,
|
|
96
|
-
hardReplace,
|
|
97
|
-
prefetch,
|
|
98
|
-
back,
|
|
99
|
-
forward,
|
|
100
|
-
refresh
|
|
215
|
+
status: accessAlive || refreshAlive ? "authenticated" : "anonymous",
|
|
216
|
+
accessExpiresAt: accessExp
|
|
101
217
|
};
|
|
102
218
|
}
|
|
103
|
-
__name(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
219
|
+
__name(computeSnapshot, "computeSnapshot");
|
|
220
|
+
var SERVER_SNAPSHOT = { status: "anonymous", accessExpiresAt: null };
|
|
221
|
+
var SESSION_SYNC_EVENT = `cfg-auth:changed:${ACCESS_KEY}`;
|
|
222
|
+
var _snapshot = computeSnapshot();
|
|
223
|
+
var _sessionListeners = /* @__PURE__ */ new Set();
|
|
224
|
+
var _expiryTimer = null;
|
|
225
|
+
var _storageListenerInstalled = false;
|
|
226
|
+
function scheduleExpiryFlip() {
|
|
227
|
+
if (!isBrowser) return;
|
|
228
|
+
if (_expiryTimer !== null) {
|
|
229
|
+
clearTimeout(_expiryTimer);
|
|
230
|
+
_expiryTimer = null;
|
|
231
|
+
}
|
|
232
|
+
if (_sessionListeners.size === 0) return;
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
const exps = [];
|
|
235
|
+
const access = _storage.get(ACCESS_KEY);
|
|
236
|
+
const refresh = _storage.get(REFRESH_KEY);
|
|
237
|
+
const accessExp = access ? jwtExpMs(access) : null;
|
|
238
|
+
const refreshExp = refresh ? jwtExpMs(refresh) : null;
|
|
239
|
+
if (accessExp !== null && accessExp > now) exps.push(accessExp);
|
|
240
|
+
if (refreshExp !== null && refreshExp > now) exps.push(refreshExp);
|
|
241
|
+
if (!exps.length) return;
|
|
242
|
+
const delay = Math.min(Math.min(...exps) - now + 250, 2147483647);
|
|
243
|
+
_expiryTimer = setTimeout(notifySessionChanged, delay);
|
|
244
|
+
}
|
|
245
|
+
__name(scheduleExpiryFlip, "scheduleExpiryFlip");
|
|
246
|
+
function notifySessionChanged() {
|
|
247
|
+
const next = computeSnapshot();
|
|
248
|
+
const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
|
|
249
|
+
if (changed) _snapshot = next;
|
|
250
|
+
scheduleExpiryFlip();
|
|
251
|
+
if (!changed) return;
|
|
252
|
+
for (const listener of Array.from(_sessionListeners)) {
|
|
253
|
+
try {
|
|
254
|
+
listener();
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (isBrowser) {
|
|
259
|
+
try {
|
|
260
|
+
window.dispatchEvent(new Event(SESSION_SYNC_EVENT));
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
__name(notifySessionChanged, "notifySessionChanged");
|
|
266
|
+
function ensureStorageSync() {
|
|
267
|
+
if (!isBrowser || _storageListenerInstalled) return;
|
|
268
|
+
_storageListenerInstalled = true;
|
|
269
|
+
window.addEventListener("storage", (e) => {
|
|
270
|
+
if (e.key === null || e.key === ACCESS_KEY || e.key === REFRESH_KEY) {
|
|
271
|
+
notifySessionChanged();
|
|
113
272
|
}
|
|
114
|
-
return new URLSearchParams(window.location.search);
|
|
115
273
|
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
274
|
+
window.addEventListener(SESSION_SYNC_EVENT, () => notifySessionChanged());
|
|
275
|
+
}
|
|
276
|
+
__name(ensureStorageSync, "ensureStorageSync");
|
|
277
|
+
var _sessionExpiredHandlers = /* @__PURE__ */ new Set();
|
|
278
|
+
var _client = null;
|
|
279
|
+
function pushClientConfig() {
|
|
280
|
+
if (!_client) return;
|
|
281
|
+
_client.setConfig({
|
|
282
|
+
baseUrl: auth.getBaseUrl(),
|
|
283
|
+
credentials: _withCredentials ? "include" : "same-origin"
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
__name(pushClientConfig, "pushClientConfig");
|
|
287
|
+
var auth = {
|
|
288
|
+
// ── Storage mode ──────────────────────────────────────────────────
|
|
289
|
+
getStorageMode() {
|
|
290
|
+
return _storageMode;
|
|
291
|
+
},
|
|
292
|
+
setStorageMode(mode) {
|
|
293
|
+
_storageMode = mode;
|
|
294
|
+
_storage = mode === "cookie" ? cookieBackend : localStorageBackend;
|
|
295
|
+
notifySessionChanged();
|
|
296
|
+
},
|
|
297
|
+
// ── Bearer token ──────────────────────────────────────────────────
|
|
298
|
+
getToken() {
|
|
299
|
+
return _storage.get(ACCESS_KEY);
|
|
300
|
+
},
|
|
301
|
+
setToken(token) {
|
|
302
|
+
_storage.set(ACCESS_KEY, token);
|
|
303
|
+
notifySessionChanged();
|
|
304
|
+
},
|
|
305
|
+
getRefreshToken() {
|
|
306
|
+
return _storage.get(REFRESH_KEY);
|
|
307
|
+
},
|
|
308
|
+
setRefreshToken(token) {
|
|
309
|
+
_storage.set(REFRESH_KEY, token);
|
|
310
|
+
notifySessionChanged();
|
|
311
|
+
},
|
|
312
|
+
clearTokens() {
|
|
313
|
+
_storage.set(ACCESS_KEY, null);
|
|
314
|
+
_storage.set(REFRESH_KEY, null);
|
|
315
|
+
notifySessionChanged();
|
|
316
|
+
},
|
|
317
|
+
/** Session-aware: token PRESENT and not past its `exp` (or a live refresh
|
|
318
|
+
* token exists that can mint one). Prefer `getSnapshot().status` in React. */
|
|
319
|
+
isAuthenticated() {
|
|
320
|
+
return computeSnapshot().status === "authenticated";
|
|
321
|
+
},
|
|
322
|
+
// ── Session (the ONE write path for login/logout flows) ──────────
|
|
323
|
+
/**
|
|
324
|
+
* Persist a token pair atomically. Every login flow (OTP verify, 2FA,
|
|
325
|
+
* OAuth callback) and the refresh handler should end here — do NOT
|
|
326
|
+
* scatter `setToken`/`setRefreshToken` pairs through app code.
|
|
327
|
+
* `refresh: undefined` keeps the current refresh token (access-only
|
|
328
|
+
* rotation); `refresh: null` explicitly drops it.
|
|
329
|
+
*/
|
|
330
|
+
setSession(tokens) {
|
|
331
|
+
_storage.set(ACCESS_KEY, tokens.access);
|
|
332
|
+
if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
|
|
333
|
+
notifySessionChanged();
|
|
334
|
+
},
|
|
335
|
+
clearSession() {
|
|
336
|
+
auth.clearTokens();
|
|
337
|
+
},
|
|
338
|
+
// ── Reactive snapshot (for useSyncExternalStore) ──────────────────
|
|
339
|
+
/**
|
|
340
|
+
* @example React:
|
|
341
|
+
* const session = useSyncExternalStore(
|
|
342
|
+
* auth.subscribe, auth.getSnapshot, auth.getServerSnapshot,
|
|
343
|
+
* );
|
|
344
|
+
* const isAuthenticated = session.status === 'authenticated';
|
|
345
|
+
*/
|
|
346
|
+
getSnapshot() {
|
|
347
|
+
return _snapshot;
|
|
348
|
+
},
|
|
349
|
+
getServerSnapshot() {
|
|
350
|
+
return SERVER_SNAPSHOT;
|
|
351
|
+
},
|
|
352
|
+
subscribe(listener) {
|
|
353
|
+
ensureStorageSync();
|
|
354
|
+
_sessionListeners.add(listener);
|
|
355
|
+
notifySessionChanged();
|
|
356
|
+
return () => {
|
|
357
|
+
_sessionListeners.delete(listener);
|
|
358
|
+
if (_sessionListeners.size === 0 && _expiryTimer !== null) {
|
|
359
|
+
clearTimeout(_expiryTimer);
|
|
360
|
+
_expiryTimer = null;
|
|
124
361
|
}
|
|
125
|
-
}, "updateQueryParams");
|
|
126
|
-
updateQueryParams();
|
|
127
|
-
window.addEventListener("popstate", updateQueryParams);
|
|
128
|
-
const origPush = window.history.pushState;
|
|
129
|
-
const origReplace = window.history.replaceState;
|
|
130
|
-
window.history.pushState = function(...args) {
|
|
131
|
-
const result = origPush.apply(this, args);
|
|
132
|
-
updateQueryParams();
|
|
133
|
-
return result;
|
|
134
|
-
};
|
|
135
|
-
window.history.replaceState = function(...args) {
|
|
136
|
-
const result = origReplace.apply(this, args);
|
|
137
|
-
updateQueryParams();
|
|
138
|
-
return result;
|
|
139
362
|
};
|
|
363
|
+
},
|
|
364
|
+
/**
|
|
365
|
+
* Fired on TERMINAL 401 — after the refresh+retry path is exhausted.
|
|
366
|
+
* The store has already cleared the session before calling back, so
|
|
367
|
+
* handlers only need to route to login (no clear-then-redirect
|
|
368
|
+
* ordering to get wrong). Returns an unsubscribe function; multiple
|
|
369
|
+
* handlers compose (unlike the legacy single-slot `onUnauthorized`).
|
|
370
|
+
*/
|
|
371
|
+
onSessionExpired(cb) {
|
|
372
|
+
_sessionExpiredHandlers.add(cb);
|
|
140
373
|
return () => {
|
|
141
|
-
|
|
142
|
-
window.history.pushState = origPush;
|
|
143
|
-
window.history.replaceState = origReplace;
|
|
374
|
+
_sessionExpiredHandlers.delete(cb);
|
|
144
375
|
};
|
|
145
|
-
},
|
|
146
|
-
|
|
376
|
+
},
|
|
377
|
+
// ── API key ───────────────────────────────────────────────────────
|
|
378
|
+
getApiKey() {
|
|
379
|
+
return _apiKeyOverride ?? _storage.get(API_KEY_KEY) ?? defaultApiKey();
|
|
380
|
+
},
|
|
381
|
+
setApiKey(key) {
|
|
382
|
+
_apiKeyOverride = key;
|
|
383
|
+
},
|
|
384
|
+
setApiKeyPersist(key) {
|
|
385
|
+
_apiKeyOverride = key;
|
|
386
|
+
_storage.set(API_KEY_KEY, key);
|
|
387
|
+
},
|
|
388
|
+
clearApiKey() {
|
|
389
|
+
_apiKeyOverride = null;
|
|
390
|
+
_storage.set(API_KEY_KEY, null);
|
|
391
|
+
},
|
|
392
|
+
// ── Locale ────────────────────────────────────────────────────────
|
|
393
|
+
getLocale() {
|
|
394
|
+
return _localeOverride ?? detectLocale();
|
|
395
|
+
},
|
|
396
|
+
setLocale(locale) {
|
|
397
|
+
_localeOverride = locale;
|
|
398
|
+
},
|
|
399
|
+
// ── Base URL ──────────────────────────────────────────────────────
|
|
400
|
+
getBaseUrl() {
|
|
401
|
+
const url = _baseUrlOverride ?? defaultBaseUrl();
|
|
402
|
+
return url.replace(/\/$/, "");
|
|
403
|
+
},
|
|
404
|
+
setBaseUrl(url) {
|
|
405
|
+
_baseUrlOverride = url ? url.replace(/\/$/, "") : null;
|
|
406
|
+
pushClientConfig();
|
|
407
|
+
},
|
|
408
|
+
// ── Credentials toggle ────────────────────────────────────────────
|
|
409
|
+
getWithCredentials() {
|
|
410
|
+
return _withCredentials;
|
|
411
|
+
},
|
|
412
|
+
setWithCredentials(value) {
|
|
413
|
+
_withCredentials = value;
|
|
414
|
+
pushClientConfig();
|
|
415
|
+
},
|
|
416
|
+
// ── 401 handler ───────────────────────────────────────────────────
|
|
417
|
+
/**
|
|
418
|
+
* Fired when the server returns 401 AND no refresh path recovers it
|
|
419
|
+
* (no refresh token, no refresh handler, refresh failed, or retry
|
|
420
|
+
* still 401). The app should clear local state and redirect to login.
|
|
421
|
+
*
|
|
422
|
+
* NOT fired for 401 that gets transparently recovered by the refresh
|
|
423
|
+
* handler — those are invisible to callers.
|
|
424
|
+
*/
|
|
425
|
+
onUnauthorized(cb) {
|
|
426
|
+
_onUnauthorized = cb;
|
|
427
|
+
},
|
|
428
|
+
/**
|
|
429
|
+
* Register the refresh strategy. The handler receives the current
|
|
430
|
+
* refresh token and must call your refresh endpoint, returning
|
|
431
|
+
* `{ access, refresh? }` on success or `null` on failure.
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* auth.setRefreshHandler(async (refresh) => {
|
|
435
|
+
* const { data } = await Auth.tokenRefreshCreate({ body: { refresh } });
|
|
436
|
+
* return data ? { access: data.access, refresh: data.refresh } : null;
|
|
437
|
+
* });
|
|
438
|
+
*/
|
|
439
|
+
setRefreshHandler(fn) {
|
|
440
|
+
_refreshHandler = fn;
|
|
441
|
+
},
|
|
442
|
+
/**
|
|
443
|
+
* Proactively run the registered refresh handler right now, reusing the
|
|
444
|
+
* SAME single-flight promise as the 401-recovery interceptor. Callers
|
|
445
|
+
* (e.g. an expiry timer / focus / reconnect) get token rotation,
|
|
446
|
+
* de-duplication and rotated-token persistence for free — they must NOT
|
|
447
|
+
* re-implement any of it. Returns the fresh access token, or null if
|
|
448
|
+
* there is no handler / no refresh token / the refresh failed.
|
|
449
|
+
*/
|
|
450
|
+
refreshNow() {
|
|
451
|
+
return tryRefresh();
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
async function tryRefresh() {
|
|
455
|
+
if (_refreshInflight) return _refreshInflight;
|
|
456
|
+
if (!_refreshHandler) return null;
|
|
457
|
+
const runRefresh = /* @__PURE__ */ __name(async () => {
|
|
458
|
+
const refresh = auth.getRefreshToken();
|
|
459
|
+
if (!refresh) return null;
|
|
460
|
+
const result = await _refreshHandler(refresh);
|
|
461
|
+
if (!result?.access) return null;
|
|
462
|
+
auth.setToken(result.access);
|
|
463
|
+
if (result.refresh) auth.setRefreshToken(result.refresh);
|
|
464
|
+
return result.access;
|
|
465
|
+
}, "runRefresh");
|
|
466
|
+
_refreshInflight = (async () => {
|
|
467
|
+
try {
|
|
468
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
469
|
+
if (locks?.request) {
|
|
470
|
+
return await locks.request(`${REFRESH_KEY}:refresh`, runRefresh);
|
|
471
|
+
}
|
|
472
|
+
return await runRefresh();
|
|
473
|
+
} catch {
|
|
474
|
+
return null;
|
|
475
|
+
} finally {
|
|
476
|
+
_refreshInflight = null;
|
|
477
|
+
}
|
|
478
|
+
})();
|
|
479
|
+
return _refreshInflight;
|
|
147
480
|
}
|
|
148
|
-
__name(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
var formatCountdown = /* @__PURE__ */ __name((s) => {
|
|
153
|
-
if (s <= 0) return "";
|
|
154
|
-
const m = Math.floor(s / 60);
|
|
155
|
-
return m > 0 ? `${m}:${String(s % 60).padStart(2, "0")}` : `${s}s`;
|
|
156
|
-
}, "formatCountdown");
|
|
157
|
-
var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
|
|
158
|
-
const [identifier, setIdentifier] = useState2(initialIdentifier);
|
|
159
|
-
const [otp, setOtp] = useState2("");
|
|
160
|
-
const [isLoading, setIsLoading] = useState2(false);
|
|
161
|
-
const [acceptedTerms, setAcceptedTerms] = useState2(true);
|
|
162
|
-
const [step, setStep] = useState2("identifier");
|
|
163
|
-
const [error, setError] = useState2("");
|
|
164
|
-
const [webmail, setWebmail] = useState2(null);
|
|
165
|
-
const [twoFactorSessionId, setTwoFactorSessionId] = useState2(null);
|
|
166
|
-
const [shouldPrompt2FA, setShouldPrompt2FA] = useState2(false);
|
|
167
|
-
const [twoFactorCode, setTwoFactorCode] = useState2("");
|
|
168
|
-
const [useBackupCode, setUseBackupCode] = useState2(false);
|
|
169
|
-
const [rateLimitSeconds, setRateLimitSeconds] = useState2(0);
|
|
170
|
-
const rateLimitTimerRef = useRef2(null);
|
|
171
|
-
const startRateLimitCountdown = useCallback2((seconds) => {
|
|
172
|
-
if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
|
|
173
|
-
setRateLimitSeconds(seconds);
|
|
174
|
-
rateLimitTimerRef.current = setInterval(() => {
|
|
175
|
-
setRateLimitSeconds((prev) => {
|
|
176
|
-
if (prev <= 1) {
|
|
177
|
-
clearInterval(rateLimitTimerRef.current);
|
|
178
|
-
rateLimitTimerRef.current = null;
|
|
179
|
-
return 0;
|
|
180
|
-
}
|
|
181
|
-
return prev - 1;
|
|
182
|
-
});
|
|
183
|
-
}, 1e3);
|
|
184
|
-
}, []);
|
|
185
|
-
useEffect2(() => () => {
|
|
186
|
-
if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
|
|
187
|
-
}, []);
|
|
188
|
-
const clearError = useCallback2(() => setError(""), []);
|
|
189
|
-
return {
|
|
190
|
-
// State
|
|
191
|
-
identifier,
|
|
192
|
-
otp,
|
|
193
|
-
isLoading,
|
|
194
|
-
acceptedTerms,
|
|
195
|
-
step,
|
|
196
|
-
error,
|
|
197
|
-
twoFactorSessionId,
|
|
198
|
-
shouldPrompt2FA,
|
|
199
|
-
twoFactorCode,
|
|
200
|
-
useBackupCode,
|
|
201
|
-
rateLimitSeconds,
|
|
202
|
-
isRateLimited: rateLimitSeconds > 0,
|
|
203
|
-
rateLimitLabel: formatCountdown(rateLimitSeconds),
|
|
204
|
-
webmail,
|
|
205
|
-
// Handlers
|
|
206
|
-
setIdentifier,
|
|
207
|
-
setOtp,
|
|
208
|
-
setAcceptedTerms,
|
|
209
|
-
setError,
|
|
210
|
-
clearError,
|
|
211
|
-
setStep,
|
|
212
|
-
setIsLoading,
|
|
213
|
-
setTwoFactorSessionId,
|
|
214
|
-
setShouldPrompt2FA,
|
|
215
|
-
setTwoFactorCode,
|
|
216
|
-
setUseBackupCode,
|
|
217
|
-
startRateLimitCountdown,
|
|
218
|
-
setWebmail
|
|
219
|
-
};
|
|
220
|
-
}, "useAuthFormState");
|
|
221
|
-
|
|
222
|
-
// src/auth/hooks/useAuthValidation.ts
|
|
223
|
-
import { useCallback as useCallback3 } from "react";
|
|
224
|
-
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
225
|
-
var useAuthValidation = /* @__PURE__ */ __name(() => {
|
|
226
|
-
const validateIdentifier2 = useCallback3((id) => {
|
|
227
|
-
return EMAIL_REGEX.test(id);
|
|
228
|
-
}, []);
|
|
229
|
-
return { validateIdentifier: validateIdentifier2 };
|
|
230
|
-
}, "useAuthValidation");
|
|
231
|
-
var validateIdentifier = /* @__PURE__ */ __name((id) => EMAIL_REGEX.test(id), "validateIdentifier");
|
|
232
|
-
|
|
233
|
-
// src/auth/hooks/useAuthForm.ts
|
|
234
|
-
import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
235
|
-
|
|
236
|
-
// src/auth/utils/logger.ts
|
|
237
|
-
import { createConsola } from "consola";
|
|
238
|
-
|
|
239
|
-
// src/log-control.ts
|
|
240
|
-
var verboseByDefault = isDev || isStaticBuild;
|
|
241
|
-
var DEFAULT_LEVEL = verboseByDefault ? 4 : 1;
|
|
242
|
-
var _level = DEFAULT_LEVEL;
|
|
243
|
-
var _subscribers = /* @__PURE__ */ new Set();
|
|
244
|
-
function getLogLevel() {
|
|
245
|
-
return _level;
|
|
246
|
-
}
|
|
247
|
-
__name(getLogLevel, "getLogLevel");
|
|
248
|
-
function setLogLevel(level) {
|
|
249
|
-
_level = level;
|
|
250
|
-
for (const fn of _subscribers) {
|
|
481
|
+
__name(tryRefresh, "tryRefresh");
|
|
482
|
+
function expireSession(response) {
|
|
483
|
+
auth.clearTokens();
|
|
484
|
+
for (const cb of Array.from(_sessionExpiredHandlers)) {
|
|
251
485
|
try {
|
|
252
|
-
|
|
486
|
+
cb(response);
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (_onUnauthorized) {
|
|
491
|
+
try {
|
|
492
|
+
_onUnauthorized(response);
|
|
253
493
|
} catch {
|
|
254
494
|
}
|
|
255
495
|
}
|
|
256
496
|
}
|
|
257
|
-
__name(
|
|
258
|
-
function
|
|
259
|
-
_subscribers.add(fn);
|
|
497
|
+
__name(expireSession, "expireSession");
|
|
498
|
+
function dpopEnabled() {
|
|
260
499
|
try {
|
|
261
|
-
|
|
500
|
+
return typeof process !== "undefined" && process.env?.NEXT_PUBLIC_DPOP_ENABLED === "true";
|
|
262
501
|
} catch {
|
|
502
|
+
return false;
|
|
263
503
|
}
|
|
264
|
-
return () => _subscribers.delete(fn);
|
|
265
504
|
}
|
|
266
|
-
__name(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
505
|
+
__name(dpopEnabled, "dpopEnabled");
|
|
506
|
+
var _DPOP_DB = "cfg-auth";
|
|
507
|
+
var _DPOP_STORE = "keys";
|
|
508
|
+
var _DPOP_KEY_ID = "dpop-ec-p256";
|
|
509
|
+
function _idbOpen() {
|
|
510
|
+
return new Promise((resolve, reject) => {
|
|
511
|
+
const req = indexedDB.open(_DPOP_DB, 1);
|
|
512
|
+
req.onupgradeneeded = () => req.result.createObjectStore(_DPOP_STORE);
|
|
513
|
+
req.onsuccess = () => resolve(req.result);
|
|
514
|
+
req.onerror = () => reject(req.error);
|
|
515
|
+
});
|
|
274
516
|
}
|
|
275
|
-
__name(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
super(message || `HTTP ${statusCode}: ${statusText}`);
|
|
346
|
-
this.statusCode = statusCode;
|
|
347
|
-
this.statusText = statusText;
|
|
348
|
-
this.response = response;
|
|
349
|
-
this.url = url;
|
|
350
|
-
this.name = "APIError";
|
|
351
|
-
}
|
|
352
|
-
static {
|
|
353
|
-
__name(this, "APIError");
|
|
354
|
-
}
|
|
355
|
-
get details() {
|
|
356
|
-
if (typeof this.response === "object" && this.response !== null) {
|
|
357
|
-
return this.response;
|
|
358
|
-
}
|
|
517
|
+
__name(_idbOpen, "_idbOpen");
|
|
518
|
+
function _idbGet(key) {
|
|
519
|
+
return _idbOpen().then((db) => new Promise((resolve, reject) => {
|
|
520
|
+
const tx = db.transaction(_DPOP_STORE, "readonly");
|
|
521
|
+
const req = tx.objectStore(_DPOP_STORE).get(key);
|
|
522
|
+
req.onsuccess = () => resolve(req.result);
|
|
523
|
+
req.onerror = () => reject(req.error);
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
__name(_idbGet, "_idbGet");
|
|
527
|
+
function _idbPut(key, value) {
|
|
528
|
+
return _idbOpen().then((db) => new Promise((resolve, reject) => {
|
|
529
|
+
const tx = db.transaction(_DPOP_STORE, "readwrite");
|
|
530
|
+
tx.objectStore(_DPOP_STORE).put(value, key);
|
|
531
|
+
tx.oncomplete = () => resolve();
|
|
532
|
+
tx.onerror = () => reject(tx.error);
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
__name(_idbPut, "_idbPut");
|
|
536
|
+
var _dpopKeyPromise = null;
|
|
537
|
+
function _getDpopKeyPair() {
|
|
538
|
+
if (_dpopKeyPromise) return _dpopKeyPromise;
|
|
539
|
+
_dpopKeyPromise = (async () => {
|
|
540
|
+
const existing = await _idbGet(_DPOP_KEY_ID).catch(() => void 0);
|
|
541
|
+
if (existing) return existing;
|
|
542
|
+
const pair = await crypto.subtle.generateKey(
|
|
543
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
544
|
+
false,
|
|
545
|
+
// extractable:false — JS can sign but never export the private key
|
|
546
|
+
["sign"]
|
|
547
|
+
);
|
|
548
|
+
await _idbPut(_DPOP_KEY_ID, pair).catch(() => {
|
|
549
|
+
});
|
|
550
|
+
return pair;
|
|
551
|
+
})();
|
|
552
|
+
return _dpopKeyPromise;
|
|
553
|
+
}
|
|
554
|
+
__name(_getDpopKeyPair, "_getDpopKeyPair");
|
|
555
|
+
function _b64urlFromBytes(bytes) {
|
|
556
|
+
const arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
557
|
+
let s = "";
|
|
558
|
+
for (let i = 0; i < arr.length; i++) s += String.fromCharCode(arr[i]);
|
|
559
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
560
|
+
}
|
|
561
|
+
__name(_b64urlFromBytes, "_b64urlFromBytes");
|
|
562
|
+
function _b64urlFromString(str) {
|
|
563
|
+
return _b64urlFromBytes(new TextEncoder().encode(str));
|
|
564
|
+
}
|
|
565
|
+
__name(_b64urlFromString, "_b64urlFromString");
|
|
566
|
+
async function _publicJwk(pub) {
|
|
567
|
+
const jwk = await crypto.subtle.exportKey("jwk", pub);
|
|
568
|
+
return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
569
|
+
}
|
|
570
|
+
__name(_publicJwk, "_publicJwk");
|
|
571
|
+
async function _makeDpopProof(method, url) {
|
|
572
|
+
try {
|
|
573
|
+
const pair = await _getDpopKeyPair();
|
|
574
|
+
const jwk = await _publicJwk(pair.publicKey);
|
|
575
|
+
const header = { typ: "dpop+jwt", alg: "ES256", jwk };
|
|
576
|
+
const htu = url.split("#")[0].split("?")[0];
|
|
577
|
+
const jti = crypto.randomUUID && crypto.randomUUID() || _b64urlFromBytes(crypto.getRandomValues(new Uint8Array(16)));
|
|
578
|
+
const payload = { htm: method.toUpperCase(), htu, iat: Math.floor(Date.now() / 1e3), jti };
|
|
579
|
+
const signingInput = `${_b64urlFromString(JSON.stringify(header))}.${_b64urlFromString(JSON.stringify(payload))}`;
|
|
580
|
+
const sig = await crypto.subtle.sign(
|
|
581
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
582
|
+
pair.privateKey,
|
|
583
|
+
new TextEncoder().encode(signingInput)
|
|
584
|
+
);
|
|
585
|
+
return `${signingInput}.${_b64urlFromBytes(sig)}`;
|
|
586
|
+
} catch {
|
|
359
587
|
return null;
|
|
360
588
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
589
|
+
}
|
|
590
|
+
__name(_makeDpopProof, "_makeDpopProof");
|
|
591
|
+
function installAuthOnClient(client2) {
|
|
592
|
+
if (_client) return;
|
|
593
|
+
_client = client2;
|
|
594
|
+
client2.setConfig({
|
|
595
|
+
baseUrl: auth.getBaseUrl(),
|
|
596
|
+
credentials: _withCredentials ? "include" : "same-origin"
|
|
597
|
+
});
|
|
598
|
+
client2.interceptors.request.use(async (request) => {
|
|
599
|
+
const token = auth.getToken();
|
|
600
|
+
if (token) request.headers.set("Authorization", `Bearer ${token}`);
|
|
601
|
+
const locale = auth.getLocale();
|
|
602
|
+
if (locale) request.headers.set("Accept-Language", locale);
|
|
603
|
+
const apiKey = auth.getApiKey();
|
|
604
|
+
if (apiKey && !request.headers.has("X-API-Key")) request.headers.set("X-API-Key", apiKey);
|
|
605
|
+
try {
|
|
606
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
607
|
+
if (tz) request.headers.set("X-Timezone", tz);
|
|
608
|
+
} catch {
|
|
367
609
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (!details) return this.message;
|
|
373
|
-
if (details.detail) {
|
|
374
|
-
return Array.isArray(details.detail) ? details.detail.join(", ") : String(details.detail);
|
|
610
|
+
request.headers.set("X-Client-Time", (/* @__PURE__ */ new Date()).toISOString());
|
|
611
|
+
if (dpopEnabled() && typeof window !== "undefined") {
|
|
612
|
+
const proof = await _makeDpopProof(request.method, request.url);
|
|
613
|
+
if (proof) request.headers.set("DPoP", proof);
|
|
375
614
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
|
|
615
|
+
return request;
|
|
616
|
+
});
|
|
617
|
+
client2.interceptors.error.use((err, res, req) => {
|
|
618
|
+
if (err instanceof APIError) return err;
|
|
619
|
+
const url = req?.url ?? "";
|
|
620
|
+
const status = res?.status ?? 0;
|
|
621
|
+
const statusText = res?.statusText ?? "";
|
|
622
|
+
return new APIError(status, statusText, err, url);
|
|
623
|
+
});
|
|
624
|
+
client2.interceptors.response.use(async (response, request) => {
|
|
625
|
+
if (response.status !== 401) return response;
|
|
626
|
+
if (request.headers.get(RETRY_MARKER)) {
|
|
627
|
+
expireSession(response);
|
|
628
|
+
return response;
|
|
382
629
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
get isAuthError() {
|
|
389
|
-
return this.statusCode === 401;
|
|
390
|
-
}
|
|
391
|
-
get isPermissionError() {
|
|
392
|
-
return this.statusCode === 403;
|
|
393
|
-
}
|
|
394
|
-
get isNotFoundError() {
|
|
395
|
-
return this.statusCode === 404;
|
|
396
|
-
}
|
|
397
|
-
get isServerError() {
|
|
398
|
-
return this.statusCode >= 500 && this.statusCode < 600;
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
// src/_api/generated/helpers/auth.ts
|
|
403
|
-
var ACCESS_KEY = "cfg.access_token";
|
|
404
|
-
var REFRESH_KEY = "cfg.refresh_token";
|
|
405
|
-
var API_KEY_KEY = "cfg.api_key";
|
|
406
|
-
var isBrowser2 = typeof window !== "undefined";
|
|
407
|
-
var localStorageBackend = {
|
|
408
|
-
get(key) {
|
|
409
|
-
if (!isBrowser2) return null;
|
|
410
|
-
try {
|
|
411
|
-
return window.localStorage.getItem(key);
|
|
412
|
-
} catch {
|
|
413
|
-
return null;
|
|
630
|
+
const newToken = await tryRefresh();
|
|
631
|
+
if (!newToken) {
|
|
632
|
+
expireSession(response);
|
|
633
|
+
return response;
|
|
414
634
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
} catch {
|
|
635
|
+
const retry = request.clone();
|
|
636
|
+
retry.headers.set("Authorization", `Bearer ${newToken}`);
|
|
637
|
+
retry.headers.set(RETRY_MARKER, "1");
|
|
638
|
+
if (dpopEnabled() && typeof window !== "undefined") {
|
|
639
|
+
const proof = await _makeDpopProof(retry.method, retry.url);
|
|
640
|
+
if (proof) retry.headers.set("DPoP", proof);
|
|
422
641
|
}
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
|
426
|
-
var cookieBackend = {
|
|
427
|
-
get(key) {
|
|
428
|
-
if (!isBrowser2) return null;
|
|
429
642
|
try {
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
return
|
|
643
|
+
const retried = await fetch(retry);
|
|
644
|
+
if (retried.status === 401) expireSession(retried);
|
|
645
|
+
return retried;
|
|
433
646
|
} catch {
|
|
434
|
-
return
|
|
647
|
+
return response;
|
|
435
648
|
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
} catch {
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
__name(installAuthOnClient, "installAuthOnClient");
|
|
652
|
+
var REFRESH_ENDPOINT = "/cfg/accounts/token/refresh/";
|
|
653
|
+
auth.setRefreshHandler(async (refresh) => {
|
|
654
|
+
try {
|
|
655
|
+
const url = `${auth.getBaseUrl()}${REFRESH_ENDPOINT}`;
|
|
656
|
+
const headers = { "Content-Type": "application/json" };
|
|
657
|
+
if (dpopEnabled() && typeof window !== "undefined") {
|
|
658
|
+
const proof = await _makeDpopProof("POST", url);
|
|
659
|
+
if (proof) headers["DPoP"] = proof;
|
|
449
660
|
}
|
|
661
|
+
const res = await fetch(url, {
|
|
662
|
+
method: "POST",
|
|
663
|
+
headers,
|
|
664
|
+
credentials: auth.getWithCredentials() ? "include" : "same-origin",
|
|
665
|
+
body: JSON.stringify({ refresh })
|
|
666
|
+
});
|
|
667
|
+
if (!res.ok) return null;
|
|
668
|
+
const data = await res.json();
|
|
669
|
+
return data?.access ? { access: data.access, refresh: data.refresh ?? refresh } : null;
|
|
670
|
+
} catch {
|
|
671
|
+
return null;
|
|
450
672
|
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// src/_api/generated/helpers/logger.ts
|
|
676
|
+
import { createConsola } from "consola";
|
|
677
|
+
var DEFAULT_CONFIG = {
|
|
678
|
+
enabled: typeof process !== "undefined" && process.env.NODE_ENV !== "production",
|
|
679
|
+
logRequests: true,
|
|
680
|
+
logResponses: true,
|
|
681
|
+
logErrors: true,
|
|
682
|
+
logBodies: true,
|
|
683
|
+
logHeaders: false
|
|
451
684
|
};
|
|
452
|
-
var
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
685
|
+
var SENSITIVE_HEADERS = [
|
|
686
|
+
"authorization",
|
|
687
|
+
"cookie",
|
|
688
|
+
"set-cookie",
|
|
689
|
+
"x-api-key",
|
|
690
|
+
"x-csrf-token"
|
|
691
|
+
];
|
|
692
|
+
var APILogger = class {
|
|
693
|
+
static {
|
|
694
|
+
__name(this, "APILogger");
|
|
695
|
+
}
|
|
696
|
+
config;
|
|
697
|
+
consola;
|
|
698
|
+
constructor(config = {}) {
|
|
699
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
700
|
+
this.consola = config.consola || createConsola({
|
|
701
|
+
level: this.config.enabled ? 4 : 0
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
enable() {
|
|
705
|
+
this.config.enabled = true;
|
|
706
|
+
}
|
|
707
|
+
disable() {
|
|
708
|
+
this.config.enabled = false;
|
|
709
|
+
}
|
|
710
|
+
setConfig(config) {
|
|
711
|
+
this.config = { ...this.config, ...config };
|
|
712
|
+
}
|
|
713
|
+
filterHeaders(headers) {
|
|
714
|
+
if (!headers) return {};
|
|
715
|
+
const filtered = {};
|
|
716
|
+
Object.keys(headers).forEach((key) => {
|
|
717
|
+
filtered[key] = SENSITIVE_HEADERS.includes(key.toLowerCase()) ? "***" : headers[key] || "";
|
|
718
|
+
});
|
|
719
|
+
return filtered;
|
|
720
|
+
}
|
|
721
|
+
logRequest(request) {
|
|
722
|
+
if (!this.config.enabled || !this.config.logRequests) return;
|
|
723
|
+
const { method, url, headers, body } = request;
|
|
724
|
+
this.consola.start(`${method} ${url}`);
|
|
725
|
+
if (this.config.logHeaders && headers) this.consola.debug("Headers:", this.filterHeaders(headers));
|
|
726
|
+
if (this.config.logBodies && body) this.consola.debug("Body:", body);
|
|
727
|
+
}
|
|
728
|
+
logResponse(request, response) {
|
|
729
|
+
if (!this.config.enabled || !this.config.logResponses) return;
|
|
730
|
+
const { method, url } = request;
|
|
731
|
+
const { status, statusText, data, duration } = response;
|
|
732
|
+
this.consola.success(`${method} ${url} ${status} ${statusText} (${duration}ms)`);
|
|
733
|
+
if (this.config.logBodies && data) this.consola.debug("Response:", data);
|
|
734
|
+
}
|
|
735
|
+
logError(request, error) {
|
|
736
|
+
if (!this.config.enabled || !this.config.logErrors) return;
|
|
737
|
+
const { method, url } = request;
|
|
738
|
+
const { message, statusCode, fieldErrors, duration } = error;
|
|
739
|
+
this.consola.error(`${method} ${url} ${statusCode || "Network"} Error (${duration}ms)`);
|
|
740
|
+
this.consola.error("Message:", message);
|
|
741
|
+
if (fieldErrors && Object.keys(fieldErrors).length > 0) {
|
|
742
|
+
this.consola.error("Field Errors:");
|
|
743
|
+
Object.entries(fieldErrors).forEach(([field, errors]) => {
|
|
744
|
+
errors.forEach((err) => this.consola.error(` \u2022 ${field}: ${err}`));
|
|
745
|
+
});
|
|
462
746
|
}
|
|
463
|
-
} catch {
|
|
464
747
|
}
|
|
465
|
-
|
|
748
|
+
info(message, ...args) {
|
|
749
|
+
if (this.config.enabled) this.consola.info(message, ...args);
|
|
750
|
+
}
|
|
751
|
+
warn(message, ...args) {
|
|
752
|
+
if (this.config.enabled) this.consola.warn(message, ...args);
|
|
753
|
+
}
|
|
754
|
+
error(message, ...args) {
|
|
755
|
+
if (this.config.enabled) this.consola.error(message, ...args);
|
|
756
|
+
}
|
|
757
|
+
debug(message, ...args) {
|
|
758
|
+
if (this.config.enabled) this.consola.debug(message, ...args);
|
|
759
|
+
}
|
|
760
|
+
success(message, ...args) {
|
|
761
|
+
if (this.config.enabled) this.consola.success(message, ...args);
|
|
762
|
+
}
|
|
763
|
+
withTag(tag) {
|
|
764
|
+
return this.consola.withTag(tag);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
var defaultLogger = new APILogger();
|
|
768
|
+
|
|
769
|
+
// src/auth/utils/env.ts
|
|
770
|
+
var isDev = process.env.NODE_ENV === "development";
|
|
771
|
+
var isBrowser2 = typeof window !== "undefined";
|
|
772
|
+
var isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
773
|
+
var dpopEnabled2 = process.env.NEXT_PUBLIC_DPOP_ENABLED === "true";
|
|
774
|
+
|
|
775
|
+
// src/log-control.ts
|
|
776
|
+
var verboseByDefault = isDev || isStaticBuild;
|
|
777
|
+
var DEFAULT_LEVEL = verboseByDefault ? 4 : 1;
|
|
778
|
+
var _level = DEFAULT_LEVEL;
|
|
779
|
+
var _subscribers = /* @__PURE__ */ new Set();
|
|
780
|
+
function getLogLevel() {
|
|
781
|
+
return _level;
|
|
466
782
|
}
|
|
467
|
-
__name(
|
|
468
|
-
function
|
|
469
|
-
|
|
783
|
+
__name(getLogLevel, "getLogLevel");
|
|
784
|
+
function setLogLevel(level) {
|
|
785
|
+
_level = level;
|
|
786
|
+
for (const fn of _subscribers) {
|
|
470
787
|
try {
|
|
471
|
-
|
|
472
|
-
if (process.env.NEXT_PUBLIC_STATIC_BUILD === "true") return "";
|
|
473
|
-
if (process.env.NEXT_PUBLIC_API_PROXY_URL !== void 0)
|
|
474
|
-
return process.env.NEXT_PUBLIC_API_PROXY_URL;
|
|
475
|
-
return process.env.NEXT_PUBLIC_API_URL || "";
|
|
476
|
-
}
|
|
788
|
+
fn(level);
|
|
477
789
|
} catch {
|
|
478
790
|
}
|
|
479
|
-
return "";
|
|
480
791
|
}
|
|
792
|
+
}
|
|
793
|
+
__name(setLogLevel, "setLogLevel");
|
|
794
|
+
function onLogLevelChange(fn) {
|
|
795
|
+
_subscribers.add(fn);
|
|
481
796
|
try {
|
|
482
|
-
|
|
483
|
-
if (process.env.NEXT_PUBLIC_STATIC_BUILD === "true") return "";
|
|
484
|
-
return process.env.NEXT_PUBLIC_API_URL || "";
|
|
485
|
-
}
|
|
797
|
+
fn(_level);
|
|
486
798
|
} catch {
|
|
487
799
|
}
|
|
488
|
-
return
|
|
800
|
+
return () => _subscribers.delete(fn);
|
|
489
801
|
}
|
|
490
|
-
__name(
|
|
491
|
-
function
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
802
|
+
__name(onLogLevelChange, "onLogLevelChange");
|
|
803
|
+
function applyRoleLogPolicy(opts) {
|
|
804
|
+
const devMode = opts.isDev ?? verboseByDefault;
|
|
805
|
+
if (opts.isAdmin || devMode) {
|
|
806
|
+
setLogLevel(4);
|
|
807
|
+
} else {
|
|
808
|
+
setLogLevel(1);
|
|
497
809
|
}
|
|
498
|
-
return null;
|
|
499
810
|
}
|
|
500
|
-
__name(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
var _refreshHandler = null;
|
|
507
|
-
var _refreshInflight = null;
|
|
508
|
-
var RETRY_MARKER = "X-Auth-Retry";
|
|
509
|
-
var _client = null;
|
|
510
|
-
function pushClientConfig() {
|
|
511
|
-
if (!_client) return;
|
|
512
|
-
_client.setConfig({
|
|
513
|
-
baseUrl: auth.getBaseUrl(),
|
|
514
|
-
credentials: _withCredentials ? "include" : "same-origin"
|
|
515
|
-
});
|
|
811
|
+
__name(applyRoleLogPolicy, "applyRoleLogPolicy");
|
|
812
|
+
|
|
813
|
+
// src/auth/hooks/useSession.ts
|
|
814
|
+
import { useSyncExternalStore } from "react";
|
|
815
|
+
function useSession() {
|
|
816
|
+
return useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getServerSnapshot);
|
|
516
817
|
}
|
|
517
|
-
__name(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
_storage = mode === "cookie" ? cookieBackend : localStorageBackend;
|
|
526
|
-
},
|
|
527
|
-
// ── Bearer token ──────────────────────────────────────────────────
|
|
528
|
-
getToken() {
|
|
529
|
-
return _storage.get(ACCESS_KEY);
|
|
530
|
-
},
|
|
531
|
-
setToken(token) {
|
|
532
|
-
_storage.set(ACCESS_KEY, token);
|
|
533
|
-
},
|
|
534
|
-
getRefreshToken() {
|
|
535
|
-
return _storage.get(REFRESH_KEY);
|
|
536
|
-
},
|
|
537
|
-
setRefreshToken(token) {
|
|
538
|
-
_storage.set(REFRESH_KEY, token);
|
|
539
|
-
},
|
|
540
|
-
clearTokens() {
|
|
541
|
-
_storage.set(ACCESS_KEY, null);
|
|
542
|
-
_storage.set(REFRESH_KEY, null);
|
|
543
|
-
},
|
|
544
|
-
isAuthenticated() {
|
|
545
|
-
return _storage.get(ACCESS_KEY) !== null;
|
|
546
|
-
},
|
|
547
|
-
// ── API key ───────────────────────────────────────────────────────
|
|
548
|
-
getApiKey() {
|
|
549
|
-
return _apiKeyOverride ?? _storage.get(API_KEY_KEY) ?? defaultApiKey();
|
|
550
|
-
},
|
|
551
|
-
setApiKey(key) {
|
|
552
|
-
_apiKeyOverride = key;
|
|
553
|
-
},
|
|
554
|
-
setApiKeyPersist(key) {
|
|
555
|
-
_apiKeyOverride = key;
|
|
556
|
-
_storage.set(API_KEY_KEY, key);
|
|
557
|
-
},
|
|
558
|
-
clearApiKey() {
|
|
559
|
-
_apiKeyOverride = null;
|
|
560
|
-
_storage.set(API_KEY_KEY, null);
|
|
561
|
-
},
|
|
562
|
-
// ── Locale ────────────────────────────────────────────────────────
|
|
563
|
-
getLocale() {
|
|
564
|
-
return _localeOverride ?? detectLocale();
|
|
565
|
-
},
|
|
566
|
-
setLocale(locale) {
|
|
567
|
-
_localeOverride = locale;
|
|
568
|
-
},
|
|
569
|
-
// ── Base URL ──────────────────────────────────────────────────────
|
|
570
|
-
getBaseUrl() {
|
|
571
|
-
const url = _baseUrlOverride ?? defaultBaseUrl();
|
|
572
|
-
return url.replace(/\/$/, "");
|
|
573
|
-
},
|
|
574
|
-
setBaseUrl(url) {
|
|
575
|
-
_baseUrlOverride = url ? url.replace(/\/$/, "") : null;
|
|
576
|
-
pushClientConfig();
|
|
577
|
-
},
|
|
578
|
-
// ── Credentials toggle ────────────────────────────────────────────
|
|
579
|
-
getWithCredentials() {
|
|
580
|
-
return _withCredentials;
|
|
581
|
-
},
|
|
582
|
-
setWithCredentials(value) {
|
|
583
|
-
_withCredentials = value;
|
|
584
|
-
pushClientConfig();
|
|
585
|
-
},
|
|
586
|
-
// ── 401 handler ───────────────────────────────────────────────────
|
|
587
|
-
/**
|
|
588
|
-
* Fired when the server returns 401 AND no refresh path recovers it
|
|
589
|
-
* (no refresh token, no refresh handler, refresh failed, or retry
|
|
590
|
-
* still 401). The app should clear local state and redirect to login.
|
|
591
|
-
*
|
|
592
|
-
* NOT fired for 401 that gets transparently recovered by the refresh
|
|
593
|
-
* handler — those are invisible to callers.
|
|
594
|
-
*/
|
|
595
|
-
onUnauthorized(cb) {
|
|
596
|
-
_onUnauthorized = cb;
|
|
597
|
-
},
|
|
598
|
-
/**
|
|
599
|
-
* Register the refresh strategy. The handler receives the current
|
|
600
|
-
* refresh token and must call your refresh endpoint, returning
|
|
601
|
-
* `{ access, refresh? }` on success or `null` on failure.
|
|
602
|
-
*
|
|
603
|
-
* @example
|
|
604
|
-
* auth.setRefreshHandler(async (refresh) => {
|
|
605
|
-
* const { data } = await Auth.tokenRefreshCreate({ body: { refresh } });
|
|
606
|
-
* return data ? { access: data.access, refresh: data.refresh } : null;
|
|
607
|
-
* });
|
|
608
|
-
*/
|
|
609
|
-
setRefreshHandler(fn) {
|
|
610
|
-
_refreshHandler = fn;
|
|
611
|
-
},
|
|
612
|
-
/**
|
|
613
|
-
* Proactively run the registered refresh handler right now, reusing the
|
|
614
|
-
* SAME single-flight promise as the 401-recovery interceptor. Callers
|
|
615
|
-
* (e.g. an expiry timer / focus / reconnect) get token rotation,
|
|
616
|
-
* de-duplication and rotated-token persistence for free — they must NOT
|
|
617
|
-
* re-implement any of it. Returns the fresh access token, or null if
|
|
618
|
-
* there is no handler / no refresh token / the refresh failed.
|
|
619
|
-
*/
|
|
620
|
-
refreshNow() {
|
|
621
|
-
return tryRefresh();
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
|
-
async function tryRefresh() {
|
|
625
|
-
if (_refreshInflight) return _refreshInflight;
|
|
626
|
-
if (!_refreshHandler) return null;
|
|
627
|
-
const refresh = auth.getRefreshToken();
|
|
628
|
-
if (!refresh) return null;
|
|
629
|
-
_refreshInflight = (async () => {
|
|
630
|
-
try {
|
|
631
|
-
const result = await _refreshHandler(refresh);
|
|
632
|
-
if (!result?.access) return null;
|
|
633
|
-
auth.setToken(result.access);
|
|
634
|
-
if (result.refresh) auth.setRefreshToken(result.refresh);
|
|
635
|
-
return result.access;
|
|
636
|
-
} catch {
|
|
637
|
-
return null;
|
|
638
|
-
} finally {
|
|
639
|
-
_refreshInflight = null;
|
|
640
|
-
}
|
|
641
|
-
})();
|
|
642
|
-
return _refreshInflight;
|
|
643
|
-
}
|
|
644
|
-
__name(tryRefresh, "tryRefresh");
|
|
645
|
-
function dpopEnabled2() {
|
|
646
|
-
try {
|
|
647
|
-
return typeof process !== "undefined" && process.env?.NEXT_PUBLIC_DPOP_ENABLED === "true";
|
|
648
|
-
} catch {
|
|
649
|
-
return false;
|
|
818
|
+
__name(useSession, "useSession");
|
|
819
|
+
|
|
820
|
+
// src/auth/hooks/useCfgRouter.ts
|
|
821
|
+
import { useRouter as useNextRouter } from "next/navigation";
|
|
822
|
+
import { useCallback, useMemo } from "react";
|
|
823
|
+
function getBasePath() {
|
|
824
|
+
if (typeof process === "undefined") {
|
|
825
|
+
return "";
|
|
650
826
|
}
|
|
827
|
+
return process.env.NEXT_PUBLIC_BASE_PATH || "";
|
|
651
828
|
}
|
|
652
|
-
__name(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
function _idbOpen() {
|
|
657
|
-
return new Promise((resolve, reject) => {
|
|
658
|
-
const req = indexedDB.open(_DPOP_DB, 1);
|
|
659
|
-
req.onupgradeneeded = () => req.result.createObjectStore(_DPOP_STORE);
|
|
660
|
-
req.onsuccess = () => resolve(req.result);
|
|
661
|
-
req.onerror = () => reject(req.error);
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
__name(_idbOpen, "_idbOpen");
|
|
665
|
-
function _idbGet(key) {
|
|
666
|
-
return _idbOpen().then((db) => new Promise((resolve, reject) => {
|
|
667
|
-
const tx = db.transaction(_DPOP_STORE, "readonly");
|
|
668
|
-
const req = tx.objectStore(_DPOP_STORE).get(key);
|
|
669
|
-
req.onsuccess = () => resolve(req.result);
|
|
670
|
-
req.onerror = () => reject(req.error);
|
|
671
|
-
}));
|
|
672
|
-
}
|
|
673
|
-
__name(_idbGet, "_idbGet");
|
|
674
|
-
function _idbPut(key, value) {
|
|
675
|
-
return _idbOpen().then((db) => new Promise((resolve, reject) => {
|
|
676
|
-
const tx = db.transaction(_DPOP_STORE, "readwrite");
|
|
677
|
-
tx.objectStore(_DPOP_STORE).put(value, key);
|
|
678
|
-
tx.oncomplete = () => resolve();
|
|
679
|
-
tx.onerror = () => reject(tx.error);
|
|
680
|
-
}));
|
|
681
|
-
}
|
|
682
|
-
__name(_idbPut, "_idbPut");
|
|
683
|
-
var _dpopKeyPromise = null;
|
|
684
|
-
function _getDpopKeyPair() {
|
|
685
|
-
if (_dpopKeyPromise) return _dpopKeyPromise;
|
|
686
|
-
_dpopKeyPromise = (async () => {
|
|
687
|
-
const existing = await _idbGet(_DPOP_KEY_ID).catch(() => void 0);
|
|
688
|
-
if (existing) return existing;
|
|
689
|
-
const pair = await crypto.subtle.generateKey(
|
|
690
|
-
{ name: "ECDSA", namedCurve: "P-256" },
|
|
691
|
-
false,
|
|
692
|
-
// extractable:false — JS can sign but never export the private key
|
|
693
|
-
["sign"]
|
|
694
|
-
);
|
|
695
|
-
await _idbPut(_DPOP_KEY_ID, pair).catch(() => {
|
|
696
|
-
});
|
|
697
|
-
return pair;
|
|
698
|
-
})();
|
|
699
|
-
return _dpopKeyPromise;
|
|
700
|
-
}
|
|
701
|
-
__name(_getDpopKeyPair, "_getDpopKeyPair");
|
|
702
|
-
function _b64urlFromBytes(bytes) {
|
|
703
|
-
const arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
704
|
-
let s = "";
|
|
705
|
-
for (let i = 0; i < arr.length; i++) s += String.fromCharCode(arr[i]);
|
|
706
|
-
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
707
|
-
}
|
|
708
|
-
__name(_b64urlFromBytes, "_b64urlFromBytes");
|
|
709
|
-
function _b64urlFromString(str) {
|
|
710
|
-
return _b64urlFromBytes(new TextEncoder().encode(str));
|
|
711
|
-
}
|
|
712
|
-
__name(_b64urlFromString, "_b64urlFromString");
|
|
713
|
-
async function _publicJwk(pub) {
|
|
714
|
-
const jwk = await crypto.subtle.exportKey("jwk", pub);
|
|
715
|
-
return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
716
|
-
}
|
|
717
|
-
__name(_publicJwk, "_publicJwk");
|
|
718
|
-
async function _makeDpopProof(method, url) {
|
|
719
|
-
try {
|
|
720
|
-
const pair = await _getDpopKeyPair();
|
|
721
|
-
const jwk = await _publicJwk(pair.publicKey);
|
|
722
|
-
const header = { typ: "dpop+jwt", alg: "ES256", jwk };
|
|
723
|
-
const htu = url.split("#")[0].split("?")[0];
|
|
724
|
-
const jti = crypto.randomUUID && crypto.randomUUID() || _b64urlFromBytes(crypto.getRandomValues(new Uint8Array(16)));
|
|
725
|
-
const payload = { htm: method.toUpperCase(), htu, iat: Math.floor(Date.now() / 1e3), jti };
|
|
726
|
-
const signingInput = `${_b64urlFromString(JSON.stringify(header))}.${_b64urlFromString(JSON.stringify(payload))}`;
|
|
727
|
-
const sig = await crypto.subtle.sign(
|
|
728
|
-
{ name: "ECDSA", hash: "SHA-256" },
|
|
729
|
-
pair.privateKey,
|
|
730
|
-
new TextEncoder().encode(signingInput)
|
|
731
|
-
);
|
|
732
|
-
return `${signingInput}.${_b64urlFromBytes(sig)}`;
|
|
733
|
-
} catch {
|
|
734
|
-
return null;
|
|
829
|
+
__name(getBasePath, "getBasePath");
|
|
830
|
+
function withBasePath(path, basePath) {
|
|
831
|
+
if (!basePath) {
|
|
832
|
+
return path;
|
|
735
833
|
}
|
|
834
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
835
|
+
const normalizedBasePath = basePath.replace(/\/$/, "");
|
|
836
|
+
return `${normalizedBasePath}${normalizedPath}`;
|
|
736
837
|
}
|
|
737
|
-
__name(
|
|
738
|
-
function
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
if (locale) request.headers.set("Accept-Language", locale);
|
|
750
|
-
const apiKey = auth.getApiKey();
|
|
751
|
-
if (apiKey && !request.headers.has("X-API-Key")) request.headers.set("X-API-Key", apiKey);
|
|
752
|
-
try {
|
|
753
|
-
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
754
|
-
if (tz) request.headers.set("X-Timezone", tz);
|
|
755
|
-
} catch {
|
|
838
|
+
__name(withBasePath, "withBasePath");
|
|
839
|
+
function useCfgRouter() {
|
|
840
|
+
const router = useNextRouter();
|
|
841
|
+
const basePath = useMemo(() => getBasePath(), []);
|
|
842
|
+
const isStaticBuild2 = useMemo(() => {
|
|
843
|
+
return typeof process !== "undefined" && process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
844
|
+
}, []);
|
|
845
|
+
const push = useCallback((href, options) => {
|
|
846
|
+
if (basePath) {
|
|
847
|
+
window.location.href = withBasePath(href, basePath);
|
|
848
|
+
} else {
|
|
849
|
+
router.push(href, options);
|
|
756
850
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
851
|
+
}, [router, basePath]);
|
|
852
|
+
const replace = useCallback((href, options) => {
|
|
853
|
+
if (basePath) {
|
|
854
|
+
window.location.replace(withBasePath(href, basePath));
|
|
855
|
+
} else {
|
|
856
|
+
router.replace(href, options);
|
|
761
857
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
try {
|
|
803
|
-
_onUnauthorized(retried);
|
|
804
|
-
} catch {
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
return retried;
|
|
808
|
-
} catch {
|
|
809
|
-
if (_onUnauthorized) {
|
|
810
|
-
try {
|
|
811
|
-
_onUnauthorized(response);
|
|
812
|
-
} catch {
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
return response;
|
|
858
|
+
}, [router, basePath]);
|
|
859
|
+
const hardPush = useCallback((href) => {
|
|
860
|
+
window.location.href = withBasePath(href, basePath);
|
|
861
|
+
}, [basePath]);
|
|
862
|
+
const hardReplace = useCallback((href) => {
|
|
863
|
+
window.location.replace(withBasePath(href, basePath));
|
|
864
|
+
}, [basePath]);
|
|
865
|
+
const prefetch = useCallback((href) => {
|
|
866
|
+
router.prefetch(href);
|
|
867
|
+
}, [router]);
|
|
868
|
+
const back = useCallback(() => {
|
|
869
|
+
router.back();
|
|
870
|
+
}, [router]);
|
|
871
|
+
const forward = useCallback(() => {
|
|
872
|
+
router.forward();
|
|
873
|
+
}, [router]);
|
|
874
|
+
const refresh = useCallback(() => {
|
|
875
|
+
router.refresh();
|
|
876
|
+
}, [router]);
|
|
877
|
+
return {
|
|
878
|
+
push,
|
|
879
|
+
replace,
|
|
880
|
+
hardPush,
|
|
881
|
+
hardReplace,
|
|
882
|
+
prefetch,
|
|
883
|
+
back,
|
|
884
|
+
forward,
|
|
885
|
+
refresh
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
__name(useCfgRouter, "useCfgRouter");
|
|
889
|
+
|
|
890
|
+
// src/auth/hooks/useQueryParams.ts
|
|
891
|
+
import { usePathname } from "next/navigation";
|
|
892
|
+
import { useEffect, useRef, useState } from "react";
|
|
893
|
+
function useQueryParams() {
|
|
894
|
+
const pathname = usePathname();
|
|
895
|
+
const [queryParams, setQueryParams] = useState(() => {
|
|
896
|
+
if (typeof window === "undefined") {
|
|
897
|
+
return new URLSearchParams();
|
|
816
898
|
}
|
|
899
|
+
return new URLSearchParams(window.location.search);
|
|
817
900
|
});
|
|
901
|
+
const lastSearchRef = useRef("");
|
|
902
|
+
useEffect(() => {
|
|
903
|
+
if (typeof window === "undefined") return;
|
|
904
|
+
const updateQueryParams = /* @__PURE__ */ __name(() => {
|
|
905
|
+
const currentSearch = window.location.search;
|
|
906
|
+
if (currentSearch !== lastSearchRef.current) {
|
|
907
|
+
lastSearchRef.current = currentSearch;
|
|
908
|
+
setQueryParams(new URLSearchParams(currentSearch));
|
|
909
|
+
}
|
|
910
|
+
}, "updateQueryParams");
|
|
911
|
+
updateQueryParams();
|
|
912
|
+
window.addEventListener("popstate", updateQueryParams);
|
|
913
|
+
const origPush = window.history.pushState;
|
|
914
|
+
const origReplace = window.history.replaceState;
|
|
915
|
+
window.history.pushState = function(...args) {
|
|
916
|
+
const result = origPush.apply(this, args);
|
|
917
|
+
updateQueryParams();
|
|
918
|
+
return result;
|
|
919
|
+
};
|
|
920
|
+
window.history.replaceState = function(...args) {
|
|
921
|
+
const result = origReplace.apply(this, args);
|
|
922
|
+
updateQueryParams();
|
|
923
|
+
return result;
|
|
924
|
+
};
|
|
925
|
+
return () => {
|
|
926
|
+
window.removeEventListener("popstate", updateQueryParams);
|
|
927
|
+
window.history.pushState = origPush;
|
|
928
|
+
window.history.replaceState = origReplace;
|
|
929
|
+
};
|
|
930
|
+
}, [pathname]);
|
|
931
|
+
return queryParams;
|
|
818
932
|
}
|
|
819
|
-
__name(
|
|
933
|
+
__name(useQueryParams, "useQueryParams");
|
|
820
934
|
|
|
821
|
-
// src/
|
|
822
|
-
import {
|
|
823
|
-
var
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
"
|
|
834
|
-
"
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
];
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
disable() {
|
|
854
|
-
this.config.enabled = false;
|
|
855
|
-
}
|
|
856
|
-
setConfig(config) {
|
|
857
|
-
this.config = { ...this.config, ...config };
|
|
858
|
-
}
|
|
859
|
-
filterHeaders(headers) {
|
|
860
|
-
if (!headers) return {};
|
|
861
|
-
const filtered = {};
|
|
862
|
-
Object.keys(headers).forEach((key) => {
|
|
863
|
-
filtered[key] = SENSITIVE_HEADERS.includes(key.toLowerCase()) ? "***" : headers[key] || "";
|
|
864
|
-
});
|
|
865
|
-
return filtered;
|
|
866
|
-
}
|
|
867
|
-
logRequest(request) {
|
|
868
|
-
if (!this.config.enabled || !this.config.logRequests) return;
|
|
869
|
-
const { method, url, headers, body } = request;
|
|
870
|
-
this.consola.start(`${method} ${url}`);
|
|
871
|
-
if (this.config.logHeaders && headers) this.consola.debug("Headers:", this.filterHeaders(headers));
|
|
872
|
-
if (this.config.logBodies && body) this.consola.debug("Body:", body);
|
|
873
|
-
}
|
|
874
|
-
logResponse(request, response) {
|
|
875
|
-
if (!this.config.enabled || !this.config.logResponses) return;
|
|
876
|
-
const { method, url } = request;
|
|
877
|
-
const { status, statusText, data, duration } = response;
|
|
878
|
-
this.consola.success(`${method} ${url} ${status} ${statusText} (${duration}ms)`);
|
|
879
|
-
if (this.config.logBodies && data) this.consola.debug("Response:", data);
|
|
880
|
-
}
|
|
881
|
-
logError(request, error) {
|
|
882
|
-
if (!this.config.enabled || !this.config.logErrors) return;
|
|
883
|
-
const { method, url } = request;
|
|
884
|
-
const { message, statusCode, fieldErrors, duration } = error;
|
|
885
|
-
this.consola.error(`${method} ${url} ${statusCode || "Network"} Error (${duration}ms)`);
|
|
886
|
-
this.consola.error("Message:", message);
|
|
887
|
-
if (fieldErrors && Object.keys(fieldErrors).length > 0) {
|
|
888
|
-
this.consola.error("Field Errors:");
|
|
889
|
-
Object.entries(fieldErrors).forEach(([field, errors]) => {
|
|
890
|
-
errors.forEach((err) => this.consola.error(` \u2022 ${field}: ${err}`));
|
|
935
|
+
// src/auth/hooks/useAuthFormState.ts
|
|
936
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
937
|
+
var formatCountdown = /* @__PURE__ */ __name((s) => {
|
|
938
|
+
if (s <= 0) return "";
|
|
939
|
+
const m = Math.floor(s / 60);
|
|
940
|
+
return m > 0 ? `${m}:${String(s % 60).padStart(2, "0")}` : `${s}s`;
|
|
941
|
+
}, "formatCountdown");
|
|
942
|
+
var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
|
|
943
|
+
const [identifier, setIdentifier] = useState2(initialIdentifier);
|
|
944
|
+
const [otp, setOtp] = useState2("");
|
|
945
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
946
|
+
const [acceptedTerms, setAcceptedTerms] = useState2(true);
|
|
947
|
+
const [step, setStep] = useState2("identifier");
|
|
948
|
+
const [error, setError] = useState2("");
|
|
949
|
+
const [webmail, setWebmail] = useState2(null);
|
|
950
|
+
const [twoFactorSessionId, setTwoFactorSessionId] = useState2(null);
|
|
951
|
+
const [shouldPrompt2FA, setShouldPrompt2FA] = useState2(false);
|
|
952
|
+
const [twoFactorCode, setTwoFactorCode] = useState2("");
|
|
953
|
+
const [useBackupCode, setUseBackupCode] = useState2(false);
|
|
954
|
+
const [rateLimitSeconds, setRateLimitSeconds] = useState2(0);
|
|
955
|
+
const rateLimitTimerRef = useRef2(null);
|
|
956
|
+
const startRateLimitCountdown = useCallback2((seconds) => {
|
|
957
|
+
if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
|
|
958
|
+
setRateLimitSeconds(seconds);
|
|
959
|
+
rateLimitTimerRef.current = setInterval(() => {
|
|
960
|
+
setRateLimitSeconds((prev) => {
|
|
961
|
+
if (prev <= 1) {
|
|
962
|
+
clearInterval(rateLimitTimerRef.current);
|
|
963
|
+
rateLimitTimerRef.current = null;
|
|
964
|
+
return 0;
|
|
965
|
+
}
|
|
966
|
+
return prev - 1;
|
|
891
967
|
});
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
if (
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
968
|
+
}, 1e3);
|
|
969
|
+
}, []);
|
|
970
|
+
useEffect2(() => () => {
|
|
971
|
+
if (rateLimitTimerRef.current) clearInterval(rateLimitTimerRef.current);
|
|
972
|
+
}, []);
|
|
973
|
+
const clearError = useCallback2(() => setError(""), []);
|
|
974
|
+
return {
|
|
975
|
+
// State
|
|
976
|
+
identifier,
|
|
977
|
+
otp,
|
|
978
|
+
isLoading,
|
|
979
|
+
acceptedTerms,
|
|
980
|
+
step,
|
|
981
|
+
error,
|
|
982
|
+
twoFactorSessionId,
|
|
983
|
+
shouldPrompt2FA,
|
|
984
|
+
twoFactorCode,
|
|
985
|
+
useBackupCode,
|
|
986
|
+
rateLimitSeconds,
|
|
987
|
+
isRateLimited: rateLimitSeconds > 0,
|
|
988
|
+
rateLimitLabel: formatCountdown(rateLimitSeconds),
|
|
989
|
+
webmail,
|
|
990
|
+
// Handlers
|
|
991
|
+
setIdentifier,
|
|
992
|
+
setOtp,
|
|
993
|
+
setAcceptedTerms,
|
|
994
|
+
setError,
|
|
995
|
+
clearError,
|
|
996
|
+
setStep,
|
|
997
|
+
setIsLoading,
|
|
998
|
+
setTwoFactorSessionId,
|
|
999
|
+
setShouldPrompt2FA,
|
|
1000
|
+
setTwoFactorCode,
|
|
1001
|
+
setUseBackupCode,
|
|
1002
|
+
startRateLimitCountdown,
|
|
1003
|
+
setWebmail
|
|
1004
|
+
};
|
|
1005
|
+
}, "useAuthFormState");
|
|
1006
|
+
|
|
1007
|
+
// src/auth/hooks/useAuthValidation.ts
|
|
1008
|
+
import { useCallback as useCallback3 } from "react";
|
|
1009
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1010
|
+
var useAuthValidation = /* @__PURE__ */ __name(() => {
|
|
1011
|
+
const validateIdentifier2 = useCallback3((id) => {
|
|
1012
|
+
return EMAIL_REGEX.test(id);
|
|
1013
|
+
}, []);
|
|
1014
|
+
return { validateIdentifier: validateIdentifier2 };
|
|
1015
|
+
}, "useAuthValidation");
|
|
1016
|
+
var validateIdentifier = /* @__PURE__ */ __name((id) => EMAIL_REGEX.test(id), "validateIdentifier");
|
|
1017
|
+
|
|
1018
|
+
// src/auth/hooks/useAuthForm.ts
|
|
1019
|
+
import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
1020
|
+
|
|
1021
|
+
// src/auth/utils/logger.ts
|
|
1022
|
+
import { createConsola as createConsola2 } from "consola";
|
|
1023
|
+
var logger = createConsola2({
|
|
1024
|
+
level: getLogLevel()
|
|
1025
|
+
}).withTag("api");
|
|
1026
|
+
onLogLevelChange((level) => {
|
|
1027
|
+
logger.level = level;
|
|
1028
|
+
});
|
|
1029
|
+
var authLogger = logger.withTag("auth");
|
|
1030
|
+
|
|
1031
|
+
// src/auth/hooks/useAutoAuth.ts
|
|
1032
|
+
import { usePathname as usePathname2 } from "next/navigation";
|
|
1033
|
+
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
1034
|
+
|
|
1035
|
+
// src/auth/utils/path.ts
|
|
1036
|
+
var normalizePath = /* @__PURE__ */ __name((path) => {
|
|
1037
|
+
if (!path) return "";
|
|
1038
|
+
const withoutLocale = path.replace(/^\/[a-z]{2}(?=\/|$)/, "");
|
|
1039
|
+
const trimmed = withoutLocale.replace(/\/+$/, "");
|
|
1040
|
+
return trimmed || "/";
|
|
1041
|
+
}, "normalizePath");
|
|
1042
|
+
var isAllowedAuthPath = /* @__PURE__ */ __name((pathname, allowedPaths) => {
|
|
1043
|
+
const normalized = normalizePath(pathname);
|
|
1044
|
+
return allowedPaths.some(
|
|
1045
|
+
(p) => normalized === p || normalized.startsWith(p + "/")
|
|
1046
|
+
);
|
|
1047
|
+
}, "isAllowedAuthPath");
|
|
1048
|
+
|
|
1049
|
+
// src/auth/hooks/useAutoAuth.ts
|
|
1050
|
+
var useAutoAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
1051
|
+
const { onOTPDetected, cleanupUrl = true, allowedPaths = ["/auth"] } = options;
|
|
1052
|
+
const queryParams = useQueryParams();
|
|
1053
|
+
const pathname = usePathname2();
|
|
1054
|
+
const router = useCfgRouter();
|
|
1055
|
+
const isAllowedPath = isAllowedAuthPath(pathname, allowedPaths);
|
|
1056
|
+
const queryOtp = queryParams.get("otp") || "";
|
|
1057
|
+
const queryEmail = queryParams.get("email") || void 0;
|
|
1058
|
+
const hasOTP = !!queryOtp;
|
|
1059
|
+
const isReady = !!pathname && hasOTP && isAllowedPath;
|
|
1060
|
+
const otpValid = queryOtp.length === AUTH_CONSTANTS.EMAIL_OTP_LENGTH;
|
|
1061
|
+
const onOTPDetectedRef = useRef3(onOTPDetected);
|
|
1062
|
+
onOTPDetectedRef.current = onOTPDetected;
|
|
1063
|
+
useEffect3(() => {
|
|
1064
|
+
if (!isReady || !otpValid) return;
|
|
1065
|
+
authLogger.info("OTP detected in URL on auth page:", queryOtp);
|
|
1066
|
+
onOTPDetectedRef.current?.(queryOtp, queryEmail);
|
|
1067
|
+
}, [isReady, otpValid, queryOtp, queryEmail]);
|
|
1068
|
+
useEffect3(() => {
|
|
1069
|
+
if (!isReady || !cleanupUrl || !queryOtp) return;
|
|
1070
|
+
const cleanQuery = Object.fromEntries(queryParams.entries());
|
|
1071
|
+
delete cleanQuery.otp;
|
|
1072
|
+
delete cleanQuery.email;
|
|
1073
|
+
const queryString = new URLSearchParams(cleanQuery).toString();
|
|
1074
|
+
const realPathname = typeof window !== "undefined" ? window.location.pathname : pathname;
|
|
1075
|
+
router.replace(queryString ? `${realPathname}?${queryString}` : realPathname);
|
|
1076
|
+
}, [isReady, cleanupUrl, queryOtp]);
|
|
1077
|
+
return {
|
|
1078
|
+
isReady,
|
|
1079
|
+
hasOTP,
|
|
1080
|
+
isAllowedPath
|
|
1081
|
+
};
|
|
1082
|
+
}, "useAutoAuth");
|
|
1083
|
+
|
|
1084
|
+
// src/auth/hooks/useTwoFactor.ts
|
|
1085
|
+
import { useCallback as useCallback4, useState as useState4 } from "react";
|
|
914
1086
|
|
|
915
1087
|
// src/_api/generated/core/bodySerializer.gen.ts
|
|
916
1088
|
var serializeFormDataPair = /* @__PURE__ */ __name((data, key, value) => {
|
|
@@ -1733,103 +1905,6 @@ var client = createClient(createConfig({ baseUrl: "http://localhost:8000" }));
|
|
|
1733
1905
|
installAuthOnClient(client);
|
|
1734
1906
|
|
|
1735
1907
|
// src/_api/generated/sdk.gen.ts
|
|
1736
|
-
var CfgAccountsApiKey = class {
|
|
1737
|
-
static {
|
|
1738
|
-
__name(this, "CfgAccountsApiKey");
|
|
1739
|
-
}
|
|
1740
|
-
/**
|
|
1741
|
-
* Get API key details
|
|
1742
|
-
*
|
|
1743
|
-
* Retrieve the current user's API key (masked) and metadata.
|
|
1744
|
-
*/
|
|
1745
|
-
static cfgAccountsApiKeyRetrieve(options) {
|
|
1746
|
-
return (options?.client ?? client).get({
|
|
1747
|
-
security: [
|
|
1748
|
-
{ scheme: "bearer", type: "http" },
|
|
1749
|
-
{
|
|
1750
|
-
in: "cookie",
|
|
1751
|
-
name: "sessionid",
|
|
1752
|
-
type: "apiKey"
|
|
1753
|
-
},
|
|
1754
|
-
{ name: "X-API-Key", type: "apiKey" }
|
|
1755
|
-
],
|
|
1756
|
-
url: "/cfg/accounts/api-key/",
|
|
1757
|
-
...options
|
|
1758
|
-
});
|
|
1759
|
-
}
|
|
1760
|
-
/**
|
|
1761
|
-
* Regenerate API key
|
|
1762
|
-
*
|
|
1763
|
-
* Generate a new API key. The full key is returned only once.
|
|
1764
|
-
*/
|
|
1765
|
-
static cfgAccountsApiKeyRegenerateCreate(options) {
|
|
1766
|
-
return (options.client ?? client).post({
|
|
1767
|
-
security: [
|
|
1768
|
-
{ scheme: "bearer", type: "http" },
|
|
1769
|
-
{
|
|
1770
|
-
in: "cookie",
|
|
1771
|
-
name: "sessionid",
|
|
1772
|
-
type: "apiKey"
|
|
1773
|
-
},
|
|
1774
|
-
{ name: "X-API-Key", type: "apiKey" }
|
|
1775
|
-
],
|
|
1776
|
-
url: "/cfg/accounts/api-key/regenerate/",
|
|
1777
|
-
...options,
|
|
1778
|
-
headers: {
|
|
1779
|
-
"Content-Type": "application/json",
|
|
1780
|
-
...options.headers
|
|
1781
|
-
}
|
|
1782
|
-
});
|
|
1783
|
-
}
|
|
1784
|
-
/**
|
|
1785
|
-
* Reveal API key
|
|
1786
|
-
*
|
|
1787
|
-
* Return the current full API key WITHOUT rotating it. The same durable value every one of the user's agents uses — for the signed-in user to copy and paste into agent onboarding. Default GET stays masked; this is the explicit reveal action.
|
|
1788
|
-
*/
|
|
1789
|
-
static cfgAccountsApiKeyRevealCreate(options) {
|
|
1790
|
-
return (options.client ?? client).post({
|
|
1791
|
-
security: [
|
|
1792
|
-
{ scheme: "bearer", type: "http" },
|
|
1793
|
-
{
|
|
1794
|
-
in: "cookie",
|
|
1795
|
-
name: "sessionid",
|
|
1796
|
-
type: "apiKey"
|
|
1797
|
-
},
|
|
1798
|
-
{ name: "X-API-Key", type: "apiKey" }
|
|
1799
|
-
],
|
|
1800
|
-
url: "/cfg/accounts/api-key/reveal/",
|
|
1801
|
-
...options,
|
|
1802
|
-
headers: {
|
|
1803
|
-
"Content-Type": "application/json",
|
|
1804
|
-
...options.headers
|
|
1805
|
-
}
|
|
1806
|
-
});
|
|
1807
|
-
}
|
|
1808
|
-
/**
|
|
1809
|
-
* Test API key
|
|
1810
|
-
*
|
|
1811
|
-
* Test whether an API key is valid without consuming it.
|
|
1812
|
-
*/
|
|
1813
|
-
static cfgAccountsApiKeyTestCreate(options) {
|
|
1814
|
-
return (options.client ?? client).post({
|
|
1815
|
-
security: [
|
|
1816
|
-
{ scheme: "bearer", type: "http" },
|
|
1817
|
-
{
|
|
1818
|
-
in: "cookie",
|
|
1819
|
-
name: "sessionid",
|
|
1820
|
-
type: "apiKey"
|
|
1821
|
-
},
|
|
1822
|
-
{ name: "X-API-Key", type: "apiKey" }
|
|
1823
|
-
],
|
|
1824
|
-
url: "/cfg/accounts/api-key/test/",
|
|
1825
|
-
...options,
|
|
1826
|
-
headers: {
|
|
1827
|
-
"Content-Type": "application/json",
|
|
1828
|
-
...options.headers
|
|
1829
|
-
}
|
|
1830
|
-
});
|
|
1831
|
-
}
|
|
1832
|
-
};
|
|
1833
1908
|
var CfgAccountsOauth = class {
|
|
1834
1909
|
static {
|
|
1835
1910
|
__name(this, "CfgAccountsOauth");
|
|
@@ -1841,7 +1916,11 @@ var CfgAccountsOauth = class {
|
|
|
1841
1916
|
*/
|
|
1842
1917
|
static cfgAccountsOauthConnectionsList(options) {
|
|
1843
1918
|
return (options?.client ?? client).get({
|
|
1844
|
-
security: [
|
|
1919
|
+
security: [
|
|
1920
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
1921
|
+
{ scheme: "bearer", type: "http" },
|
|
1922
|
+
{ name: "Authorization", type: "apiKey" }
|
|
1923
|
+
],
|
|
1845
1924
|
url: "/cfg/accounts/oauth/connections/",
|
|
1846
1925
|
...options
|
|
1847
1926
|
});
|
|
@@ -1853,7 +1932,11 @@ var CfgAccountsOauth = class {
|
|
|
1853
1932
|
*/
|
|
1854
1933
|
static cfgAccountsOauthDisconnectCreate(options) {
|
|
1855
1934
|
return (options.client ?? client).post({
|
|
1856
|
-
security: [
|
|
1935
|
+
security: [
|
|
1936
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
1937
|
+
{ scheme: "bearer", type: "http" },
|
|
1938
|
+
{ name: "Authorization", type: "apiKey" }
|
|
1939
|
+
],
|
|
1857
1940
|
url: "/cfg/accounts/oauth/disconnect/",
|
|
1858
1941
|
...options,
|
|
1859
1942
|
headers: {
|
|
@@ -1910,7 +1993,11 @@ var CfgAccounts = class {
|
|
|
1910
1993
|
*/
|
|
1911
1994
|
static cfgAccountsOtpRequestCreate(options) {
|
|
1912
1995
|
return (options.client ?? client).post({
|
|
1913
|
-
security: [
|
|
1996
|
+
security: [
|
|
1997
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
1998
|
+
{ scheme: "bearer", type: "http" },
|
|
1999
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2000
|
+
],
|
|
1914
2001
|
url: "/cfg/accounts/otp/request/",
|
|
1915
2002
|
...options,
|
|
1916
2003
|
headers: {
|
|
@@ -1931,7 +2018,11 @@ var CfgAccounts = class {
|
|
|
1931
2018
|
*/
|
|
1932
2019
|
static cfgAccountsOtpVerifyCreate(options) {
|
|
1933
2020
|
return (options.client ?? client).post({
|
|
1934
|
-
security: [
|
|
2021
|
+
security: [
|
|
2022
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2023
|
+
{ scheme: "bearer", type: "http" },
|
|
2024
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2025
|
+
],
|
|
1935
2026
|
url: "/cfg/accounts/otp/verify/",
|
|
1936
2027
|
...options,
|
|
1937
2028
|
headers: {
|
|
@@ -1969,7 +2060,11 @@ var CfgAccountsProfile = class {
|
|
|
1969
2060
|
static cfgAccountsProfileAvatarCreate(options) {
|
|
1970
2061
|
return (options?.client ?? client).post({
|
|
1971
2062
|
...formDataBodySerializer,
|
|
1972
|
-
security: [
|
|
2063
|
+
security: [
|
|
2064
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2065
|
+
{ scheme: "bearer", type: "http" },
|
|
2066
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2067
|
+
],
|
|
1973
2068
|
url: "/cfg/accounts/profile/avatar/",
|
|
1974
2069
|
...options,
|
|
1975
2070
|
headers: {
|
|
@@ -2090,17 +2185,15 @@ var CfgAccountsAuth = class {
|
|
|
2090
2185
|
__name(this, "CfgAccountsAuth");
|
|
2091
2186
|
}
|
|
2092
2187
|
/**
|
|
2093
|
-
*
|
|
2188
|
+
* Revoke a refresh token (logout).
|
|
2094
2189
|
*
|
|
2095
|
-
*
|
|
2096
|
-
*
|
|
2097
|
-
*
|
|
2098
|
-
* proof is required on the refresh request — so a stolen refresh token can't
|
|
2099
|
-
* be used to mint fresh tokens.
|
|
2190
|
+
* Blacklists the posted refresh token so it can never mint another access
|
|
2191
|
+
* token. Called best-effort by the client's logout — without it, "logout"
|
|
2192
|
+
* is purely client-side and a stolen refresh token survives until expiry.
|
|
2100
2193
|
*/
|
|
2101
|
-
static
|
|
2194
|
+
static cfgAccountsTokenBlacklistCreate(options) {
|
|
2102
2195
|
return (options.client ?? client).post({
|
|
2103
|
-
url: "/cfg/accounts/token/
|
|
2196
|
+
url: "/cfg/accounts/token/blacklist/",
|
|
2104
2197
|
...options,
|
|
2105
2198
|
headers: {
|
|
2106
2199
|
"Content-Type": "application/json",
|
|
@@ -2108,48 +2201,18 @@ var CfgAccountsAuth = class {
|
|
|
2108
2201
|
}
|
|
2109
2202
|
});
|
|
2110
2203
|
}
|
|
2111
|
-
};
|
|
2112
|
-
var CfgCentrifugo = class {
|
|
2113
|
-
static {
|
|
2114
|
-
__name(this, "CfgCentrifugo");
|
|
2115
|
-
}
|
|
2116
|
-
/**
|
|
2117
|
-
* Get Centrifugo connection token
|
|
2118
|
-
*
|
|
2119
|
-
* Generate JWT token for WebSocket connection to Centrifugo. Token includes user's allowed channels based on their permissions. Requires authentication.
|
|
2120
|
-
*/
|
|
2121
|
-
static cfgCentrifugoAuthTokenRetrieve(options) {
|
|
2122
|
-
return (options?.client ?? client).get({
|
|
2123
|
-
security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
|
|
2124
|
-
url: "/cfg/centrifugo/auth/token/",
|
|
2125
|
-
...options
|
|
2126
|
-
});
|
|
2127
|
-
}
|
|
2128
|
-
};
|
|
2129
|
-
var CfgTotpBackupCodes = class {
|
|
2130
|
-
static {
|
|
2131
|
-
__name(this, "CfgTotpBackupCodes");
|
|
2132
|
-
}
|
|
2133
|
-
/**
|
|
2134
|
-
* Get backup codes status for user.
|
|
2135
|
-
*/
|
|
2136
|
-
static cfgTotpBackupCodesRetrieve(options) {
|
|
2137
|
-
return (options?.client ?? client).get({
|
|
2138
|
-
security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
|
|
2139
|
-
url: "/cfg/totp/backup-codes/",
|
|
2140
|
-
...options
|
|
2141
|
-
});
|
|
2142
|
-
}
|
|
2143
2204
|
/**
|
|
2144
|
-
*
|
|
2205
|
+
* Refresh JWT token.
|
|
2145
2206
|
*
|
|
2146
|
-
*
|
|
2147
|
-
*
|
|
2207
|
+
* DPoP-aware: when the incoming refresh token is key-bound (`cnf.jkt`), the
|
|
2208
|
+
* rotated access/refresh in the response are re-stamped with the same `cnf`
|
|
2209
|
+
* (stock SimpleJWT drops it from the derived access), and a matching DPoP
|
|
2210
|
+
* proof is required on the refresh request — so a stolen refresh token can't
|
|
2211
|
+
* be used to mint fresh tokens.
|
|
2148
2212
|
*/
|
|
2149
|
-
static
|
|
2213
|
+
static cfgAccountsTokenRefreshCreate(options) {
|
|
2150
2214
|
return (options.client ?? client).post({
|
|
2151
|
-
|
|
2152
|
-
url: "/cfg/totp/backup-codes/regenerate/",
|
|
2215
|
+
url: "/cfg/accounts/token/refresh/",
|
|
2153
2216
|
...options,
|
|
2154
2217
|
headers: {
|
|
2155
2218
|
"Content-Type": "application/json",
|
|
@@ -2167,7 +2230,11 @@ var CfgTotp = class {
|
|
|
2167
2230
|
*/
|
|
2168
2231
|
static cfgTotpDevicesRetrieve(options) {
|
|
2169
2232
|
return (options?.client ?? client).get({
|
|
2170
|
-
security: [
|
|
2233
|
+
security: [
|
|
2234
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2235
|
+
{ scheme: "bearer", type: "http" },
|
|
2236
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2237
|
+
],
|
|
2171
2238
|
url: "/cfg/totp/devices/",
|
|
2172
2239
|
...options
|
|
2173
2240
|
});
|
|
@@ -2179,7 +2246,11 @@ var CfgTotp = class {
|
|
|
2179
2246
|
*/
|
|
2180
2247
|
static cfgTotpDevicesDestroy(options) {
|
|
2181
2248
|
return (options.client ?? client).delete({
|
|
2182
|
-
security: [
|
|
2249
|
+
security: [
|
|
2250
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2251
|
+
{ scheme: "bearer", type: "http" },
|
|
2252
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2253
|
+
],
|
|
2183
2254
|
url: "/cfg/totp/devices/{id}/",
|
|
2184
2255
|
...options
|
|
2185
2256
|
});
|
|
@@ -2191,7 +2262,11 @@ var CfgTotp = class {
|
|
|
2191
2262
|
*/
|
|
2192
2263
|
static cfgTotpDisableCreate(options) {
|
|
2193
2264
|
return (options.client ?? client).post({
|
|
2194
|
-
security: [
|
|
2265
|
+
security: [
|
|
2266
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2267
|
+
{ scheme: "bearer", type: "http" },
|
|
2268
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2269
|
+
],
|
|
2195
2270
|
url: "/cfg/totp/disable/",
|
|
2196
2271
|
...options,
|
|
2197
2272
|
headers: {
|
|
@@ -2212,298 +2287,86 @@ var CfgTotpSetup = class {
|
|
|
2212
2287
|
*/
|
|
2213
2288
|
static cfgTotpSetupCreate(options) {
|
|
2214
2289
|
return (options?.client ?? client).post({
|
|
2215
|
-
security: [
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
* Activates the device and generates backup codes.
|
|
2228
|
-
*/
|
|
2229
|
-
static cfgTotpSetupConfirmCreate(options) {
|
|
2230
|
-
return (options.client ?? client).post({
|
|
2231
|
-
security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
|
|
2232
|
-
url: "/cfg/totp/setup/confirm/",
|
|
2233
|
-
...options,
|
|
2234
|
-
headers: {
|
|
2235
|
-
"Content-Type": "application/json",
|
|
2236
|
-
...options.headers
|
|
2237
|
-
}
|
|
2238
|
-
});
|
|
2239
|
-
}
|
|
2240
|
-
};
|
|
2241
|
-
var CfgTotpVerify = class {
|
|
2242
|
-
static {
|
|
2243
|
-
__name(this, "CfgTotpVerify");
|
|
2244
|
-
}
|
|
2245
|
-
/**
|
|
2246
|
-
* Verify TOTP code for 2FA session.
|
|
2247
|
-
*
|
|
2248
|
-
* Completes authentication and returns JWT tokens on success.
|
|
2249
|
-
*/
|
|
2250
|
-
static cfgTotpVerifyCreate(options) {
|
|
2251
|
-
return (options.client ?? client).post({
|
|
2252
|
-
security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
|
|
2253
|
-
url: "/cfg/totp/verify/",
|
|
2254
|
-
...options,
|
|
2255
|
-
headers: {
|
|
2256
|
-
"Content-Type": "application/json",
|
|
2257
|
-
...options.headers
|
|
2258
|
-
}
|
|
2259
|
-
});
|
|
2260
|
-
}
|
|
2261
|
-
/**
|
|
2262
|
-
* Verify backup recovery code for 2FA session.
|
|
2263
|
-
*
|
|
2264
|
-
* Alternative verification method when TOTP device unavailable.
|
|
2265
|
-
*/
|
|
2266
|
-
static cfgTotpVerifyBackupCreate(options) {
|
|
2267
|
-
return (options.client ?? client).post({
|
|
2268
|
-
security: [{ name: "X-API-Key", type: "apiKey" }, { scheme: "bearer", type: "http" }],
|
|
2269
|
-
url: "/cfg/totp/verify/backup/",
|
|
2270
|
-
...options,
|
|
2271
|
-
headers: {
|
|
2272
|
-
"Content-Type": "application/json",
|
|
2273
|
-
...options.headers
|
|
2274
|
-
}
|
|
2275
|
-
});
|
|
2276
|
-
}
|
|
2277
|
-
};
|
|
2278
|
-
|
|
2279
|
-
// src/_api/generated/_cfg_accounts/api.ts
|
|
2280
|
-
var API = class {
|
|
2281
|
-
static {
|
|
2282
|
-
__name(this, "API");
|
|
2283
|
-
}
|
|
2284
|
-
logger;
|
|
2285
|
-
cfgAccountsApiKey = CfgAccountsApiKey;
|
|
2286
|
-
cfgAccountsOauth = CfgAccountsOauth;
|
|
2287
|
-
cfgAccounts = CfgAccounts;
|
|
2288
|
-
cfgAccountsProfile = CfgAccountsProfile;
|
|
2289
|
-
cfgAccountsAuth = CfgAccountsAuth;
|
|
2290
|
-
constructor(_baseUrl, opts = {}) {
|
|
2291
|
-
this.logger = new APILogger(opts.logger);
|
|
2292
|
-
if (_baseUrl) auth.setBaseUrl(_baseUrl);
|
|
2293
|
-
if (opts.locale !== void 0) auth.setLocale(opts.locale);
|
|
2294
|
-
if (opts.apiKey !== void 0) auth.setApiKey(opts.apiKey);
|
|
2295
|
-
if (opts.withCredentials !== void 0) auth.setWithCredentials(opts.withCredentials);
|
|
2296
|
-
}
|
|
2297
|
-
// ── Base URL ────────────────────────────────────────────────────────────
|
|
2298
|
-
getBaseUrl() {
|
|
2299
|
-
return auth.getBaseUrl();
|
|
2300
|
-
}
|
|
2301
|
-
setBaseUrl(url) {
|
|
2302
|
-
auth.setBaseUrl(url);
|
|
2303
|
-
}
|
|
2304
|
-
// ── Tokens ──────────────────────────────────────────────────────────────
|
|
2305
|
-
getToken() {
|
|
2306
|
-
return auth.getToken();
|
|
2307
|
-
}
|
|
2308
|
-
setToken(token) {
|
|
2309
|
-
auth.setToken(token);
|
|
2310
|
-
}
|
|
2311
|
-
getRefreshToken() {
|
|
2312
|
-
return auth.getRefreshToken();
|
|
2313
|
-
}
|
|
2314
|
-
setRefreshToken(token) {
|
|
2315
|
-
auth.setRefreshToken(token);
|
|
2316
|
-
}
|
|
2317
|
-
clearToken() {
|
|
2318
|
-
auth.clearTokens();
|
|
2319
|
-
}
|
|
2320
|
-
isAuthenticated() {
|
|
2321
|
-
return auth.isAuthenticated();
|
|
2322
|
-
}
|
|
2323
|
-
// ── Locale / API key ────────────────────────────────────────────────────
|
|
2324
|
-
getLocale() {
|
|
2325
|
-
return auth.getLocale();
|
|
2326
|
-
}
|
|
2327
|
-
setLocale(locale) {
|
|
2328
|
-
auth.setLocale(locale);
|
|
2329
|
-
}
|
|
2330
|
-
getApiKey() {
|
|
2331
|
-
return auth.getApiKey();
|
|
2332
|
-
}
|
|
2333
|
-
setApiKey(key) {
|
|
2334
|
-
auth.setApiKey(key);
|
|
2335
|
-
}
|
|
2336
|
-
// ── 401 handling ────────────────────────────────────────────────────────
|
|
2337
|
-
/** Fired only on terminal 401 (after refresh+retry path is exhausted). */
|
|
2338
|
-
onUnauthorized(cb) {
|
|
2339
|
-
auth.onUnauthorized(cb);
|
|
2340
|
-
}
|
|
2341
|
-
/** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
|
|
2342
|
-
setRefreshHandler(fn) {
|
|
2343
|
-
auth.setRefreshHandler(fn);
|
|
2344
|
-
}
|
|
2345
|
-
/**
|
|
2346
|
-
* Proactively refresh now via the registered handler, sharing the single
|
|
2347
|
-
* 401-recovery flight (rotation + dedup + rotated-token persistence).
|
|
2348
|
-
* See `auth.refreshNow`.
|
|
2349
|
-
*/
|
|
2350
|
-
refreshNow() {
|
|
2351
|
-
return auth.refreshNow();
|
|
2352
|
-
}
|
|
2353
|
-
};
|
|
2354
|
-
|
|
2355
|
-
// src/_api/generated/_cfg_centrifugo/api.ts
|
|
2356
|
-
var API2 = class {
|
|
2357
|
-
static {
|
|
2358
|
-
__name(this, "API");
|
|
2359
|
-
}
|
|
2360
|
-
logger;
|
|
2361
|
-
cfgCentrifugo = CfgCentrifugo;
|
|
2362
|
-
constructor(_baseUrl, opts = {}) {
|
|
2363
|
-
this.logger = new APILogger(opts.logger);
|
|
2364
|
-
if (_baseUrl) auth.setBaseUrl(_baseUrl);
|
|
2365
|
-
if (opts.locale !== void 0) auth.setLocale(opts.locale);
|
|
2366
|
-
if (opts.apiKey !== void 0) auth.setApiKey(opts.apiKey);
|
|
2367
|
-
if (opts.withCredentials !== void 0) auth.setWithCredentials(opts.withCredentials);
|
|
2368
|
-
}
|
|
2369
|
-
// ── Base URL ────────────────────────────────────────────────────────────
|
|
2370
|
-
getBaseUrl() {
|
|
2371
|
-
return auth.getBaseUrl();
|
|
2372
|
-
}
|
|
2373
|
-
setBaseUrl(url) {
|
|
2374
|
-
auth.setBaseUrl(url);
|
|
2375
|
-
}
|
|
2376
|
-
// ── Tokens ──────────────────────────────────────────────────────────────
|
|
2377
|
-
getToken() {
|
|
2378
|
-
return auth.getToken();
|
|
2379
|
-
}
|
|
2380
|
-
setToken(token) {
|
|
2381
|
-
auth.setToken(token);
|
|
2382
|
-
}
|
|
2383
|
-
getRefreshToken() {
|
|
2384
|
-
return auth.getRefreshToken();
|
|
2385
|
-
}
|
|
2386
|
-
setRefreshToken(token) {
|
|
2387
|
-
auth.setRefreshToken(token);
|
|
2388
|
-
}
|
|
2389
|
-
clearToken() {
|
|
2390
|
-
auth.clearTokens();
|
|
2391
|
-
}
|
|
2392
|
-
isAuthenticated() {
|
|
2393
|
-
return auth.isAuthenticated();
|
|
2394
|
-
}
|
|
2395
|
-
// ── Locale / API key ────────────────────────────────────────────────────
|
|
2396
|
-
getLocale() {
|
|
2397
|
-
return auth.getLocale();
|
|
2398
|
-
}
|
|
2399
|
-
setLocale(locale) {
|
|
2400
|
-
auth.setLocale(locale);
|
|
2401
|
-
}
|
|
2402
|
-
getApiKey() {
|
|
2403
|
-
return auth.getApiKey();
|
|
2404
|
-
}
|
|
2405
|
-
setApiKey(key) {
|
|
2406
|
-
auth.setApiKey(key);
|
|
2407
|
-
}
|
|
2408
|
-
// ── 401 handling ────────────────────────────────────────────────────────
|
|
2409
|
-
/** Fired only on terminal 401 (after refresh+retry path is exhausted). */
|
|
2410
|
-
onUnauthorized(cb) {
|
|
2411
|
-
auth.onUnauthorized(cb);
|
|
2412
|
-
}
|
|
2413
|
-
/** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
|
|
2414
|
-
setRefreshHandler(fn) {
|
|
2415
|
-
auth.setRefreshHandler(fn);
|
|
2290
|
+
security: [
|
|
2291
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2292
|
+
{ scheme: "bearer", type: "http" },
|
|
2293
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2294
|
+
],
|
|
2295
|
+
url: "/cfg/totp/setup/",
|
|
2296
|
+
...options,
|
|
2297
|
+
headers: {
|
|
2298
|
+
"Content-Type": "application/json",
|
|
2299
|
+
...options?.headers
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2416
2302
|
}
|
|
2417
2303
|
/**
|
|
2418
|
-
*
|
|
2419
|
-
*
|
|
2420
|
-
*
|
|
2304
|
+
* Confirm 2FA setup with first valid code.
|
|
2305
|
+
*
|
|
2306
|
+
* Activates the device and generates backup codes.
|
|
2421
2307
|
*/
|
|
2422
|
-
|
|
2423
|
-
return
|
|
2308
|
+
static cfgTotpSetupConfirmCreate(options) {
|
|
2309
|
+
return (options.client ?? client).post({
|
|
2310
|
+
security: [
|
|
2311
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2312
|
+
{ scheme: "bearer", type: "http" },
|
|
2313
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2314
|
+
],
|
|
2315
|
+
url: "/cfg/totp/setup/confirm/",
|
|
2316
|
+
...options,
|
|
2317
|
+
headers: {
|
|
2318
|
+
"Content-Type": "application/json",
|
|
2319
|
+
...options.headers
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2424
2322
|
}
|
|
2425
2323
|
};
|
|
2426
|
-
|
|
2427
|
-
// src/_api/generated/_cfg_totp/api.ts
|
|
2428
|
-
var API3 = class {
|
|
2324
|
+
var CfgTotpVerify = class {
|
|
2429
2325
|
static {
|
|
2430
|
-
__name(this, "
|
|
2431
|
-
}
|
|
2432
|
-
logger;
|
|
2433
|
-
cfgTotpBackupCodes = CfgTotpBackupCodes;
|
|
2434
|
-
cfgTotp = CfgTotp;
|
|
2435
|
-
cfgTotpSetup = CfgTotpSetup;
|
|
2436
|
-
cfgTotpVerify = CfgTotpVerify;
|
|
2437
|
-
constructor(_baseUrl, opts = {}) {
|
|
2438
|
-
this.logger = new APILogger(opts.logger);
|
|
2439
|
-
if (_baseUrl) auth.setBaseUrl(_baseUrl);
|
|
2440
|
-
if (opts.locale !== void 0) auth.setLocale(opts.locale);
|
|
2441
|
-
if (opts.apiKey !== void 0) auth.setApiKey(opts.apiKey);
|
|
2442
|
-
if (opts.withCredentials !== void 0) auth.setWithCredentials(opts.withCredentials);
|
|
2443
|
-
}
|
|
2444
|
-
// ── Base URL ────────────────────────────────────────────────────────────
|
|
2445
|
-
getBaseUrl() {
|
|
2446
|
-
return auth.getBaseUrl();
|
|
2447
|
-
}
|
|
2448
|
-
setBaseUrl(url) {
|
|
2449
|
-
auth.setBaseUrl(url);
|
|
2450
|
-
}
|
|
2451
|
-
// ── Tokens ──────────────────────────────────────────────────────────────
|
|
2452
|
-
getToken() {
|
|
2453
|
-
return auth.getToken();
|
|
2454
|
-
}
|
|
2455
|
-
setToken(token) {
|
|
2456
|
-
auth.setToken(token);
|
|
2457
|
-
}
|
|
2458
|
-
getRefreshToken() {
|
|
2459
|
-
return auth.getRefreshToken();
|
|
2460
|
-
}
|
|
2461
|
-
setRefreshToken(token) {
|
|
2462
|
-
auth.setRefreshToken(token);
|
|
2463
|
-
}
|
|
2464
|
-
clearToken() {
|
|
2465
|
-
auth.clearTokens();
|
|
2466
|
-
}
|
|
2467
|
-
isAuthenticated() {
|
|
2468
|
-
return auth.isAuthenticated();
|
|
2469
|
-
}
|
|
2470
|
-
// ── Locale / API key ────────────────────────────────────────────────────
|
|
2471
|
-
getLocale() {
|
|
2472
|
-
return auth.getLocale();
|
|
2473
|
-
}
|
|
2474
|
-
setLocale(locale) {
|
|
2475
|
-
auth.setLocale(locale);
|
|
2476
|
-
}
|
|
2477
|
-
getApiKey() {
|
|
2478
|
-
return auth.getApiKey();
|
|
2479
|
-
}
|
|
2480
|
-
setApiKey(key) {
|
|
2481
|
-
auth.setApiKey(key);
|
|
2482
|
-
}
|
|
2483
|
-
// ── 401 handling ────────────────────────────────────────────────────────
|
|
2484
|
-
/** Fired only on terminal 401 (after refresh+retry path is exhausted). */
|
|
2485
|
-
onUnauthorized(cb) {
|
|
2486
|
-
auth.onUnauthorized(cb);
|
|
2326
|
+
__name(this, "CfgTotpVerify");
|
|
2487
2327
|
}
|
|
2488
|
-
/**
|
|
2489
|
-
|
|
2490
|
-
|
|
2328
|
+
/**
|
|
2329
|
+
* Verify TOTP code for 2FA session.
|
|
2330
|
+
*
|
|
2331
|
+
* Completes authentication and returns JWT tokens on success.
|
|
2332
|
+
*/
|
|
2333
|
+
static cfgTotpVerifyCreate(options) {
|
|
2334
|
+
return (options.client ?? client).post({
|
|
2335
|
+
security: [
|
|
2336
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2337
|
+
{ scheme: "bearer", type: "http" },
|
|
2338
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2339
|
+
],
|
|
2340
|
+
url: "/cfg/totp/verify/",
|
|
2341
|
+
...options,
|
|
2342
|
+
headers: {
|
|
2343
|
+
"Content-Type": "application/json",
|
|
2344
|
+
...options.headers
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2491
2347
|
}
|
|
2492
2348
|
/**
|
|
2493
|
-
*
|
|
2494
|
-
*
|
|
2495
|
-
*
|
|
2349
|
+
* Verify backup recovery code for 2FA session.
|
|
2350
|
+
*
|
|
2351
|
+
* Alternative verification method when TOTP device unavailable.
|
|
2496
2352
|
*/
|
|
2497
|
-
|
|
2498
|
-
return
|
|
2353
|
+
static cfgTotpVerifyBackupCreate(options) {
|
|
2354
|
+
return (options.client ?? client).post({
|
|
2355
|
+
security: [
|
|
2356
|
+
{ name: "X-API-Key", type: "apiKey" },
|
|
2357
|
+
{ scheme: "bearer", type: "http" },
|
|
2358
|
+
{ name: "Authorization", type: "apiKey" }
|
|
2359
|
+
],
|
|
2360
|
+
url: "/cfg/totp/verify/backup/",
|
|
2361
|
+
...options,
|
|
2362
|
+
headers: {
|
|
2363
|
+
"Content-Type": "application/json",
|
|
2364
|
+
...options.headers
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2499
2367
|
}
|
|
2500
2368
|
};
|
|
2501
2369
|
|
|
2502
|
-
// src/_api/generated/index.ts
|
|
2503
|
-
var CfgAccountsApi = new API();
|
|
2504
|
-
var CfgCentrifugoApi = new API2();
|
|
2505
|
-
var CfgTotpApi = new API3();
|
|
2506
|
-
|
|
2507
2370
|
// src/auth/utils/analytics.ts
|
|
2508
2371
|
var AnalyticsEvent = /* @__PURE__ */ ((AnalyticsEvent2) => {
|
|
2509
2372
|
AnalyticsEvent2["AUTH_OTP_REQUEST"] = "auth_otp_request";
|
|
@@ -2537,24 +2400,229 @@ var Analytics = {
|
|
|
2537
2400
|
if (isDev) {
|
|
2538
2401
|
console.log("[Analytics] Set user:", userId);
|
|
2539
2402
|
}
|
|
2540
|
-
}
|
|
2541
|
-
};
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2406
|
+
// src/auth/hooks/useSessionStorage.ts
|
|
2407
|
+
import { useState as useState3 } from "react";
|
|
2408
|
+
function useSessionStorage(key, initialValue) {
|
|
2409
|
+
const [storedValue, setStoredValue] = useState3(() => {
|
|
2410
|
+
if (typeof window === "undefined") {
|
|
2411
|
+
return initialValue;
|
|
2412
|
+
}
|
|
2413
|
+
try {
|
|
2414
|
+
const item = window.sessionStorage.getItem(key);
|
|
2415
|
+
return item ? JSON.parse(item) : initialValue;
|
|
2416
|
+
} catch (error) {
|
|
2417
|
+
authLogger.error(`Error reading sessionStorage key "${key}":`, error);
|
|
2418
|
+
return initialValue;
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
const checkDataSize = /* @__PURE__ */ __name((data) => {
|
|
2422
|
+
try {
|
|
2423
|
+
const jsonString = JSON.stringify(data);
|
|
2424
|
+
const sizeInBytes = new Blob([jsonString]).size;
|
|
2425
|
+
const sizeInKB = sizeInBytes / 1024;
|
|
2426
|
+
if (sizeInKB > 1024) {
|
|
2427
|
+
authLogger.warn(`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`);
|
|
2428
|
+
return false;
|
|
2429
|
+
}
|
|
2430
|
+
return true;
|
|
2431
|
+
} catch (error) {
|
|
2432
|
+
authLogger.error(`Error checking data size for key "${key}":`, error);
|
|
2433
|
+
return false;
|
|
2434
|
+
}
|
|
2435
|
+
}, "checkDataSize");
|
|
2436
|
+
const clearOldData = /* @__PURE__ */ __name(() => {
|
|
2437
|
+
try {
|
|
2438
|
+
const keys = Object.keys(sessionStorage).filter((key2) => key2 && typeof key2 === "string");
|
|
2439
|
+
if (keys.length > 50) {
|
|
2440
|
+
const itemsToRemove = Math.ceil(keys.length * 0.2);
|
|
2441
|
+
for (let i = 0; i < itemsToRemove; i++) {
|
|
2442
|
+
try {
|
|
2443
|
+
const key2 = keys[i];
|
|
2444
|
+
if (key2) {
|
|
2445
|
+
sessionStorage.removeItem(key2);
|
|
2446
|
+
sessionStorage.removeItem(`${key2}_timestamp`);
|
|
2447
|
+
}
|
|
2448
|
+
} catch {
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
} catch (error) {
|
|
2453
|
+
authLogger.error("Error clearing old sessionStorage data:", error);
|
|
2454
|
+
}
|
|
2455
|
+
}, "clearOldData");
|
|
2456
|
+
const forceClearAll = /* @__PURE__ */ __name(() => {
|
|
2457
|
+
try {
|
|
2458
|
+
const keys = Object.keys(sessionStorage);
|
|
2459
|
+
for (const key2 of keys) {
|
|
2460
|
+
try {
|
|
2461
|
+
sessionStorage.removeItem(key2);
|
|
2462
|
+
} catch {
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
authLogger.error("Error force clearing sessionStorage:", error);
|
|
2467
|
+
}
|
|
2468
|
+
}, "forceClearAll");
|
|
2469
|
+
const setValue = /* @__PURE__ */ __name((value) => {
|
|
2470
|
+
try {
|
|
2471
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
2472
|
+
if (!checkDataSize(valueToStore)) {
|
|
2473
|
+
authLogger.warn(`Data size too large for key "${key}", removing key`);
|
|
2474
|
+
try {
|
|
2475
|
+
window.sessionStorage.removeItem(key);
|
|
2476
|
+
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
2477
|
+
} catch {
|
|
2478
|
+
}
|
|
2479
|
+
setStoredValue(valueToStore);
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
setStoredValue(valueToStore);
|
|
2483
|
+
if (typeof window !== "undefined") {
|
|
2484
|
+
try {
|
|
2485
|
+
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
|
2486
|
+
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
2487
|
+
} catch (storageError) {
|
|
2488
|
+
if (storageError.name === "QuotaExceededError" || storageError.code === 22 || storageError.message?.includes("quota")) {
|
|
2489
|
+
authLogger.warn("sessionStorage quota exceeded, clearing old data...");
|
|
2490
|
+
clearOldData();
|
|
2491
|
+
try {
|
|
2492
|
+
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
|
2493
|
+
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
2494
|
+
} catch (retryError) {
|
|
2495
|
+
authLogger.error(`Failed to set sessionStorage key "${key}" after clearing old data:`, retryError);
|
|
2496
|
+
try {
|
|
2497
|
+
forceClearAll();
|
|
2498
|
+
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
|
2499
|
+
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
2500
|
+
} catch (finalError) {
|
|
2501
|
+
authLogger.error(`Failed to set sessionStorage key "${key}" after force clearing:`, finalError);
|
|
2502
|
+
setStoredValue(valueToStore);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
} else {
|
|
2506
|
+
throw storageError;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
authLogger.error(`Error setting sessionStorage key "${key}":`, error);
|
|
2512
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
2513
|
+
setStoredValue(valueToStore);
|
|
2514
|
+
}
|
|
2515
|
+
}, "setValue");
|
|
2516
|
+
const removeValue = /* @__PURE__ */ __name(() => {
|
|
2517
|
+
try {
|
|
2518
|
+
setStoredValue(initialValue);
|
|
2519
|
+
if (typeof window !== "undefined") {
|
|
2520
|
+
try {
|
|
2521
|
+
window.sessionStorage.removeItem(key);
|
|
2522
|
+
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
2523
|
+
} catch (removeError) {
|
|
2524
|
+
if (removeError.name === "QuotaExceededError" || removeError.code === 22 || removeError.message?.includes("quota")) {
|
|
2525
|
+
authLogger.warn("sessionStorage quota exceeded during removal, clearing old data...");
|
|
2526
|
+
clearOldData();
|
|
2527
|
+
try {
|
|
2528
|
+
window.sessionStorage.removeItem(key);
|
|
2529
|
+
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
2530
|
+
} catch (retryError) {
|
|
2531
|
+
authLogger.error(`Failed to remove sessionStorage key "${key}" after clearing:`, retryError);
|
|
2532
|
+
forceClearAll();
|
|
2533
|
+
}
|
|
2534
|
+
} else {
|
|
2535
|
+
throw removeError;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
} catch (error) {
|
|
2540
|
+
authLogger.error(`Error removing sessionStorage key "${key}":`, error);
|
|
2541
|
+
}
|
|
2542
|
+
}, "removeValue");
|
|
2543
|
+
return [storedValue, setValue, removeValue];
|
|
2544
|
+
}
|
|
2545
|
+
__name(useSessionStorage, "useSessionStorage");
|
|
2546
|
+
|
|
2547
|
+
// src/auth/hooks/useAuthRedirect.ts
|
|
2548
|
+
var AUTH_REDIRECT_KEY = "auth_redirect_url";
|
|
2549
|
+
var getRedirectFromStorage = /* @__PURE__ */ __name(() => {
|
|
2550
|
+
if (typeof window === "undefined") return "";
|
|
2551
|
+
try {
|
|
2552
|
+
const item = window.sessionStorage.getItem(AUTH_REDIRECT_KEY);
|
|
2553
|
+
return item ? JSON.parse(item) : "";
|
|
2554
|
+
} catch {
|
|
2555
|
+
return "";
|
|
2556
|
+
}
|
|
2557
|
+
}, "getRedirectFromStorage");
|
|
2558
|
+
var clearRedirectFromStorage = /* @__PURE__ */ __name(() => {
|
|
2559
|
+
if (typeof window === "undefined") return;
|
|
2560
|
+
try {
|
|
2561
|
+
window.sessionStorage.removeItem(AUTH_REDIRECT_KEY);
|
|
2562
|
+
window.sessionStorage.removeItem(`${AUTH_REDIRECT_KEY}_timestamp`);
|
|
2563
|
+
} catch {
|
|
2564
|
+
}
|
|
2565
|
+
}, "clearRedirectFromStorage");
|
|
2566
|
+
var peekSavedRedirect = /* @__PURE__ */ __name(() => getRedirectFromStorage() || null, "peekSavedRedirect");
|
|
2567
|
+
var consumeSavedRedirect = /* @__PURE__ */ __name(() => {
|
|
2568
|
+
const stored = getRedirectFromStorage();
|
|
2569
|
+
if (stored) clearRedirectFromStorage();
|
|
2570
|
+
return stored || null;
|
|
2571
|
+
}, "consumeSavedRedirect");
|
|
2572
|
+
var useAuthRedirectManager = /* @__PURE__ */ __name((options = {}) => {
|
|
2573
|
+
const { fallbackUrl = "/dashboard", clearOnUse = true } = options;
|
|
2574
|
+
const [redirectUrl, setRedirectUrl, removeRedirectUrl] = useSessionStorage(AUTH_REDIRECT_KEY, "");
|
|
2575
|
+
const setRedirect = /* @__PURE__ */ __name((url) => {
|
|
2576
|
+
setRedirectUrl(url);
|
|
2577
|
+
}, "setRedirect");
|
|
2578
|
+
const getRedirect = /* @__PURE__ */ __name(() => {
|
|
2579
|
+
return getRedirectFromStorage() || redirectUrl;
|
|
2580
|
+
}, "getRedirect");
|
|
2581
|
+
const clearRedirect = /* @__PURE__ */ __name(() => {
|
|
2582
|
+
removeRedirectUrl();
|
|
2583
|
+
clearRedirectFromStorage();
|
|
2584
|
+
}, "clearRedirect");
|
|
2585
|
+
const hasRedirect = /* @__PURE__ */ __name(() => {
|
|
2586
|
+
const stored = getRedirectFromStorage();
|
|
2587
|
+
return stored.length > 0 || redirectUrl.length > 0;
|
|
2588
|
+
}, "hasRedirect");
|
|
2589
|
+
const getFinalRedirectUrl = /* @__PURE__ */ __name(() => {
|
|
2590
|
+
const stored = getRedirectFromStorage();
|
|
2591
|
+
return stored || redirectUrl || fallbackUrl;
|
|
2592
|
+
}, "getFinalRedirectUrl");
|
|
2593
|
+
const useAndClearRedirect = /* @__PURE__ */ __name(() => {
|
|
2594
|
+
const stored = getRedirectFromStorage();
|
|
2595
|
+
const finalUrl = stored || redirectUrl || fallbackUrl;
|
|
2596
|
+
if (clearOnUse) {
|
|
2597
|
+
clearRedirect();
|
|
2598
|
+
}
|
|
2599
|
+
return finalUrl;
|
|
2600
|
+
}, "useAndClearRedirect");
|
|
2601
|
+
return {
|
|
2602
|
+
redirectUrl,
|
|
2603
|
+
setRedirect,
|
|
2604
|
+
getRedirect,
|
|
2605
|
+
clearRedirect,
|
|
2606
|
+
hasRedirect,
|
|
2607
|
+
getFinalRedirectUrl,
|
|
2608
|
+
useAndClearRedirect
|
|
2609
|
+
};
|
|
2610
|
+
}, "useAuthRedirectManager");
|
|
2542
2611
|
|
|
2543
2612
|
// src/auth/hooks/useTwoFactor.ts
|
|
2544
2613
|
var useTwoFactor = /* @__PURE__ */ __name((options = {}) => {
|
|
2545
2614
|
const { onSuccess, onError, redirectUrl, skipRedirect = false } = options;
|
|
2546
2615
|
const router = useCfgRouter();
|
|
2547
|
-
const [isLoading, setIsLoading] =
|
|
2548
|
-
const [error, setError] =
|
|
2549
|
-
const [warning, setWarning] =
|
|
2550
|
-
const [remainingBackupCodes, setRemainingBackupCodes] =
|
|
2551
|
-
const [attemptsRemaining, setAttemptsRemaining] =
|
|
2616
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
2617
|
+
const [error, setError] = useState4(null);
|
|
2618
|
+
const [warning, setWarning] = useState4(null);
|
|
2619
|
+
const [remainingBackupCodes, setRemainingBackupCodes] = useState4(null);
|
|
2620
|
+
const [attemptsRemaining, setAttemptsRemaining] = useState4(null);
|
|
2552
2621
|
const clearError = useCallback4(() => {
|
|
2553
2622
|
setError(null);
|
|
2554
2623
|
}, []);
|
|
2555
2624
|
const handleSuccess = useCallback4((response) => {
|
|
2556
|
-
|
|
2557
|
-
CfgAccountsApi.setRefreshToken(response.refresh_token);
|
|
2625
|
+
auth.setSession({ access: response.access_token, refresh: response.refresh_token });
|
|
2558
2626
|
if (response.warning) {
|
|
2559
2627
|
setWarning(response.warning);
|
|
2560
2628
|
}
|
|
@@ -2570,7 +2638,7 @@ var useTwoFactor = /* @__PURE__ */ __name((options = {}) => {
|
|
|
2570
2638
|
}
|
|
2571
2639
|
onSuccess?.(response.user);
|
|
2572
2640
|
if (!skipRedirect) {
|
|
2573
|
-
const finalRedirectUrl = redirectUrl || "/
|
|
2641
|
+
const finalRedirectUrl = consumeSavedRedirect() || redirectUrl || "/";
|
|
2574
2642
|
authLogger.info("2FA successful, redirecting to:", finalRedirectUrl);
|
|
2575
2643
|
router.hardPush(finalRedirectUrl);
|
|
2576
2644
|
}
|
|
@@ -2960,12 +3028,12 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
|
|
|
2960
3028
|
}, "useAuthForm");
|
|
2961
3029
|
|
|
2962
3030
|
// src/auth/hooks/useGithubAuth.ts
|
|
2963
|
-
import { useCallback as useCallback6, useState as
|
|
3031
|
+
import { useCallback as useCallback6, useState as useState5 } from "react";
|
|
2964
3032
|
var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
2965
3033
|
const { sourceUrl, onSuccess, onError, onRequires2FA, redirectUrl, skipRedirect = false } = options;
|
|
2966
3034
|
const router = useCfgRouter();
|
|
2967
|
-
const [isLoading, setIsLoading] =
|
|
2968
|
-
const [error, setError] =
|
|
3035
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
3036
|
+
const [error, setError] = useState5(null);
|
|
2969
3037
|
const startGithubAuth = useCallback6(async () => {
|
|
2970
3038
|
setIsLoading(true);
|
|
2971
3039
|
setError(null);
|
|
@@ -3035,8 +3103,7 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3035
3103
|
throw new Error("Invalid response from OAuth callback");
|
|
3036
3104
|
}
|
|
3037
3105
|
authLogger.info("GitHub OAuth successful, user:", response.user);
|
|
3038
|
-
|
|
3039
|
-
CfgAccountsApi.setRefreshToken(response.refresh);
|
|
3106
|
+
auth.setSession({ access: response.access, refresh: response.refresh });
|
|
3040
3107
|
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
3041
3108
|
category: "auth" /* AUTH */,
|
|
3042
3109
|
label: "github"
|
|
@@ -3071,15 +3138,15 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3071
3138
|
}, "useGithubAuth");
|
|
3072
3139
|
|
|
3073
3140
|
// src/auth/hooks/useTwoFactorSetup.ts
|
|
3074
|
-
import { useCallback as useCallback7, useState as
|
|
3141
|
+
import { useCallback as useCallback7, useState as useState6 } from "react";
|
|
3075
3142
|
var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
|
|
3076
3143
|
const { onComplete, onError } = options;
|
|
3077
|
-
const [isLoading, setIsLoading] =
|
|
3078
|
-
const [error, setError] =
|
|
3079
|
-
const [setupData, setSetupData] =
|
|
3080
|
-
const [backupCodes, setBackupCodes] =
|
|
3081
|
-
const [backupCodesWarning, setBackupCodesWarning] =
|
|
3082
|
-
const [setupStep, setSetupStep] =
|
|
3144
|
+
const [isLoading, setIsLoading] = useState6(false);
|
|
3145
|
+
const [error, setError] = useState6(null);
|
|
3146
|
+
const [setupData, setSetupData] = useState6(null);
|
|
3147
|
+
const [backupCodes, setBackupCodes] = useState6(null);
|
|
3148
|
+
const [backupCodesWarning, setBackupCodesWarning] = useState6(null);
|
|
3149
|
+
const [setupStep, setSetupStep] = useState6("idle");
|
|
3083
3150
|
const clearError = useCallback7(() => {
|
|
3084
3151
|
setError(null);
|
|
3085
3152
|
}, []);
|
|
@@ -3178,7 +3245,7 @@ var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
|
|
|
3178
3245
|
}, "useTwoFactorSetup");
|
|
3179
3246
|
|
|
3180
3247
|
// src/auth/hooks/useTwoFactorStatus.ts
|
|
3181
|
-
import { useCallback as useCallback8, useState as
|
|
3248
|
+
import { useCallback as useCallback8, useState as useState7 } from "react";
|
|
3182
3249
|
function extractErrorMessage(err, fallback) {
|
|
3183
3250
|
if (err instanceof APIError) {
|
|
3184
3251
|
const body = err.response;
|
|
@@ -3191,10 +3258,10 @@ function extractErrorMessage(err, fallback) {
|
|
|
3191
3258
|
}
|
|
3192
3259
|
__name(extractErrorMessage, "extractErrorMessage");
|
|
3193
3260
|
var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
|
|
3194
|
-
const [isLoading, setIsLoading] =
|
|
3195
|
-
const [error, setError] =
|
|
3196
|
-
const [has2FAEnabled, setHas2FAEnabled] =
|
|
3197
|
-
const [devices, setDevices] =
|
|
3261
|
+
const [isLoading, setIsLoading] = useState7(false);
|
|
3262
|
+
const [error, setError] = useState7(null);
|
|
3263
|
+
const [has2FAEnabled, setHas2FAEnabled] = useState7(null);
|
|
3264
|
+
const [devices, setDevices] = useState7([]);
|
|
3198
3265
|
const clearError = useCallback8(() => {
|
|
3199
3266
|
setError(null);
|
|
3200
3267
|
}, []);
|
|
@@ -3229,258 +3296,38 @@ var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
|
|
|
3229
3296
|
return false;
|
|
3230
3297
|
}
|
|
3231
3298
|
setIsLoading(true);
|
|
3232
|
-
setError(null);
|
|
3233
|
-
try {
|
|
3234
|
-
authLogger.info("Disabling 2FA...");
|
|
3235
|
-
await CfgTotp.cfgTotpDisableCreate({ body: { code }, throwOnError: true });
|
|
3236
|
-
setHas2FAEnabled(false);
|
|
3237
|
-
setDevices([]);
|
|
3238
|
-
authLogger.info("2FA disabled successfully");
|
|
3239
|
-
return true;
|
|
3240
|
-
} catch (err) {
|
|
3241
|
-
const errorMessage = extractErrorMessage(err, "Invalid verification code");
|
|
3242
|
-
authLogger.error("Failed to disable 2FA:", err);
|
|
3243
|
-
setError(errorMessage);
|
|
3244
|
-
return false;
|
|
3245
|
-
} finally {
|
|
3246
|
-
setIsLoading(false);
|
|
3247
|
-
}
|
|
3248
|
-
}, []);
|
|
3249
|
-
return {
|
|
3250
|
-
isLoading,
|
|
3251
|
-
error,
|
|
3252
|
-
has2FAEnabled,
|
|
3253
|
-
devices,
|
|
3254
|
-
fetchStatus,
|
|
3255
|
-
disable2FA,
|
|
3256
|
-
clearError
|
|
3257
|
-
};
|
|
3258
|
-
}, "useTwoFactorStatus");
|
|
3259
|
-
|
|
3260
|
-
// src/auth/hooks/useSessionStorage.ts
|
|
3261
|
-
import { useState as useState7 } from "react";
|
|
3262
|
-
function useSessionStorage(key, initialValue) {
|
|
3263
|
-
const [storedValue, setStoredValue] = useState7(() => {
|
|
3264
|
-
if (typeof window === "undefined") {
|
|
3265
|
-
return initialValue;
|
|
3266
|
-
}
|
|
3267
|
-
try {
|
|
3268
|
-
const item = window.sessionStorage.getItem(key);
|
|
3269
|
-
return item ? JSON.parse(item) : initialValue;
|
|
3270
|
-
} catch (error) {
|
|
3271
|
-
authLogger.error(`Error reading sessionStorage key "${key}":`, error);
|
|
3272
|
-
return initialValue;
|
|
3273
|
-
}
|
|
3274
|
-
});
|
|
3275
|
-
const checkDataSize = /* @__PURE__ */ __name((data) => {
|
|
3276
|
-
try {
|
|
3277
|
-
const jsonString = JSON.stringify(data);
|
|
3278
|
-
const sizeInBytes = new Blob([jsonString]).size;
|
|
3279
|
-
const sizeInKB = sizeInBytes / 1024;
|
|
3280
|
-
if (sizeInKB > 1024) {
|
|
3281
|
-
authLogger.warn(`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`);
|
|
3282
|
-
return false;
|
|
3283
|
-
}
|
|
3284
|
-
return true;
|
|
3285
|
-
} catch (error) {
|
|
3286
|
-
authLogger.error(`Error checking data size for key "${key}":`, error);
|
|
3287
|
-
return false;
|
|
3288
|
-
}
|
|
3289
|
-
}, "checkDataSize");
|
|
3290
|
-
const clearOldData = /* @__PURE__ */ __name(() => {
|
|
3291
|
-
try {
|
|
3292
|
-
const keys = Object.keys(sessionStorage).filter((key2) => key2 && typeof key2 === "string");
|
|
3293
|
-
if (keys.length > 50) {
|
|
3294
|
-
const itemsToRemove = Math.ceil(keys.length * 0.2);
|
|
3295
|
-
for (let i = 0; i < itemsToRemove; i++) {
|
|
3296
|
-
try {
|
|
3297
|
-
const key2 = keys[i];
|
|
3298
|
-
if (key2) {
|
|
3299
|
-
sessionStorage.removeItem(key2);
|
|
3300
|
-
sessionStorage.removeItem(`${key2}_timestamp`);
|
|
3301
|
-
}
|
|
3302
|
-
} catch {
|
|
3303
|
-
}
|
|
3304
|
-
}
|
|
3305
|
-
}
|
|
3306
|
-
} catch (error) {
|
|
3307
|
-
authLogger.error("Error clearing old sessionStorage data:", error);
|
|
3308
|
-
}
|
|
3309
|
-
}, "clearOldData");
|
|
3310
|
-
const forceClearAll = /* @__PURE__ */ __name(() => {
|
|
3311
|
-
try {
|
|
3312
|
-
const keys = Object.keys(sessionStorage);
|
|
3313
|
-
for (const key2 of keys) {
|
|
3314
|
-
try {
|
|
3315
|
-
sessionStorage.removeItem(key2);
|
|
3316
|
-
} catch {
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
} catch (error) {
|
|
3320
|
-
authLogger.error("Error force clearing sessionStorage:", error);
|
|
3321
|
-
}
|
|
3322
|
-
}, "forceClearAll");
|
|
3323
|
-
const setValue = /* @__PURE__ */ __name((value) => {
|
|
3324
|
-
try {
|
|
3325
|
-
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
3326
|
-
if (!checkDataSize(valueToStore)) {
|
|
3327
|
-
authLogger.warn(`Data size too large for key "${key}", removing key`);
|
|
3328
|
-
try {
|
|
3329
|
-
window.sessionStorage.removeItem(key);
|
|
3330
|
-
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
3331
|
-
} catch {
|
|
3332
|
-
}
|
|
3333
|
-
setStoredValue(valueToStore);
|
|
3334
|
-
return;
|
|
3335
|
-
}
|
|
3336
|
-
setStoredValue(valueToStore);
|
|
3337
|
-
if (typeof window !== "undefined") {
|
|
3338
|
-
try {
|
|
3339
|
-
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
|
3340
|
-
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
3341
|
-
} catch (storageError) {
|
|
3342
|
-
if (storageError.name === "QuotaExceededError" || storageError.code === 22 || storageError.message?.includes("quota")) {
|
|
3343
|
-
authLogger.warn("sessionStorage quota exceeded, clearing old data...");
|
|
3344
|
-
clearOldData();
|
|
3345
|
-
try {
|
|
3346
|
-
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
|
3347
|
-
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
3348
|
-
} catch (retryError) {
|
|
3349
|
-
authLogger.error(`Failed to set sessionStorage key "${key}" after clearing old data:`, retryError);
|
|
3350
|
-
try {
|
|
3351
|
-
forceClearAll();
|
|
3352
|
-
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
|
3353
|
-
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
3354
|
-
} catch (finalError) {
|
|
3355
|
-
authLogger.error(`Failed to set sessionStorage key "${key}" after force clearing:`, finalError);
|
|
3356
|
-
setStoredValue(valueToStore);
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
} else {
|
|
3360
|
-
throw storageError;
|
|
3361
|
-
}
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
} catch (error) {
|
|
3365
|
-
authLogger.error(`Error setting sessionStorage key "${key}":`, error);
|
|
3366
|
-
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
3367
|
-
setStoredValue(valueToStore);
|
|
3368
|
-
}
|
|
3369
|
-
}, "setValue");
|
|
3370
|
-
const removeValue = /* @__PURE__ */ __name(() => {
|
|
3371
|
-
try {
|
|
3372
|
-
setStoredValue(initialValue);
|
|
3373
|
-
if (typeof window !== "undefined") {
|
|
3374
|
-
try {
|
|
3375
|
-
window.sessionStorage.removeItem(key);
|
|
3376
|
-
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
3377
|
-
} catch (removeError) {
|
|
3378
|
-
if (removeError.name === "QuotaExceededError" || removeError.code === 22 || removeError.message?.includes("quota")) {
|
|
3379
|
-
authLogger.warn("sessionStorage quota exceeded during removal, clearing old data...");
|
|
3380
|
-
clearOldData();
|
|
3381
|
-
try {
|
|
3382
|
-
window.sessionStorage.removeItem(key);
|
|
3383
|
-
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
3384
|
-
} catch (retryError) {
|
|
3385
|
-
authLogger.error(`Failed to remove sessionStorage key "${key}" after clearing:`, retryError);
|
|
3386
|
-
forceClearAll();
|
|
3387
|
-
}
|
|
3388
|
-
} else {
|
|
3389
|
-
throw removeError;
|
|
3390
|
-
}
|
|
3391
|
-
}
|
|
3392
|
-
}
|
|
3393
|
-
} catch (error) {
|
|
3394
|
-
authLogger.error(`Error removing sessionStorage key "${key}":`, error);
|
|
3395
|
-
}
|
|
3396
|
-
}, "removeValue");
|
|
3397
|
-
return [storedValue, setValue, removeValue];
|
|
3398
|
-
}
|
|
3399
|
-
__name(useSessionStorage, "useSessionStorage");
|
|
3400
|
-
|
|
3401
|
-
// src/auth/hooks/useAuthRedirect.ts
|
|
3402
|
-
var AUTH_REDIRECT_KEY = "auth_redirect_url";
|
|
3403
|
-
var getRedirectFromStorage = /* @__PURE__ */ __name(() => {
|
|
3404
|
-
if (typeof window === "undefined") return "";
|
|
3405
|
-
try {
|
|
3406
|
-
const item = window.sessionStorage.getItem(AUTH_REDIRECT_KEY);
|
|
3407
|
-
return item ? JSON.parse(item) : "";
|
|
3408
|
-
} catch {
|
|
3409
|
-
return "";
|
|
3410
|
-
}
|
|
3411
|
-
}, "getRedirectFromStorage");
|
|
3412
|
-
var clearRedirectFromStorage = /* @__PURE__ */ __name(() => {
|
|
3413
|
-
if (typeof window === "undefined") return;
|
|
3414
|
-
try {
|
|
3415
|
-
window.sessionStorage.removeItem(AUTH_REDIRECT_KEY);
|
|
3416
|
-
window.sessionStorage.removeItem(`${AUTH_REDIRECT_KEY}_timestamp`);
|
|
3417
|
-
} catch {
|
|
3418
|
-
}
|
|
3419
|
-
}, "clearRedirectFromStorage");
|
|
3420
|
-
var useAuthRedirectManager = /* @__PURE__ */ __name((options = {}) => {
|
|
3421
|
-
const { fallbackUrl = "/dashboard", clearOnUse = true } = options;
|
|
3422
|
-
const [redirectUrl, setRedirectUrl, removeRedirectUrl] = useSessionStorage(AUTH_REDIRECT_KEY, "");
|
|
3423
|
-
const setRedirect = /* @__PURE__ */ __name((url) => {
|
|
3424
|
-
setRedirectUrl(url);
|
|
3425
|
-
}, "setRedirect");
|
|
3426
|
-
const getRedirect = /* @__PURE__ */ __name(() => {
|
|
3427
|
-
return getRedirectFromStorage() || redirectUrl;
|
|
3428
|
-
}, "getRedirect");
|
|
3429
|
-
const clearRedirect = /* @__PURE__ */ __name(() => {
|
|
3430
|
-
removeRedirectUrl();
|
|
3431
|
-
clearRedirectFromStorage();
|
|
3432
|
-
}, "clearRedirect");
|
|
3433
|
-
const hasRedirect = /* @__PURE__ */ __name(() => {
|
|
3434
|
-
const stored = getRedirectFromStorage();
|
|
3435
|
-
return stored.length > 0 || redirectUrl.length > 0;
|
|
3436
|
-
}, "hasRedirect");
|
|
3437
|
-
const getFinalRedirectUrl = /* @__PURE__ */ __name(() => {
|
|
3438
|
-
const stored = getRedirectFromStorage();
|
|
3439
|
-
return stored || redirectUrl || fallbackUrl;
|
|
3440
|
-
}, "getFinalRedirectUrl");
|
|
3441
|
-
const useAndClearRedirect = /* @__PURE__ */ __name(() => {
|
|
3442
|
-
const stored = getRedirectFromStorage();
|
|
3443
|
-
const finalUrl = stored || redirectUrl || fallbackUrl;
|
|
3444
|
-
if (clearOnUse) {
|
|
3445
|
-
clearRedirect();
|
|
3299
|
+
setError(null);
|
|
3300
|
+
try {
|
|
3301
|
+
authLogger.info("Disabling 2FA...");
|
|
3302
|
+
await CfgTotp.cfgTotpDisableCreate({ body: { code }, throwOnError: true });
|
|
3303
|
+
setHas2FAEnabled(false);
|
|
3304
|
+
setDevices([]);
|
|
3305
|
+
authLogger.info("2FA disabled successfully");
|
|
3306
|
+
return true;
|
|
3307
|
+
} catch (err) {
|
|
3308
|
+
const errorMessage = extractErrorMessage(err, "Invalid verification code");
|
|
3309
|
+
authLogger.error("Failed to disable 2FA:", err);
|
|
3310
|
+
setError(errorMessage);
|
|
3311
|
+
return false;
|
|
3312
|
+
} finally {
|
|
3313
|
+
setIsLoading(false);
|
|
3446
3314
|
}
|
|
3447
|
-
|
|
3448
|
-
}, "useAndClearRedirect");
|
|
3315
|
+
}, []);
|
|
3449
3316
|
return {
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3317
|
+
isLoading,
|
|
3318
|
+
error,
|
|
3319
|
+
has2FAEnabled,
|
|
3320
|
+
devices,
|
|
3321
|
+
fetchStatus,
|
|
3322
|
+
disable2FA,
|
|
3323
|
+
clearError
|
|
3457
3324
|
};
|
|
3458
|
-
}, "
|
|
3459
|
-
|
|
3460
|
-
// src/auth/hooks/useAuthGuard.ts
|
|
3461
|
-
import { useEffect as useEffect5, useState as useState8 } from "react";
|
|
3462
|
-
var useAuthGuard = /* @__PURE__ */ __name((options = {}) => {
|
|
3463
|
-
const { redirectTo = "/auth", requireAuth = true, saveRedirectUrl: shouldSaveUrl = true } = options;
|
|
3464
|
-
const { isAuthenticated, isLoading, saveRedirectUrl } = useAuth();
|
|
3465
|
-
const router = useCfgRouter();
|
|
3466
|
-
const [isRedirecting, setIsRedirecting] = useState8(false);
|
|
3467
|
-
useEffect5(() => {
|
|
3468
|
-
if (!isLoading && requireAuth && !isAuthenticated && !isRedirecting) {
|
|
3469
|
-
if (shouldSaveUrl && typeof window !== "undefined") {
|
|
3470
|
-
const currentUrl = window.location.pathname + window.location.search;
|
|
3471
|
-
saveRedirectUrl(currentUrl);
|
|
3472
|
-
}
|
|
3473
|
-
setIsRedirecting(true);
|
|
3474
|
-
router.push(redirectTo);
|
|
3475
|
-
}
|
|
3476
|
-
}, [isAuthenticated, isLoading, router, redirectTo, requireAuth, isRedirecting, shouldSaveUrl, saveRedirectUrl]);
|
|
3477
|
-
return { isAuthenticated, isLoading, isRedirecting };
|
|
3478
|
-
}, "useAuthGuard");
|
|
3325
|
+
}, "useTwoFactorStatus");
|
|
3479
3326
|
|
|
3480
3327
|
// src/auth/hooks/useLocalStorage.ts
|
|
3481
|
-
import { useState as
|
|
3328
|
+
import { useState as useState8 } from "react";
|
|
3482
3329
|
function useLocalStorage(key, initialValue) {
|
|
3483
|
-
const [storedValue, setStoredValue] =
|
|
3330
|
+
const [storedValue, setStoredValue] = useState8(() => {
|
|
3484
3331
|
if (typeof window === "undefined") {
|
|
3485
3332
|
return initialValue;
|
|
3486
3333
|
}
|
|
@@ -3775,150 +3622,16 @@ function getCacheMetadata() {
|
|
|
3775
3622
|
}
|
|
3776
3623
|
__name(getCacheMetadata, "getCacheMetadata");
|
|
3777
3624
|
|
|
3778
|
-
// src/auth/hooks/useTokenRefresh.ts
|
|
3779
|
-
import { useCallback as useCallback9, useEffect as useEffect7 } from "react";
|
|
3780
|
-
|
|
3781
|
-
// src/auth/refreshHandler.ts
|
|
3782
|
-
var _registered = false;
|
|
3783
|
-
function ensureRefreshHandler() {
|
|
3784
|
-
if (_registered) return;
|
|
3785
|
-
_registered = true;
|
|
3786
|
-
CfgAccountsApi.setRefreshHandler(async (refresh) => {
|
|
3787
|
-
const result = await CfgAccountsAuth.cfgAccountsTokenRefreshCreate({
|
|
3788
|
-
body: { refresh },
|
|
3789
|
-
throwOnError: true
|
|
3790
|
-
});
|
|
3791
|
-
const access = result.data?.access;
|
|
3792
|
-
if (!access) {
|
|
3793
|
-
authLogger.error("Refresh response missing access token");
|
|
3794
|
-
return null;
|
|
3795
|
-
}
|
|
3796
|
-
return { access, refresh: result.data?.refresh ?? refresh };
|
|
3797
|
-
});
|
|
3798
|
-
authLogger.debug("Canonical refresh handler registered");
|
|
3799
|
-
}
|
|
3800
|
-
__name(ensureRefreshHandler, "ensureRefreshHandler");
|
|
3801
|
-
function triggerRefresh() {
|
|
3802
|
-
ensureRefreshHandler();
|
|
3803
|
-
const maybe = CfgAccountsApi;
|
|
3804
|
-
if (typeof maybe.refreshNow === "function") {
|
|
3805
|
-
return maybe.refreshNow();
|
|
3806
|
-
}
|
|
3807
|
-
authLogger.warn("api.refreshNow() unavailable \u2014 run `make generate` to regenerate the client");
|
|
3808
|
-
return Promise.resolve(null);
|
|
3809
|
-
}
|
|
3810
|
-
__name(triggerRefresh, "triggerRefresh");
|
|
3811
|
-
|
|
3812
|
-
// src/auth/utils/jwt.ts
|
|
3813
|
-
function decodeSegment(segment) {
|
|
3814
|
-
try {
|
|
3815
|
-
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
3816
|
-
if (typeof atob !== "function") return null;
|
|
3817
|
-
const json = atob(base64);
|
|
3818
|
-
return JSON.parse(json);
|
|
3819
|
-
} catch {
|
|
3820
|
-
return null;
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
|
-
__name(decodeSegment, "decodeSegment");
|
|
3824
|
-
function getTokenExpiry(token) {
|
|
3825
|
-
if (!token || typeof token !== "string") return null;
|
|
3826
|
-
const parts = token.split(".");
|
|
3827
|
-
if (parts.length !== 3) return null;
|
|
3828
|
-
const payload = decodeSegment(parts[1]);
|
|
3829
|
-
if (!payload || typeof payload !== "object") return null;
|
|
3830
|
-
const exp = payload.exp;
|
|
3831
|
-
if (typeof exp !== "number" || !Number.isFinite(exp)) return null;
|
|
3832
|
-
return exp * 1e3;
|
|
3833
|
-
}
|
|
3834
|
-
__name(getTokenExpiry, "getTokenExpiry");
|
|
3835
|
-
function isTokenExpired(token, skewMs = 0, now = Date.now()) {
|
|
3836
|
-
const expiry = getTokenExpiry(token);
|
|
3837
|
-
if (expiry === null) return true;
|
|
3838
|
-
return expiry - skewMs <= now;
|
|
3839
|
-
}
|
|
3840
|
-
__name(isTokenExpired, "isTokenExpired");
|
|
3841
|
-
function isTokenExpiringSoon(token, thresholdMs, now = Date.now()) {
|
|
3842
|
-
const expiry = getTokenExpiry(token);
|
|
3843
|
-
if (expiry === null) return false;
|
|
3844
|
-
return expiry - now < thresholdMs;
|
|
3845
|
-
}
|
|
3846
|
-
__name(isTokenExpiringSoon, "isTokenExpiringSoon");
|
|
3847
|
-
|
|
3848
|
-
// src/auth/hooks/useTokenRefresh.ts
|
|
3849
|
-
var TOKEN_REFRESH_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
3850
|
-
var CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
3851
|
-
function useTokenRefresh(options = {}) {
|
|
3852
|
-
const { enabled = true, onRefresh, onRefreshError } = options;
|
|
3853
|
-
const refreshToken = useCallback9(async () => {
|
|
3854
|
-
if (!CfgAccountsApi.getRefreshToken()) {
|
|
3855
|
-
authLogger.warn("No refresh token available");
|
|
3856
|
-
return false;
|
|
3857
|
-
}
|
|
3858
|
-
authLogger.info("Refreshing token...");
|
|
3859
|
-
try {
|
|
3860
|
-
const newAccessToken = await triggerRefresh();
|
|
3861
|
-
if (!newAccessToken) {
|
|
3862
|
-
throw new Error("Token refresh failed");
|
|
3863
|
-
}
|
|
3864
|
-
authLogger.info("Token refreshed successfully");
|
|
3865
|
-
onRefresh?.(newAccessToken);
|
|
3866
|
-
return true;
|
|
3867
|
-
} catch (error) {
|
|
3868
|
-
authLogger.error("Token refresh error:", error);
|
|
3869
|
-
onRefreshError?.(error instanceof Error ? error : new Error(String(error)));
|
|
3870
|
-
return false;
|
|
3871
|
-
}
|
|
3872
|
-
}, [onRefresh, onRefreshError]);
|
|
3873
|
-
const checkAndRefresh = useCallback9(async () => {
|
|
3874
|
-
const token = CfgAccountsApi.getToken();
|
|
3875
|
-
if (!token) return;
|
|
3876
|
-
if (isTokenExpiringSoon(token, TOKEN_REFRESH_THRESHOLD_MS)) {
|
|
3877
|
-
authLogger.info("Token expiring soon, refreshing proactively");
|
|
3878
|
-
await refreshToken();
|
|
3879
|
-
}
|
|
3880
|
-
}, [refreshToken]);
|
|
3881
|
-
useEffect7(() => {
|
|
3882
|
-
if (!enabled) return;
|
|
3883
|
-
checkAndRefresh();
|
|
3884
|
-
const intervalId = setInterval(checkAndRefresh, CHECK_INTERVAL_MS);
|
|
3885
|
-
return () => clearInterval(intervalId);
|
|
3886
|
-
}, [enabled, checkAndRefresh]);
|
|
3887
|
-
useEffect7(() => {
|
|
3888
|
-
if (!enabled) return;
|
|
3889
|
-
const handleFocus = /* @__PURE__ */ __name(() => {
|
|
3890
|
-
authLogger.debug("Window focused, checking token...");
|
|
3891
|
-
checkAndRefresh();
|
|
3892
|
-
}, "handleFocus");
|
|
3893
|
-
window.addEventListener("focus", handleFocus);
|
|
3894
|
-
return () => window.removeEventListener("focus", handleFocus);
|
|
3895
|
-
}, [enabled, checkAndRefresh]);
|
|
3896
|
-
useEffect7(() => {
|
|
3897
|
-
if (!enabled) return;
|
|
3898
|
-
const handleOnline = /* @__PURE__ */ __name(() => {
|
|
3899
|
-
authLogger.info("Network reconnected, checking token...");
|
|
3900
|
-
checkAndRefresh();
|
|
3901
|
-
}, "handleOnline");
|
|
3902
|
-
window.addEventListener("online", handleOnline);
|
|
3903
|
-
return () => window.removeEventListener("online", handleOnline);
|
|
3904
|
-
}, [enabled, checkAndRefresh]);
|
|
3905
|
-
return {
|
|
3906
|
-
refreshToken,
|
|
3907
|
-
checkAndRefresh
|
|
3908
|
-
};
|
|
3909
|
-
}
|
|
3910
|
-
__name(useTokenRefresh, "useTokenRefresh");
|
|
3911
|
-
|
|
3912
3625
|
// src/auth/hooks/useDeleteAccount.ts
|
|
3913
|
-
import { useCallback as
|
|
3626
|
+
import { useCallback as useCallback9, useState as useState9 } from "react";
|
|
3914
3627
|
var useDeleteAccount = /* @__PURE__ */ __name(() => {
|
|
3915
|
-
const [isLoading, setIsLoading] =
|
|
3916
|
-
const [error, setError] =
|
|
3628
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
3629
|
+
const [error, setError] = useState9(null);
|
|
3917
3630
|
const { logout } = useAuth();
|
|
3918
|
-
const clearError =
|
|
3631
|
+
const clearError = useCallback9(() => {
|
|
3919
3632
|
setError(null);
|
|
3920
3633
|
}, []);
|
|
3921
|
-
const deleteAccount =
|
|
3634
|
+
const deleteAccount = useCallback9(async () => {
|
|
3922
3635
|
setIsLoading(true);
|
|
3923
3636
|
setError(null);
|
|
3924
3637
|
try {
|
|
@@ -3947,39 +3660,15 @@ var useDeleteAccount = /* @__PURE__ */ __name(() => {
|
|
|
3947
3660
|
};
|
|
3948
3661
|
}, "useDeleteAccount");
|
|
3949
3662
|
|
|
3950
|
-
// src/auth/utils/guard.ts
|
|
3951
|
-
function resolveGuardIsLoading(s) {
|
|
3952
|
-
if (!s.mounted) return true;
|
|
3953
|
-
if (!s.requireAuth) return false;
|
|
3954
|
-
return s.authLoading || s.isRedirecting || !s.isAuthenticated;
|
|
3955
|
-
}
|
|
3956
|
-
__name(resolveGuardIsLoading, "resolveGuardIsLoading");
|
|
3957
|
-
function shouldRedirectToAuth(s) {
|
|
3958
|
-
return s.mounted && s.requireAuth && !s.authLoading && !s.isAuthenticated && !s.isRedirecting;
|
|
3959
|
-
}
|
|
3960
|
-
__name(shouldRedirectToAuth, "shouldRedirectToAuth");
|
|
3961
|
-
function resolveGuardIsAuthenticated(s) {
|
|
3962
|
-
return !s.requireAuth || s.isAuthenticated;
|
|
3963
|
-
}
|
|
3964
|
-
__name(resolveGuardIsAuthenticated, "resolveGuardIsAuthenticated");
|
|
3965
|
-
function nextAuthEvaluationDelay(accessExp, refreshExp, now) {
|
|
3966
|
-
const future = [accessExp, refreshExp].filter(
|
|
3967
|
-
(d) => d !== null && d > now
|
|
3968
|
-
);
|
|
3969
|
-
if (future.length === 0) return null;
|
|
3970
|
-
return Math.max(0, Math.min(...future) - now) + 1e3;
|
|
3971
|
-
}
|
|
3972
|
-
__name(nextAuthEvaluationDelay, "nextAuthEvaluationDelay");
|
|
3973
|
-
|
|
3974
3663
|
// src/auth/context/AccountsContext.tsx
|
|
3975
3664
|
import {
|
|
3976
3665
|
createContext,
|
|
3977
|
-
useCallback as
|
|
3666
|
+
useCallback as useCallback10,
|
|
3978
3667
|
useContext,
|
|
3979
|
-
useEffect as
|
|
3668
|
+
useEffect as useEffect6,
|
|
3980
3669
|
useMemo as useMemo2,
|
|
3981
3670
|
useRef as useRef5,
|
|
3982
|
-
useState as
|
|
3671
|
+
useState as useState10
|
|
3983
3672
|
} from "react";
|
|
3984
3673
|
|
|
3985
3674
|
// src/_api/generated/_cfg_accounts/hooks/useCfgAccountsApiKeyRegenerateCreate.ts
|
|
@@ -4432,9 +4121,12 @@ function useCfgAccountsProfileUpdateUpdate(config) {
|
|
|
4432
4121
|
}
|
|
4433
4122
|
__name(useCfgAccountsProfileUpdateUpdate, "useCfgAccountsProfileUpdateUpdate");
|
|
4434
4123
|
|
|
4435
|
-
// src/_api/generated/_cfg_accounts/hooks/
|
|
4124
|
+
// src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenBlacklistCreate.ts
|
|
4436
4125
|
import useSWRMutation15 from "swr/mutation";
|
|
4437
4126
|
|
|
4127
|
+
// src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
|
|
4128
|
+
import useSWRMutation16 from "swr/mutation";
|
|
4129
|
+
|
|
4438
4130
|
// src/_api/generated/_cfg_accounts/schemas/TokenRefresh.ts
|
|
4439
4131
|
import { z as z19 } from "zod";
|
|
4440
4132
|
var TokenRefreshSchema = z19.object({
|
|
@@ -4442,53 +4134,6 @@ var TokenRefreshSchema = z19.object({
|
|
|
4442
4134
|
refresh: z19.string()
|
|
4443
4135
|
});
|
|
4444
4136
|
|
|
4445
|
-
// src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
|
|
4446
|
-
function useCfgAccountsTokenRefreshCreate(config) {
|
|
4447
|
-
return useSWRMutation15(
|
|
4448
|
-
["cfg_accounts_token_refresh_create"],
|
|
4449
|
-
async (_key, { arg }) => {
|
|
4450
|
-
const res = await CfgAccountsAuth.cfgAccountsTokenRefreshCreate({ ...arg, throwOnError: true });
|
|
4451
|
-
const data = res.data;
|
|
4452
|
-
const parsed = TokenRefreshSchema.safeParse(data);
|
|
4453
|
-
if (!parsed.success) {
|
|
4454
|
-
console.warn(
|
|
4455
|
-
"[zod] response did not match schema",
|
|
4456
|
-
{
|
|
4457
|
-
operation: "cfg_accounts_token_refresh_create",
|
|
4458
|
-
method: "POST",
|
|
4459
|
-
path: "/cfg/accounts/token/refresh/",
|
|
4460
|
-
issues: parsed.error.issues,
|
|
4461
|
-
data
|
|
4462
|
-
}
|
|
4463
|
-
);
|
|
4464
|
-
if (typeof window !== "undefined") {
|
|
4465
|
-
try {
|
|
4466
|
-
window.dispatchEvent(
|
|
4467
|
-
new CustomEvent("zod-validation-error", {
|
|
4468
|
-
detail: {
|
|
4469
|
-
operation: "cfg_accounts_token_refresh_create",
|
|
4470
|
-
method: "POST",
|
|
4471
|
-
path: "/cfg/accounts/token/refresh/",
|
|
4472
|
-
issues: parsed.error.issues,
|
|
4473
|
-
data,
|
|
4474
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
4475
|
-
},
|
|
4476
|
-
bubbles: true,
|
|
4477
|
-
cancelable: false
|
|
4478
|
-
})
|
|
4479
|
-
);
|
|
4480
|
-
} catch {
|
|
4481
|
-
}
|
|
4482
|
-
}
|
|
4483
|
-
return data;
|
|
4484
|
-
}
|
|
4485
|
-
return parsed.data;
|
|
4486
|
-
},
|
|
4487
|
-
config
|
|
4488
|
-
);
|
|
4489
|
-
}
|
|
4490
|
-
__name(useCfgAccountsTokenRefreshCreate, "useCfgAccountsTokenRefreshCreate");
|
|
4491
|
-
|
|
4492
4137
|
// src/_api/generated/_cfg_accounts/schemas/APIKeyRequest.ts
|
|
4493
4138
|
import { z as z20 } from "zod";
|
|
4494
4139
|
var APIKeyRequestSchema = z20.object({
|
|
@@ -4578,28 +4223,34 @@ var PatchedCfgUserUpdateRequestSchema = z30.object({
|
|
|
4578
4223
|
timezone: z30.string().max(64).optional()
|
|
4579
4224
|
});
|
|
4580
4225
|
|
|
4581
|
-
// src/_api/generated/_cfg_accounts/schemas/
|
|
4226
|
+
// src/_api/generated/_cfg_accounts/schemas/TokenBlacklistRequest.ts
|
|
4582
4227
|
import { z as z31 } from "zod";
|
|
4583
|
-
var
|
|
4228
|
+
var TokenBlacklistRequestSchema = z31.object({
|
|
4584
4229
|
refresh: z31.string().min(1)
|
|
4585
4230
|
});
|
|
4586
4231
|
|
|
4232
|
+
// src/_api/generated/_cfg_accounts/schemas/TokenRefreshRequest.ts
|
|
4233
|
+
import { z as z32 } from "zod";
|
|
4234
|
+
var TokenRefreshRequestSchema = z32.object({
|
|
4235
|
+
refresh: z32.string().min(1)
|
|
4236
|
+
});
|
|
4237
|
+
|
|
4587
4238
|
// src/auth/context/AccountsContext.tsx
|
|
4588
4239
|
import { jsx } from "react/jsx-runtime";
|
|
4589
4240
|
var AccountsContext = createContext(void 0);
|
|
4590
4241
|
function AccountsProvider({ children }) {
|
|
4591
|
-
const [profile, setProfile] =
|
|
4242
|
+
const [profile, setProfile] = useState10(() => {
|
|
4592
4243
|
const cached = getCachedProfile();
|
|
4593
4244
|
return cached || void 0;
|
|
4594
4245
|
});
|
|
4595
|
-
const [isLoadingProfile, setIsLoadingProfile] =
|
|
4596
|
-
const [profileError, setProfileError] =
|
|
4246
|
+
const [isLoadingProfile, setIsLoadingProfile] = useState10(false);
|
|
4247
|
+
const [profileError, setProfileError] = useState10(null);
|
|
4597
4248
|
const profileRef = useRef5(profile);
|
|
4598
4249
|
const isLoadingRef = useRef5(false);
|
|
4599
|
-
|
|
4250
|
+
useEffect6(() => {
|
|
4600
4251
|
profileRef.current = profile;
|
|
4601
4252
|
}, [profile]);
|
|
4602
|
-
|
|
4253
|
+
useEffect6(() => {
|
|
4603
4254
|
isLoadingRef.current = isLoadingProfile;
|
|
4604
4255
|
}, [isLoadingProfile]);
|
|
4605
4256
|
const { trigger: triggerProfileUpdate } = useCfgAccountsProfileUpdateUpdate();
|
|
@@ -4607,8 +4258,7 @@ function AccountsProvider({ children }) {
|
|
|
4607
4258
|
const { trigger: triggerAvatar } = useCfgAccountsProfileAvatarCreate();
|
|
4608
4259
|
const { trigger: triggerOtpRequest } = useCfgAccountsOtpRequestCreate();
|
|
4609
4260
|
const { trigger: triggerOtpVerify } = useCfgAccountsOtpVerifyCreate();
|
|
4610
|
-
const
|
|
4611
|
-
const refreshProfile = useCallback11(async (options) => {
|
|
4261
|
+
const refreshProfile = useCallback10(async (options) => {
|
|
4612
4262
|
const { callerId, force } = options || {};
|
|
4613
4263
|
if (isLoadingRef.current) {
|
|
4614
4264
|
authLogger.debug(`Profile loading in progress, skipping (caller: ${callerId})`);
|
|
@@ -4660,8 +4310,7 @@ function AccountsProvider({ children }) {
|
|
|
4660
4310
|
return result;
|
|
4661
4311
|
}
|
|
4662
4312
|
if (result.access && result.refresh) {
|
|
4663
|
-
|
|
4664
|
-
CfgAccountsApi.setRefreshToken(result.refresh);
|
|
4313
|
+
auth.setSession({ access: result.access, refresh: result.refresh });
|
|
4665
4314
|
try {
|
|
4666
4315
|
await refreshProfile({ callerId: "verifyOTP", force: true });
|
|
4667
4316
|
} catch (profileError2) {
|
|
@@ -4670,17 +4319,14 @@ function AccountsProvider({ children }) {
|
|
|
4670
4319
|
}
|
|
4671
4320
|
return result;
|
|
4672
4321
|
}, "verifyOTP");
|
|
4673
|
-
const
|
|
4674
|
-
const
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4322
|
+
const logout = useCallback10(() => {
|
|
4323
|
+
const refresh = auth.getRefreshToken();
|
|
4324
|
+
if (refresh) {
|
|
4325
|
+
CfgAccountsAuth.cfgAccountsTokenBlacklistCreate({ body: { refresh } }).catch(() => {
|
|
4326
|
+
authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
|
|
4327
|
+
});
|
|
4679
4328
|
}
|
|
4680
|
-
|
|
4681
|
-
}, "refreshToken");
|
|
4682
|
-
const logout = useCallback11(() => {
|
|
4683
|
-
CfgAccountsApi.clearToken();
|
|
4329
|
+
auth.clearSession();
|
|
4684
4330
|
setProfile(void 0);
|
|
4685
4331
|
setProfileError(null);
|
|
4686
4332
|
clearProfileCache();
|
|
@@ -4695,7 +4341,6 @@ function AccountsProvider({ children }) {
|
|
|
4695
4341
|
refreshProfile,
|
|
4696
4342
|
requestOTP,
|
|
4697
4343
|
verifyOTP,
|
|
4698
|
-
refreshToken,
|
|
4699
4344
|
logout
|
|
4700
4345
|
}), [
|
|
4701
4346
|
profile,
|
|
@@ -4707,7 +4352,6 @@ function AccountsProvider({ children }) {
|
|
|
4707
4352
|
refreshProfile,
|
|
4708
4353
|
requestOTP,
|
|
4709
4354
|
verifyOTP,
|
|
4710
|
-
refreshToken,
|
|
4711
4355
|
logout
|
|
4712
4356
|
]);
|
|
4713
4357
|
return /* @__PURE__ */ jsx(AccountsContext.Provider, { value, children });
|
|
@@ -4726,281 +4370,127 @@ __name(useAccountsContext, "useAccountsContext");
|
|
|
4726
4370
|
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
4727
4371
|
var defaultRoutes = {
|
|
4728
4372
|
auth: "/auth",
|
|
4729
|
-
defaultCallback: "/
|
|
4373
|
+
defaultCallback: "/",
|
|
4730
4374
|
defaultAuthCallback: "/auth"
|
|
4731
4375
|
};
|
|
4732
|
-
var
|
|
4376
|
+
var resolveAuthRoutes = /* @__PURE__ */ __name((routes) => ({
|
|
4377
|
+
auth: routes?.auth || defaultRoutes.auth,
|
|
4378
|
+
defaultCallback: routes?.defaultCallback || defaultRoutes.defaultCallback,
|
|
4379
|
+
defaultAuthCallback: routes?.defaultAuthCallback || routes?.auth || defaultRoutes.defaultAuthCallback
|
|
4380
|
+
}), "resolveAuthRoutes");
|
|
4733
4381
|
var EMAIL_STORAGE_KEY = "auth_email";
|
|
4734
|
-
var
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
const accessDead = isTokenExpired(accessToken, 0, now);
|
|
4740
|
-
if (!accessDead) return false;
|
|
4741
|
-
const refreshDead = isTokenExpired(refreshToken, 0, now);
|
|
4742
|
-
return refreshDead;
|
|
4743
|
-
}, "isSessionDeadOnBootstrap");
|
|
4744
|
-
var isSessionAlive = /* @__PURE__ */ __name((now = Date.now()) => {
|
|
4745
|
-
if (typeof window === "undefined") return false;
|
|
4746
|
-
return !isSessionDeadOnBootstrap(
|
|
4747
|
-
CfgAccountsApi.getToken(),
|
|
4748
|
-
CfgAccountsApi.getRefreshToken(),
|
|
4749
|
-
now
|
|
4750
|
-
);
|
|
4751
|
-
}, "isSessionAlive");
|
|
4382
|
+
var AuthContext = createContext2(void 0);
|
|
4383
|
+
var apiErrorMessage = /* @__PURE__ */ __name((error) => {
|
|
4384
|
+
const body = error.response;
|
|
4385
|
+
return body?.error || body?.detail || body?.message || error.errorMessage;
|
|
4386
|
+
}, "apiErrorMessage");
|
|
4752
4387
|
var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
4753
4388
|
const accounts = useAccountsContext();
|
|
4754
|
-
|
|
4755
|
-
const
|
|
4756
|
-
fallbackUrl: config?.routes?.defaultCallback || defaultRoutes.defaultCallback,
|
|
4757
|
-
clearOnUse: true
|
|
4758
|
-
});
|
|
4759
|
-
const [isLoading, setIsLoading] = useState12(() => {
|
|
4760
|
-
if (typeof window !== "undefined") {
|
|
4761
|
-
const hasTokens = hasValidTokens();
|
|
4762
|
-
return !hasTokens;
|
|
4763
|
-
}
|
|
4764
|
-
return true;
|
|
4765
|
-
});
|
|
4766
|
-
const [initialized, setInitialized] = useState12(false);
|
|
4767
|
-
const [authTick, setAuthTick] = useState12(0);
|
|
4768
|
-
const bumpAuthTick = useCallback12(() => setAuthTick((t) => t + 1), []);
|
|
4769
|
-
useEffect9(() => {
|
|
4770
|
-
if (typeof window === "undefined") return;
|
|
4771
|
-
const delay = nextAuthEvaluationDelay(
|
|
4772
|
-
getTokenExpiry(CfgAccountsApi.getToken()),
|
|
4773
|
-
getTokenExpiry(CfgAccountsApi.getRefreshToken()),
|
|
4774
|
-
Date.now()
|
|
4775
|
-
);
|
|
4776
|
-
if (delay === null) return;
|
|
4777
|
-
const timer = setTimeout(bumpAuthTick, Math.min(delay, 2e9));
|
|
4778
|
-
return () => clearTimeout(timer);
|
|
4779
|
-
}, [authTick, bumpAuthTick]);
|
|
4780
|
-
const onUnauthorizedRef = useRef6(false);
|
|
4389
|
+
const session = useSession();
|
|
4390
|
+
const isAuthenticated = session.status === "authenticated";
|
|
4781
4391
|
const router = useCfgRouter();
|
|
4782
4392
|
const pathname = usePathname3();
|
|
4783
4393
|
const queryParams = useQueryParams();
|
|
4784
|
-
const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage(
|
|
4785
|
-
|
|
4786
|
-
|
|
4394
|
+
const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage(
|
|
4395
|
+
EMAIL_STORAGE_KEY,
|
|
4396
|
+
null
|
|
4397
|
+
);
|
|
4398
|
+
const routes = useMemo3(() => resolveAuthRoutes(config?.routes), [config?.routes]);
|
|
4399
|
+
const routesRef = useRef6(routes);
|
|
4400
|
+
useEffect7(() => {
|
|
4401
|
+
routesRef.current = routes;
|
|
4402
|
+
}, [routes]);
|
|
4403
|
+
const redirectManager = useAuthRedirectManager({
|
|
4404
|
+
fallbackUrl: routes.defaultCallback,
|
|
4405
|
+
clearOnUse: true
|
|
4406
|
+
});
|
|
4787
4407
|
const configRef = useRef6(config);
|
|
4788
|
-
|
|
4789
|
-
useEffect9(() => {
|
|
4790
|
-
userRef.current = user;
|
|
4791
|
-
}, [user]);
|
|
4792
|
-
useEffect9(() => {
|
|
4408
|
+
useEffect7(() => {
|
|
4793
4409
|
configRef.current = config;
|
|
4794
4410
|
}, [config]);
|
|
4795
|
-
const
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
authLogger.info("Token auto-refreshed successfully");
|
|
4815
|
-
}, "onRefresh"),
|
|
4816
|
-
onRefreshError: handleProactiveRefreshError
|
|
4817
|
-
});
|
|
4818
|
-
const handleGlobalAuthError = useCallback12((error, context = "API Request") => {
|
|
4819
|
-
const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
|
|
4820
|
-
if (isAuthError) {
|
|
4821
|
-
authLogger.warn(`Authentication error in ${context}, clearing tokens`);
|
|
4822
|
-
clearAuthState(`globalAuthError:${context}`);
|
|
4823
|
-
return true;
|
|
4411
|
+
const accountsRef = useRef6(accounts);
|
|
4412
|
+
useEffect7(() => {
|
|
4413
|
+
accountsRef.current = accounts;
|
|
4414
|
+
}, [accounts]);
|
|
4415
|
+
const pendingManualNavRef = useRef6(false);
|
|
4416
|
+
const [profileSettled, setProfileSettled] = useState11(false);
|
|
4417
|
+
const hasProfile = Boolean(accounts.profile);
|
|
4418
|
+
const profileAttemptedRef = useRef6(false);
|
|
4419
|
+
useEffect7(() => {
|
|
4420
|
+
if (!isAuthenticated) {
|
|
4421
|
+
profileAttemptedRef.current = false;
|
|
4422
|
+
const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
|
|
4423
|
+
if (hasTokens && !auth.isAuthenticated()) {
|
|
4424
|
+
authLogger.warn("Dead session in storage \u2014 clearing");
|
|
4425
|
+
auth.clearSession();
|
|
4426
|
+
clearProfileCache();
|
|
4427
|
+
}
|
|
4428
|
+
setProfileSettled(true);
|
|
4429
|
+
return;
|
|
4824
4430
|
}
|
|
4825
|
-
if (
|
|
4826
|
-
|
|
4431
|
+
if (hasProfile || profileAttemptedRef.current) {
|
|
4432
|
+
setProfileSettled(true);
|
|
4433
|
+
return;
|
|
4827
4434
|
}
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
clearAuthState("onUnauthorized:401");
|
|
4836
|
-
const authCallbackUrl = configRef.current?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
|
|
4837
|
-
router.hardReplace(authCallbackUrl);
|
|
4838
|
-
}, "handler");
|
|
4839
|
-
CfgAccountsApi.onUnauthorized(handler);
|
|
4435
|
+
profileAttemptedRef.current = true;
|
|
4436
|
+
let cancelled = false;
|
|
4437
|
+
accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
|
|
4438
|
+
authLogger.warn("Profile load failed (session kept):", error);
|
|
4439
|
+
}).finally(() => {
|
|
4440
|
+
if (!cancelled) setProfileSettled(true);
|
|
4441
|
+
});
|
|
4840
4442
|
return () => {
|
|
4841
|
-
|
|
4443
|
+
cancelled = true;
|
|
4842
4444
|
};
|
|
4843
|
-
}, [
|
|
4844
|
-
const
|
|
4845
|
-
const
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
const isAuth = CfgAccountsApi.isAuthenticated();
|
|
4866
|
-
const token = CfgAccountsApi.getToken();
|
|
4867
|
-
if (!isAuth) {
|
|
4868
|
-
authLogger.warn("No valid authentication token, throwing error");
|
|
4869
|
-
throw new Error("No valid authentication token");
|
|
4870
|
-
}
|
|
4871
|
-
if (accounts.profile && !accounts.isLoadingProfile) {
|
|
4872
|
-
authLogger.debug("Profile already loaded in AccountsContext, skipping API call");
|
|
4873
|
-
setInitialized(true);
|
|
4874
|
-
return;
|
|
4875
|
-
}
|
|
4876
|
-
const refreshedProfile = await accounts.refreshProfile({ callerId: finalCallerId });
|
|
4877
|
-
if (refreshedProfile) {
|
|
4878
|
-
authLogger.info("Profile loaded successfully:", refreshedProfile.id);
|
|
4879
|
-
} else {
|
|
4880
|
-
authLogger.warn("Profile refresh returned undefined - but keeping tokens");
|
|
4881
|
-
}
|
|
4882
|
-
setInitialized(true);
|
|
4883
|
-
} catch (error) {
|
|
4884
|
-
authLogger.error("Failed to load profile:", error);
|
|
4885
|
-
const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
|
|
4886
|
-
if (isAuthError) {
|
|
4887
|
-
authLogger.warn("Authentication error, clearing session");
|
|
4888
|
-
clearAuthState("loadCurrentProfile:authError");
|
|
4889
|
-
} else {
|
|
4890
|
-
authLogger.warn("Profile load failed but keeping session (non-auth error)");
|
|
4891
|
-
setInitialized(true);
|
|
4892
|
-
}
|
|
4893
|
-
} finally {
|
|
4894
|
-
isLoadingProfileRef.current = false;
|
|
4445
|
+
}, [isAuthenticated, hasProfile]);
|
|
4446
|
+
const isLoading = !profileSettled;
|
|
4447
|
+
const expiredHandledRef = useRef6(false);
|
|
4448
|
+
useEffect7(() => {
|
|
4449
|
+
const unsubscribe = auth.onSessionExpired(() => {
|
|
4450
|
+
if (expiredHandledRef.current) return;
|
|
4451
|
+
expiredHandledRef.current = true;
|
|
4452
|
+
authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
|
|
4453
|
+
clearProfileCache();
|
|
4454
|
+
Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
|
|
4455
|
+
category: "auth" /* AUTH */
|
|
4456
|
+
});
|
|
4457
|
+
router.hardReplace(routesRef.current.defaultAuthCallback);
|
|
4458
|
+
});
|
|
4459
|
+
return unsubscribe;
|
|
4460
|
+
}, [router]);
|
|
4461
|
+
useEffect7(() => {
|
|
4462
|
+
if (!isAuthenticated || pendingManualNavRef.current) return;
|
|
4463
|
+
if (pathname === routes.auth && !queryParams.get("flow")) {
|
|
4464
|
+
const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
|
|
4465
|
+
if (saved) redirectManager.clearRedirect();
|
|
4466
|
+
router.push(saved || routes.defaultCallback);
|
|
4895
4467
|
}
|
|
4896
|
-
}, [
|
|
4897
|
-
|
|
4898
|
-
if (
|
|
4899
|
-
|
|
4900
|
-
authLogger.info("Initializing auth...");
|
|
4901
|
-
const isInIframe = typeof window !== "undefined" && window.self !== window.top;
|
|
4902
|
-
authLogger.info("Is in iframe:", isInIframe);
|
|
4903
|
-
const token = CfgAccountsApi.getToken();
|
|
4904
|
-
const refreshToken2 = CfgAccountsApi.getRefreshToken();
|
|
4905
|
-
authLogger.info("Token from API:", token ? `${token.substring(0, 20)}...` : "null");
|
|
4906
|
-
authLogger.info("Refresh token from API:", refreshToken2 ? `${refreshToken2.substring(0, 20)}...` : "null");
|
|
4907
|
-
authLogger.info("localStorage keys:", Object.keys(localStorage).filter((k) => k.includes("token") || k.includes("auth")));
|
|
4908
|
-
if (!isInIframe && isSessionDeadOnBootstrap(token, refreshToken2)) {
|
|
4909
|
-
authLogger.warn("Bootstrap: dead session in storage (access + refresh both unusable) \u2014 clearing and showing login");
|
|
4910
|
-
clearAuthState("initializeAuth:deadSession");
|
|
4911
|
-
return;
|
|
4912
|
-
}
|
|
4913
|
-
const hasTokens = hasValidTokens();
|
|
4914
|
-
authLogger.info("Has tokens:", hasTokens);
|
|
4915
|
-
if (userRef.current) {
|
|
4916
|
-
authLogger.info("Profile already loaded from AccountsContext cache, skipping API request");
|
|
4917
|
-
setInitialized(true);
|
|
4918
|
-
setIsLoading(false);
|
|
4919
|
-
return;
|
|
4920
|
-
}
|
|
4921
|
-
const cachedProfile = getCachedProfile();
|
|
4922
|
-
if (cachedProfile) {
|
|
4923
|
-
authLogger.info("Profile found in localStorage cache, skipping API request");
|
|
4924
|
-
setInitialized(true);
|
|
4925
|
-
setIsLoading(false);
|
|
4926
|
-
return;
|
|
4927
|
-
}
|
|
4928
|
-
if (isInIframe && !hasTokens) {
|
|
4929
|
-
authLogger.info("Running in iframe without tokens - waiting for parent to send via postMessage");
|
|
4930
|
-
authLogger.info("AdminLayout will handle auth initialization, skipping AuthContext init");
|
|
4931
|
-
setInitialized(true);
|
|
4932
|
-
setIsLoading(false);
|
|
4933
|
-
return;
|
|
4934
|
-
}
|
|
4935
|
-
if (hasTokens) {
|
|
4936
|
-
setIsLoading(true);
|
|
4937
|
-
try {
|
|
4938
|
-
authLogger.info("No cached profile found, loading from API...");
|
|
4939
|
-
await loadCurrentProfile("AuthContext.initializeAuth");
|
|
4940
|
-
} catch (error) {
|
|
4941
|
-
authLogger.error("Failed to load profile during initialization:", error);
|
|
4942
|
-
const isAuthError = error?.status === 401 || error?.statusCode === 401 || error?.code === "token_not_valid" || error?.code === "authentication_failed";
|
|
4943
|
-
if (isAuthError) {
|
|
4944
|
-
clearAuthState("initializeAuth:authError");
|
|
4945
|
-
} else {
|
|
4946
|
-
authLogger.warn("Init profile load failed but keeping session");
|
|
4947
|
-
setInitialized(true);
|
|
4948
|
-
}
|
|
4949
|
-
}
|
|
4950
|
-
setIsLoading(false);
|
|
4951
|
-
} else {
|
|
4952
|
-
setInitialized(true);
|
|
4953
|
-
setIsLoading(false);
|
|
4954
|
-
}
|
|
4955
|
-
}, "initializeAuth");
|
|
4956
|
-
initializeAuth();
|
|
4957
|
-
}, [initialized]);
|
|
4958
|
-
useEffect9(() => {
|
|
4959
|
-
if (!initialized) return;
|
|
4960
|
-
const isAuthenticated = CfgAccountsApi.isAuthenticated();
|
|
4961
|
-
const authRoute = config?.routes?.auth || defaultRoutes.auth;
|
|
4962
|
-
const isAuthPage = pathname === authRoute;
|
|
4963
|
-
const flowParam = queryParams.get("flow");
|
|
4964
|
-
if (isAuthenticated && isAuthPage && !flowParam) {
|
|
4965
|
-
const callbackUrl = config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
|
|
4966
|
-
router.push(callbackUrl);
|
|
4468
|
+
}, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
|
|
4469
|
+
const loadCurrentProfile = useCallback11(async (callerId) => {
|
|
4470
|
+
if (!auth.isAuthenticated()) {
|
|
4471
|
+
throw new Error("No valid authentication token");
|
|
4967
4472
|
}
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
try {
|
|
4979
|
-
setIsLoading(true);
|
|
4980
|
-
const isAuthenticated = CfgAccountsApi.isAuthenticated();
|
|
4981
|
-
if (isAuthenticated) {
|
|
4982
|
-
await loadCurrentProfile();
|
|
4983
|
-
if (userRef.current) {
|
|
4984
|
-
pushToDefaultCallbackUrl();
|
|
4985
|
-
}
|
|
4986
|
-
} else {
|
|
4987
|
-
pushToDefaultAuthCallbackUrl();
|
|
4988
|
-
}
|
|
4989
|
-
} catch (error) {
|
|
4990
|
-
authLogger.error("Failed to check authentication:", error);
|
|
4991
|
-
if (!handleGlobalAuthError(error, "checkAuthAndRedirect")) {
|
|
4992
|
-
clearAuthState("checkAuthAndRedirect");
|
|
4473
|
+
await accountsRef.current.refreshProfile({
|
|
4474
|
+
callerId: callerId || "AuthContext.loadCurrentProfile"
|
|
4475
|
+
});
|
|
4476
|
+
}, []);
|
|
4477
|
+
const checkAuthAndRedirect = useCallback11(async () => {
|
|
4478
|
+
if (auth.isAuthenticated()) {
|
|
4479
|
+
try {
|
|
4480
|
+
await loadCurrentProfile("checkAuthAndRedirect");
|
|
4481
|
+
} catch (error) {
|
|
4482
|
+
authLogger.warn("checkAuthAndRedirect: profile load failed", error);
|
|
4993
4483
|
}
|
|
4994
|
-
|
|
4995
|
-
}
|
|
4996
|
-
|
|
4484
|
+
router.push(routesRef.current.defaultCallback);
|
|
4485
|
+
} else {
|
|
4486
|
+
router.push(routesRef.current.defaultAuthCallback);
|
|
4997
4487
|
}
|
|
4998
|
-
}, [loadCurrentProfile,
|
|
4999
|
-
const requestOTP =
|
|
4488
|
+
}, [loadCurrentProfile, router]);
|
|
4489
|
+
const requestOTP = useCallback11(
|
|
5000
4490
|
async (identifier, sourceUrl) => {
|
|
5001
|
-
|
|
4491
|
+
auth.clearSession();
|
|
5002
4492
|
try {
|
|
5003
|
-
const result = await
|
|
4493
|
+
const result = await accountsRef.current.requestOTP({
|
|
5004
4494
|
identifier,
|
|
5005
4495
|
source_url: sourceUrl
|
|
5006
4496
|
});
|
|
@@ -5010,33 +4500,31 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5010
4500
|
});
|
|
5011
4501
|
return {
|
|
5012
4502
|
success: true,
|
|
5013
|
-
message: result.message ||
|
|
4503
|
+
message: result.message || "OTP code sent to your email address",
|
|
5014
4504
|
webmail: result.webmail ?? null
|
|
5015
4505
|
};
|
|
5016
4506
|
} catch (error) {
|
|
5017
4507
|
authLogger.error("Request OTP error:", error);
|
|
5018
4508
|
if (error instanceof APIError) {
|
|
5019
|
-
const
|
|
5020
|
-
const
|
|
4509
|
+
const body = error.response;
|
|
4510
|
+
const retryAfter = body?.retry_after ?? body?.retryAfter;
|
|
5021
4511
|
return {
|
|
5022
4512
|
success: false,
|
|
5023
4513
|
statusCode: error.statusCode,
|
|
5024
|
-
message,
|
|
4514
|
+
message: apiErrorMessage(error),
|
|
5025
4515
|
retryAfter: typeof retryAfter === "number" ? retryAfter : void 0
|
|
5026
4516
|
};
|
|
5027
4517
|
}
|
|
5028
|
-
return {
|
|
5029
|
-
success: false,
|
|
5030
|
-
message: "Failed to send OTP"
|
|
5031
|
-
};
|
|
4518
|
+
return { success: false, message: "Failed to send OTP" };
|
|
5032
4519
|
}
|
|
5033
4520
|
},
|
|
5034
|
-
[
|
|
4521
|
+
[]
|
|
5035
4522
|
);
|
|
5036
|
-
const verifyOTP =
|
|
4523
|
+
const verifyOTP = useCallback11(
|
|
5037
4524
|
async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
|
|
5038
4525
|
try {
|
|
5039
|
-
|
|
4526
|
+
pendingManualNavRef.current = Boolean(skipRedirect);
|
|
4527
|
+
const result = await accountsRef.current.verifyOTP({
|
|
5040
4528
|
identifier,
|
|
5041
4529
|
otp: otpCode,
|
|
5042
4530
|
source_url: sourceUrl
|
|
@@ -5053,15 +4541,11 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5053
4541
|
}
|
|
5054
4542
|
if (!result.access || !result.refresh) {
|
|
5055
4543
|
authLogger.error("Verify OTP returned invalid response:", result);
|
|
5056
|
-
return {
|
|
5057
|
-
success: false,
|
|
5058
|
-
message: "Invalid OTP verification response"
|
|
5059
|
-
};
|
|
4544
|
+
return { success: false, message: "Invalid OTP verification response" };
|
|
5060
4545
|
}
|
|
5061
4546
|
if (identifier.includes("@")) {
|
|
5062
4547
|
setStoredEmail(identifier);
|
|
5063
4548
|
}
|
|
5064
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
5065
4549
|
Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
|
|
5066
4550
|
category: "auth" /* AUTH */,
|
|
5067
4551
|
label: "email"
|
|
@@ -5070,8 +4554,9 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5070
4554
|
Analytics.setUser(String(result.user.id));
|
|
5071
4555
|
}
|
|
5072
4556
|
if (!skipRedirect) {
|
|
5073
|
-
const
|
|
5074
|
-
|
|
4557
|
+
const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
|
|
4558
|
+
if (saved) redirectManager.clearRedirect();
|
|
4559
|
+
const finalRedirectUrl = saved || redirectUrl || routesRef.current.defaultCallback;
|
|
5075
4560
|
authLogger.info("Redirecting after auth to:", finalRedirectUrl);
|
|
5076
4561
|
router.hardPush(finalRedirectUrl);
|
|
5077
4562
|
}
|
|
@@ -5088,96 +4573,62 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5088
4573
|
label: "email"
|
|
5089
4574
|
});
|
|
5090
4575
|
if (error instanceof APIError) {
|
|
5091
|
-
|
|
5092
|
-
return { success: false, message };
|
|
4576
|
+
return { success: false, message: apiErrorMessage(error) };
|
|
5093
4577
|
}
|
|
5094
|
-
return {
|
|
5095
|
-
success: false,
|
|
5096
|
-
message: "Failed to verify OTP"
|
|
5097
|
-
};
|
|
4578
|
+
return { success: false, message: "Failed to verify OTP" };
|
|
5098
4579
|
}
|
|
5099
4580
|
},
|
|
5100
|
-
[setStoredEmail,
|
|
4581
|
+
[setStoredEmail, redirectManager, router]
|
|
5101
4582
|
);
|
|
5102
|
-
const refreshToken =
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
if (!refreshTokenValue) {
|
|
5106
|
-
clearAuthState("refreshToken:noToken");
|
|
5107
|
-
Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
|
|
5108
|
-
category: "auth" /* AUTH */
|
|
5109
|
-
});
|
|
5110
|
-
return {
|
|
5111
|
-
success: false,
|
|
5112
|
-
message: "No refresh token available"
|
|
5113
|
-
};
|
|
5114
|
-
}
|
|
5115
|
-
await accounts.refreshToken(refreshTokenValue);
|
|
4583
|
+
const refreshToken = useCallback11(async () => {
|
|
4584
|
+
const access = await auth.refreshNow();
|
|
4585
|
+
if (access) {
|
|
5116
4586
|
Analytics.event("auth_token_refresh" /* AUTH_TOKEN_REFRESH */, {
|
|
5117
4587
|
category: "auth" /* AUTH */
|
|
5118
4588
|
});
|
|
5119
|
-
return {
|
|
5120
|
-
success: true,
|
|
5121
|
-
message: "Token refreshed"
|
|
5122
|
-
};
|
|
5123
|
-
} catch (error) {
|
|
5124
|
-
authLogger.error("Refresh token error:", error);
|
|
5125
|
-
clearAuthState("refreshToken:error");
|
|
5126
|
-
Analytics.event("auth_token_refresh_fail" /* AUTH_TOKEN_REFRESH_FAIL */, {
|
|
5127
|
-
category: "auth" /* AUTH */
|
|
5128
|
-
});
|
|
5129
|
-
return {
|
|
5130
|
-
success: false,
|
|
5131
|
-
message: "Error refreshing token"
|
|
5132
|
-
};
|
|
4589
|
+
return { success: true, message: "Token refreshed" };
|
|
5133
4590
|
}
|
|
5134
|
-
|
|
5135
|
-
|
|
4591
|
+
Analytics.event("auth_token_refresh_fail" /* AUTH_TOKEN_REFRESH_FAIL */, {
|
|
4592
|
+
category: "auth" /* AUTH */
|
|
4593
|
+
});
|
|
4594
|
+
return { success: false, message: "Error refreshing token" };
|
|
4595
|
+
}, []);
|
|
4596
|
+
const logout = useCallback11(() => {
|
|
5136
4597
|
Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
|
|
5137
4598
|
category: "auth" /* AUTH */
|
|
5138
4599
|
});
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
const isAdminUser = useMemo3(() => {
|
|
5146
|
-
return Boolean(user?.is_staff || user?.is_superuser);
|
|
5147
|
-
}, [user]);
|
|
5148
|
-
useEffect9(() => {
|
|
4600
|
+
accountsRef.current.logout();
|
|
4601
|
+
router.hardReplace(routesRef.current.defaultAuthCallback);
|
|
4602
|
+
}, [router]);
|
|
4603
|
+
const user = accounts.profile ?? null;
|
|
4604
|
+
const isAdminUser = Boolean(user?.is_staff || user?.is_superuser);
|
|
4605
|
+
useEffect7(() => {
|
|
5149
4606
|
applyRoleLogPolicy({ isAdmin: isAdminUser });
|
|
5150
4607
|
}, [isAdminUser]);
|
|
5151
|
-
const updateProfile =
|
|
4608
|
+
const updateProfile = useCallback11(
|
|
5152
4609
|
async (data) => {
|
|
5153
|
-
const result = await
|
|
5154
|
-
return result;
|
|
5155
|
-
},
|
|
5156
|
-
[accounts]
|
|
5157
|
-
);
|
|
5158
|
-
const uploadAvatar = useCallback12(
|
|
5159
|
-
async (avatar) => {
|
|
5160
|
-
const result = await accounts.uploadAvatar(avatar);
|
|
4610
|
+
const result = await accountsRef.current.partialUpdateProfile(data);
|
|
5161
4611
|
return result;
|
|
5162
4612
|
},
|
|
5163
|
-
[
|
|
4613
|
+
[]
|
|
5164
4614
|
);
|
|
4615
|
+
const uploadAvatar = useCallback11(async (avatar) => {
|
|
4616
|
+
const result = await accountsRef.current.uploadAvatar(avatar);
|
|
4617
|
+
return result;
|
|
4618
|
+
}, []);
|
|
5165
4619
|
const value = useMemo3(
|
|
5166
4620
|
() => ({
|
|
5167
4621
|
user,
|
|
5168
4622
|
isLoading,
|
|
5169
|
-
//
|
|
5170
|
-
//
|
|
5171
|
-
|
|
5172
|
-
// unauthenticated WITHOUT waiting for a server 401 (which CSP / a hung
|
|
5173
|
-
// request / offline can swallow). `authTick` (in deps) forces recompute
|
|
5174
|
-
// after tokens are cleared/set, since token state lives in storage.
|
|
5175
|
-
isAuthenticated: isSessionAlive(),
|
|
4623
|
+
// Reactive: flips via the store snapshot (token writes, refreshes,
|
|
4624
|
+
// expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
|
|
4625
|
+
isAuthenticated,
|
|
5176
4626
|
isAdminUser,
|
|
4627
|
+
routes,
|
|
5177
4628
|
loadCurrentProfile,
|
|
5178
4629
|
checkAuthAndRedirect,
|
|
5179
|
-
getToken: /* @__PURE__ */ __name(() =>
|
|
5180
|
-
getRefreshToken: /* @__PURE__ */ __name(() =>
|
|
4630
|
+
getToken: /* @__PURE__ */ __name(() => auth.getToken(), "getToken"),
|
|
4631
|
+
getRefreshToken: /* @__PURE__ */ __name(() => auth.getRefreshToken(), "getRefreshToken"),
|
|
5181
4632
|
getSavedEmail: /* @__PURE__ */ __name(() => storedEmail, "getSavedEmail"),
|
|
5182
4633
|
saveEmail: setStoredEmail,
|
|
5183
4634
|
clearSavedEmail: clearStoredEmail,
|
|
@@ -5185,21 +4636,19 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5185
4636
|
verifyOTP,
|
|
5186
4637
|
refreshToken,
|
|
5187
4638
|
logout,
|
|
5188
|
-
// Redirect URL methods
|
|
5189
4639
|
saveRedirectUrl: redirectManager.setRedirect,
|
|
5190
4640
|
getRedirectUrl: redirectManager.getFinalRedirectUrl,
|
|
5191
4641
|
clearRedirectUrl: redirectManager.clearRedirect,
|
|
5192
4642
|
hasRedirectUrl: redirectManager.hasRedirect,
|
|
5193
|
-
// Profile management
|
|
5194
4643
|
updateProfile,
|
|
5195
4644
|
uploadAvatar
|
|
5196
4645
|
}),
|
|
5197
4646
|
[
|
|
5198
4647
|
user,
|
|
5199
4648
|
isLoading,
|
|
5200
|
-
|
|
5201
|
-
// recompute isAuthenticated when tokens are cleared/set
|
|
4649
|
+
isAuthenticated,
|
|
5202
4650
|
isAdminUser,
|
|
4651
|
+
routes,
|
|
5203
4652
|
loadCurrentProfile,
|
|
5204
4653
|
checkAuthAndRedirect,
|
|
5205
4654
|
storedEmail,
|
|
@@ -5214,7 +4663,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
|
|
|
5214
4663
|
uploadAvatar
|
|
5215
4664
|
]
|
|
5216
4665
|
);
|
|
5217
|
-
return /* @__PURE__ */ jsx2(AuthContext.Provider, { value, children
|
|
4666
|
+
return /* @__PURE__ */ jsx2(AuthContext.Provider, { value, children });
|
|
5218
4667
|
}, "AuthProviderInternal");
|
|
5219
4668
|
function AuthProviderRaw({ children, config, enabled = true }) {
|
|
5220
4669
|
if (!enabled) return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
@@ -5227,6 +4676,7 @@ var defaultAuthState = {
|
|
|
5227
4676
|
isLoading: false,
|
|
5228
4677
|
isAuthenticated: false,
|
|
5229
4678
|
isAdminUser: false,
|
|
4679
|
+
routes: resolveAuthRoutes(),
|
|
5230
4680
|
loadCurrentProfile: /* @__PURE__ */ __name(async () => {
|
|
5231
4681
|
authLogger.warn("useAuth: loadCurrentProfile called outside AuthProvider");
|
|
5232
4682
|
}, "loadCurrentProfile"),
|
|
@@ -5253,12 +4703,12 @@ var defaultAuthState = {
|
|
|
5253
4703
|
authLogger.warn("useAuth: refreshToken called outside AuthProvider");
|
|
5254
4704
|
return { success: false, message: "AuthProvider not available" };
|
|
5255
4705
|
}, "refreshToken"),
|
|
5256
|
-
logout: /* @__PURE__ */ __name(
|
|
4706
|
+
logout: /* @__PURE__ */ __name(() => {
|
|
5257
4707
|
authLogger.warn("useAuth: logout called outside AuthProvider");
|
|
5258
4708
|
}, "logout"),
|
|
5259
4709
|
saveRedirectUrl: /* @__PURE__ */ __name(() => {
|
|
5260
4710
|
}, "saveRedirectUrl"),
|
|
5261
|
-
getRedirectUrl: /* @__PURE__ */ __name(() =>
|
|
4711
|
+
getRedirectUrl: /* @__PURE__ */ __name(() => "", "getRedirectUrl"),
|
|
5262
4712
|
clearRedirectUrl: /* @__PURE__ */ __name(() => {
|
|
5263
4713
|
}, "clearRedirectUrl"),
|
|
5264
4714
|
hasRedirectUrl: /* @__PURE__ */ __name(() => false, "hasRedirectUrl"),
|
|
@@ -5274,7 +4724,7 @@ var defaultAuthState = {
|
|
|
5274
4724
|
var useAuth = /* @__PURE__ */ __name(() => {
|
|
5275
4725
|
const context = useContext2(AuthContext);
|
|
5276
4726
|
if (context === void 0) {
|
|
5277
|
-
if (
|
|
4727
|
+
if (isBrowser2 && isDev) {
|
|
5278
4728
|
authLogger.debug("useAuth called outside AuthProvider, returning default state");
|
|
5279
4729
|
}
|
|
5280
4730
|
return defaultAuthState;
|
|
@@ -5305,6 +4755,22 @@ var formatAuthError = /* @__PURE__ */ __name((error) => {
|
|
|
5305
4755
|
}
|
|
5306
4756
|
return "An unexpected error occurred";
|
|
5307
4757
|
}, "formatAuthError");
|
|
4758
|
+
|
|
4759
|
+
// src/auth/utils/guard.ts
|
|
4760
|
+
function resolveGuardIsLoading(s) {
|
|
4761
|
+
if (!s.mounted) return true;
|
|
4762
|
+
if (!s.requireAuth) return false;
|
|
4763
|
+
return s.authLoading || s.isRedirecting || !s.isAuthenticated;
|
|
4764
|
+
}
|
|
4765
|
+
__name(resolveGuardIsLoading, "resolveGuardIsLoading");
|
|
4766
|
+
function shouldRedirectToAuth(s) {
|
|
4767
|
+
return s.mounted && s.requireAuth && !s.authLoading && !s.isAuthenticated && !s.isRedirecting;
|
|
4768
|
+
}
|
|
4769
|
+
__name(shouldRedirectToAuth, "shouldRedirectToAuth");
|
|
4770
|
+
function resolveGuardIsAuthenticated(s) {
|
|
4771
|
+
return !s.requireAuth || s.isAuthenticated;
|
|
4772
|
+
}
|
|
4773
|
+
__name(resolveGuardIsAuthenticated, "resolveGuardIsAuthenticated");
|
|
5308
4774
|
export {
|
|
5309
4775
|
AUTH_CONSTANTS,
|
|
5310
4776
|
AccountsProvider,
|
|
@@ -5316,19 +4782,17 @@ export {
|
|
|
5316
4782
|
PatchedCfgUserUpdateRequestSchema,
|
|
5317
4783
|
authLogger,
|
|
5318
4784
|
clearProfileCache,
|
|
4785
|
+
consumeSavedRedirect,
|
|
5319
4786
|
decodeBase64,
|
|
5320
4787
|
encodeBase64,
|
|
5321
4788
|
formatAuthError,
|
|
5322
4789
|
getCacheMetadata,
|
|
5323
4790
|
getCachedProfile,
|
|
5324
|
-
getTokenExpiry,
|
|
5325
4791
|
hasValidCache,
|
|
5326
4792
|
isAllowedAuthPath,
|
|
5327
|
-
isTokenExpired,
|
|
5328
|
-
isTokenExpiringSoon,
|
|
5329
4793
|
logger,
|
|
5330
|
-
nextAuthEvaluationDelay,
|
|
5331
4794
|
normalizePath,
|
|
4795
|
+
peekSavedRedirect,
|
|
5332
4796
|
resolveGuardIsAuthenticated,
|
|
5333
4797
|
resolveGuardIsLoading,
|
|
5334
4798
|
setCachedProfile,
|
|
@@ -5337,7 +4801,6 @@ export {
|
|
|
5337
4801
|
useAuth,
|
|
5338
4802
|
useAuthForm,
|
|
5339
4803
|
useAuthFormState,
|
|
5340
|
-
useAuthGuard,
|
|
5341
4804
|
useAuthRedirectManager,
|
|
5342
4805
|
useAuthValidation,
|
|
5343
4806
|
useAutoAuth,
|
|
@@ -5347,8 +4810,8 @@ export {
|
|
|
5347
4810
|
useGithubAuth,
|
|
5348
4811
|
useLocalStorage,
|
|
5349
4812
|
useQueryParams,
|
|
4813
|
+
useSession,
|
|
5350
4814
|
useSessionStorage,
|
|
5351
|
-
useTokenRefresh,
|
|
5352
4815
|
useTwoFactor,
|
|
5353
4816
|
useTwoFactorSetup,
|
|
5354
4817
|
useTwoFactorStatus,
|