@classic-homes/auth 1.0.0 → 1.1.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-B5LAK7V6.js';
2
+ import './chunk-FDOOY3IL.js';
3
+ import './chunk-VFEGS6FR.js';
@@ -1,6 +1,6 @@
1
- import { authService } from './chunk-VVREZ25E.js';
2
- import { authStore, currentUser, isAuthenticated, authActions } from './chunk-QBTMNWJH.js';
3
- import { isInitialized, initAuth, getUserRoles } from './chunk-O47YKG2F.js';
1
+ import { authService } from './chunk-RCD423GF.js';
2
+ import { authStore, currentUser, isAuthenticated, authActions } from './chunk-B5LAK7V6.js';
3
+ import { isInitialized, initAuth, getUserRoles } from './chunk-VFEGS6FR.js';
4
4
 
5
5
  // src/svelte/guards/auth-guard.ts
6
6
  function checkAuth(options = {}) {
@@ -1,5 +1,5 @@
1
- import { authApi } from './chunk-2N2MDCWL.js';
2
- import { isInitialized, getConfig, hasRole, hasAnyRole, hasAllRoles, decodeJWT, getStorage, getDefaultStorage } from './chunk-O47YKG2F.js';
1
+ import { authApi } from './chunk-FDOOY3IL.js';
2
+ import { isInitialized, getConfig, hasRole, hasAnyRole, hasAllRoles, decodeJWT, getStorage, getDefaultStorage } from './chunk-VFEGS6FR.js';
3
3
 
4
4
  // src/svelte/stores/auth.svelte.ts
5
5
  var STORAGE_VERSION = 2;
@@ -1,4 +1,4 @@
1
- import { getConfig, getStorage, getFetch, isValidRedirectUrl } from './chunk-O47YKG2F.js';
1
+ import { getConfig, getStorage, getFetch, isValidRedirectUrl } from './chunk-VFEGS6FR.js';
2
2
 
3
3
  // src/core/client.ts
4
4
  var isRefreshing = false;
@@ -14,17 +14,23 @@ function subscribeTokenRefresh(cb) {
14
14
  if (idx > -1) refreshSubscribers.splice(idx, 1);
15
15
  };
16
16
  }
