@classic-homes/auth 1.23.0 → 1.27.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.
@@ -0,0 +1,3 @@
1
+ export { authActions, authStore, currentUser, isAuthenticated } from './chunk-POOONCPP.js';
2
+ import './chunk-FEKQXUPJ.js';
3
+ import './chunk-K6TV3ZVS.js';
@@ -1,12 +1,12 @@
1
- import { authApi } from './chunk-ETKDIPD2.js';
2
- import { getUserRoles, hasAnyRole } from './chunk-2CYGQUUY.js';
1
+ import { authApi, InvalidAuthResponseError, RoleDeniedError } from './chunk-FEKQXUPJ.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 "accessToken" in response && "user" in response && !isMfaChallengeResponse(response);
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-WROD55FW.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 (options?.allowedRoles?.length && !isMfaChallengeResponse(response)) {
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 && !isMfaChallengeResponse(response)) {
61
- try {
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
- try {
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, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError };
390
+ export { AuthService, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse };
@@ -1,4 +1,33 @@
1
- import { getConfig, getStorage, isTokenExpired, getFetch, isValidRedirectUrl } from './chunk-2CYGQUUY.js';
1
+ import { getUserRoles, getConfig, getStorage, isTokenExpired, getFetch, isValidRedirectUrl } from './chunk-K6TV3ZVS.js';
2
+
3
+ // src/core/errors.ts
4
+ var RoleDeniedError = class _RoleDeniedError extends Error {
5
+ constructor(user, requiredRoles, redirectTo) {
6
+ const userRoles = getUserRoles(user).join(", ") || "none";
7
+ super(
8
+ `User "${user.username}" does not have required role(s). Has: [${userRoles}], Required: [${requiredRoles.join(", ")}]`
9
+ );
10
+ this.name = "RoleDeniedError";
11
+ this.user = user;
12
+ this.requiredRoles = requiredRoles;
13
+ this.redirectTo = redirectTo;
14
+ if (Error.captureStackTrace) {
15
+ Error.captureStackTrace(this, _RoleDeniedError);
16
+ }
17
+ }
18
+ };
19
+ function isRoleDeniedError(error) {
20
+ return error instanceof RoleDeniedError || error?.name === "RoleDeniedError";
21
+ }
22
+ var InvalidAuthResponseError = class _InvalidAuthResponseError extends Error {
23
+ constructor(message = "Unexpected authentication response: no access token or user") {
24
+ super(message);
25
+ this.name = "InvalidAuthResponseError";
26
+ if (Error.captureStackTrace) {
27
+ Error.captureStackTrace(this, _InvalidAuthResponseError);
28
+ }
29
+ }
30
+ };
2
31
 
3
32
  // src/core/client.ts
4
33
  var DEFAULT_REFRESH_RETRY_DELAYS = [1e3, 3e3];
@@ -58,7 +87,7 @@ function writeStoredTokens(accessToken, refreshToken) {
58
87
  }
59
88
  async function syncSvelteStore(apply) {
60
89
  try {
61
- const { authStore } = await import('./auth.svelte-T4LKTTDZ.js');
90
+ const { authStore } = await import('./auth.svelte-WROD55FW.js');
62
91
  apply(authStore);
63
92
  } catch {
64
93
  }
@@ -197,14 +226,41 @@ function extractData(response) {
197
226
  }
198
227
  return response;
199
228
  }
229
+ function isApiOrigin(url, baseUrl) {
230
+ try {
231
+ const page = typeof location !== "undefined" ? location.href : void 0;
232
+ return new URL(url).origin === new URL(baseUrl, page).origin;
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
200
237
  function apiRequest(endpoint, options = {}) {
201
238
  return apiRequestInternal(endpoint, options, false);
202
239
  }
203
240
  async function apiRequestInternal(endpoint, options, isRetry) {
204
- const { authenticated = false, customFetch, body, timeout, signal, ...fetchOptions } = options;
241
+ const {
242
+ authenticated: wantsAuth = false,
243
+ customFetch,
244
+ body,
245
+ timeout,
246
+ signal,
247
+ noAuthRetry = false,
248
+ ...fetchOptions
249
+ } = options;
205
250
  const config = getConfig();
206
251
  const fetchFn = customFetch ?? getFetch();
207
- const url = endpoint.startsWith("http") ? endpoint : `${config.baseUrl}${endpoint}`;
252
+ const isAbsolute = /^https?:\/\//i.test(endpoint);
253
+ const url = isAbsolute ? endpoint : `${config.baseUrl}${endpoint}`;
254
+ const authenticated = wantsAuth && (!isAbsolute || isApiOrigin(url, config.baseUrl));
255
+ if (authenticated && noAuthRetry && !isRetry && typeof window !== "undefined") {
256
+ const accessToken = getAccessToken();
257
+ if (accessToken && isTokenExpired(accessToken)) {
258
+ const outcome = await refreshSession({ onlyIfExpired: true });
259
+ if (outcome === "rejected") {
260
+ throw new Error("Authentication failed. Please sign in again.");
261
+ }
262
+ }
263
+ }
208
264
  const timeoutMs = timeout ?? config.requestTimeout ?? 3e4;
209
265
  const controller = new AbortController();
210
266
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
@@ -238,7 +294,7 @@ async function apiRequestInternal(endpoint, options, isRetry) {
238
294
  signal: controller.signal
239
295
  });
240
296
  clearTimeout(timeoutId);
241
- if (response.status === 401 && authenticated && typeof window !== "undefined") {
297
+ if (response.status === 401 && authenticated && !noAuthRetry && typeof window !== "undefined") {
242
298
  if (isRetry) {
243
299
  await expireSession();
244
300
  throw new Error("Authentication failed. Please sign in again.");
@@ -468,7 +524,12 @@ var authApi = {
468
524
  * Change the current user's password.
469
525
  */
470
526
  async changePassword(data) {
471
- await api.put("/auth/change-password", data, true);
527
+ await apiRequest("/auth/change-password", {
528
+ method: "PUT",
529
+ body: data,
530
+ authenticated: true,
531
+ noAuthRetry: true
532
+ });
472
533
  },
473
534
  // ============================================================================
474
535
  // Email Verification
@@ -570,16 +631,20 @@ var authApi = {
570
631
  * Disable MFA.
571
632
  */
572
633
  async disableMFA(password) {
573
- await api.delete("/auth/mfa", true, void 0, { password });
634
+ await apiRequest("/auth/mfa", {
635
+ method: "DELETE",
636
+ body: { password },
637
+ authenticated: true,
638
+ noAuthRetry: true
639
+ });
574
640
  },
575
641
  /**
576
642
  * Regenerate MFA backup codes.
577
643
  */
578
644
  async regenerateBackupCodes(password) {
579
- const response = await api.post(
645
+ const response = await apiRequest(
580
646
  "/auth/mfa/regenerate-backup-codes",
581
- { password },
582
- true
647
+ { method: "POST", body: { password }, authenticated: true, noAuthRetry: true }
583
648
  );
584
649
  return extractData(response);
585
650
  },
@@ -707,7 +772,12 @@ var authApi = {
707
772
  * Unlink an SSO account.
708
773
  */
709
774
  async unlinkSSOAccount(provider, password) {
710
- await api.delete("/auth/sso/unlink", true, void 0, { provider, password });
775
+ await apiRequest("/auth/sso/unlink", {
776
+ method: "DELETE",
777
+ body: { provider, password },
778
+ authenticated: true,
779
+ noAuthRetry: true
780
+ });
711
781
  },
712
782
  /**
713
783
  * Link an SSO account.
@@ -834,4 +904,4 @@ var authApi = {
834
904
  }
835
905
  };
836
906
 
837
- export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, refreshSession, updateStoredTokens };
907
+ export { InvalidAuthResponseError, RoleDeniedError, api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens };
@@ -1,6 +1,6 @@
1
- import { authService } from './chunk-DFCBWKCU.js';
2
- import { authStore, currentUser, isAuthenticated, authActions } from './chunk-KSOYQYUZ.js';
3
- import { isInitialized, initAuth, getUserRoles } from './chunk-2CYGQUUY.js';
1
+ import { authService } from './chunk-C2UTC5B7.js';
2
+ import { authStore, currentUser, isAuthenticated, authActions } from './chunk-POOONCPP.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
- const { pathname } = event.url;
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
  }
@@ -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
- const sentinel = "https://relative.invalid";
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-ETKDIPD2.js';
2
- import { isInitialized, getConfig, hasRole, hasAnyRole, hasAllRoles, decodeJWT, getStorage, getDefaultStorage } from './chunk-2CYGQUUY.js';
1
+ import { InvalidAuthResponseError, authApi } from './chunk-FEKQXUPJ.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
- return { ssoLogoutUrl: response.logoutUrl };
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,6 +1,6 @@
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-Bqh2gkvu.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-Bqh2gkvu.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-92PeSjcw.js';
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-D_5ze47z.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-D_5ze47z.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-Tc6yv4-Q.js';
4
4
 
5
5
  /**
6
6
  * HTTP Client
@@ -19,6 +19,14 @@ interface ApiRequestOptions extends Omit<RequestInit, 'body' | 'signal'> {
19
19
  timeout?: number;
20
20
  /** AbortSignal for manual cancellation */
21
21
  signal?: AbortSignal;
22
+ /**
23
+ * Treat a 401 as an ordinary request error: no token refresh, no retry
24
+ * and no session expiry. For endpoints where a 401 means a wrong password
25
+ * rather than a dead token (change password, disable MFA, ...), so a typo
26
+ * does not sign the user out. An already-expired access token is still
27
+ * refreshed before the request is sent.
28
+ */
29
+ noAuthRetry?: boolean;
22
30
  }
23
31
  /**
24
32
  * The CHAPI envelope shape, for endpoints that wrap their payload in `data`.
@@ -389,7 +397,9 @@ declare const authApi: {
389
397
  declare function isMfaChallengeResponse(response: LoginResponse): boolean;
390
398
  /**
391
399
  * Check if a login response is a successful login (with tokens and user).
392
- * Returns true if the response contains valid authentication data.
400
+ * Returns true only for a non-empty access token plus a user object, so an
401
+ * MFA challenge in a shape isMfaChallengeResponse doesn't recognise is never
402
+ * mistaken for a login.
393
403
  */
394
404
  declare function isLoginSuccessResponse(response: LoginResponse): boolean;
395
405
  /**
@@ -1,3 +1,3 @@
1
- export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from '../chunk-DFCBWKCU.js';
2
- export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, refreshSession, updateStoredTokens } from '../chunk-ETKDIPD2.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-2CYGQUUY.js';
1
+ export { AuthService, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse } from '../chunk-C2UTC5B7.js';
2
+ export { InvalidAuthResponseError, RoleDeniedError, api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens } from '../chunk-FEKQXUPJ.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-Bqh2gkvu.js';
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-D_5ze47z.js';
2
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-92PeSjcw.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-Tc6yv4-Q.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-NLG6NSE4.js';
2
- export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from './chunk-DFCBWKCU.js';
3
- export { authActions, authStore, currentUser, isAuthenticated } from './chunk-KSOYQYUZ.js';
4
- export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, refreshSession, updateStoredTokens } from './chunk-ETKDIPD2.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-2CYGQUUY.js';
1
+ export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from './chunk-JFWZUQGP.js';
2
+ export { AuthService, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse } from './chunk-C2UTC5B7.js';
3
+ export { authActions, authStore, currentUser, isAuthenticated } from './chunk-POOONCPP.js';
4
+ export { InvalidAuthResponseError, RoleDeniedError, api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, refreshSession, updateStoredTokens } from './chunk-FEKQXUPJ.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';
@@ -1,7 +1,7 @@
1
- import { b as AuthState, p as User, a as AuthConfig } from '../types-Bqh2gkvu.js';
2
- export { y as initAuth, z as isInitialized } from '../types-Bqh2gkvu.js';
3
- import { a as authService } from '../user-utils-92PeSjcw.js';
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-92PeSjcw.js';
1
+ import { b as AuthState, p as User, a as AuthConfig } from '../types-D_5ze47z.js';
2
+ export { y as initAuth, z as isInitialized } from '../types-D_5ze47z.js';
3
+ import { a as authService } from '../user-utils-Tc6yv4-Q.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-Tc6yv4-Q.js';
5
5
 
6
6
  /**
7
7
  * Auth Store
@@ -1,5 +1,5 @@
1
- export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from '../chunk-NLG6NSE4.js';
2
- export { RoleDeniedError, formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials, isRoleDeniedError } from '../chunk-DFCBWKCU.js';
3
- export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-KSOYQYUZ.js';
4
- import '../chunk-ETKDIPD2.js';
5
- export { initAuth, isInitialized } from '../chunk-2CYGQUUY.js';
1
+ export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from '../chunk-JFWZUQGP.js';
2
+ export { formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials } from '../chunk-C2UTC5B7.js';
3
+ export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-POOONCPP.js';
4
+ export { InvalidAuthResponseError, RoleDeniedError, isRoleDeniedError } from '../chunk-FEKQXUPJ.js';
5
+ export { initAuth, isInitialized } from '../chunk-K6TV3ZVS.js';
@@ -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-Bqh2gkvu.js';
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-D_5ze47z.js';
2
2
 
3
3
  /**
4
4
  * User Fixtures
@@ -1,4 +1,4 @@
1
- import { hasRole, hasAnyRole, hasAllRoles, initAuth, resetConfig, getUserRoles, isTokenExpired, decodeJWT } from '../chunk-2CYGQUUY.js';
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,13 @@ 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[];
14
21
  }
15
22
  interface StorageAdapter {
16
23
  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-Bqh2gkvu.js';
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-D_5ze47z.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/auth",
3
- "version": "1.23.0",
3
+ "version": "1.27.0",
4
4
  "description": "Authentication services and Svelte bindings for Classic Theme apps",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,3 +0,0 @@
1
- export { authActions, authStore, currentUser, isAuthenticated } from './chunk-KSOYQYUZ.js';
2
- import './chunk-ETKDIPD2.js';
3
- import './chunk-2CYGQUUY.js';