@classic-homes/auth 0.1.43 → 0.1.44

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.
@@ -1,3 +1,3 @@
1
- export { authActions, authStore, currentUser, isAuthenticated } from './chunk-7M4DUK45.js';
2
- import './chunk-BDIQSTES.js';
1
+ export { authActions, authStore, currentUser, isAuthenticated } from './chunk-DSNTNK6T.js';
2
+ import './chunk-ES4UOD62.js';
3
3
  import './chunk-DCGC6CNV.js';
@@ -1,10 +1,13 @@
1
- import { authApi } from './chunk-BDIQSTES.js';
1
+ import { authApi } from './chunk-ES4UOD62.js';
2
2
  import { decodeJWT, isInitialized, getConfig, getStorage, getDefaultStorage } from './chunk-DCGC6CNV.js';
3
3
 
4
4
  // src/svelte/stores/auth.svelte.ts
5
5
  function getStorageKey() {
6
6
  return isInitialized() ? getConfig().storageKey ?? "classic_auth" : "classic_auth";
7
7
  }
8
+ function getSessionTokenKey() {
9
+ return `${getStorageKey()}_session`;
10
+ }
8
11
  function getStorageAdapter() {
9
12
  return isInitialized() ? getStorage() : getDefaultStorage();
10
13
  }
@@ -101,7 +104,7 @@ var AuthStore = class {
101
104
  };
102
105
  saveAuthToStorage(this.state);
103
106
  if (sessionToken && typeof window !== "undefined") {
104
- getStorageAdapter().setItem("sessionToken", sessionToken);
107
+ getStorageAdapter().setItem(getSessionTokenKey(), sessionToken);
105
108
  }
106
109
  if (typeof window !== "undefined") {
107
110
  window.dispatchEvent(
@@ -170,7 +173,7 @@ var AuthStore = class {
170
173
  };
171
174
  const storage = getStorageAdapter();
172
175
  storage.removeItem(getStorageKey());
173
- storage.removeItem("sessionToken");
176
+ storage.removeItem(getSessionTokenKey());
174
177
  if (typeof window !== "undefined") {
175
178
  window.dispatchEvent(new CustomEvent("auth:logout"));
176
179
  }
@@ -196,6 +199,35 @@ var AuthStore = class {
196
199
  }
197
200
  return {};
198
201
  }
202
+ /**
203
+ * Handle session expiration (e.g., token refresh failure).
204
+ * Clears auth state and calls the onSessionExpired callback if configured.
205
+ *
206
+ * Use this instead of logout() when the session expires unexpectedly,
207
+ * to trigger different handling (e.g., redirect with error message).
208
+ */
209
+ handleSessionExpired() {
210
+ const currentPath = typeof window !== "undefined" ? window.location.pathname : "/";
211
+ this.state = {
212
+ accessToken: null,
213
+ refreshToken: null,
214
+ user: null,
215
+ isAuthenticated: false
216
+ };
217
+ const storage = getStorageAdapter();
218
+ storage.removeItem(getStorageKey());
219
+ storage.removeItem(getSessionTokenKey());
220
+ if (typeof window !== "undefined") {
221
+ window.dispatchEvent(new CustomEvent("auth:session-expired"));
222
+ }
223
+ if (isInitialized()) {
224
+ const config = getConfig();
225
+ if (config.onSessionExpired) {
226
+ config.onSessionExpired(currentPath);
227
+ }
228
+ }
229
+ this.notify();
230
+ }
199
231
  /**
200
232
  * Check if user has a specific permission.
201
233
  */
@@ -239,6 +271,36 @@ var AuthStore = class {
239
271
  this.state = loadAuthFromStorage();
240
272
  this.notify();
241
273
  }
274
+ /**
275
+ * Reset the store to initial state and clear all storage.
276
+ * Primarily intended for testing scenarios where auth needs to be fully reset.
277
+ *
278
+ * Unlike logout(), this also clears all subscribers and doesn't
279
+ * dispatch events or call config callbacks.
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * // In test setup/teardown
284
+ * beforeEach(() => {
285
+ * authStore.reset();
286
+ * });
287
+ * ```
288
+ */
289
+ reset() {
290
+ this.state = {
291
+ accessToken: null,
292
+ refreshToken: null,
293
+ user: null,
294
+ isAuthenticated: false
295
+ };
296
+ try {
297
+ const storage = getStorageAdapter();
298
+ storage.removeItem(getStorageKey());
299
+ storage.removeItem(getSessionTokenKey());
300
+ } catch {
301
+ }
302
+ this.subscribers.clear();
303
+ }
242
304
  };
