@classic-homes/auth 1.23.0 → 1.28.0
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/README.md +23 -0
- package/dist/auth.svelte-JLSRQJAC.js +3 -0
- package/dist/{chunk-ETKDIPD2.js → chunk-GBYHYVXX.js} +165 -21
- package/dist/{chunk-2CYGQUUY.js → chunk-K6TV3ZVS.js} +38 -3
- package/dist/{chunk-KSOYQYUZ.js → chunk-S3DNV7VV.js} +11 -3
- package/dist/{chunk-DFCBWKCU.js → chunk-TUWTOMZZ.js} +26 -46
- package/dist/{chunk-NLG6NSE4.js → chunk-XUOSWSDO.js} +13 -5
- package/dist/core/index.d.ts +80 -5
- package/dist/core/index.js +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -5
- package/dist/svelte/index.d.ts +4 -4
- package/dist/svelte/index.js +5 -5
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/{types-Bqh2gkvu.d.ts → types-B70Kgcc_.d.ts} +15 -0
- package/dist/{user-utils-92PeSjcw.d.ts → user-utils-DWEU-Ovm.d.ts} +12 -2
- package/package.json +1 -1
- package/dist/auth.svelte-T4LKTTDZ.js +0 -3
package/README.md
CHANGED
|
@@ -148,6 +148,27 @@ function handleSSOLogin(redirectUrl: string) {
|
|
|
148
148
|
}
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
+
`initiateSSOLogin` also sends a random `client_state`, kept in this tab's
|
|
152
|
+
sessionStorage (single-use, 10-minute TTL). The API echoes it in the callback
|
|
153
|
+
hash (`client_state`, in both the token and the MFA-challenge variants); check
|
|
154
|
+
it **before** storing any tokens from the hash, so a crafted callback URL can't
|
|
155
|
+
sign the user into someone else's account (login CSRF):
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { verifySsoClientState } from '@classic-homes/auth/core';
|
|
159
|
+
|
|
160
|
+
const params = new URLSearchParams(window.location.hash.slice(1));
|
|
161
|
+
const { accepted, result } = verifySsoClientState(params); // 'ok' | 'mismatch' | 'missing'
|
|
162
|
+
if (!accepted) {
|
|
163
|
+
window.history.replaceState({}, '', window.location.pathname);
|
|
164
|
+
throw new Error('This sign-in link was not started from this browser tab. Please sign in again.');
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Set `sso.requireClientState: true` once the API echoing `client_state` is
|
|
169
|
+
deployed to that environment. Until then (the default) every result is
|
|
170
|
+
accepted, so log `result` to watch the rollout.
|
|
171
|
+
|
|
151
172
|
### 5. MFA Challenge Verification
|
|
152
173
|
|
|
153
174
|
```typescript
|
|
@@ -395,6 +416,8 @@ interface AuthConfig {
|
|
|
395
416
|
enabled: boolean;
|
|
396
417
|
provider: string;
|
|
397
418
|
authorizeUrl?: string;
|
|
419
|
+
/** Reject SSO callbacks whose client_state is missing or wrong (default false) */
|
|
420
|
+
requireClientState?: boolean;
|
|
398
421
|
};
|
|
399
422
|
|
|
400
423
|
/** Callback when auth errors occur */
|
|
@@ -1,4 +1,101 @@
|
|
|
1
|
-
import { getConfig, getStorage, isTokenExpired, getFetch, isValidRedirectUrl } from './chunk-
|
|
1
|
+
import { isInitialized, getConfig, getUserRoles, getStorage, isTokenExpired, getFetch, isValidRedirectUrl } from './chunk-K6TV3ZVS.js';
|
|
2
|
+
|
|
3
|
+
// src/core/sso-state.ts
|
|
4
|
+
var SSO_CLIENT_STATE_PARAM = "client_state";
|
|
5
|
+
var SSO_CLIENT_STATE_TTL_MS = 10 * 60 * 1e3;
|
|
6
|
+
function storageKey() {
|
|
7
|
+
const prefix = isInitialized() ? getConfig().storageKey ?? "classic_auth" : "classic_auth";
|
|
8
|
+
return `${prefix}_sso_client_state`;
|
|
9
|
+
}
|
|
10
|
+
function sessionStore() {
|
|
11
|
+
try {
|
|
12
|
+
return typeof globalThis.sessionStorage === "undefined" ? null : globalThis.sessionStorage;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function randomUrlSafe(byteLength) {
|
|
18
|
+
const bytes = new Uint8Array(byteLength);
|
|
19
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
20
|
+
let binary = "";
|
|
21
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
22
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
23
|
+
}
|
|
24
|
+
function createSsoClientState() {
|
|
25
|
+
const store = sessionStore();
|
|
26
|
+
if (!store) return null;
|
|
27
|
+
const value = randomUrlSafe(32);
|
|
28
|
+
const entry = { value, createdAt: Date.now() };
|
|
29
|
+
try {
|
|
30
|
+
store.setItem(storageKey(), JSON.stringify(entry));
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function takeStored() {
|
|
37
|
+
const store = sessionStore();
|
|
38
|
+
if (!store) return null;
|
|
39
|
+
const key = storageKey();
|
|
40
|
+
try {
|
|
41
|
+
const raw = store.getItem(key);
|
|
42
|
+
store.removeItem(key);
|
|
43
|
+
if (!raw) return null;
|
|
44
|
+
const parsed = JSON.parse(raw);
|
|
45
|
+
if (typeof parsed?.value !== "string" || typeof parsed.createdAt !== "number") return null;
|
|
46
|
+
return { value: parsed.value, createdAt: parsed.createdAt };
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function timingSafeEqual(a, b) {
|
|
52
|
+
if (a.length !== b.length) return false;
|
|
53
|
+
let diff = 0;
|
|
54
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
55
|
+
return diff === 0;
|
|
56
|
+
}
|
|
57
|
+
function consumeSsoClientState(received) {
|
|
58
|
+
const stored = takeStored();
|
|
59
|
+
if (!received) return "missing";
|
|
60
|
+
if (!stored) return "mismatch";
|
|
61
|
+
if (Date.now() - stored.createdAt > SSO_CLIENT_STATE_TTL_MS) return "mismatch";
|
|
62
|
+
return timingSafeEqual(stored.value, received) ? "ok" : "mismatch";
|
|
63
|
+
}
|
|
64
|
+
function verifySsoClientState(input) {
|
|
65
|
+
const received = input instanceof URLSearchParams ? input.get(SSO_CLIENT_STATE_PARAM) : input;
|
|
66
|
+
const result = consumeSsoClientState(received);
|
|
67
|
+
const required = isInitialized() && getConfig().sso?.requireClientState === true;
|
|
68
|
+
return { result, accepted: result === "ok" || !required };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/core/errors.ts
|
|
72
|
+
var RoleDeniedError = class _RoleDeniedError extends Error {
|
|
73
|
+
constructor(user, requiredRoles, redirectTo) {
|
|
74
|
+
const userRoles = getUserRoles(user).join(", ") || "none";
|
|
75
|
+
super(
|
|
76
|
+
`User "${user.username}" does not have required role(s). Has: [${userRoles}], Required: [${requiredRoles.join(", ")}]`
|
|
77
|
+
);
|
|
78
|
+
this.name = "RoleDeniedError";
|
|
79
|
+
this.user = user;
|
|
80
|
+
this.requiredRoles = requiredRoles;
|
|
81
|
+
this.redirectTo = redirectTo;
|
|
82
|
+
if (Error.captureStackTrace) {
|
|
83
|
+
Error.captureStackTrace(this, _RoleDeniedError);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
function isRoleDeniedError(error) {
|
|
88
|
+
return error instanceof RoleDeniedError || error?.name === "RoleDeniedError";
|
|
89
|
+
}
|
|
90
|
+
var InvalidAuthResponseError = class _InvalidAuthResponseError extends Error {
|
|
91
|
+
constructor(message = "Unexpected authentication response: no access token or user") {
|
|
92
|
+
super(message);
|
|
93
|
+
this.name = "InvalidAuthResponseError";
|
|
94
|
+
if (Error.captureStackTrace) {
|
|
95
|
+
Error.captureStackTrace(this, _InvalidAuthResponseError);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
2
99
|
|
|
3
100
|
// src/core/client.ts
|
|
4
101
|
var DEFAULT_REFRESH_RETRY_DELAYS = [1e3, 3e3];
|
|
@@ -30,8 +127,8 @@ function getRefreshToken() {
|
|
|
30
127
|
}
|
|
31
128
|
function getSessionTokenKey() {
|
|
32
129
|
const config = getConfig();
|
|
33
|
-
const
|
|
34
|
-
return `${
|
|
130
|
+
const storageKey2 = config.storageKey ?? "classic_auth";
|
|
131
|
+
return `${storageKey2}_session`;
|
|
35
132
|
}
|
|
36
133
|
function getSessionToken() {
|
|
37
134
|
const storage = getStorage();
|
|
@@ -43,22 +140,22 @@ function updateStoredTokens(accessToken, refreshToken) {
|
|
|
43
140
|
function writeStoredTokens(accessToken, refreshToken) {
|
|
44
141
|
const config = getConfig();
|
|
45
142
|
const storage = getStorage();
|
|
46
|
-
const
|
|
143
|
+
const storageKey2 = config.storageKey ?? "classic_auth";
|
|
47
144
|
let parsed = {};
|
|
48
145
|
try {
|
|
49
|
-
const authData = storage.getItem(
|
|
146
|
+
const authData = storage.getItem(storageKey2);
|
|
50
147
|
if (authData) parsed = JSON.parse(authData);
|
|
51
148
|
} catch {
|
|
52
149
|
}
|
|
53
150
|
parsed.accessToken = accessToken;
|
|
54
151
|
parsed.refreshToken = refreshToken;
|
|
55
|
-
storage.setItem(
|
|
152
|
+
storage.setItem(storageKey2, JSON.stringify(parsed));
|
|
56
153
|
config.onTokenRefresh?.({ accessToken, refreshToken });
|
|
57
154
|
return syncSvelteStore((authStore) => authStore.updateTokens(accessToken, refreshToken));
|
|
58
155
|
}
|
|
59
156
|
async function syncSvelteStore(apply) {
|
|
60
157
|
try {
|
|
61
|
-
const { authStore } = await import('./auth.svelte-
|
|
158
|
+
const { authStore } = await import('./auth.svelte-JLSRQJAC.js');
|
|
62
159
|
apply(authStore);
|
|
63
160
|
} catch {
|
|
64
161
|
}
|
|
@@ -121,8 +218,8 @@ async function refreshOnce(refreshToken) {
|
|
|
121
218
|
function withRefreshLock(fn) {
|
|
122
219
|
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
123
220
|
if (!locks) return fn();
|
|
124
|
-
const
|
|
125
|
-
return locks.request(`${
|
|
221
|
+
const storageKey2 = getConfig().storageKey ?? "classic_auth";
|
|
222
|
+
return locks.request(`${storageKey2}:refresh`, fn);
|
|
126
223
|
}
|
|
127
224
|
function refreshSession(options = {}) {
|
|
128
225
|
if (options.onlyIfExpired) {
|
|
@@ -197,14 +294,41 @@ function extractData(response) {
|
|
|
197
294
|
}
|
|
198
295
|
return response;
|
|
199
296
|
}
|
|
297
|
+
function isApiOrigin(url, baseUrl) {
|
|
298
|
+
try {
|
|
299
|
+
const page = typeof location !== "undefined" ? location.href : void 0;
|
|
300
|
+
return new URL(url).origin === new URL(baseUrl, page).origin;
|
|
301
|
+
} catch {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
200
305
|
function apiRequest(endpoint, options = {}) {
|
|
201
306
|
return apiRequestInternal(endpoint, options, false);
|
|
202
307
|
}
|
|
203
308
|
async function apiRequestInternal(endpoint, options, isRetry) {
|
|
204
|
-
const {
|
|
309
|
+
const {
|
|
310
|
+
authenticated: wantsAuth = false,
|
|
311
|
+
customFetch,
|
|
312
|
+
body,
|
|
313
|
+
timeout,
|
|
314
|
+
signal,
|
|
315
|
+
noAuthRetry = false,
|
|
316
|
+
...fetchOptions
|
|
317
|
+
} = options;
|
|
205
318
|
const config = getConfig();
|
|
206
319
|
const fetchFn = customFetch ?? getFetch();
|
|
207
|
-
const
|
|
320
|
+
const isAbsolute = /^https?:\/\//i.test(endpoint);
|
|
321
|
+
const url = isAbsolute ? endpoint : `${config.baseUrl}${endpoint}`;
|
|
322
|
+
const authenticated = wantsAuth && (!isAbsolute || isApiOrigin(url, config.baseUrl));
|
|
323
|
+
if (authenticated && noAuthRetry && !isRetry && typeof window !== "undefined") {
|
|
324
|
+
const accessToken = getAccessToken();
|
|
325
|
+
if (accessToken && isTokenExpired(accessToken)) {
|
|
326
|
+
const outcome = await refreshSession({ onlyIfExpired: true });
|
|
327
|
+
if (outcome === "rejected") {
|
|
328
|
+
throw new Error("Authentication failed. Please sign in again.");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
208
332
|
const timeoutMs = timeout ?? config.requestTimeout ?? 3e4;
|
|
209
333
|
const controller = new AbortController();
|
|
210
334
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -238,7 +362,7 @@ async function apiRequestInternal(endpoint, options, isRetry) {
|
|
|
238
362
|
signal: controller.signal
|
|
239
363
|
});
|
|
240
364
|
clearTimeout(timeoutId);
|
|
241
|
-
if (response.status === 401 && authenticated && typeof window !== "undefined") {
|
|
365
|
+
if (response.status === 401 && authenticated && !noAuthRetry && typeof window !== "undefined") {
|
|
242
366
|
if (isRetry) {
|
|
243
367
|
await expireSession();
|
|
244
368
|
throw new Error("Authentication failed. Please sign in again.");
|
|
@@ -312,9 +436,9 @@ function initCrossTabSync() {
|
|
|
312
436
|
if (typeof window === "undefined") return () => {
|
|
313
437
|
};
|
|
314
438
|
const config = getConfig();
|
|
315
|
-
const
|
|
439
|
+
const storageKey2 = config.storageKey ?? "classic_auth";
|
|
316
440
|
function handleStorage(event) {
|
|
317
|
-
if (event.key !==
|
|
441
|
+
if (event.key !== storageKey2) return;
|
|
318
442
|
void syncSvelteStore((authStore) => authStore.rehydrate());
|
|
319
443
|
}
|
|
320
444
|
window.addEventListener("storage", handleStorage);
|
|
@@ -425,6 +549,8 @@ var authApi = {
|
|
|
425
549
|
* Initiate SSO login by redirecting to the SSO provider.
|
|
426
550
|
* @param options.callbackUrl - The URL where the SSO provider should redirect after auth
|
|
427
551
|
* @param options.redirectUrl - The final URL to redirect to after processing the callback
|
|
552
|
+
* Also sends a fresh `client_state` (kept in this tab's sessionStorage) that
|
|
553
|
+
* the callback page must check with `verifySsoClientState()`.
|
|
428
554
|
* @throws Error if redirectUrl or callbackUrl fail validation
|
|
429
555
|
*/
|
|
430
556
|
initiateSSOLogin(options) {
|
|
@@ -444,6 +570,10 @@ var authApi = {
|
|
|
444
570
|
if (options?.redirectUrl) {
|
|
445
571
|
params.set("redirect", options.redirectUrl);
|
|
446
572
|
}
|
|
573
|
+
const clientState = createSsoClientState();
|
|
574
|
+
if (clientState) {
|
|
575
|
+
params.set(SSO_CLIENT_STATE_PARAM, clientState);
|
|
576
|
+
}
|
|
447
577
|
const url = params.toString() ? `${authorizeUrl}?${params}` : authorizeUrl;
|
|
448
578
|
window.location.href = url;
|
|
449
579
|
},
|
|
@@ -468,7 +598,12 @@ var authApi = {
|
|
|
468
598
|
* Change the current user's password.
|
|
469
599
|
*/
|
|
470
600
|
async changePassword(data) {
|
|
471
|
-
await
|
|
601
|
+
await apiRequest("/auth/change-password", {
|
|
602
|
+
method: "PUT",
|
|
603
|
+
body: data,
|
|
604
|
+
authenticated: true,
|
|
605
|
+
noAuthRetry: true
|
|
606
|
+
});
|
|
472
607
|
},
|
|
473
608
|
// ============================================================================
|
|
474
609
|
// Email Verification
|
|
@@ -570,16 +705,20 @@ var authApi = {
|
|
|
570
705
|
* Disable MFA.
|
|
571
706
|
*/
|
|
572
707
|
async disableMFA(password) {
|
|
573
|
-
await
|
|
708
|
+
await apiRequest("/auth/mfa", {
|
|
709
|
+
method: "DELETE",
|
|
710
|
+
body: { password },
|
|
711
|
+
authenticated: true,
|
|
712
|
+
noAuthRetry: true
|
|
713
|
+
});
|
|
574
714
|
},
|
|
575
715
|
/**
|
|
576
716
|
* Regenerate MFA backup codes.
|
|
577
717
|
*/
|
|
578
718
|
async regenerateBackupCodes(password) {
|
|
579
|
-
const response = await
|
|
719
|
+
const response = await apiRequest(
|
|
580
720
|
"/auth/mfa/regenerate-backup-codes",
|
|
581
|
-
{ password },
|
|
582
|
-
true
|
|
721
|
+
{ method: "POST", body: { password }, authenticated: true, noAuthRetry: true }
|
|
583
722
|
);
|
|
584
723
|
return extractData(response);
|
|
585
724
|
},
|
|
@@ -707,7 +846,12 @@ var authApi = {
|
|
|
707
846
|
* Unlink an SSO account.
|
|
708
847
|
*/
|
|
709
848
|
async unlinkSSOAccount(provider, password) {
|
|
710
|
-
await
|
|
849
|
+
await apiRequest("/auth/sso/unlink", {
|
|
850
|
+
method: "DELETE",
|
|
851
|
+
body: { provider, password },
|
|
852
|
+
authenticated: true,
|
|
853
|
+
noAuthRetry: true
|
|
854
|
+
});
|
|
711
855
|
},
|
|
712
856
|
/**
|
|
713
857
|
* Link an SSO account.
|
|
@@ -834,4 +978,4 @@ var authApi = {
|
|
|
834
978
|
}
|
|
835
979
|
};
|
|
836
980
|
|
|
837
|
-
export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, refreshSession, updateStoredTokens };
|
|
981
|
+
export { InvalidAuthResponseError, RoleDeniedError, SSO_CLIENT_STATE_PARAM, SSO_CLIENT_STATE_TTL_MS, api, apiRequest, authApi, clearStoredAuth, consumeSsoClientState, createSsoClientState, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens, verifySsoClientState };
|
|
@@ -97,13 +97,48 @@ function isValidRedirectUrl(url) {
|
|
|
97
97
|
}
|
|
98
98
|
} catch {
|
|
99
99
|
if (url.includes("://") || url.startsWith("//")) return false;
|
|
100
|
+
return isSameOriginRelative(url);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function isSameOriginRelative(url) {
|
|
104
|
+
const sentinel = "https://relative.invalid";
|
|
105
|
+
let current = url;
|
|
106
|
+
for (let round = 0; round < 4; round++) {
|
|
107
|
+
try {
|
|
108
|
+
if (new URL(current, sentinel).origin !== sentinel) return false;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
let decoded;
|
|
100
113
|
try {
|
|
101
|
-
|
|
102
|
-
return new URL(url, sentinel).origin === sentinel;
|
|
114
|
+
decoded = decodeURIComponent(current);
|
|
103
115
|
} catch {
|
|
104
116
|
return false;
|
|
105
117
|
}
|
|
118
|
+
if (decoded === current) return true;
|
|
119
|
+
current = decoded;
|
|
106
120
|
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
function isValidSSOLogoutUrl(url) {
|
|
124
|
+
if (isValidRedirectUrl(url)) return true;
|
|
125
|
+
const cfg = getConfig();
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = new URL(url);
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
133
|
+
const origins = [...cfg.sso?.logoutOrigins ?? []];
|
|
134
|
+
if (cfg.sso?.authorizeUrl) origins.push(cfg.sso.authorizeUrl);
|
|
135
|
+
return origins.some((origin) => {
|
|
136
|
+
try {
|
|
137
|
+
return new URL(origin).origin === parsed.origin;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
107
142
|
}
|
|
108
143
|
|
|
109
144
|
// src/core/jwt.ts
|
|
@@ -251,4 +286,4 @@ function hasAllRoles(user, roles) {
|
|
|
251
286
|
return roles.every((role) => held.includes(role));
|
|
252
287
|
}
|
|
253
288
|
|
|
254
|
-
export { ROLE_HIERARCHY, ROLE_NAMES, computePrimaryRole, createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getEffectiveLevel, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initAuth, isInitialized, isTokenExpired, isValidRedirectUrl, resetConfig };
|
|
289
|
+
export { ROLE_HIERARCHY, ROLE_NAMES, computePrimaryRole, createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getEffectiveLevel, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initAuth, isInitialized, isTokenExpired, isValidRedirectUrl, isValidSSOLogoutUrl, resetConfig };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { authApi } from './chunk-
|
|
2
|
-
import { isInitialized, getConfig, hasRole, hasAnyRole, hasAllRoles, decodeJWT, getStorage, getDefaultStorage } from './chunk-
|
|
1
|
+
import { InvalidAuthResponseError, authApi } from './chunk-GBYHYVXX.js';
|
|
2
|
+
import { isInitialized, getConfig, isValidSSOLogoutUrl, hasRole, hasAnyRole, hasAllRoles, decodeJWT, getStorage, getDefaultStorage } from './chunk-K6TV3ZVS.js';
|
|
3
3
|
|
|
4
4
|
// src/svelte/stores/auth.svelte.ts
|
|
5
5
|
var STORAGE_VERSION = 2;
|
|
@@ -107,6 +107,9 @@ var AuthStore = class {
|
|
|
107
107
|
* Set auth data after successful login.
|
|
108
108
|
*/
|
|
109
109
|
setAuth(accessToken, refreshToken, user, sessionToken) {
|
|
110
|
+
if (typeof accessToken !== "string" || accessToken === "" || !user) {
|
|
111
|
+
throw new InvalidAuthResponseError("setAuth requires an access token and a user");
|
|
112
|
+
}
|
|
110
113
|
const userWithPermissions = normalizeUser(user, accessToken);
|
|
111
114
|
this.state = {
|
|
112
115
|
accessToken,
|
|
@@ -198,7 +201,12 @@ var AuthStore = class {
|
|
|
198
201
|
const response = await authApi.logout();
|
|
199
202
|
this.logout();
|
|
200
203
|
if (response?.ssoLogout && response?.logoutUrl) {
|
|
201
|
-
|
|
204
|
+
if (isValidSSOLogoutUrl(response.logoutUrl)) {
|
|
205
|
+
return { ssoLogoutUrl: response.logoutUrl };
|
|
206
|
+
}
|
|
207
|
+
getConfig().onAuthError?.(
|
|
208
|
+
new Error("Ignored SSO logout URL: origin is not allowed (see sso.logoutOrigins)")
|
|
209
|
+
);
|
|
202
210
|
}
|
|
203
211
|
} catch {
|
|
204
212
|
this.logout();
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { authApi } from './chunk-
|
|
2
|
-
import {
|
|
1
|
+
import { authApi, InvalidAuthResponseError, RoleDeniedError } from './chunk-GBYHYVXX.js';
|
|
2
|
+
import { hasAnyRole, getUserRoles } from './chunk-K6TV3ZVS.js';
|
|
3
3
|
|
|
4
4
|
// src/core/guards.ts
|
|
5
5
|
function isMfaChallengeResponse(response) {
|
|
6
6
|
return "requiresMFA" in response && response.requiresMFA === true || "mfaRequired" in response && response.mfaRequired === true;
|
|
7
7
|
}
|
|
8
8
|
function isLoginSuccessResponse(response) {
|
|
9
|
-
return
|
|
9
|
+
return typeof response?.accessToken === "string" && response.accessToken !== "" && typeof response.user === "object" && response.user !== null && !isMfaChallengeResponse(response);
|
|
10
10
|
}
|
|
11
11
|
function getMfaToken(response) {
|
|
12
12
|
return response.mfaToken ?? response.mfaChallengeToken;
|
|
@@ -15,27 +15,16 @@ function getAvailableMethods(response) {
|
|
|
15
15
|
return response.availableMethods ?? ["totp"];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// src/core/errors.ts
|
|
19
|
-
var RoleDeniedError = class _RoleDeniedError extends Error {
|
|
20
|
-
constructor(user, requiredRoles, redirectTo) {
|
|
21
|
-
const userRoles = getUserRoles(user).join(", ") || "none";
|
|
22
|
-
super(
|
|
23
|
-
`User "${user.username}" does not have required role(s). Has: [${userRoles}], Required: [${requiredRoles.join(", ")}]`
|
|
24
|
-
);
|
|
25
|
-
this.name = "RoleDeniedError";
|
|
26
|
-
this.user = user;
|
|
27
|
-
this.requiredRoles = requiredRoles;
|
|
28
|
-
this.redirectTo = redirectTo;
|
|
29
|
-
if (Error.captureStackTrace) {
|
|
30
|
-
Error.captureStackTrace(this, _RoleDeniedError);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
function isRoleDeniedError(error) {
|
|
35
|
-
return error instanceof RoleDeniedError || error?.name === "RoleDeniedError";
|
|
36
|
-
}
|
|
37
|
-
|
|
38
18
|
// src/core/service.ts
|
|
19
|
+
async function setStoreAuth(response) {
|
|
20
|
+
let store;
|
|
21
|
+
try {
|
|
22
|
+
store = (await import('./auth.svelte-JLSRQJAC.js')).authStore;
|
|
23
|
+
} catch {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
store.setAuth(response.accessToken, response.refreshToken, response.user, response.sessionToken);
|
|
27
|
+
}
|
|
39
28
|
var AuthService = class {
|
|
40
29
|
// ============================================================================
|
|
41
30
|
// Authentication
|
|
@@ -48,7 +37,13 @@ var AuthService = class {
|
|
|
48
37
|
*/
|
|
49
38
|
async login(credentials, options) {
|
|
50
39
|
const response = await authApi.login(credentials);
|
|
51
|
-
if (
|
|
40
|
+
if (isMfaChallengeResponse(response)) {
|
|
41
|
+
return response;
|
|
42
|
+
}
|
|
43
|
+
if (!isLoginSuccessResponse(response)) {
|
|
44
|
+
throw new InvalidAuthResponseError();
|
|
45
|
+
}
|
|
46
|
+
if (options?.allowedRoles?.length) {
|
|
52
47
|
if (!hasAnyRole(response.user, options.allowedRoles)) {
|
|
53
48
|
throw new RoleDeniedError(
|
|
54
49
|
response.user,
|
|
@@ -57,17 +52,8 @@ var AuthService = class {
|
|
|
57
52
|
);
|
|
58
53
|
}
|
|
59
54
|
}
|
|
60
|
-
if (options?.autoSetAuth !== false
|
|
61
|
-
|
|
62
|
-
const { authStore } = await import('./auth.svelte-T4LKTTDZ.js');
|
|
63
|
-
authStore.setAuth(
|
|
64
|
-
response.accessToken,
|
|
65
|
-
response.refreshToken,
|
|
66
|
-
response.user,
|
|
67
|
-
response.sessionToken
|
|
68
|
-
);
|
|
69
|
-
} catch {
|
|
70
|
-
}
|
|
55
|
+
if (options?.autoSetAuth !== false) {
|
|
56
|
+
await setStoreAuth(response);
|
|
71
57
|
}
|
|
72
58
|
return response;
|
|
73
59
|
}
|
|
@@ -234,6 +220,9 @@ var AuthService = class {
|
|
|
234
220
|
*/
|
|
235
221
|
async verifyMFAChallenge(data, options) {
|
|
236
222
|
const response = await authApi.verifyMFAChallenge(data);
|
|
223
|
+
if (!isLoginSuccessResponse(response)) {
|
|
224
|
+
throw new InvalidAuthResponseError();
|
|
225
|
+
}
|
|
237
226
|
if (options?.allowedRoles?.length) {
|
|
238
227
|
if (!hasAnyRole(response.user, options.allowedRoles)) {
|
|
239
228
|
throw new RoleDeniedError(
|
|
@@ -244,16 +233,7 @@ var AuthService = class {
|
|
|
244
233
|
}
|
|
245
234
|
}
|
|
246
235
|
if (options?.autoSetAuth !== false) {
|
|
247
|
-
|
|
248
|
-
const { authStore } = await import('./auth.svelte-T4LKTTDZ.js');
|
|
249
|
-
authStore.setAuth(
|
|
250
|
-
response.accessToken,
|
|
251
|
-
response.refreshToken,
|
|
252
|
-
response.user,
|
|
253
|
-
response.sessionToken
|
|
254
|
-
);
|
|
255
|
-
} catch {
|
|
256
|
-
}
|
|
236
|
+
await setStoreAuth(response);
|
|
257
237
|
}
|
|
258
238
|
return response;
|
|
259
239
|
}
|
|
@@ -407,4 +387,4 @@ function formatUserRoles(user, options = {}) {
|
|
|
407
387
|
return formatted.join(separator);
|
|
408
388
|
}
|
|
409
389
|
|
|
410
|
-
export { AuthService,
|
|
390
|
+
export { AuthService, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { authService } from './chunk-
|
|
2
|
-
import { authStore, currentUser, isAuthenticated, authActions } from './chunk-
|
|
3
|
-
import { isInitialized, initAuth, getUserRoles } from './chunk-
|
|
1
|
+
import { authService } from './chunk-TUWTOMZZ.js';
|
|
2
|
+
import { authStore, currentUser, isAuthenticated, authActions } from './chunk-S3DNV7VV.js';
|
|
3
|
+
import { isInitialized, initAuth, getUserRoles } from './chunk-K6TV3ZVS.js';
|
|
4
4
|
|
|
5
5
|
// src/svelte/guards/auth-guard.ts
|
|
6
6
|
function checkAuth(options = {}) {
|
|
@@ -175,7 +175,12 @@ function createAuthHook(options = {}) {
|
|
|
175
175
|
event,
|
|
176
176
|
resolve
|
|
177
177
|
}) => {
|
|
178
|
-
|
|
178
|
+
let pathname;
|
|
179
|
+
try {
|
|
180
|
+
pathname = decodePathname(event.url.pathname);
|
|
181
|
+
} catch {
|
|
182
|
+
return new Response("Malformed URI", { status: 400 });
|
|
183
|
+
}
|
|
179
184
|
const isLoginPage = pathname.replace(/(.)\/+$/, "$1") === loginPathname;
|
|
180
185
|
const isPublic = isLoginPage || publicRoutes.some((r) => r.test(pathname));
|
|
181
186
|
if (isPublic) {
|
|
@@ -198,7 +203,7 @@ function createAuthHook(options = {}) {
|
|
|
198
203
|
return onUnauthenticated(event);
|
|
199
204
|
}
|
|
200
205
|
const loginUrl = new URL(loginPath, event.url.origin);
|
|
201
|
-
loginUrl.searchParams.set(redirectParam, pathname + event.url.search);
|
|
206
|
+
loginUrl.searchParams.set(redirectParam, event.url.pathname + event.url.search);
|
|
202
207
|
return new Response(null, {
|
|
203
208
|
status: 302,
|
|
204
209
|
headers: { Location: loginUrl.pathname + loginUrl.search }
|
|
@@ -207,6 +212,9 @@ function createAuthHook(options = {}) {
|
|
|
207
212
|
return resolve(event);
|
|
208
213
|
};
|
|
209
214
|
}
|
|
215
|
+
function decodePathname(pathname) {
|
|
216
|
+
return pathname.split("%25").map(decodeURI).join("%25");
|
|
217
|
+
}
|
|
210
218
|
function matchesRoute(pathname, patterns) {
|
|
211
219
|
return patterns.some((pattern) => pattern.test(pathname));
|
|
212
220
|
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
|
-
import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, k as RegisterResponse, p as User, j as ProfileUpdateData, C as ChangePasswordData, n as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, U as UpdateApiKeyRequest, i as MFAStatus, h as MFASetupResponse, M as MFAChallengeData, D as Device, q as UserPreferences, L as LinkedAccount, m as SecurityEvent, P as Pagination, r as UserRolesResponse } from '../types-
|
|
2
|
-
export { a as AuthConfig, b as AuthState, l as ResetPasswordData, S as SSOConfig, o as StorageAdapter, s as createMemoryStorage, t as createSessionStorage, u as getConfig, v as getDefaultStorage, w as getFetch, x as getStorage, y as initAuth, z as isInitialized, B as resetConfig } from '../types-
|
|
3
|
-
export { A as AuthService, L as LoginOptions, M as MFAVerifyOptions, R as RoleDeniedError, a as authService, f as formatUserRoles, g as getAvatarFallback, b as getDisplayName, c as getGreeting, d as getUserEmail, e as getUserInitials, i as isRoleDeniedError } from '../user-utils-
|
|
1
|
+
import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, k as RegisterResponse, p as User, j as ProfileUpdateData, C as ChangePasswordData, n as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, U as UpdateApiKeyRequest, i as MFAStatus, h as MFASetupResponse, M as MFAChallengeData, D as Device, q as UserPreferences, L as LinkedAccount, m as SecurityEvent, P as Pagination, r as UserRolesResponse } from '../types-B70Kgcc_.js';
|
|
2
|
+
export { a as AuthConfig, b as AuthState, l as ResetPasswordData, S as SSOConfig, o as StorageAdapter, s as createMemoryStorage, t as createSessionStorage, u as getConfig, v as getDefaultStorage, w as getFetch, x as getStorage, y as initAuth, z as isInitialized, B as resetConfig } from '../types-B70Kgcc_.js';
|
|
3
|
+
export { A as AuthService, I as InvalidAuthResponseError, L as LoginOptions, M as MFAVerifyOptions, R as RoleDeniedError, a as authService, f as formatUserRoles, g as getAvatarFallback, b as getDisplayName, c as getGreeting, d as getUserEmail, e as getUserInitials, i as isRoleDeniedError } from '../user-utils-DWEU-Ovm.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SSO client state
|
|
7
|
+
*
|
|
8
|
+
* Binds an SSO callback to the browser tab that started the login. Before
|
|
9
|
+
* redirecting to `/auth/sso/authorize`, the tab generates a random
|
|
10
|
+
* `client_state`, keeps it in sessionStorage (per-tab, single-use, 10-minute
|
|
11
|
+
* TTL) and sends it along; the server echoes it back in the callback hash
|
|
12
|
+
* fragment (token and MFA-challenge variants alike). The callback page then
|
|
13
|
+
* calls `verifySsoClientState()` before trusting anything in the hash.
|
|
14
|
+
*
|
|
15
|
+
* Without this, an attacker could complete SSO for their own account and send
|
|
16
|
+
* the victim the resulting callback URL, silently signing the victim in as the
|
|
17
|
+
* attacker (login CSRF / session fixation).
|
|
18
|
+
*/
|
|
19
|
+
/** Hash-fragment (and authorize query) parameter carrying the client state. */
|
|
20
|
+
declare const SSO_CLIENT_STATE_PARAM = "client_state";
|
|
21
|
+
/** How long a stored client state stays valid. */
|
|
22
|
+
declare const SSO_CLIENT_STATE_TTL_MS: number;
|
|
23
|
+
/**
|
|
24
|
+
* Result of checking a callback's `client_state` against the one this tab stored:
|
|
25
|
+
* - `ok`: present and matches an unexpired stored value
|
|
26
|
+
* - `mismatch`: present but this tab stored none, it expired, or it differs
|
|
27
|
+
* - `missing`: the callback carried no `client_state`
|
|
28
|
+
*/
|
|
29
|
+
type SsoClientStateResult = 'ok' | 'mismatch' | 'missing';
|
|
30
|
+
interface SsoClientStateVerification {
|
|
31
|
+
result: SsoClientStateResult;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the callback may proceed: always for `ok`; for `missing` and
|
|
34
|
+
* `mismatch` only while `sso.requireClientState` is off (rollout mode).
|
|
35
|
+
*/
|
|
36
|
+
accepted: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate a fresh client state and store it for this tab, replacing any
|
|
40
|
+
* earlier one. Returns `null` when sessionStorage is unavailable — sending a
|
|
41
|
+
* value the callback can't check would only produce a mismatch.
|
|
42
|
+
*/
|
|
43
|
+
declare function createSsoClientState(): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Compare the `client_state` an SSO callback received with the one this tab
|
|
46
|
+
* stored, and consume the stored value (single-use) whatever the outcome.
|
|
47
|
+
* This is the raw check; `verifySsoClientState()` applies the
|
|
48
|
+
* `sso.requireClientState` policy on top.
|
|
49
|
+
*/
|
|
50
|
+
declare function consumeSsoClientState(received: string | null | undefined): SsoClientStateResult;
|
|
51
|
+
/**
|
|
52
|
+
* Verify an SSO callback's `client_state` and decide whether to proceed.
|
|
53
|
+
* Accepts the received value, or the callback's parsed hash params.
|
|
54
|
+
*
|
|
55
|
+
* With `sso.requireClientState: true`, anything but `ok` is rejected. While it
|
|
56
|
+
* is off (the default, so apps can ship before the API echoes the value),
|
|
57
|
+
* every result is accepted — log `result` to watch the rollout.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* const params = new URLSearchParams(window.location.hash.slice(1));
|
|
62
|
+
* const { accepted, result } = verifySsoClientState(params);
|
|
63
|
+
* if (!accepted) throw new Error(`SSO sign-in could not be verified (${result})`);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function verifySsoClientState(input: string | URLSearchParams | null | undefined): SsoClientStateVerification;
|
|
4
67
|
|
|
5
68
|
/**
|
|
6
69
|
* HTTP Client
|
|
@@ -19,6 +82,14 @@ interface ApiRequestOptions extends Omit<RequestInit, 'body' | 'signal'> {
|
|
|
19
82
|
timeout?: number;
|
|
20
83
|
/** AbortSignal for manual cancellation */
|
|
21
84
|
signal?: AbortSignal;
|
|
85
|
+
/**
|
|
86
|
+
* Treat a 401 as an ordinary request error: no token refresh, no retry
|
|
87
|
+
* and no session expiry. For endpoints where a 401 means a wrong password
|
|
88
|
+
* rather than a dead token (change password, disable MFA, ...), so a typo
|
|
89
|
+
* does not sign the user out. An already-expired access token is still
|
|
90
|
+
* refreshed before the request is sent.
|
|
91
|
+
*/
|
|
92
|
+
noAuthRetry?: boolean;
|
|
22
93
|
}
|
|
23
94
|
/**
|
|
24
95
|
* The CHAPI envelope shape, for endpoints that wrap their payload in `data`.
|
|
@@ -186,6 +257,8 @@ declare const authApi: {
|
|
|
186
257
|
* Initiate SSO login by redirecting to the SSO provider.
|
|
187
258
|
* @param options.callbackUrl - The URL where the SSO provider should redirect after auth
|
|
188
259
|
* @param options.redirectUrl - The final URL to redirect to after processing the callback
|
|
260
|
+
* Also sends a fresh `client_state` (kept in this tab's sessionStorage) that
|
|
261
|
+
* the callback page must check with `verifySsoClientState()`.
|
|
189
262
|
* @throws Error if redirectUrl or callbackUrl fail validation
|
|
190
263
|
*/
|
|
191
264
|
initiateSSOLogin(options?: {
|
|
@@ -389,7 +462,9 @@ declare const authApi: {
|
|
|
389
462
|
declare function isMfaChallengeResponse(response: LoginResponse): boolean;
|
|
390
463
|
/**
|
|
391
464
|
* Check if a login response is a successful login (with tokens and user).
|
|
392
|
-
* Returns true
|
|
465
|
+
* Returns true only for a non-empty access token plus a user object, so an
|
|
466
|
+
* MFA challenge in a shape isMfaChallengeResponse doesn't recognise is never
|
|
467
|
+
* mistaken for a login.
|
|
393
468
|
*/
|
|
394
469
|
declare function isLoginSuccessResponse(response: LoginResponse): boolean;
|
|
395
470
|
/**
|
|
@@ -621,4 +696,4 @@ declare function hasAnyRole(user: Pick<User, 'roles'> | null | undefined, roles:
|
|
|
621
696
|
*/
|
|
622
697
|
declare function hasAllRoles(user: Pick<User, 'roles'> | null | undefined, roles: readonly string[]): boolean;
|
|
623
698
|
|
|
624
|
-
export { ApiKey, type ApiRequestOptions, type ApiResponse, ChangePasswordData, CreateApiKeyRequest, CreateApiKeyResponse, Device, type JWTPayload, LinkedAccount, LoginCredentials, LoginResponse, LogoutResponse, MFAChallengeData, MFASetupResponse, MFAStatus, Pagination, ProfileUpdateData, ROLE_HIERARCHY, ROLE_NAMES, type RefreshOutcome, type RefreshSessionOptions, RegisterData, RegisterResponse, type RoleName, SecurityEvent, Session, UpdateApiKeyRequest, User, UserPreferences, UserRolesResponse, api, apiRequest, authApi, clearStoredAuth, computePrimaryRole, decodeJWT, extractClaims, extractData, getAccessToken, getAvailableMethods, getEffectiveLevel, getMfaToken, getRefreshToken, getSessionToken, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initCrossTabSync, isLoginSuccessResponse, isMfaChallengeResponse, isTokenExpired, refreshSession, updateStoredTokens };
|
|
699
|
+
export { ApiKey, type ApiRequestOptions, type ApiResponse, ChangePasswordData, CreateApiKeyRequest, CreateApiKeyResponse, Device, type JWTPayload, LinkedAccount, LoginCredentials, LoginResponse, LogoutResponse, MFAChallengeData, MFASetupResponse, MFAStatus, Pagination, ProfileUpdateData, ROLE_HIERARCHY, ROLE_NAMES, type RefreshOutcome, type RefreshSessionOptions, RegisterData, RegisterResponse, type RoleName, SSO_CLIENT_STATE_PARAM, SSO_CLIENT_STATE_TTL_MS, SecurityEvent, Session, type SsoClientStateResult, type SsoClientStateVerification, UpdateApiKeyRequest, User, UserPreferences, UserRolesResponse, api, apiRequest, authApi, clearStoredAuth, computePrimaryRole, consumeSsoClientState, createSsoClientState, decodeJWT, extractClaims, extractData, getAccessToken, getAvailableMethods, getEffectiveLevel, getMfaToken, getRefreshToken, getSessionToken, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initCrossTabSync, isLoginSuccessResponse, isMfaChallengeResponse, isTokenExpired, refreshSession, updateStoredTokens, verifySsoClientState };
|
package/dist/core/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { AuthService,
|
|
2
|
-
export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, refreshSession, updateStoredTokens } from '../chunk-
|
|
3
|
-
export { ROLE_HIERARCHY, ROLE_NAMES, computePrimaryRole, createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getEffectiveLevel, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initAuth, isInitialized, isTokenExpired, resetConfig } from '../chunk-
|
|
1
|
+
export { AuthService, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse } from '../chunk-TUWTOMZZ.js';
|
|
2
|
+
export { InvalidAuthResponseError, RoleDeniedError, SSO_CLIENT_STATE_PARAM, SSO_CLIENT_STATE_TTL_MS, api, apiRequest, authApi, clearStoredAuth, consumeSsoClientState, createSsoClientState, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens, verifySsoClientState } from '../chunk-GBYHYVXX.js';
|
|
3
|
+
export { ROLE_HIERARCHY, ROLE_NAMES, computePrimaryRole, createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getEffectiveLevel, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initAuth, isInitialized, isTokenExpired, resetConfig } from '../chunk-K6TV3ZVS.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as ApiKey, a as AuthConfig, b as AuthState, C as ChangePasswordData, c as CreateApiKeyRequest, d as CreateApiKeyResponse, D as Device, L as LinkedAccount, e as LoginCredentials, f as LoginResponse, g as LogoutResponse, M as MFAChallengeData, h as MFASetupResponse, i as MFAStatus, P as Pagination, j as ProfileUpdateData, R as RegisterData, k as RegisterResponse, l as ResetPasswordData, S as SSOConfig, m as SecurityEvent, n as Session, o as StorageAdapter, U as UpdateApiKeyRequest, p as User, q as UserPreferences, r as UserRolesResponse, s as createMemoryStorage, t as createSessionStorage, u as getConfig, v as getDefaultStorage, w as getFetch, x as getStorage, y as initAuth, z as isInitialized, B as resetConfig } from './types-
|
|
2
|
-
export { ApiRequestOptions, ApiResponse, JWTPayload, ROLE_HIERARCHY, ROLE_NAMES, RefreshOutcome, RefreshSessionOptions, RoleName, api, apiRequest, authApi, clearStoredAuth, computePrimaryRole, decodeJWT, extractClaims, extractData, getAccessToken, getAvailableMethods, getEffectiveLevel, getMfaToken, getRefreshToken, getSessionToken, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initCrossTabSync, isLoginSuccessResponse, isMfaChallengeResponse, isTokenExpired, refreshSession, updateStoredTokens } from './core/index.js';
|
|
3
|
-
export { A as AuthService, L as LoginOptions, M as MFAVerifyOptions, R as RoleDeniedError, a as authService, f as formatUserRoles, g as getAvatarFallback, b as getDisplayName, c as getGreeting, d as getUserEmail, e as getUserInitials, i as isRoleDeniedError } from './user-utils-
|
|
1
|
+
export { A as ApiKey, a as AuthConfig, b as AuthState, C as ChangePasswordData, c as CreateApiKeyRequest, d as CreateApiKeyResponse, D as Device, L as LinkedAccount, e as LoginCredentials, f as LoginResponse, g as LogoutResponse, M as MFAChallengeData, h as MFASetupResponse, i as MFAStatus, P as Pagination, j as ProfileUpdateData, R as RegisterData, k as RegisterResponse, l as ResetPasswordData, S as SSOConfig, m as SecurityEvent, n as Session, o as StorageAdapter, U as UpdateApiKeyRequest, p as User, q as UserPreferences, r as UserRolesResponse, s as createMemoryStorage, t as createSessionStorage, u as getConfig, v as getDefaultStorage, w as getFetch, x as getStorage, y as initAuth, z as isInitialized, B as resetConfig } from './types-B70Kgcc_.js';
|
|
2
|
+
export { ApiRequestOptions, ApiResponse, JWTPayload, ROLE_HIERARCHY, ROLE_NAMES, RefreshOutcome, RefreshSessionOptions, RoleName, SSO_CLIENT_STATE_PARAM, SSO_CLIENT_STATE_TTL_MS, SsoClientStateResult, SsoClientStateVerification, api, apiRequest, authApi, clearStoredAuth, computePrimaryRole, consumeSsoClientState, createSsoClientState, decodeJWT, extractClaims, extractData, getAccessToken, getAvailableMethods, getEffectiveLevel, getMfaToken, getRefreshToken, getSessionToken, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initCrossTabSync, isLoginSuccessResponse, isMfaChallengeResponse, isTokenExpired, refreshSession, updateStoredTokens, verifySsoClientState } from './core/index.js';
|
|
3
|
+
export { A as AuthService, I as InvalidAuthResponseError, L as LoginOptions, M as MFAVerifyOptions, R as RoleDeniedError, a as authService, f as formatUserRoles, g as getAvatarFallback, b as getDisplayName, c as getGreeting, d as getUserEmail, e as getUserInitials, i as isRoleDeniedError } from './user-utils-DWEU-Ovm.js';
|
|
4
4
|
export { AuthClient, AuthGuardOptions, AuthGuardResult, AuthHookOptions, CreateAuthClientOptions, NavFilterOptions, RoleRestrictedItem, authActions, authStore, canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, currentUser, filterByAccess, filterNavSections, isAuthenticated, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from './svelte/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from './chunk-
|
|
2
|
-
export { AuthService,
|
|
3
|
-
export { authActions, authStore, currentUser, isAuthenticated } from './chunk-
|
|
4
|
-
export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, refreshSession, updateStoredTokens } from './chunk-
|
|
5
|
-
export { ROLE_HIERARCHY, ROLE_NAMES, computePrimaryRole, createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getEffectiveLevel, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initAuth, isInitialized, isTokenExpired, resetConfig } from './chunk-
|
|
1
|
+
export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from './chunk-XUOSWSDO.js';
|
|
2
|
+
export { AuthService, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse } from './chunk-TUWTOMZZ.js';
|
|
3
|
+
export { authActions, authStore, currentUser, isAuthenticated } from './chunk-S3DNV7VV.js';
|
|
4
|
+
export { InvalidAuthResponseError, RoleDeniedError, SSO_CLIENT_STATE_PARAM, SSO_CLIENT_STATE_TTL_MS, api, apiRequest, authApi, clearStoredAuth, consumeSsoClientState, createSsoClientState, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens, verifySsoClientState } from './chunk-GBYHYVXX.js';
|
|
5
|
+
export { ROLE_HIERARCHY, ROLE_NAMES, computePrimaryRole, createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getEffectiveLevel, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, getUserRoles, hasAllRoles, hasAnyRole, hasRole, initAuth, isInitialized, isTokenExpired, resetConfig } from './chunk-K6TV3ZVS.js';
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { b as AuthState, p as User, a as AuthConfig } from '../types-
|
|
2
|
-
export { y as initAuth, z as isInitialized } from '../types-
|
|
3
|
-
import { a as authService } from '../user-utils-
|
|
4
|
-
export { R as RoleDeniedError, f as formatUserRoles, g as getAvatarFallback, b as getDisplayName, c as getGreeting, d as getUserEmail, e as getUserInitials, i as isRoleDeniedError } from '../user-utils-
|
|
1
|
+
import { b as AuthState, p as User, a as AuthConfig } from '../types-B70Kgcc_.js';
|
|
2
|
+
export { y as initAuth, z as isInitialized } from '../types-B70Kgcc_.js';
|
|
3
|
+
import { a as authService } from '../user-utils-DWEU-Ovm.js';
|
|
4
|
+
export { I as InvalidAuthResponseError, R as RoleDeniedError, f as formatUserRoles, g as getAvatarFallback, b as getDisplayName, c as getGreeting, d as getUserEmail, e as getUserInitials, i as isRoleDeniedError } from '../user-utils-DWEU-Ovm.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Auth Store
|
package/dist/svelte/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from '../chunk-
|
|
2
|
-
export {
|
|
3
|
-
export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-
|
|
4
|
-
|
|
5
|
-
export { initAuth, isInitialized } from '../chunk-
|
|
1
|
+
export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from '../chunk-XUOSWSDO.js';
|
|
2
|
+
export { formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials } from '../chunk-TUWTOMZZ.js';
|
|
3
|
+
export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-S3DNV7VV.js';
|
|
4
|
+
export { InvalidAuthResponseError, RoleDeniedError, isRoleDeniedError } from '../chunk-GBYHYVXX.js';
|
|
5
|
+
export { initAuth, isInitialized } from '../chunk-K6TV3ZVS.js';
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as User, D as Device, f as LoginResponse, m as SecurityEvent, n as Session, A as ApiKey, L as LinkedAccount, g as LogoutResponse, h as MFASetupResponse, i as MFAStatus, k as RegisterResponse, q as UserPreferences, o as StorageAdapter, b as AuthState, a as AuthConfig } from '../types-
|
|
1
|
+
import { p as User, D as Device, f as LoginResponse, m as SecurityEvent, n as Session, A as ApiKey, L as LinkedAccount, g as LogoutResponse, h as MFASetupResponse, i as MFAStatus, k as RegisterResponse, q as UserPreferences, o as StorageAdapter, b as AuthState, a as AuthConfig } from '../types-B70Kgcc_.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* User Fixtures
|
package/dist/testing/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hasRole, hasAnyRole, hasAllRoles, initAuth, resetConfig, getUserRoles, isTokenExpired, decodeJWT } from '../chunk-
|
|
1
|
+
import { hasRole, hasAnyRole, hasAllRoles, initAuth, resetConfig, getUserRoles, isTokenExpired, decodeJWT } from '../chunk-K6TV3ZVS.js';
|
|
2
2
|
|
|
3
3
|
// src/testing/fixtures/users.ts
|
|
4
4
|
var mockUser = {
|
|
@@ -11,6 +11,21 @@ interface SSOConfig {
|
|
|
11
11
|
provider: string;
|
|
12
12
|
/** Override the default authorize URL (defaults to /auth/sso/authorize) */
|
|
13
13
|
authorizeUrl?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Origins the server's SSO logout URL may point to (the identity
|
|
16
|
+
* provider's, e.g. 'https://sso.example.com'). The authorizeUrl origin and
|
|
17
|
+
* the redirect origins accepted by isValidRedirectUrl are always allowed;
|
|
18
|
+
* a logout URL anywhere else is dropped.
|
|
19
|
+
*/
|
|
20
|
+
logoutOrigins?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Reject SSO callbacks whose `client_state` is missing or doesn't match the
|
|
23
|
+
* one this tab sent to the authorize endpoint (see `verifySsoClientState`).
|
|
24
|
+
* Default `false` for one release so apps can roll out before the API
|
|
25
|
+
* echoes `client_state`; turn it on once the API is deployed to that
|
|
26
|
+
* environment. It becomes the default in the next major.
|
|
27
|
+
*/
|
|
28
|
+
requireClientState?: boolean;
|
|
14
29
|
}
|
|
15
30
|
interface StorageAdapter {
|
|
16
31
|
getItem(key: string): string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, k as RegisterResponse, p as User, j as ProfileUpdateData, n as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, U as UpdateApiKeyRequest, i as MFAStatus, h as MFASetupResponse, M as MFAChallengeData, D as Device, q as UserPreferences, L as LinkedAccount, m as SecurityEvent, P as Pagination } from './types-
|
|
1
|
+
import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, k as RegisterResponse, p as User, j as ProfileUpdateData, n as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, U as UpdateApiKeyRequest, i as MFAStatus, h as MFASetupResponse, M as MFAChallengeData, D as Device, q as UserPreferences, L as LinkedAccount, m as SecurityEvent, P as Pagination } from './types-B70Kgcc_.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Auth Service
|
|
@@ -309,6 +309,16 @@ declare class RoleDeniedError extends Error {
|
|
|
309
309
|
* Type guard to check if an error is a RoleDeniedError.
|
|
310
310
|
*/
|
|
311
311
|
declare function isRoleDeniedError(error: unknown): error is RoleDeniedError;
|
|
312
|
+
/**
|
|
313
|
+
* Error thrown when a login or MFA verification response is neither a
|
|
314
|
+
* recognised MFA challenge nor a success (an access token and a user).
|
|
315
|
+
* The auth state is left untouched, so an unexpected server shape fails
|
|
316
|
+
* closed instead of signing the user in without a token.
|
|
317
|
+
*/
|
|
318
|
+
declare class InvalidAuthResponseError extends Error {
|
|
319
|
+
readonly name = "InvalidAuthResponseError";
|
|
320
|
+
constructor(message?: string);
|
|
321
|
+
}
|
|
312
322
|
|
|
313
323
|
/**
|
|
314
324
|
* User Display Utilities
|
|
@@ -413,4 +423,4 @@ declare function formatUserRoles(user: User | null | undefined, options?: {
|
|
|
413
423
|
capitalize?: boolean;
|
|
414
424
|
}): string;
|
|
415
425
|
|
|
416
|
-
export { AuthService as A, type LoginOptions as L, type MFAVerifyOptions as M, RoleDeniedError as R, authService as a, getDisplayName as b, getGreeting as c, getUserEmail as d, getUserInitials as e, formatUserRoles as f, getAvatarFallback as g, isRoleDeniedError as i };
|
|
426
|
+
export { AuthService as A, InvalidAuthResponseError as I, type LoginOptions as L, type MFAVerifyOptions as M, RoleDeniedError as R, authService as a, getDisplayName as b, getGreeting as c, getUserEmail as d, getUserInitials as e, formatUserRoles as f, getAvatarFallback as g, isRoleDeniedError as i };
|
package/package.json
CHANGED