@classic-homes/auth 0.1.55 → 0.1.56

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
@@ -37,6 +37,12 @@ initAuth({
37
37
  setItem: (key, value) => localStorage.setItem(key, value),
38
38
  removeItem: (key) => localStorage.removeItem(key),
39
39
  },
40
+ // ⚠️ SECURITY: localStorage (also the default when `storage` is omitted)
41
+ // persists the long-lived refresh token where any XSS can read it.
42
+ // Safer options exported from '@classic-homes/auth/core':
43
+ // storage: createSessionStorage() // per-tab, cleared on close
44
+ // storage: createMemoryStorage() // never persisted, lost on reload
45
+ // or keep tokens out of JS entirely with an httpOnly-cookie flow.
40
46
  // SSO configuration (optional)
41
47
  sso: {
42
48
  enabled: true,
@@ -0,0 +1,3 @@
1
+ export { authActions, authStore, currentUser, isAuthenticated } from './chunk-CPFM7JEI.js';
2
+ import './chunk-2QYFVJEM.js';
3
+ import './chunk-6NTZVDLW.js';
@@ -1,4 +1,4 @@
1
- import { getConfig, getStorage, getFetch, isValidRedirectUrl } from './chunk-EM7PUNZB.js';
1
+ import { getConfig, getStorage, getFetch, isValidRedirectUrl } from './chunk-6NTZVDLW.js';
2
2
 
3
3
  // src/core/client.ts
4
4
  var isRefreshing = false;
@@ -70,9 +70,8 @@ function updateStoredTokens(accessToken, refreshToken) {
70
70
  }
71
71
  parsed.accessToken = accessToken;
72
72
  parsed.refreshToken = refreshToken;
73
- parsed._version = (parsed._version || 0) + 1;
74
73
  storage.setItem(storageKey, JSON.stringify(parsed));
75
- import('./auth.svelte-BBLX2MKB.js').then(({ authStore }) => {
74
+ import('./auth.svelte-WPCYPHEE.js').then(({ authStore }) => {
76
75
  authStore.updateTokens(accessToken, refreshToken);
77
76
  }).catch(() => {
78
77
  });
@@ -91,13 +90,17 @@ async function refreshAccessToken() {
91
90
  if (!refreshToken) {
92
91
  return false;
93
92
  }
93
+ const timeoutMs = config.requestTimeout ?? 3e4;
94
+ const controller = new AbortController();
95
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
94
96
  try {
95
97
  const response = await fetchFn(`${config.baseUrl}/auth/refresh`, {
96
98
  method: "POST",
97
99
  headers: {
98
100
  "Content-Type": "application/json"
99
101
  },
100
- body: JSON.stringify({ refreshToken })
102
+ body: JSON.stringify({ refreshToken }),
103
+ signal: controller.signal
101
104
  });
102
105
  if (response.ok) {
103
106
  let apiResponse;
@@ -126,6 +129,8 @@ async function refreshAccessToken() {
126
129
  }
127
130
  } catch (error) {
128
131
  config.onAuthError?.(error instanceof Error ? error : new Error("Token refresh failed"));
132
+ } finally {
133
+ clearTimeout(timeoutId);
129
134
  }
130
135
  return false;
131
136
  }
@@ -141,7 +146,10 @@ function extractData(response) {
141
146
  }
142
147
  return response;
143
148
  }
144
- async function apiRequest(endpoint, options = {}) {
149
+ function apiRequest(endpoint, options = {}) {
150
+ return apiRequestInternal(endpoint, options, false);
151
+ }
152
+ async function apiRequestInternal(endpoint, options, isRetry) {
145
153
  const { authenticated = false, customFetch, body, timeout, signal, ...fetchOptions } = options;
146
154
  const config = getConfig();
147
155
  const fetchFn = customFetch ?? getFetch();
@@ -179,12 +187,21 @@ async function apiRequest(endpoint, options = {}) {
179
187
  });
180
188
  clearTimeout(timeoutId);
181
189
  if (response.status === 401 && authenticated && typeof window !== "undefined") {
190
+ if (isRetry) {
191
+ clearStoredAuth();
192
+ config.onLogout?.();
193
+ throw new Error("Authentication failed. Please sign in again.");
194
+ }
182
195
  if (!isRefreshing) {
183
196
  isRefreshing = true;
184
- const refreshed = await refreshAccessToken();
185
- isRefreshing = false;
197
+ let refreshed = false;
198
+ try {
199
+ refreshed = await refreshAccessToken();
200
+ } finally {
201
+ isRefreshing = false;
202
+ }
186
203
  if (refreshed) {
187
- return apiRequest(endpoint, options);
204
+ return apiRequestInternal(endpoint, options, true);
188
205
  } else {
189
206
  clearStoredAuth();
190
207
  config.onLogout?.();
@@ -194,21 +211,29 @@ async function apiRequest(endpoint, options = {}) {
194
211
  const REFRESH_TIMEOUT_MS = 3e4;
195
212
  return new Promise((resolve, reject) => {
196
213
  let settled = false;
197
- let unsubscribe = null;
198
- const timeoutId2 = setTimeout(() => {
214
+ const refreshTimeoutId = setTimeout(() => {
199
215
  if (!settled) {
200
216
  settled = true;
201
217
  unsubscribe?.();
202
218
  reject(new Error("Token refresh timed out. Please sign in again."));
203
219
  }
204
220
  }, REFRESH_TIMEOUT_MS);
205
- unsubscribe = subscribeTokenRefresh(() => {
221
+ let unsubscribe = null;
222
+ try {
223
+ unsubscribe = subscribeTokenRefresh(() => {
224
+ if (!settled) {
225
+ settled = true;
226
+ clearTimeout(refreshTimeoutId);
227
+ apiRequestInternal(endpoint, options, true).then(resolve).catch(reject);
228
+ }
229
+ });
230
+ } catch (subscribeError) {
231
+ clearTimeout(refreshTimeoutId);
206
232
  if (!settled) {
207
233
  settled = true;
208
- clearTimeout(timeoutId2);
209
- apiRequest(endpoint, options).then(resolve).catch(reject);
234
+ reject(subscribeError);
210
235
  }
211
- });
236
+ }
212
237
  });
213
238
  }
214
239
  }
@@ -294,6 +319,23 @@ var api = {
294
319
  };
295
320
 
296
321
  // src/core/api.ts
322
+ async function fetchWithTimeout(url, init) {
323
+ const config = getConfig();
324
+ const fetchFn = config.fetch ?? fetch;
325
+ const timeoutMs = config.requestTimeout ?? 3e4;
326
+ const controller = new AbortController();
327
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
328
+ try {
329
+ return await fetchFn(url, { ...init, signal: controller.signal });
330
+ } catch (error) {
331
+ if (error instanceof DOMException && error.name === "AbortError") {
332
+ throw new Error(`Request timed out after ${timeoutMs}ms`);
333
+ }
334
+ throw error;
335
+ } finally {
336
+ clearTimeout(timeoutId);
337
+ }
338
+ }
297
339
  var authApi = {
298
340
  // ============================================================================
299
341
  // Authentication
@@ -318,8 +360,9 @@ var authApi = {
318
360
  return extractData(response);
319
361
  } catch (error) {
320
362
  const config = getConfig();
321
- config.onAuthError?.(error instanceof Error ? error : new Error("Logout API call failed"));
322
- return { success: true };
363
+ const err = error instanceof Error ? error : new Error("Logout API call failed");
364
+ config.onAuthError?.(err);
365
+ return { success: true, serverError: err.message };
323
366
  }
324
367
  },
325
368
  /**
@@ -515,13 +558,12 @@ var authApi = {
515
558
  */
516
559
  async verifyMFAChallenge(data) {
517
560
  const config = getConfig();
518
- const fetchFn = config.fetch ?? fetch;
519
561
  const requestBody = {
520
562
  code: data.code,
521
563
  type: data.method === "backup" ? "backup" : "totp",
522
564
  trustDevice: data.trustDevice
523
565
  };
524
- const response = await fetchFn(`${config.baseUrl}/auth/mfa/challenge`, {
566
+ const response = await fetchWithTimeout(`${config.baseUrl}/auth/mfa/challenge`, {
525
567
  method: "POST",
526
568
  headers: {
527
569
  "Content-Type": "application/json",
@@ -650,8 +692,7 @@ var authApi = {
650
692
  throw new Error("Not authenticated");
651
693
  }
652
694
  const config = getConfig();
653
- const fetchFn = config.fetch ?? fetch;
654
- const response = await fetchFn(`${config.baseUrl}/auth/sso/link`, {
695
+ const response = await fetchWithTimeout(`${config.baseUrl}/auth/sso/link`, {
655
696
  method: "POST",
656
697
  headers: {
657
698
  "Content-Type": "application/json",
@@ -36,6 +36,34 @@ function getDefaultStorage() {
36
36
  }
37
37
  };
38
38
  }
39
+ function createSessionStorage() {
40
+ if (typeof window !== "undefined" && window.sessionStorage) {
41
+ return {
42
+ getItem: (key) => sessionStorage.getItem(key),
43
+ setItem: (key, value) => sessionStorage.setItem(key, value),
44
+ removeItem: (key) => sessionStorage.removeItem(key)
45
+ };
46
+ }
47
+ return {
48
+ getItem: () => null,
49
+ setItem: () => {
50
+ },
51
+ removeItem: () => {
52
+ }
53
+ };
54
+ }
55
+ function createMemoryStorage() {
56
+ const store = /* @__PURE__ */ new Map();
57
+ return {
58
+ getItem: (key) => store.get(key) ?? null,
59
+ setItem: (key, value) => {
60
+ store.set(key, value);
61
+ },
62
+ removeItem: (key) => {
63
+ store.delete(key);
64
+ }
65
+ };
66
+ }
39
67
  function getStorage() {
40
68
  const cfg = getConfig();
41
69
  return cfg.storage ?? getDefaultStorage();
@@ -91,7 +119,8 @@ function isTokenExpired(token) {
91
119
  if (!payload || !payload.exp) {
92
120
  return true;
93
121
  }
94
- return payload.exp * 1e3 < Date.now();
122
+ const CLOCK_SKEW_MS = 3e4;
123
+ return payload.exp * 1e3 < Date.now() - CLOCK_SKEW_MS;
95
124
  }
96
125
  function getTokenRemainingTime(token) {
97
126
  const payload = decodeJWT(token);
@@ -122,4 +151,4 @@ function extractClaims(token, claims) {
122
151
  return result;
123
152
  }
124
153
 
125
- export { decodeJWT, extractClaims, getConfig, getDefaultStorage, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, initAuth, isInitialized, isTokenExpired, isValidRedirectUrl, resetConfig };
154
+ export { createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, initAuth, isInitialized, isTokenExpired, isValidRedirectUrl, resetConfig };
@@ -1,5 +1,5 @@
1
- import { authApi } from './chunk-GRQL5FSH.js';
2
- import { decodeJWT, isInitialized, getConfig, getStorage, getDefaultStorage } from './chunk-EM7PUNZB.js';
1
+ import { authApi } from './chunk-2QYFVJEM.js';
2
+ import { decodeJWT, isInitialized, getConfig, getStorage, getDefaultStorage } from './chunk-6NTZVDLW.js';
3
3
 
4
4
  // src/svelte/stores/auth.svelte.ts
5
5
  function getStorageKey() {
@@ -1,4 +1,4 @@
1
- import { authApi } from './chunk-GRQL5FSH.js';
1
+ import { authApi } from './chunk-2QYFVJEM.js';
2
2
 
3
3
  // src/core/guards.ts
4
4
  function isMfaChallengeResponse(response) {
@@ -60,7 +60,7 @@ var AuthService = class {
60
60
  }
61
61
  if (options?.autoSetAuth !== false && !isMfaChallengeResponse(response)) {
62
62
  try {
63
- const { authStore } = await import('./auth.svelte-BBLX2MKB.js');
63
+ const { authStore } = await import('./auth.svelte-WPCYPHEE.js');
64
64
  authStore.setAuth(
65
65
  response.accessToken,
66
66
  response.refreshToken,
@@ -246,7 +246,7 @@ var AuthService = class {
246
246
  }
247
247
  if (options?.autoSetAuth !== false) {
248
248
  try {
249
- const { authStore } = await import('./auth.svelte-BBLX2MKB.js');
249
+ const { authStore } = await import('./auth.svelte-WPCYPHEE.js');
250
250
  authStore.setAuth(
251
251
  response.accessToken,
252
252
  response.refreshToken,
@@ -1,6 +1,6 @@
1
- import { authService } from './chunk-TUHQ4FHC.js';
2
- import { authStore, currentUser, isAuthenticated, authActions } from './chunk-BK34CVH5.js';
3
- import { isInitialized, initAuth } from './chunk-EM7PUNZB.js';
1
+ import { authService } from './chunk-MPPV5S7S.js';
2
+ import { authStore, currentUser, isAuthenticated, authActions } from './chunk-CPFM7JEI.js';
3
+ import { isInitialized, initAuth } from './chunk-6NTZVDLW.js';
4
4
 
5
5
  // src/svelte/guards/auth-guard.ts
6
6
  function checkAuth(options = {}) {
@@ -1,6 +1,6 @@
1
- import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, k as RegisterResponse, U as User, j as ProfileUpdateData, C as ChangePasswordData, n as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, i as MFAStatus, h as MFASetupResponse, M as MFAChallengeData, D as Device, p as UserPreferences, L as LinkedAccount, m as SecurityEvent, P as Pagination } from '../types-BRxyphcX.js';
2
- export { a as AuthConfig, b as AuthState, l as ResetPasswordData, S as SSOConfig, o as StorageAdapter, q as getConfig, r as getDefaultStorage, s as getFetch, t as getStorage, u as initAuth, v as isInitialized, w as resetConfig } from '../types-BRxyphcX.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-BSbNN3Hb.js';
1
+ import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, k as RegisterResponse, U as User, j as ProfileUpdateData, C as ChangePasswordData, n as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, i as MFAStatus, h as MFASetupResponse, M as MFAChallengeData, D as Device, p as UserPreferences, L as LinkedAccount, m as SecurityEvent, P as Pagination } from '../types-Di6SR1q5.js';
2
+ export { a as AuthConfig, b as AuthState, l as ResetPasswordData, S as SSOConfig, o as StorageAdapter, q as createMemoryStorage, r as createSessionStorage, s as getConfig, t as getDefaultStorage, u as getFetch, v as getStorage, w as initAuth, x as isInitialized, y as resetConfig } from '../types-Di6SR1q5.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-C-JbXqbj.js';
4
4
 
5
5
  /**
6
6
  * HTTP Client
@@ -47,9 +47,10 @@ declare function getSessionToken(): string | null;
47
47
  * Update stored tokens.
48
48
  * Also updates the Svelte auth store if available.
49
49
  *
50
- * Note: The read-modify-write on localStorage is not atomic across tabs.
51
- * A version counter is used to detect and discard stale writes from other tabs.
52
- * The `storage` event listener (set up via `initCrossTabSync`) keeps tabs in sync.
50
+ * Note: The read-modify-write on localStorage is not atomic across tabs
51
+ * concurrent refreshes in two tabs are last-write-wins. The `storage` event
52
+ * listener (set up via `initCrossTabSync`) propagates whichever write landed
53
+ * last, so tabs converge on the same tokens.
53
54
  */
54
55
  declare function updateStoredTokens(accessToken: string, refreshToken: string): void;
55
56
  /**
@@ -1,3 +1,3 @@
1
- export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from '../chunk-TUHQ4FHC.js';
2
- export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens } from '../chunk-GRQL5FSH.js';
3
- export { decodeJWT, extractClaims, getConfig, getDefaultStorage, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, initAuth, isInitialized, isTokenExpired, resetConfig } from '../chunk-EM7PUNZB.js';
1
+ export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from '../chunk-MPPV5S7S.js';
2
+ export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens } from '../chunk-2QYFVJEM.js';
3
+ export { createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, initAuth, isInitialized, isTokenExpired, resetConfig } from '../chunk-6NTZVDLW.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 User, p as UserPreferences, q as getConfig, r as getDefaultStorage, s as getFetch, t as getStorage, u as initAuth, v as isInitialized, w as resetConfig } from './types-BRxyphcX.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 User, p as UserPreferences, q as createMemoryStorage, r as createSessionStorage, s as getConfig, t as getDefaultStorage, u as getFetch, v as getStorage, w as initAuth, x as isInitialized, y as resetConfig } from './types-Di6SR1q5.js';
2
2
  export { ApiRequestOptions, ApiResponse, JWTPayload, api, apiRequest, authApi, clearStoredAuth, decodeJWT, extractClaims, extractData, getAccessToken, getAvailableMethods, getMfaToken, getRefreshToken, getSessionToken, getTokenExpiration, getTokenRemainingTime, isLoginSuccessResponse, isMfaChallengeResponse, isTokenExpired, 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-BSbNN3Hb.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-C-JbXqbj.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-RKSETJ4V.js';
2
- export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from './chunk-TUHQ4FHC.js';
3
- export { authActions, authStore, currentUser, isAuthenticated } from './chunk-BK34CVH5.js';
4
- export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens } from './chunk-GRQL5FSH.js';
5
- export { decodeJWT, extractClaims, getConfig, getDefaultStorage, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, initAuth, isInitialized, isTokenExpired, resetConfig } from './chunk-EM7PUNZB.js';
1
+ export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from './chunk-UG4GSPGL.js';
2
+ export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from './chunk-MPPV5S7S.js';
3
+ export { authActions, authStore, currentUser, isAuthenticated } from './chunk-CPFM7JEI.js';
4
+ export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens } from './chunk-2QYFVJEM.js';
5
+ export { createMemoryStorage, createSessionStorage, decodeJWT, extractClaims, getConfig, getDefaultStorage, getFetch, getStorage, getTokenExpiration, getTokenRemainingTime, initAuth, isInitialized, isTokenExpired, resetConfig } from './chunk-6NTZVDLW.js';
@@ -1,7 +1,7 @@
1
- import { b as AuthState, U as User, a as AuthConfig } from '../types-BRxyphcX.js';
2
- export { u as initAuth, v as isInitialized } from '../types-BRxyphcX.js';
3
- import { a as authService } from '../user-utils-BSbNN3Hb.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-BSbNN3Hb.js';
1
+ import { b as AuthState, U as User, a as AuthConfig } from '../types-Di6SR1q5.js';
2
+ export { w as initAuth, x as isInitialized } from '../types-Di6SR1q5.js';
3
+ import { a as authService } from '../user-utils-C-JbXqbj.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-C-JbXqbj.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-RKSETJ4V.js';
2
- export { RoleDeniedError, formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials, isRoleDeniedError } from '../chunk-TUHQ4FHC.js';
3
- export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-BK34CVH5.js';
4
- import '../chunk-GRQL5FSH.js';
5
- export { initAuth, isInitialized } from '../chunk-EM7PUNZB.js';
1
+ export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from '../chunk-UG4GSPGL.js';
2
+ export { RoleDeniedError, formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials, isRoleDeniedError } from '../chunk-MPPV5S7S.js';
3
+ export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-CPFM7JEI.js';
4
+ import '../chunk-2QYFVJEM.js';
5
+ export { initAuth, isInitialized } from '../chunk-6NTZVDLW.js';
@@ -1,4 +1,4 @@
1
- import { U 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, p as UserPreferences, o as StorageAdapter, b as AuthState, a as AuthConfig } from '../types-BRxyphcX.js';
1
+ import { U 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, p as UserPreferences, o as StorageAdapter, b as AuthState, a as AuthConfig } from '../types-Di6SR1q5.js';
2
2
 
3
3
  /**
4
4
  * User Fixtures
@@ -1,4 +1,4 @@
1
- import { initAuth, resetConfig, isTokenExpired, decodeJWT } from '../chunk-EM7PUNZB.js';
1
+ import { initAuth, resetConfig, isTokenExpired, decodeJWT } from '../chunk-6NTZVDLW.js';
2
2
 
3
3
  // src/testing/fixtures/users.ts
4
4
  var mockUser = {
@@ -105,8 +105,29 @@ declare function resetConfig(): void;
105
105
  /**
106
106
  * Get the default storage adapter.
107
107
  * Returns localStorage in browser, or a no-op adapter in SSR.
108
+ *
109
+ * SECURITY NOTE: localStorage persists tokens (including the long-lived
110
+ * refresh token) where any XSS on the page can read them. It is the default
111
+ * for cross-tab session continuity, but consider the safer alternatives:
112
+ * `createSessionStorage()` (per-tab, cleared on close),
113
+ * `createMemoryStorage()` (never persisted, lost on reload), or an
114
+ * httpOnly-cookie flow on your backend so tokens never reach JS at all.
115
+ * Pass the adapter via `initAuth({ storage: ... })`.
108
116
  */
109
117
  declare function getDefaultStorage(): StorageAdapter;
118
+ /**
119
+ * Storage adapter backed by sessionStorage: tokens live per-tab and are
120
+ * cleared when the tab closes. Safer than localStorage against persistent
121
+ * token theft, at the cost of cross-tab session continuity.
122
+ * Falls back to no-op in SSR.
123
+ */
124
+ declare function createSessionStorage(): StorageAdapter;
125
+ /**
126
+ * In-memory storage adapter: tokens are never persisted and are lost on
127
+ * page reload (the user re-authenticates or a silent SSO flow restores the
128
+ * session). The safest JS-side option against token exfiltration.
129
+ */
130
+ declare function createMemoryStorage(): StorageAdapter;
110
131
  /**
111
132
  * Get the storage adapter from config or use default.
112
133
  */
@@ -174,6 +195,12 @@ interface LogoutResponse {
174
195
  message?: string;
175
196
  ssoLogout?: boolean;
176
197
  logoutUrl?: string;
198
+ /**
199
+ * Set when the server-side logout call failed. `success` stays true so
200
+ * callers clear local state, but the server session may still be alive —
201
+ * check this to warn the user or retry revocation.
202
+ */
203
+ serverError?: string;
177
204
  }
178
205
  interface RegisterResponse {
179
206
  message: string;
@@ -320,4 +347,4 @@ interface ResetPasswordData {
320
347
  newPassword: string;
321
348
  }
322
349
 
323
- export { type ApiKey as A, type ChangePasswordData as C, type Device as D, type LinkedAccount as L, type MFAChallengeData as M, type Pagination as P, type RegisterData as R, type SSOConfig as S, type User 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 MFASetupResponse as h, type MFAStatus as i, type ProfileUpdateData as j, type RegisterResponse as k, type ResetPasswordData as l, type SecurityEvent as m, type Session as n, type StorageAdapter as o, type UserPreferences as p, getConfig as q, getDefaultStorage as r, getFetch as s, getStorage as t, initAuth as u, isInitialized as v, resetConfig as w };
350
+ export { type ApiKey as A, type ChangePasswordData as C, type Device as D, type LinkedAccount as L, type MFAChallengeData as M, type Pagination as P, type RegisterData as R, type SSOConfig as S, type User 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 MFASetupResponse as h, type MFAStatus as i, type ProfileUpdateData as j, type RegisterResponse as k, type ResetPasswordData as l, type SecurityEvent as m, type Session as n, type StorageAdapter as o, type UserPreferences as p, createMemoryStorage as q, createSessionStorage as r, getConfig as s, getDefaultStorage as t, getFetch as u, getStorage as v, initAuth as w, isInitialized as x, resetConfig as y };
@@ -1,4 +1,4 @@
1
- import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, k as RegisterResponse, U as User, j as ProfileUpdateData, n as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, i as MFAStatus, h as MFASetupResponse, M as MFAChallengeData, D as Device, p as UserPreferences, L as LinkedAccount, m as SecurityEvent, P as Pagination } from './types-BRxyphcX.js';
1
+ import { e as LoginCredentials, f as LoginResponse, g as LogoutResponse, R as RegisterData, k as RegisterResponse, U as User, j as ProfileUpdateData, n as Session, A as ApiKey, c as CreateApiKeyRequest, d as CreateApiKeyResponse, i as MFAStatus, h as MFASetupResponse, M as MFAChallengeData, D as Device, p as UserPreferences, L as LinkedAccount, m as SecurityEvent, P as Pagination } from './types-Di6SR1q5.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": "0.1.55",
3
+ "version": "0.1.56",
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-BK34CVH5.js';
2
- import './chunk-GRQL5FSH.js';
3
- import './chunk-EM7PUNZB.js';