@classic-homes/auth 1.27.0 → 1.41.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 +41 -0
- package/dist/{auth.svelte-WROD55FW.js → auth.svelte-DSE2NZMP.js} +2 -2
- package/dist/{chunk-JFWZUQGP.js → chunk-5SZT7GKG.js} +2 -2
- package/dist/{chunk-C2UTC5B7.js → chunk-BMCGFHVM.js} +11 -2
- package/dist/{chunk-POOONCPP.js → chunk-HMZRBRIU.js} +1 -1
- package/dist/{chunk-FEKQXUPJ.js → chunk-QLEEAHJJ.js} +121 -13
- package/dist/core/index.d.ts +76 -4
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/svelte/index.d.ts +4 -4
- package/dist/svelte/index.js +4 -4
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +5 -0
- package/dist/{types-D_5ze47z.d.ts → types-DF5Ny2IA.d.ts} +15 -1
- package/dist/{user-utils-Tc6yv4-Q.d.ts → user-utils-B6BNJzF2.d.ts} +23 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,6 +96,24 @@ async function handleLogin(email: string, password: string) {
|
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
Email-MFA accounts get their code by email: send it with the challenge token,
|
|
100
|
+
then verify with `method: 'email'`. A cooldown (one code a minute, five an
|
|
101
|
+
hour) throws `MFAChallengeEmailError` with `retryAfterSeconds`.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { authService, MFAChallengeEmailError } from '@classic-homes/auth';
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const { email } = await authService.sendMFAChallengeEmail(mfaToken); // "jo***@example.com"
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error instanceof MFAChallengeEmailError && error.retryAfterSeconds) {
|
|
110
|
+
// Show "Resend in Ns"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await authService.verifyMFAChallenge({ mfaToken, code, method: 'email' });
|
|
115
|
+
```
|
|
116
|
+
|
|
99
117
|
To disable auto-set auth (for manual control):
|
|
100
118
|
|
|
101
119
|
```typescript
|
|
@@ -148,6 +166,27 @@ function handleSSOLogin(redirectUrl: string) {
|
|
|
148
166
|
}
|
|
149
167
|
```
|
|
150
168
|
|
|
169
|
+
`initiateSSOLogin` also sends a random `client_state`, kept in this tab's
|
|
170
|
+
sessionStorage (single-use, 10-minute TTL). The API echoes it in the callback
|
|
171
|
+
hash (`client_state`, in both the token and the MFA-challenge variants); check
|
|
172
|
+
it **before** storing any tokens from the hash, so a crafted callback URL can't
|
|
173
|
+
sign the user into someone else's account (login CSRF):
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { verifySsoClientState } from '@classic-homes/auth/core';
|
|
177
|
+
|
|
178
|
+
const params = new URLSearchParams(window.location.hash.slice(1));
|
|
179
|
+
const { accepted, result } = verifySsoClientState(params); // 'ok' | 'mismatch' | 'missing'
|
|
180
|
+
if (!accepted) {
|
|
181
|
+
window.history.replaceState({}, '', window.location.pathname);
|
|
182
|
+
throw new Error('This sign-in link was not started from this browser tab. Please sign in again.');
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Set `sso.requireClientState: true` once the API echoing `client_state` is
|
|
187
|
+
deployed to that environment. Until then (the default) every result is
|
|
188
|
+
accepted, so log `result` to watch the rollout.
|
|
189
|
+
|
|
151
190
|
### 5. MFA Challenge Verification
|
|
152
191
|
|
|
153
192
|
```typescript
|
|
@@ -395,6 +434,8 @@ interface AuthConfig {
|
|
|
395
434
|
enabled: boolean;
|
|
396
435
|
provider: string;
|
|
397
436
|
authorizeUrl?: string;
|
|
437
|
+
/** Reject SSO callbacks whose client_state is missing or wrong (default false) */
|
|
438
|
+
requireClientState?: boolean;
|
|
398
439
|
};
|
|
399
440
|
|
|
400
441
|
/** Callback when auth errors occur */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { authActions, authStore, currentUser, isAuthenticated } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { authActions, authStore, currentUser, isAuthenticated } from './chunk-HMZRBRIU.js';
|
|
2
|
+
import './chunk-QLEEAHJJ.js';
|
|
3
3
|
import './chunk-K6TV3ZVS.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { authService } from './chunk-
|
|
2
|
-
import { authStore, currentUser, isAuthenticated, authActions } from './chunk-
|
|
1
|
+
import { authService } from './chunk-BMCGFHVM.js';
|
|
2
|
+
import { authStore, currentUser, isAuthenticated, authActions } from './chunk-HMZRBRIU.js';
|
|
3
3
|
import { isInitialized, initAuth, getUserRoles } from './chunk-K6TV3ZVS.js';
|
|
4
4
|
|
|
5
5
|
// src/svelte/guards/auth-guard.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { authApi, InvalidAuthResponseError, RoleDeniedError } from './chunk-
|
|
1
|
+
import { authApi, InvalidAuthResponseError, RoleDeniedError } from './chunk-QLEEAHJJ.js';
|
|
2
2
|
import { hasAnyRole, getUserRoles } from './chunk-K6TV3ZVS.js';
|
|
3
3
|
|
|
4
4
|
// src/core/guards.ts
|
|
@@ -19,7 +19,7 @@ function getAvailableMethods(response) {
|
|
|
19
19
|
async function setStoreAuth(response) {
|
|
20
20
|
let store;
|
|
21
21
|
try {
|
|
22
|
-
store = (await import('./auth.svelte-
|
|
22
|
+
store = (await import('./auth.svelte-DSE2NZMP.js')).authStore;
|
|
23
23
|
} catch {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
@@ -237,6 +237,15 @@ var AuthService = class {
|
|
|
237
237
|
}
|
|
238
238
|
return response;
|
|
239
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Email a sign-in code for an MFA challenge (accounts with email MFA).
|
|
242
|
+
* Pass the `mfaToken` from the login / SSO challenge; answer with
|
|
243
|
+
* `verifyMFAChallenge({ mfaToken, code, method: 'email' })`.
|
|
244
|
+
* Throws MFAChallengeEmailError — `retryAfterSeconds` is set on a cooldown.
|
|
245
|
+
*/
|
|
246
|
+
async sendMFAChallengeEmail(mfaToken) {
|
|
247
|
+
return await authApi.sendMFAChallengeEmail(mfaToken);
|
|
248
|
+
}
|
|
240
249
|
// ============================================================================
|
|
241
250
|
// Device Management
|
|
242
251
|
// ============================================================================
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InvalidAuthResponseError, authApi } from './chunk-
|
|
1
|
+
import { InvalidAuthResponseError, authApi } from './chunk-QLEEAHJJ.js';
|
|
2
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
|
|
@@ -1,4 +1,72 @@
|
|
|
1
|
-
import {
|
|
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
|
+
}
|
|
2
70
|
|
|
3
71
|
// src/core/errors.ts
|
|
4
72
|
var RoleDeniedError = class _RoleDeniedError extends Error {
|
|
@@ -28,6 +96,17 @@ var InvalidAuthResponseError = class _InvalidAuthResponseError extends Error {
|
|
|
28
96
|
}
|
|
29
97
|
}
|
|
30
98
|
};
|
|
99
|
+
var MFAChallengeEmailError = class _MFAChallengeEmailError extends Error {
|
|
100
|
+
constructor(message, status, retryAfterSeconds) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.name = "MFAChallengeEmailError";
|
|
103
|
+
this.status = status;
|
|
104
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
105
|
+
if (Error.captureStackTrace) {
|
|
106
|
+
Error.captureStackTrace(this, _MFAChallengeEmailError);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
31
110
|
|
|
32
111
|
// src/core/client.ts
|
|
33
112
|
var DEFAULT_REFRESH_RETRY_DELAYS = [1e3, 3e3];
|
|
@@ -59,8 +138,8 @@ function getRefreshToken() {
|
|
|
59
138
|
}
|
|
60
139
|
function getSessionTokenKey() {
|
|
61
140
|
const config = getConfig();
|
|
62
|
-
const
|
|
63
|
-
return `${
|
|
141
|
+
const storageKey2 = config.storageKey ?? "classic_auth";
|
|
142
|
+
return `${storageKey2}_session`;
|
|
64
143
|
}
|
|
65
144
|
function getSessionToken() {
|
|
66
145
|
const storage = getStorage();
|
|
@@ -72,22 +151,22 @@ function updateStoredTokens(accessToken, refreshToken) {
|
|
|
72
151
|
function writeStoredTokens(accessToken, refreshToken) {
|
|
73
152
|
const config = getConfig();
|
|
74
153
|
const storage = getStorage();
|
|
75
|
-
const
|
|
154
|
+
const storageKey2 = config.storageKey ?? "classic_auth";
|
|
76
155
|
let parsed = {};
|
|
77
156
|
try {
|
|
78
|
-
const authData = storage.getItem(
|
|
157
|
+
const authData = storage.getItem(storageKey2);
|
|
79
158
|
if (authData) parsed = JSON.parse(authData);
|
|
80
159
|
} catch {
|
|
81
160
|
}
|
|
82
161
|
parsed.accessToken = accessToken;
|
|
83
162
|
parsed.refreshToken = refreshToken;
|
|
84
|
-
storage.setItem(
|
|
163
|
+
storage.setItem(storageKey2, JSON.stringify(parsed));
|
|
85
164
|
config.onTokenRefresh?.({ accessToken, refreshToken });
|
|
86
165
|
return syncSvelteStore((authStore) => authStore.updateTokens(accessToken, refreshToken));
|
|
87
166
|
}
|
|
88
167
|
async function syncSvelteStore(apply) {
|
|
89
168
|
try {
|
|
90
|
-
const { authStore } = await import('./auth.svelte-
|
|
169
|
+
const { authStore } = await import('./auth.svelte-DSE2NZMP.js');
|
|
91
170
|
apply(authStore);
|
|
92
171
|
} catch {
|
|
93
172
|
}
|
|
@@ -150,8 +229,8 @@ async function refreshOnce(refreshToken) {
|
|
|
150
229
|
function withRefreshLock(fn) {
|
|
151
230
|
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
152
231
|
if (!locks) return fn();
|
|
153
|
-
const
|
|
154
|
-
return locks.request(`${
|
|
232
|
+
const storageKey2 = getConfig().storageKey ?? "classic_auth";
|
|
233
|
+
return locks.request(`${storageKey2}:refresh`, fn);
|
|
155
234
|
}
|
|
156
235
|
function refreshSession(options = {}) {
|
|
157
236
|
if (options.onlyIfExpired) {
|
|
@@ -368,9 +447,9 @@ function initCrossTabSync() {
|
|
|
368
447
|
if (typeof window === "undefined") return () => {
|
|
369
448
|
};
|
|
370
449
|
const config = getConfig();
|
|
371
|
-
const
|
|
450
|
+
const storageKey2 = config.storageKey ?? "classic_auth";
|
|
372
451
|
function handleStorage(event) {
|
|
373
|
-
if (event.key !==
|
|
452
|
+
if (event.key !== storageKey2) return;
|
|
374
453
|
void syncSvelteStore((authStore) => authStore.rehydrate());
|
|
375
454
|
}
|
|
376
455
|
window.addEventListener("storage", handleStorage);
|
|
@@ -481,6 +560,8 @@ var authApi = {
|
|
|
481
560
|
* Initiate SSO login by redirecting to the SSO provider.
|
|
482
561
|
* @param options.callbackUrl - The URL where the SSO provider should redirect after auth
|
|
483
562
|
* @param options.redirectUrl - The final URL to redirect to after processing the callback
|
|
563
|
+
* Also sends a fresh `client_state` (kept in this tab's sessionStorage) that
|
|
564
|
+
* the callback page must check with `verifySsoClientState()`.
|
|
484
565
|
* @throws Error if redirectUrl or callbackUrl fail validation
|
|
485
566
|
*/
|
|
486
567
|
initiateSSOLogin(options) {
|
|
@@ -500,6 +581,10 @@ var authApi = {
|
|
|
500
581
|
if (options?.redirectUrl) {
|
|
501
582
|
params.set("redirect", options.redirectUrl);
|
|
502
583
|
}
|
|
584
|
+
const clientState = createSsoClientState();
|
|
585
|
+
if (clientState) {
|
|
586
|
+
params.set(SSO_CLIENT_STATE_PARAM, clientState);
|
|
587
|
+
}
|
|
503
588
|
const url = params.toString() ? `${authorizeUrl}?${params}` : authorizeUrl;
|
|
504
589
|
window.location.href = url;
|
|
505
590
|
},
|
|
@@ -655,7 +740,9 @@ var authApi = {
|
|
|
655
740
|
const config = getConfig();
|
|
656
741
|
const requestBody = {
|
|
657
742
|
code: data.code,
|
|
658
|
-
|
|
743
|
+
// CHAPI answers the challenge per method: an authenticator code, an
|
|
744
|
+
// emailed sign-in code or a backup code (no method → authenticator).
|
|
745
|
+
type: data.method === "backup" || data.method === "email" ? data.method : "totp",
|
|
659
746
|
trustDevice: data.trustDevice
|
|
660
747
|
};
|
|
661
748
|
const response = await fetchWithTimeout(`${config.baseUrl}/auth/mfa/challenge`, {
|
|
@@ -673,6 +760,27 @@ var authApi = {
|
|
|
673
760
|
const result = await response.json();
|
|
674
761
|
return extractData(result);
|
|
675
762
|
},
|
|
763
|
+
/**
|
|
764
|
+
* Email a sign-in code for an MFA challenge (accounts with email MFA).
|
|
765
|
+
* Authenticated by the challenge token from login / the SSO callback, not
|
|
766
|
+
* a session. Answer it with verifyMFAChallenge({ method: 'email' }).
|
|
767
|
+
* Throws MFAChallengeEmailError (with `retryAfterSeconds` on a cooldown).
|
|
768
|
+
*/
|
|
769
|
+
async sendMFAChallengeEmail(mfaToken) {
|
|
770
|
+
const config = getConfig();
|
|
771
|
+
const response = await fetchWithTimeout(`${config.baseUrl}/auth/mfa/challenge/email/send`, {
|
|
772
|
+
method: "POST",
|
|
773
|
+
headers: { Authorization: `Bearer ${mfaToken}` }
|
|
774
|
+
});
|
|
775
|
+
const body = await response.json().catch(() => ({}));
|
|
776
|
+
if (!response.ok) {
|
|
777
|
+
const details = body?.error?.details;
|
|
778
|
+
const retryAfter = typeof details?.retryAfterSeconds === "number" ? details.retryAfterSeconds : typeof body?.retryAfterSeconds === "number" ? body.retryAfterSeconds : void 0;
|
|
779
|
+
const message = (typeof body?.error === "string" ? body.error : body?.error?.message) || body?.message || "Could not send the verification code";
|
|
780
|
+
throw new MFAChallengeEmailError(message, response.status, retryAfter);
|
|
781
|
+
}
|
|
782
|
+
return extractData(body);
|
|
783
|
+
},
|
|
676
784
|
// ============================================================================
|
|
677
785
|
// Device Management
|
|
678
786
|
// ============================================================================
|
|
@@ -904,4 +1012,4 @@ var authApi = {
|
|
|
904
1012
|
}
|
|
905
1013
|
};
|
|
906
1014
|
|
|
907
|
-
export { InvalidAuthResponseError, RoleDeniedError, api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens };
|
|
1015
|
+
export { InvalidAuthResponseError, MFAChallengeEmailError, 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 };
|
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,
|
|
2
|
-
export { a as AuthConfig, b as AuthState,
|
|
3
|
-
export { A as AuthService, I as InvalidAuthResponseError, L as LoginOptions, M as MFAVerifyOptions, R as RoleDeniedError,
|
|
1
|
+
import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, l as RegisterResponse, q as User, k as ProfileUpdateData, C as ChangePasswordData, o as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, U as UpdateApiKeyRequest, j as MFAStatus, i as MFASetupResponse, M as MFAChallengeData, h as MFAChallengeEmailResponse, D as Device, r as UserPreferences, L as LinkedAccount, n as SecurityEvent, P as Pagination, s as UserRolesResponse } from '../types-DF5Ny2IA.js';
|
|
2
|
+
export { a as AuthConfig, b as AuthState, m as ResetPasswordData, S as SSOConfig, p as StorageAdapter, t as createMemoryStorage, u as createSessionStorage, v as getConfig, w as getDefaultStorage, x as getFetch, y as getStorage, z as initAuth, B as isInitialized, E as resetConfig } from '../types-DF5Ny2IA.js';
|
|
3
|
+
export { A as AuthService, I as InvalidAuthResponseError, L as LoginOptions, M as MFAChallengeEmailError, a as MFAVerifyOptions, R as RoleDeniedError, b as authService, f as formatUserRoles, g as getAvatarFallback, c as getDisplayName, d as getGreeting, e as getUserEmail, h as getUserInitials, i as isRoleDeniedError } from '../user-utils-B6BNJzF2.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
|
|
@@ -194,6 +257,8 @@ declare const authApi: {
|
|
|
194
257
|
* Initiate SSO login by redirecting to the SSO provider.
|
|
195
258
|
* @param options.callbackUrl - The URL where the SSO provider should redirect after auth
|
|
196
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()`.
|
|
197
262
|
* @throws Error if redirectUrl or callbackUrl fail validation
|
|
198
263
|
*/
|
|
199
264
|
initiateSSOLogin(options?: {
|
|
@@ -282,6 +347,13 @@ declare const authApi: {
|
|
|
282
347
|
* Verify MFA challenge during login.
|
|
283
348
|
*/
|
|
284
349
|
verifyMFAChallenge(data: MFAChallengeData): Promise<LoginResponse>;
|
|
350
|
+
/**
|
|
351
|
+
* Email a sign-in code for an MFA challenge (accounts with email MFA).
|
|
352
|
+
* Authenticated by the challenge token from login / the SSO callback, not
|
|
353
|
+
* a session. Answer it with verifyMFAChallenge({ method: 'email' }).
|
|
354
|
+
* Throws MFAChallengeEmailError (with `retryAfterSeconds` on a cooldown).
|
|
355
|
+
*/
|
|
356
|
+
sendMFAChallengeEmail(mfaToken: string): Promise<MFAChallengeEmailResponse>;
|
|
285
357
|
/**
|
|
286
358
|
* Get all devices.
|
|
287
359
|
*/
|
|
@@ -631,4 +703,4 @@ declare function hasAnyRole(user: Pick<User, 'roles'> | null | undefined, roles:
|
|
|
631
703
|
*/
|
|
632
704
|
declare function hasAllRoles(user: Pick<User, 'roles'> | null | undefined, roles: readonly string[]): boolean;
|
|
633
705
|
|
|
634
|
-
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 };
|
|
706
|
+
export { ApiKey, type ApiRequestOptions, type ApiResponse, ChangePasswordData, CreateApiKeyRequest, CreateApiKeyResponse, Device, type JWTPayload, LinkedAccount, LoginCredentials, LoginResponse, LogoutResponse, MFAChallengeData, MFAChallengeEmailResponse, 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, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse } from '../chunk-
|
|
2
|
-
export { InvalidAuthResponseError, RoleDeniedError, api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens } from '../chunk-
|
|
1
|
+
export { AuthService, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse } from '../chunk-BMCGFHVM.js';
|
|
2
|
+
export { InvalidAuthResponseError, MFAChallengeEmailError, 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-QLEEAHJJ.js';
|
|
3
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
|
|
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, I as InvalidAuthResponseError, L as LoginOptions, M as MFAVerifyOptions, R as RoleDeniedError,
|
|
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 MFAChallengeEmailResponse, i as MFASetupResponse, j as MFAStatus, P as Pagination, k as ProfileUpdateData, R as RegisterData, l as RegisterResponse, m as ResetPasswordData, S as SSOConfig, n as SecurityEvent, o as Session, p as StorageAdapter, U as UpdateApiKeyRequest, q as User, r as UserPreferences, s as UserRolesResponse, t as createMemoryStorage, u as createSessionStorage, v as getConfig, w as getDefaultStorage, x as getFetch, y as getStorage, z as initAuth, B as isInitialized, E as resetConfig } from './types-DF5Ny2IA.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 MFAChallengeEmailError, a as MFAVerifyOptions, R as RoleDeniedError, b as authService, f as formatUserRoles, g as getAvatarFallback, c as getDisplayName, d as getGreeting, e as getUserEmail, h as getUserInitials, i as isRoleDeniedError } from './user-utils-B6BNJzF2.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, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse } from './chunk-
|
|
3
|
-
export { authActions, authStore, currentUser, isAuthenticated } from './chunk-
|
|
4
|
-
export { InvalidAuthResponseError, RoleDeniedError, api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens } from './chunk-
|
|
1
|
+
export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from './chunk-5SZT7GKG.js';
|
|
2
|
+
export { AuthService, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse } from './chunk-BMCGFHVM.js';
|
|
3
|
+
export { authActions, authStore, currentUser, isAuthenticated } from './chunk-HMZRBRIU.js';
|
|
4
|
+
export { InvalidAuthResponseError, MFAChallengeEmailError, 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-QLEEAHJJ.js';
|
|
5
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,
|
|
2
|
-
export {
|
|
3
|
-
import {
|
|
4
|
-
export { I as InvalidAuthResponseError, R as RoleDeniedError, f as formatUserRoles, g as getAvatarFallback,
|
|
1
|
+
import { b as AuthState, q as User, a as AuthConfig } from '../types-DF5Ny2IA.js';
|
|
2
|
+
export { z as initAuth, B as isInitialized } from '../types-DF5Ny2IA.js';
|
|
3
|
+
import { b as authService } from '../user-utils-B6BNJzF2.js';
|
|
4
|
+
export { I as InvalidAuthResponseError, R as RoleDeniedError, f as formatUserRoles, g as getAvatarFallback, c as getDisplayName, d as getGreeting, e as getUserEmail, h as getUserInitials, i as isRoleDeniedError } from '../user-utils-B6BNJzF2.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 { formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials } from '../chunk-
|
|
3
|
-
export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-
|
|
4
|
-
export { InvalidAuthResponseError, RoleDeniedError, isRoleDeniedError } from '../chunk-
|
|
1
|
+
export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from '../chunk-5SZT7GKG.js';
|
|
2
|
+
export { formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials } from '../chunk-BMCGFHVM.js';
|
|
3
|
+
export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-HMZRBRIU.js';
|
|
4
|
+
export { InvalidAuthResponseError, RoleDeniedError, isRoleDeniedError } from '../chunk-QLEEAHJJ.js';
|
|
5
5
|
export { initAuth, isInitialized } from '../chunk-K6TV3ZVS.js';
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { q as User, D as Device, f as LoginResponse, n as SecurityEvent, o as Session, A as ApiKey, L as LinkedAccount, g as LogoutResponse, i as MFASetupResponse, j as MFAStatus, l as RegisterResponse, r as UserPreferences, p as StorageAdapter, b as AuthState, a as AuthConfig } from '../types-DF5Ny2IA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* User Fixtures
|
package/dist/testing/index.js
CHANGED
|
@@ -1065,6 +1065,11 @@ var MockFetchInstance = class {
|
|
|
1065
1065
|
path: "/auth/mfa/challenge",
|
|
1066
1066
|
response: { data: mockLoginSuccess }
|
|
1067
1067
|
});
|
|
1068
|
+
this.addRoute({
|
|
1069
|
+
method: "POST",
|
|
1070
|
+
path: "/auth/mfa/challenge/email/send",
|
|
1071
|
+
response: { success: true, email: "te***@example.com" }
|
|
1072
|
+
});
|
|
1068
1073
|
this.addRoute({
|
|
1069
1074
|
method: "DELETE",
|
|
1070
1075
|
path: "/auth/mfa",
|
|
@@ -18,6 +18,14 @@ interface SSOConfig {
|
|
|
18
18
|
* a logout URL anywhere else is dropped.
|
|
19
19
|
*/
|
|
20
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;
|
|
21
29
|
}
|
|
22
30
|
interface StorageAdapter {
|
|
23
31
|
getItem(key: string): string | null;
|
|
@@ -318,6 +326,12 @@ interface MFAChallengeData {
|
|
|
318
326
|
method?: 'totp' | 'email' | 'backup';
|
|
319
327
|
trustDevice?: boolean;
|
|
320
328
|
}
|
|
329
|
+
/** CHAPI's answer to sending an MFA challenge email code. */
|
|
330
|
+
interface MFAChallengeEmailResponse {
|
|
331
|
+
success: boolean;
|
|
332
|
+
/** The address the code went to, masked (`jo***@example.com`). */
|
|
333
|
+
email: string;
|
|
334
|
+
}
|
|
321
335
|
interface Device {
|
|
322
336
|
id: string;
|
|
323
337
|
deviceFingerprint: string;
|
|
@@ -399,4 +413,4 @@ interface ResetPasswordData {
|
|
|
399
413
|
newPassword: string;
|
|
400
414
|
}
|
|
401
415
|
|
|
402
|
-
export { type ApiKey as A,
|
|
416
|
+
export { type ApiKey as A, isInitialized as B, type ChangePasswordData as C, type Device as D, resetConfig as E, type LinkedAccount as L, type MFAChallengeData as M, type Pagination as P, type RegisterData as R, type SSOConfig as S, type UpdateApiKeyRequest as U, type AuthConfig as a, type AuthState as b, type CreateApiKeyRequest as c, type CreateApiKeyResponse as d, type LoginCredentials as e, type LoginResponse as f, type LogoutResponse as g, type MFAChallengeEmailResponse as h, type MFASetupResponse as i, type MFAStatus as j, type ProfileUpdateData as k, type RegisterResponse as l, type ResetPasswordData as m, type SecurityEvent as n, type Session as o, type StorageAdapter as p, type User as q, type UserPreferences as r, type UserRolesResponse as s, createMemoryStorage as t, createSessionStorage as u, getConfig as v, getDefaultStorage as w, getFetch as x, getStorage as y, initAuth as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData,
|
|
1
|
+
import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, l as RegisterResponse, q as User, k as ProfileUpdateData, o as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, U as UpdateApiKeyRequest, j as MFAStatus, i as MFASetupResponse, M as MFAChallengeData, h as MFAChallengeEmailResponse, D as Device, r as UserPreferences, L as LinkedAccount, n as SecurityEvent, P as Pagination } from './types-DF5Ny2IA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Auth Service
|
|
@@ -192,6 +192,13 @@ declare class AuthService {
|
|
|
192
192
|
* @param options - Optional settings for verification behavior
|
|
193
193
|
*/
|
|
194
194
|
verifyMFAChallenge(data: MFAChallengeData, options?: MFAVerifyOptions): Promise<LoginResponse>;
|
|
195
|
+
/**
|
|
196
|
+
* Email a sign-in code for an MFA challenge (accounts with email MFA).
|
|
197
|
+
* Pass the `mfaToken` from the login / SSO challenge; answer with
|
|
198
|
+
* `verifyMFAChallenge({ mfaToken, code, method: 'email' })`.
|
|
199
|
+
* Throws MFAChallengeEmailError — `retryAfterSeconds` is set on a cooldown.
|
|
200
|
+
*/
|
|
201
|
+
sendMFAChallengeEmail(mfaToken: string): Promise<MFAChallengeEmailResponse>;
|
|
195
202
|
/**
|
|
196
203
|
* Get all devices.
|
|
197
204
|
*/
|
|
@@ -319,6 +326,20 @@ declare class InvalidAuthResponseError extends Error {
|
|
|
319
326
|
readonly name = "InvalidAuthResponseError";
|
|
320
327
|
constructor(message?: string);
|
|
321
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Error thrown when CHAPI won't send an MFA challenge email code: the
|
|
331
|
+
* challenge token is invalid or expired (401), the account has no email MFA
|
|
332
|
+
* (400), or a code was requested too soon / too often (429). On a 429,
|
|
333
|
+
* `retryAfterSeconds` says how long to wait when the server gave one.
|
|
334
|
+
*/
|
|
335
|
+
declare class MFAChallengeEmailError extends Error {
|
|
336
|
+
readonly name = "MFAChallengeEmailError";
|
|
337
|
+
/** HTTP status CHAPI answered with. */
|
|
338
|
+
readonly status: number;
|
|
339
|
+
/** Seconds until another code may be requested (cooldown 429s only). */
|
|
340
|
+
readonly retryAfterSeconds?: number;
|
|
341
|
+
constructor(message: string, status: number, retryAfterSeconds?: number);
|
|
342
|
+
}
|
|
322
343
|
|
|
323
344
|
/**
|
|
324
345
|
* User Display Utilities
|
|
@@ -423,4 +444,4 @@ declare function formatUserRoles(user: User | null | undefined, options?: {
|
|
|
423
444
|
capitalize?: boolean;
|
|
424
445
|
}): string;
|
|
425
446
|
|
|
426
|
-
export { AuthService as A, InvalidAuthResponseError as I, type LoginOptions as L,
|
|
447
|
+
export { AuthService as A, InvalidAuthResponseError as I, type LoginOptions as L, MFAChallengeEmailError as M, RoleDeniedError as R, type MFAVerifyOptions as a, authService as b, getDisplayName as c, getGreeting as d, getUserEmail as e, formatUserRoles as f, getAvatarFallback as g, getUserInitials as h, isRoleDeniedError as i };
|