243
305
  var authStore = new AuthStore();
244
306
  var authActions = {
@@ -247,13 +309,15 @@ var authActions = {
247
309
  updateUser: (user) => authStore.updateUser(user),
248
310
  logout: () => authStore.logout(),
249
311
  logoutWithSSO: () => authStore.logoutWithSSO(),
312
+ handleSessionExpired: () => authStore.handleSessionExpired(),
250
313
  hasPermission: (permission) => authStore.hasPermission(permission),
251
314
  hasRole: (role) => authStore.hasRole(role),
252
315
  hasAnyRole: (roles) => authStore.hasAnyRole(roles),
253
316
  hasAllRoles: (roles) => authStore.hasAllRoles(roles),
254
317
  hasAnyPermission: (permissions) => authStore.hasAnyPermission(permissions),
255
318
  hasAllPermissions: (permissions) => authStore.hasAllPermissions(permissions),
256
- rehydrate: () => authStore.rehydrate()
319
+ rehydrate: () => authStore.rehydrate(),
320
+ reset: () => authStore.reset()
257
321
  };
258
322
  var isAuthenticated = {
259
323
  subscribe: (subscriber) => {
@@ -34,9 +34,14 @@ function getRefreshToken() {
34
34
  return null;
35
35
  }
36
36
  }
37
+ function getSessionTokenKey() {
38
+ const config = getConfig();
39
+ const storageKey = config.storageKey ?? "classic_auth";
40
+ return `${storageKey}_session`;
41
+ }
37
42
  function getSessionToken() {
38
43
  const storage = getStorage();
39
- return storage.getItem("sessionToken");
44
+ return storage.getItem(getSessionTokenKey());
40
45
  }
41
46
  function updateStoredTokens(accessToken, refreshToken) {
42
47
  const config = getConfig();
@@ -53,7 +58,7 @@ function updateStoredTokens(accessToken, refreshToken) {
53
58
  parsed.accessToken = accessToken;
54
59
  parsed.refreshToken = refreshToken;
55
60
  storage.setItem(storageKey, JSON.stringify(parsed));
56
- import('./auth.svelte-LJJ7MGDE.js').then(({ authStore }) => {
61
+ import('./auth.svelte-DTSHZMJ4.js').then(({ authStore }) => {
57
62
  authStore.updateTokens(accessToken, refreshToken);
58
63
  }).catch(() => {
59
64
  });
@@ -63,7 +68,7 @@ function clearStoredAuth() {
63
68
  const config = getConfig();
64
69
  const storage = getStorage();
65
70
  storage.removeItem(config.storageKey ?? "classic_auth");
66
- storage.removeItem("sessionToken");
71
+ storage.removeItem(getSessionTokenKey());
67
72
  }
68
73
  async function refreshAccessToken() {
69
74
  const refreshToken = getRefreshToken();
@@ -160,9 +165,21 @@ async function apiRequest(endpoint, options = {}) {
160
165
  throw new Error("Authentication failed. Please sign in again.");
161
166
  }
162
167
  } else {
168
+ const REFRESH_TIMEOUT_MS = 3e4;
163
169
  return new Promise((resolve, reject) => {
170
+ let settled = false;
171
+ const timeoutId = setTimeout(() => {
172
+ if (!settled) {
173
+ settled = true;
174
+ reject(new Error("Token refresh timed out. Please sign in again."));
175
+ }
176
+ }, REFRESH_TIMEOUT_MS);
164
177
  subscribeTokenRefresh(() => {
165
- apiRequest(endpoint, options).then(resolve).catch(reject);
178
+ if (!settled) {
179
+ settled = true;
180
+ clearTimeout(timeoutId);
181
+ apiRequest(endpoint, options).then(resolve).catch(reject);
182
+ }
166
183
  });
167
184
  });
168
185
  }
@@ -259,12 +276,17 @@ var authApi = {
259
276
  /**
260
277
  * Logout the current user.
261
278
  * Returns SSO logout URL if applicable for SSO users.
279
+ *
280
+ * Note: API errors are logged via onAuthError callback but still return success
281
+ * so the client can clear local state even if the server call fails.
262
282
  */
263
283
  async logout() {
264
284
  try {
265
285
  const response = await api.post("/auth/logout", {}, true);
266
286
  return extractData(response);
267
- } catch {
287
+ } catch (error) {
288
+ const config = getConfig();
289
+ config.onAuthError?.(error instanceof Error ? error : new Error("Logout API call failed"));
268
290
  return { success: true };
269
291
  }
270
292
  },
@@ -530,6 +552,8 @@ var authApi = {
530
552
  // ============================================================================
531
553
  /**
532
554
  * Get user preferences.
555
+ *
556
+ * @throws Error if the API returns a malformed response
533
557
  */
534
558
  async getPreferences(customFetch) {
535
559
  const response = await api.get(
@@ -540,7 +564,10 @@ var authApi = {
540
564
  if (response && typeof response === "object" && "preferences" in response) {
541
565
  return response.preferences;
542
566
  }
543
- return response ?? {};
567
+ if (response && typeof response === "object") {
568
+ return response;
569
+ }
570
+ throw new Error("Invalid response from preferences API: expected object with preferences");
544
571
  },
545
572
  /**
546
573
  * Update user preferences.
@@ -572,7 +599,10 @@ var authApi = {
572
599
  await api.delete("/auth/sso/unlink", true, void 0, { provider, password });
573
600
  },
574
601
  /**
575
- * Link an SSO account (redirects to SSO provider).
602
+ * Link an SSO account (redirects to SSO provider via form POST).
603
+ *
604
+ * Uses form submission to avoid exposing the access token in URL parameters,
605
+ * which could leak via browser history, referrer headers, or server logs.
576
606
  */
577
607
  async linkSSOAccount(provider = "authentik") {
578
608
  if (typeof window === "undefined") return;
@@ -581,11 +611,22 @@ var authApi = {
581
611
  throw new Error("Not authenticated");
582
612
  }
583
613
  const config = getConfig();
584
- const params = new URLSearchParams({
585
- token: accessToken,
586
- provider
587
- });
588
- window.location.href = `${config.baseUrl}/auth/sso/link?${params.toString()}`;
614
+ const form = document.createElement("form");
615
+ form.method = "POST";
616
+ form.action = `${config.baseUrl}/auth/sso/link`;
617
+ form.style.display = "none";
618
+ const tokenInput = document.createElement("input");
619
+ tokenInput.type = "hidden";
620
+ tokenInput.name = "token";
621
+ tokenInput.value = accessToken;
622
+ form.appendChild(tokenInput);
623
+ const providerInput = document.createElement("input");
624
+ providerInput.type = "hidden";
625
+ providerInput.name = "provider";
626
+ providerInput.value = provider;
627
+ form.appendChild(providerInput);
628
+ document.body.appendChild(form);
629
+ form.submit();
589
630
  },
590
631
  // ============================================================================
591
632
  // Security Events
@@ -1,4 +1,4 @@
1
- import { authApi } from './chunk-BDIQSTES.js';
1
+ import { authApi } from './chunk-ES4UOD62.js';
2
2
 
3
3
  // src/core/guards.ts
4
4
  function isMfaChallengeResponse(response) {
@@ -14,6 +14,26 @@ function getAvailableMethods(response) {
14
14
  return response.availableMethods ?? ["totp"];
15
15
  }
16
16
 
17
+ // src/core/errors.ts
18
+ var RoleDeniedError = class _RoleDeniedError extends Error {
19
+ constructor(user, requiredRoles, redirectTo) {
20
+ const userRoles = user.roles?.join(", ") || user.role || "none";
21
+ super(
22
+ `User "${user.username}" does not have required role(s). Has: [${userRoles}], Required: [${requiredRoles.join(", ")}]`
23
+ );
24
+ this.name = "RoleDeniedError";
25
+ this.user = user;
26
+ this.requiredRoles = requiredRoles;
27
+ this.redirectTo = redirectTo;
28
+ if (Error.captureStackTrace) {
29
+ Error.captureStackTrace(this, _RoleDeniedError);
30
+ }
31
+ }
32
+ };
33
+ function isRoleDeniedError(error) {
34
+ return error instanceof RoleDeniedError || error?.name === "RoleDeniedError";
35
+ }
36
+
17
37
  // src/core/service.ts
18
38
  var AuthService = class {
19
39
  // ============================================================================
@@ -27,9 +47,20 @@ var AuthService = class {
27
47
  */
28
48
  async login(credentials, options) {
29
49
  const response = await authApi.login(credentials);
50
+ if (options?.allowedRoles?.length && !isMfaChallengeResponse(response)) {
51
+ const userRoles = response.user.roles || (response.user.role ? [response.user.role] : []);
52
+ const hasAllowedRole = options.allowedRoles.some((r) => userRoles.includes(r));
53
+ if (!hasAllowedRole) {
54
+ throw new RoleDeniedError(
55
+ response.user,
56
+ options.allowedRoles,
57
+ options.onRoleDeniedRedirect
58
+ );
59
+ }
60
+ }
30
61
  if (options?.autoSetAuth !== false && !isMfaChallengeResponse(response)) {
31
62
  try {
32
- const { authStore } = await import('./auth.svelte-LJJ7MGDE.js');
63
+ const { authStore } = await import('./auth.svelte-DTSHZMJ4.js');
33
64
  authStore.setAuth(
34
65
  response.accessToken,
35
66
  response.refreshToken,
@@ -202,9 +233,20 @@ var AuthService = class {
202
233
  */
203
234
  async verifyMFAChallenge(data, options) {
204
235
  const response = await authApi.verifyMFAChallenge(data);
236
+ if (options?.allowedRoles?.length) {
237
+ const userRoles = response.user.roles || (response.user.role ? [response.user.role] : []);
238
+ const hasAllowedRole = options.allowedRoles.some((r) => userRoles.includes(r));
239
+ if (!hasAllowedRole) {
240
+ throw new RoleDeniedError(
241
+ response.user,
242
+ options.allowedRoles,
243
+ options.onRoleDeniedRedirect
244
+ );
245
+ }
246
+ }
205
247
  if (options?.autoSetAuth !== false) {
206
248
  try {
207
- const { authStore } = await import('./auth.svelte-LJJ7MGDE.js');
249
+ const { authStore } = await import('./auth.svelte-DTSHZMJ4.js');
208
250
  authStore.setAuth(
209
251
  response.accessToken,
210
252
  response.refreshToken,
@@ -303,4 +345,67 @@ var AuthService = class {
303
345
  };
304
346
  var authService = new AuthService();
305
347
 
306
- export { AuthService, authService, getAvailableMethods, getMfaToken, isLoginSuccessResponse, isMfaChallengeResponse };
348
+ // src/core/user-utils.ts
349
+ function getDisplayName(user) {
350
+ if (!user) return "Unknown";
351
+ if (user.firstName && user.lastName) {
352
+ return `${user.firstName} ${user.lastName}`;
353
+ }
354
+ return user.firstName || user.username || user.email || "Unknown";
355
+ }
356
+ function getUserInitials(user, maxLength = 2) {
357
+ const name = getDisplayName(user);
358
+ if (name === "Unknown") {
359
+ return "?";
360
+ }
361
+ return name.split(" ").filter((part) => part.length > 0).map((part) => part[0]).join("").toUpperCase().slice(0, maxLength);
362
+ }
363
+ function getAvatarFallback(user) {
364
+ return getUserInitials(user);
365
+ }
366
+ function getUserEmail(user, masked = false) {
367
+ if (!user?.email) return "";
368
+ if (!masked) {
369
+ return user.email;
370
+ }
371
+ const [local, domain] = user.email.split("@");
372
+ if (!domain) return user.email;
373
+ const maskedLocal = local.length > 1 ? local[0] + "***" : local;
374
+ return `${maskedLocal}@${domain}`;
375
+ }
376
+ function getGreeting(user, includeTime = true) {
377
+ const name = user?.firstName || getDisplayName(user);
378
+ if (!includeTime) {
379
+ return `Hello, ${name}`;
380
+ }
381
+ const hour = (/* @__PURE__ */ new Date()).getHours();
382
+ let timeGreeting;
383
+ if (hour < 12) {
384
+ timeGreeting = "Good morning";
385
+ } else if (hour < 17) {
386
+ timeGreeting = "Good afternoon";
387
+ } else {
388
+ timeGreeting = "Good evening";
389
+ }
390
+ return `${timeGreeting}, ${name}`;
391
+ }
392
+ function formatUserRoles(user, options = {}) {
393
+ if (!user) return "";
394
+ const { max, separator = ", ", lowercase = false, capitalize = true } = options;
395
+ const roles = user.roles || (user.role ? [user.role] : []);
396
+ if (roles.length === 0) return "";
397
+ const formatRole = (role) => {
398
+ if (lowercase) return role.toLowerCase();
399
+ if (capitalize) return role.charAt(0).toUpperCase() + role.slice(1).toLowerCase();
400
+ return role;
401
+ };
402
+ const formatted = roles.map(formatRole);
403
+ if (max && formatted.length > max) {
404
+ const shown = formatted.slice(0, max);
405
+ const remaining = formatted.length - max;
406
+ return `${shown.join(separator)} +${remaining} more`;
407
+ }
408
+ return formatted.join(separator);
409
+ }
410
+
411
+ export { AuthService, RoleDeniedError, authService, formatUserRoles, getAvailableMethods, getAvatarFallback, getDisplayName, getGreeting, getMfaToken, getUserEmail, getUserInitials, isLoginSuccessResponse, isMfaChallengeResponse, isRoleDeniedError };
@@ -0,0 +1,216 @@
1
+ import { authService } from './chunk-XSQYERC6.js';
2
+ import { authStore, currentUser, isAuthenticated, authActions } from './chunk-DSNTNK6T.js';
3
+ import { isInitialized, initAuth } from './chunk-DCGC6CNV.js';
4
+
5
+ // src/svelte/guards/auth-guard.ts
6
+ function checkAuth(options = {}) {
7
+ const { roles, permissions, requireAllRoles, requireAllPermissions } = options;
8
+ if (!authStore.isAuthenticated) {
9
+ return {
10
+ allowed: false,
11
+ reason: "not_authenticated",
12
+ redirectTo: "/login"
13
+ };
14
+ }
15
+ if (roles && roles.length > 0) {
16
+ const hasRoles = requireAllRoles ? authStore.hasAllRoles(roles) : authStore.hasAnyRole(roles);
17
+ if (!hasRoles) {
18
+ return {
19
+ allowed: false,
20
+ reason: "missing_role",
21
+ redirectTo: "/unauthorized"
22
+ };
23
+ }
24
+ }
25
+ if (permissions && permissions.length > 0) {
26
+ const hasPermissions = requireAllPermissions ? authStore.hasAllPermissions(permissions) : authStore.hasAnyPermission(permissions);
27
+ if (!hasPermissions) {
28
+ return {
29
+ allowed: false,
30
+ reason: "missing_permission",
31
+ redirectTo: "/unauthorized"
32
+ };
33
+ }
34
+ }
35
+ return { allowed: true };
36
+ }
37
+ function createAuthGuard(options = {}) {
38
+ return (onDenied) => {
39
+ const result = checkAuth(options);
40
+ if (!result.allowed) {
41
+ onDenied(result.redirectTo ?? "/login", result.reason ?? "not_authenticated");
42
+ return false;
43
+ }
44
+ return true;
45
+ };
46
+ }
47
+ function requireAuth() {
48
+ return checkAuth();
49
+ }
50
+ function requireRole(roles, requireAll = false) {
51
+ const roleArray = Array.isArray(roles) ? roles : [roles];
52
+ return checkAuth({ roles: roleArray, requireAllRoles: requireAll });
53
+ }
54
+ function requirePermission(permissions, requireAll = false) {
55
+ const permArray = Array.isArray(permissions) ? permissions : [permissions];
56
+ return checkAuth({ permissions: permArray, requireAllPermissions: requireAll });
57
+ }
58
+ function protectedLoad(options, loadFn) {
59
+ return async (event) => {
60
+ const result = checkAuth(options);
61
+ if (!result.allowed) {
62
+ return { redirect: result.redirectTo ?? "/login" };
63
+ }
64
+ return loadFn(event);
65
+ };
66
+ }
67
+
68
+ // src/svelte/client.ts
69
+ function createAuthClient(options) {
70
+ const { autoRehydrate = true, ...config } = options;
71
+ if (!isInitialized()) {
72
+ initAuth(config);
73
+ }
74
+ if (autoRehydrate && typeof window !== "undefined") {
75
+ authStore.rehydrate();
76
+ }
77
+ return {
78
+ authStore,
79
+ authActions,
80
+ authService,
81
+ isAuthenticated,
82
+ currentUser
83
+ };
84
+ }
85
+
86
+ // src/svelte/utils/nav-filter.ts
87
+ function filterByAccess(items, user, options = {}) {
88
+ const {
89
+ requireAllRoles = false,
90
+ requireAllPermissions = false,
91
+ hideUnrestrictedForGuests = false
92
+ } = options;
93
+ const userRoles = user?.roles || (user?.role ? [user.role] : []);
94
+ const userPerms = user?.permissions || [];
95
+ return items.filter((item) => {
96
+ const hasRoleRestriction = item.roles && item.roles.length > 0;
97
+ const hasPermRestriction = item.permissions && item.permissions.length > 0;
98
+ if (!hasRoleRestriction && !hasPermRestriction) {
99
+ if (hideUnrestrictedForGuests && !user) {
100
+ return false;
101
+ }
102
+ return true;
103
+ }
104
+ if (!user) {
105
+ return false;
106
+ }
107
+ if (hasRoleRestriction) {
108
+ const hasRoles = requireAllRoles ? item.roles.every((r) => userRoles.includes(r)) : item.roles.some((r) => userRoles.includes(r));
109
+ if (!hasRoles) {
110
+ return false;
111
+ }
112
+ }
113
+ if (hasPermRestriction) {
114
+ const hasPerms = requireAllPermissions ? item.permissions.every((p) => userPerms.includes(p)) : item.permissions.some((p) => userPerms.includes(p));
115
+ if (!hasPerms) {
116
+ return false;
117
+ }
118
+ }
119
+ return true;
120
+ });
121
+ }
122
+ function filterNavSections(sections, user, options = {}) {
123
+ return sections.map((section) => ({
124
+ ...section,
125
+ items: filterByAccess(section.items, user, options)
126
+ })).filter((section) => section.items.length > 0);
127
+ }
128
+ function canAccess(item, user, options = {}) {
129
+ return filterByAccess([item], user, options).length > 0;
130
+ }
131
+ function createNavFilter(defaultOptions = {}) {
132
+ return (items, user, options) => {
133
+ return filterByAccess(items, user, { ...defaultOptions, ...options });
134
+ };
135
+ }
136
+
137
+ // src/svelte/hooks/auth-hook.ts
138
+ function createAuthHook(options = {}) {
139
+ const {
140
+ loginPath = "/login",
141
+ publicRoutes = [/^\/auth\//, /^\/api\/auth\//],
142
+ protectedRoutes,
143
+ cookieName = "classic_auth",
144
+ headerName,
145
+ redirectParam = "redirect",
146
+ isAuthenticated: customIsAuthenticated,
147
+ onUnauthenticated,
148
+ onAuthCheck
149
+ } = options;
150
+ return async ({
151
+ event,
152
+ resolve
153
+ }) => {
154
+ const { pathname } = event.url;
155
+ const isPublic = publicRoutes.some((r) => r.test(pathname));
156
+ if (isPublic) {
157
+ return resolve(event);
158
+ }
159
+ if (protectedRoutes && !protectedRoutes.some((r) => r.test(pathname))) {
160
+ return resolve(event);
161
+ }
162
+ let authenticated;
163
+ if (customIsAuthenticated) {
164
+ authenticated = await customIsAuthenticated(event);
165
+ } else {
166
+ const hasCookie = !!event.cookies.get(cookieName);
167
+ const hasHeader = headerName ? !!event.request.headers.get(headerName) : false;
168
+ authenticated = hasCookie || hasHeader;
169
+ }
170
+ onAuthCheck?.(event, authenticated);
171
+ if (!authenticated) {
172
+ if (onUnauthenticated) {
173
+ return onUnauthenticated(event);
174
+ }
175
+ const redirectUrl = encodeURIComponent(pathname + event.url.search);
176
+ const loginUrl = new URL(loginPath, event.url.origin);
177
+ loginUrl.searchParams.set(redirectParam, redirectUrl);
178
+ return new Response(null, {
179
+ status: 302,
180
+ headers: { Location: loginUrl.pathname + loginUrl.search }
181
+ });
182
+ }
183
+ return resolve(event);
184
+ };
185
+ }
186
+ function matchesRoute(pathname, patterns) {
187
+ return patterns.some((pattern) => pattern.test(pathname));
188
+ }
189
+ var routePatterns = {
190
+ /** Match all /auth/* routes */
191
+ auth: /^\/auth\//,
192
+ /** Match all /api/* routes */
193
+ api: /^\/api\//,
194
+ /** Match all /api/auth/* routes */
195
+ apiAuth: /^\/api\/auth\//,
196
+ /** Match all /public/* routes */
197
+ public: /^\/public\//,
198
+ /** Match all /admin/* routes */
199
+ admin: /^\/admin\//,
200
+ /** Match exact root path */
201
+ root: /^\/$/,
202
+ /** Match health check endpoints */
203
+ health: /^\/(api\/)?(health|healthz|ready|live)\/?$/,
204
+ /**
205
+ * Create a pattern for a specific path prefix.
206
+ * @param prefix - Path prefix (e.g., '/dashboard')
207
+ */
208
+ prefix: (prefix) => new RegExp(`^${prefix.replace(/\//g, "\\/")}`),
209
+ /**
210
+ * Create a pattern for exact path match.
211
+ * @param path - Exact path (e.g., '/about')
212
+ */
213
+ exact: (path) => new RegExp(`^${path.replace(/\//g, "\\/")}$`)
214
+ };
215
+
216
+ export { canAccess, checkAuth, createAuthClient, createAuthGuard, createAuthHook, createNavFilter, filterByAccess, filterNavSections, matchesRoute, protectedLoad, requireAuth, requirePermission, requireRole, routePatterns };