17
- function onTokenRefreshed(token) {
17
+ function flushRefreshSubscribers(token, terminal = false) {
18
18
  const subs = refreshSubscribers;
19
19
  refreshSubscribers = [];
20
20
  subs.forEach((cb) => {
21
21
  try {
22
- cb(token);
22
+ cb(token, terminal);
23
23
  } catch (e) {
24
24
  console.error("Token refresh subscriber error:", e);
25
25
  }
26
26
  });
27
27
  }
28
+ function onTokenRefreshed(token) {
29
+ flushRefreshSubscribers(token);
30
+ }
31
+ function onTokenRefreshFailed(terminal) {
32
+ flushRefreshSubscribers(null, terminal);
33
+ }
28
34
  function getAccessToken() {
29
35
  const config = getConfig();
30
36
  const storage = getStorage();
@@ -71,7 +77,7 @@ function updateStoredTokens(accessToken, refreshToken) {
71
77
  parsed.accessToken = accessToken;
72
78
  parsed.refreshToken = refreshToken;
73
79
  storage.setItem(storageKey, JSON.stringify(parsed));
74
- import('./auth.svelte-J62VV4TA.js').then(({ authStore }) => {
80
+ import('./auth.svelte-54WDRZ6V.js').then(({ authStore }) => {
75
81
  authStore.updateTokens(accessToken, refreshToken);
76
82
  }).catch(() => {
77
83
  });
@@ -83,12 +89,21 @@ function clearStoredAuth() {
83
89
  storage.removeItem(config.storageKey ?? "classic_auth");
84
90
  storage.removeItem(getSessionTokenKey());
85
91
  }
92
+ async function expireSession() {
93
+ clearStoredAuth();
94
+ try {
95
+ const { authStore } = await import('./auth.svelte-54WDRZ6V.js');
96
+ authStore.handleSessionExpired();
97
+ } catch {
98
+ }
99
+ getConfig().onLogout?.();
100
+ }
86
101
  async function refreshAccessToken() {
87
102
  const refreshToken = getRefreshToken();
88
103
  const config = getConfig();
89
104
  const fetchFn = getFetch();
90
105
  if (!refreshToken) {
91
- return false;
106
+ return "rejected";
92
107
  }
93
108
  const timeoutMs = config.requestTimeout ?? 3e4;
94
109
  const controller = new AbortController();
@@ -109,13 +124,13 @@ async function refreshAccessToken() {
109
124
  try {
110
125
  apiResponse = await response.json();
111
126
  } catch {
112
- return false;
127
+ return "unavailable";
113
128
  }
114
129
  } else {
115
130
  try {
116
131
  apiResponse = await response.json();
117
132
  } catch {
118
- return false;
133
+ return "unavailable";
119
134
  }
120
135
  }
121
136
  const data = apiResponse?.data;
@@ -124,15 +139,20 @@ async function refreshAccessToken() {
124
139
  const newRefreshToken = tokens.refreshToken ?? refreshToken;
125
140
  updateStoredTokens(tokens.accessToken, newRefreshToken);
126
141
  onTokenRefreshed(tokens.accessToken);
127
- return true;
142
+ return "refreshed";
128
143
  }
144
+ return "unavailable";
129
145
  }
146
+ if (response.status >= 500) {
147
+ return "unavailable";
148
+ }
149
+ return "rejected";
130
150
  } catch (error) {
131
151
  config.onAuthError?.(error instanceof Error ? error : new Error("Token refresh failed"));
152
+ return "unavailable";
132
153
  } finally {
133
154
  clearTimeout(timeoutId);
134
155
  }
135
- return false;
136
156
  }
137
157
  function extractData(response) {
138
158
  if (response && typeof response === "object" && "data" in response) {
@@ -188,25 +208,27 @@ async function apiRequestInternal(endpoint, options, isRetry) {
188
208
  clearTimeout(timeoutId);
189
209
  if (response.status === 401 && authenticated && typeof window !== "undefined") {
190
210
  if (isRetry) {
191
- clearStoredAuth();
192
- config.onLogout?.();
211
+ await expireSession();
193
212
  throw new Error("Authentication failed. Please sign in again.");
194
213
  }
195
214
  if (!isRefreshing) {
196
215
  isRefreshing = true;
197
- let refreshed = false;
216
+ let refreshResult = "unavailable";
198
217
  try {
199
- refreshed = await refreshAccessToken();
218
+ refreshResult = await refreshAccessToken();
200
219
  } finally {
201
220
  isRefreshing = false;
202
221
  }
203
- if (refreshed) {
222
+ if (refreshResult === "refreshed") {
204
223
  return apiRequestInternal(endpoint, options, true);
205
- } else {
206
- clearStoredAuth();
207
- config.onLogout?.();
224
+ }
225
+ if (refreshResult === "rejected") {
226
+ onTokenRefreshFailed(true);
227
+ await expireSession();
208
228
  throw new Error("Authentication failed. Please sign in again.");
209
229
  }
230
+ onTokenRefreshFailed(false);
231
+ throw new Error("Authentication service unavailable. Please try again.");
210
232
  } else {
211
233
  const REFRESH_TIMEOUT_MS = 3e4;
212
234
  return new Promise((resolve, reject) => {
@@ -220,11 +242,19 @@ async function apiRequestInternal(endpoint, options, isRetry) {
220
242
  }, REFRESH_TIMEOUT_MS);
221
243
  let unsubscribe = null;
222
244
  try {
223
- unsubscribe = subscribeTokenRefresh(() => {
245
+ unsubscribe = subscribeTokenRefresh((token, terminal) => {
224
246
  if (!settled) {
225
247
  settled = true;
226
248
  clearTimeout(refreshTimeoutId);
227
- apiRequestInternal(endpoint, options, true).then(resolve).catch(reject);
249
+ if (token) {
250
+ apiRequestInternal(endpoint, options, true).then(resolve).catch(reject);
251
+ } else {
252
+ reject(
253
+ new Error(
254
+ terminal ? "Authentication failed. Please sign in again." : "Authentication service unavailable. Please try again."
255
+ )
256
+ );
257
+ }
228
258
  }
229
259
  });
230
260
  } catch (subscribeError) {
@@ -1,5 +1,5 @@
1
- import { authApi } from './chunk-2N2MDCWL.js';
2
- import { getUserRoles, hasAnyRole } from './chunk-O47YKG2F.js';
1
+ import { authApi } from './chunk-FDOOY3IL.js';
2
+ import { getUserRoles, hasAnyRole } from './chunk-VFEGS6FR.js';
3
3
 
4
4
  // src/core/guards.ts
5
5
  function isMfaChallengeResponse(response) {
@@ -59,7 +59,7 @@ var AuthService = class {
59
59
  }
60
60
  if (options?.autoSetAuth !== false && !isMfaChallengeResponse(response)) {
61
61
  try {
62
- const { authStore } = await import('./auth.svelte-J62VV4TA.js');
62
+ const { authStore } = await import('./auth.svelte-54WDRZ6V.js');
63
63
  authStore.setAuth(
64
64
  response.accessToken,
65
65
  response.refreshToken,
@@ -243,7 +243,7 @@ var AuthService = class {
243
243
  }
244
244
  if (options?.autoSetAuth !== false) {
245
245
  try {
246
- const { authStore } = await import('./auth.svelte-J62VV4TA.js');
246
+ const { authStore } = await import('./auth.svelte-54WDRZ6V.js');
247
247
  authStore.setAuth(
248
248
  response.accessToken,
249
249
  response.refreshToken,
@@ -120,7 +120,7 @@ function isTokenExpired(token) {
120
120
  return true;
121
121
  }
122
122
  const CLOCK_SKEW_MS = 3e4;
123
- return payload.exp * 1e3 < Date.now() - CLOCK_SKEW_MS;
123
+ return payload.exp * 1e3 < Date.now() + CLOCK_SKEW_MS;
124
124
  }
125
125
  function getTokenRemainingTime(token) {
126
126
  const payload = decodeJWT(token);
@@ -159,6 +159,20 @@ var ROLE_NAMES = [
159
159
  "builder",
160
160
  "customer_service",
161
161
  "demo",
162
+ "stormwater",
163
+ "accounting_staff",
164
+ "architecture_staff",
165
+ "construction_staff",
166
+ "crl_staff",
167
+ "design_staff",
168
+ "hr_staff",
169
+ "it_staff",
170
+ "landdev_staff",
171
+ "marketing_staff",
172
+ "purchasing_staff",
173
+ "sales_staff",
174
+ "warranty_staff",
175
+ "employee",
162
176
  "user",
163
177
  "vendor",
164
178
  "customer",
@@ -171,10 +185,26 @@ var ROLE_HIERARCHY = {
171
185
  vendor: 1,
172
186
  customer: 1,
173
187
  subcontractor: 1,
188
+ // Staff tier (2): the read-only `employee` floor, the field-app clones of
189
+ // `agent`, and every `<department>_staff` docs role.
190
+ employee: 2,
174
191
  agent: 2,
175
192
  builder: 2,
176
193
  customer_service: 2,
177
194
  demo: 2,
195
+ stormwater: 2,
196
+ accounting_staff: 2,
197
+ architecture_staff: 2,
198
+ construction_staff: 2,
199
+ crl_staff: 2,
200
+ design_staff: 2,
201
+ hr_staff: 2,
202
+ it_staff: 2,
203
+ landdev_staff: 2,
204
+ marketing_staff: 2,
205
+ purchasing_staff: 2,
206
+ sales_staff: 2,
207
+ warranty_staff: 2,
178
208
  manager: 3,
179
209
  admin: 4
180
210
  };
@@ -469,11 +469,13 @@ declare function extractClaims<T extends string>(token: string, claims: T[]): Pi
469
469
  * the first match, which is what makes the result deterministic when several
470
470
  * roles share a tier (e.g. `agent` + `builder`).
471
471
  *
472
- * Mirrors `ROLE_NAMES` in the API. `builder`/`customer_service`/`demo` are
473
- * staff-tier clones of `agent`; `vendor`/`customer`/`subcontractor` are clones
474
- * of `user`, row-scoped to their own homes.
472
+ * Mirrors `ROLE_NAMES` in the API. `builder`/`customer_service`/`demo`/
473
+ * `stormwater` are staff-tier clones of `agent`; the `<department>_staff` roles
474
+ * are grant-on-top docs roles held alongside another role; `employee` is the
475
+ * read-only base staff floor (default for Authentik SSO); `vendor`/`customer`/
476
+ * `subcontractor` are clones of `user`, row-scoped to their own homes.
475
477
  */
476
- declare const ROLE_NAMES: readonly ["admin", "manager", "agent", "builder", "customer_service", "demo", "user", "vendor", "customer", "subcontractor", "guest"];
478
+ declare const ROLE_NAMES: readonly ["admin", "manager", "agent", "builder", "customer_service", "demo", "stormwater", "accounting_staff", "architecture_staff", "construction_staff", "crl_staff", "design_staff", "hr_staff", "it_staff", "landdev_staff", "marketing_staff", "purchasing_staff", "sales_staff", "warranty_staff", "employee", "user", "vendor", "customer", "subcontractor", "guest"];
477
479
  /**
478
480
  * A known role name.
479
481
  *
@@ -1,3 +1,3 @@
1
- export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from '../chunk-VVREZ25E.js';
2
- export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens } from '../chunk-2N2MDCWL.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-O47YKG2F.js';
1
+ export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from '../chunk-RCD423GF.js';
2
+ export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens } from '../chunk-FDOOY3IL.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-VFEGS6FR.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-ILRNO4WH.js';
2
- export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from './chunk-VVREZ25E.js';
3
- export { authActions, authStore, currentUser, isAuthenticated } from './chunk-QBTMNWJH.js';
4
- export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens } from './chunk-2N2MDCWL.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-O47YKG2F.js';
1
+ export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from './chunk-A2KRBRIC.js';
2
+ export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError } from './chunk-RCD423GF.js';
3
+ export { authActions, authStore, currentUser, isAuthenticated } from './chunk-B5LAK7V6.js';
4
+ export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens } from './chunk-FDOOY3IL.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-VFEGS6FR.js';
@@ -1,5 +1,5 @@
1
- export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from '../chunk-ILRNO4WH.js';
2
- export { RoleDeniedError, formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials, isRoleDeniedError } from '../chunk-VVREZ25E.js';
3
- export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-QBTMNWJH.js';
4
- import '../chunk-2N2MDCWL.js';
5
- export { initAuth, isInitialized } from '../chunk-O47YKG2F.js';
1
+ export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns } from '../chunk-A2KRBRIC.js';
2
+ export { RoleDeniedError, formatUserRoles, getAvatarFallback, getDisplayName, getGreeting, getUserEmail, getUserInitials, isRoleDeniedError } from '../chunk-RCD423GF.js';
3
+ export { authActions, authStore, currentUser, isAuthenticated } from '../chunk-B5LAK7V6.js';
4
+ import '../chunk-FDOOY3IL.js';
5
+ export { initAuth, isInitialized } from '../chunk-VFEGS6FR.js';
@@ -1,4 +1,4 @@
1
- import { hasRole, hasAnyRole, hasAllRoles, initAuth, resetConfig, getUserRoles, isTokenExpired, decodeJWT } from '../chunk-O47YKG2F.js';
1
+ import { hasRole, hasAnyRole, hasAllRoles, initAuth, resetConfig, getUserRoles, isTokenExpired, decodeJWT } from '../chunk-VFEGS6FR.js';
2
2
 
3
3
  // src/testing/fixtures/users.ts
4
4
  var mockUser = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/auth",
3
- "version": "1.0.0",
3
+ "version": "1.1.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-QBTMNWJH.js';
2
- import './chunk-2N2MDCWL.js';
3
- import './chunk-O47YKG2F.js';