@classic-homes/auth 1.27.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 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,3 +1,3 @@
1
- export { authActions, authStore, currentUser, isAuthenticated } from './chunk-POOONCPP.js';
2
- import './chunk-FEKQXUPJ.js';
1
+ export { authActions, authStore, currentUser, isAuthenticated } from './chunk-S3DNV7VV.js';
2
+ import './chunk-GBYHYVXX.js';
3
3
  import './chunk-K6TV3ZVS.js';
@@ -1,4 +1,72 @@
1
- import { getUserRoles, getConfig, getStorage, isTokenExpired, getFetch, isValidRedirectUrl } from './chunk-K6TV3ZVS.js';
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 {
@@ -59,8 +127,8 @@ function getRefreshToken() {
59
127
  }
60
128
  function getSessionTokenKey() {
61
129
  const config = getConfig();
62
- const storageKey = config.storageKey ?? "classic_auth";
63
- return `${storageKey}_session`;
130
+ const storageKey2 = config.storageKey ?? "classic_auth";
131
+ return `${storageKey2}_session`;
64
132
  }
65
133
  function getSessionToken() {
66
134
  const storage = getStorage();
@@ -72,22 +140,22 @@ function updateStoredTokens(accessToken, refreshToken) {
72
140
  function writeStoredTokens(accessToken, refreshToken) {
73
141
  const config = getConfig();
74
142
  const storage = getStorage();
75
- const storageKey = config.storageKey ?? "classic_auth";
143
+ const storageKey2 = config.storageKey ?? "classic_auth";
76
144
  let parsed = {};
77
145
  try {
78
- const authData = storage.getItem(storageKey);
146
+ const authData = storage.getItem(storageKey2);
79
147
  if (authData) parsed = JSON.parse(authData);
80
148
  } catch {
81
149
  }
82
150
  parsed.accessToken = accessToken;
83
151
  parsed.refreshToken = refreshToken;
84
- storage.setItem(storageKey, JSON.stringify(parsed));
152
+ storage.setItem(storageKey2, JSON.stringify(parsed));
85
153
  config.onTokenRefresh?.({ accessToken, refreshToken });
86
154
  return syncSvelteStore((authStore) => authStore.updateTokens(accessToken, refreshToken));
87
155
  }
88
156
  async function syncSvelteStore(apply) {
89
157
  try {
90
- const { authStore } = await import('./auth.svelte-WROD55FW.js');
158
+ const { authStore } = await import('./auth.svelte-JLSRQJAC.js');
91
159
  apply(authStore);
92
160
  } catch {
93
161
  }
@@ -150,8 +218,8 @@ async function refreshOnce(refreshToken) {
150
218
  function withRefreshLock(fn) {
151
219
  const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
152
220
  if (!locks) return fn();
153
- const storageKey = getConfig().storageKey ?? "classic_auth";
154
- return locks.request(`${storageKey}:refresh`, fn);
221
+ const storageKey2 = getConfig().storageKey ?? "classic_auth";
222
+ return locks.request(`${storageKey2}:refresh`, fn);
155
223
  }
156
224
  function refreshSession(options = {}) {
157
225
  if (options.onlyIfExpired) {
@@ -368,9 +436,9 @@ function initCrossTabSync() {
368
436
  if (typeof window === "undefined") return () => {
369
437
  };
370
438
  const config = getConfig();
371
- const storageKey = config.storageKey ?? "classic_auth";
439
+ const storageKey2 = config.storageKey ?? "classic_auth";
372
440
  function handleStorage(event) {
373
- if (event.key !== storageKey) return;
441
+ if (event.key !== storageKey2) return;
374
442
  void syncSvelteStore((authStore) => authStore.rehydrate());
375
443
  }
376
444
  window.addEventListener("storage", handleStorage);
@@ -481,6 +549,8 @@ var authApi = {
481
549
  * Initiate SSO login by redirecting to the SSO provider.
482
550
  * @param options.callbackUrl - The URL where the SSO provider should redirect after auth
483
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()`.
484
554
  * @throws Error if redirectUrl or callbackUrl fail validation
485
555
  */
486
556
  initiateSSOLogin(options) {
@@ -500,6 +570,10 @@ var authApi = {
500
570
  if (options?.redirectUrl) {
501
571
  params.set("redirect", options.redirectUrl);
502
572
  }
573
+ const clientState = createSsoClientState();
574
+ if (clientState) {
575
+ params.set(SSO_CLIENT_STATE_PARAM, clientState);
576
+ }
503
577
  const url = params.toString() ? `${authorizeUrl}?${params}` : authorizeUrl;
504
578
  window.location.href = url;
505
579
  },
@@ -904,4 +978,4 @@ var authApi = {
904
978
  }
905
979
  };
906
980
 
907
- export { InvalidAuthResponseError, RoleDeniedError, api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, initCrossTabSync, isRoleDeniedError, 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 };
@@ -1,4 +1,4 @@
1
- import { InvalidAuthResponseError, authApi } from './chunk-FEKQXUPJ.js';
1
+ import { InvalidAuthResponseError, authApi } from './chunk-GBYHYVXX.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,4 @@
1
- import { authApi, InvalidAuthResponseError, RoleDeniedError } from './chunk-FEKQXUPJ.js';
1
+ import { authApi, InvalidAuthResponseError, RoleDeniedError } from './chunk-GBYHYVXX.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-WROD55FW.js')).authStore;
22
+ store = (await import('./auth.svelte-JLSRQJAC.js')).authStore;
23
23
  } catch {
24
24
  return;
25
25
  }
@@ -1,5 +1,5 @@
1
- import { authService } from './chunk-C2UTC5B7.js';
2
- import { authStore, currentUser, isAuthenticated, authActions } from './chunk-POOONCPP.js';
1
+ import { authService } from './chunk-TUWTOMZZ.js';
2
+ import { authStore, currentUser, isAuthenticated, authActions } from './chunk-S3DNV7VV.js';
3
3
  import { isInitialized, initAuth, getUserRoles } from './chunk-K6TV3ZVS.js';
4
4
 
5
5
  // src/svelte/guards/auth-guard.ts
@@ -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-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';
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
@@ -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?: {
@@ -631,4 +696,4 @@ declare function hasAnyRole(user: Pick<User, 'roles'> | null | undefined, roles:
631
696
  */
632
697
  declare function hasAllRoles(user: Pick<User, 'roles'> | null | undefined, roles: readonly string[]): boolean;
633
698
 
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 };
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 };
@@ -1,3 +1,3 @@
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';
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
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-D_5ze47z.js';
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, 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';
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-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';
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
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-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';
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
@@ -1,5 +1,5 @@
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';
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
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-D_5ze47z.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-B70Kgcc_.js';
2
2
 
3
3
  /**
4
4
  * User Fixtures
@@ -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;
@@ -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-D_5ze47z.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-B70Kgcc_.js';
2
2
 
3
3
  /**
4
4
  * Auth Service
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/auth",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "description": "Authentication services and Svelte bindings for Classic Theme apps",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",