@getstrata/bootstrap 0.2.48 → 0.2.50

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/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # @getstrata/bootstrap changelog
2
+
3
+ ## 0.2.50
4
+
5
+ - `createCookieSessionAuthManager` / `CookieSessionGuard` turn `CookieSessionStore` into an `AuthGuard` / `AuthManager` for `CORE_AUTH_TOKEN`. Supply a `mapUser` mapper; WorkHub abilities are not baked in. Omit `sql` to read the client from `bindDatabaseConnection()` on every call.
6
+ - `checkDatabase()` / `createHealthRoutes()` ping the bound database client, not WorkHub’s private connection holder. `/health` stays `{ status: "ok" }` unless `{ pingOnHealth: true }`. Extra JSON fields are optional.
7
+ - `assertProductionSecrets()` is feature-gated. A production HTMX app with `SESSION_SECRET` (32+), `AUTH_DEV_HEADERS=false`, and feature flags off does not need WorkHub API tokens. WorkHub’s current production env (rotated tokens, CORS, pepper, expiry) still passes.
8
+ - Re-exports `CORE_ABILITY_CHECKER_TOKEN` and `CORE_AUTH_USER_DIRECTORY_TOKEN`.
package/README.md CHANGED
@@ -28,7 +28,11 @@ import {
28
28
  import { createHttpKernel, createAppContext, coreProviders } from "@getstrata/bootstrap";
29
29
  ```
30
30
 
31
- `assertProductionSecrets` (`@getstrata/bootstrap/secretsGuard`) is env-only. It still rejects the well-known WorkHub test tokens.
31
+ `createAppContext()` does not call `assertProductionSecrets`. WorkHub calls that from `App.serve()` / `queue:work`. Sibling HTMX apps can call it in production without WorkHub API tokens when feature flags are off — see [SIBLING-HTMX.md](../../docs/SIBLING-HTMX.md).
32
+
33
+ Sibling HTMX apps should bind `createCookieSessionAuthManager` from `@getstrata/bootstrap/web/session` instead of HMAC `SessionGuard`. `CookieSessionStore` reads optional `is_admin` from the user row for `wrapWebGlobalAdmin`. Pass `mapUser` to map `is_admin` / `learn_subscriber` onto `AuthUser.role`.
34
+
35
+ `wrapWeb` applies the web group (CSRF + flash) and `withErrorHandling`, so CSRF `ForbiddenError` becomes an HTML 403 in `FRONTEND_MODE=server-htmx`. `wrapWebLogin` / `wrapWebRegister` include that web group plus throttle — do not wrap them with `wrapWeb` again.
32
36
 
33
37
  These subpaths remain WorkHub-oriented and are not a generic starter API: `@getstrata/bootstrap/createRoutes` (includes SCIM), `@getstrata/bootstrap/schedule`, and `@getstrata/bootstrap/createWebRoutes` (redirects `/` to `/organizations`).
34
38
 
@@ -4,7 +4,7 @@ declare const CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
4
4
  declare const CACHE_DRIVER_CONFIG_KEY = "cache.driver";
5
5
  declare const REDIS_URL_CONFIG_KEY = "cache.redisUrl";
6
6
  declare const DATABASE_URL_CONFIG_KEY = "database.url";
7
- export { CORE_AUTH_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, } from "@getstrata/core/contracts/serviceTokens";
7
+ export { CORE_ABILITY_CHECKER_TOKEN, CORE_AUTH_TOKEN, CORE_AUTH_USER_DIRECTORY_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, } from "@getstrata/core/contracts/serviceTokens";
8
8
  declare const AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
9
9
  declare const DEFAULT_APP_PORT = 3000;
10
10
  declare const DEFAULT_CACHE_TTL_MS = 3600000;
@@ -1,8 +1,14 @@
1
1
  import type { AppDependencies } from "./contracts";
2
+ interface CreateHealthRoutesOptions {
3
+ pingOnHealth?: boolean;
4
+ extra?: Record<string, unknown> | (() => Record<string, unknown> | Promise<Record<string, unknown>>);
5
+ }
2
6
  declare function checkDatabase(): Promise<boolean>;
7
+ declare function pingDatabase(): Promise<boolean>;
3
8
  declare function checkRedis(redisUrl: string): Promise<boolean>;
4
- declare function createHealthRoutes(dependencies: AppDependencies): {
9
+ declare function createHealthRoutes(dependencies: AppDependencies, options?: CreateHealthRoutesOptions): {
5
10
  "/health": () => Promise<Response>;
6
11
  "/ready": () => Promise<Response>;
7
12
  };
8
- export { checkDatabase, checkRedis, createHealthRoutes };
13
+ export type { CreateHealthRoutesOptions };
14
+ export { checkDatabase, checkRedis, createHealthRoutes, pingDatabase };
@@ -9,7 +9,7 @@ export { buildModuleRoutes } from "./buildModuleRoutes.ts";
9
9
  export type { BuildWebModuleRoutesOptions } from "./buildWebModuleRoutes.ts";
10
10
  export { buildWebModuleRoutes } from "./buildWebModuleRoutes.ts";
11
11
  export { cacheTagsForModelWrite, discoverModelTableNames } from "./cache/modelCacheTags.ts";
12
- export { APP_PORT_CONFIG_KEY, CORE_AUTH_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, DATABASE_URL_CONFIG_KEY, DEFAULT_APP_PORT, REDIS_URL_CONFIG_KEY, } from "./config.ts";
12
+ export { APP_PORT_CONFIG_KEY, CORE_ABILITY_CHECKER_TOKEN, CORE_AUTH_TOKEN, CORE_AUTH_USER_DIRECTORY_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, DATABASE_URL_CONFIG_KEY, DEFAULT_APP_PORT, REDIS_URL_CONFIG_KEY, } from "./config.ts";
13
13
  export { collectProviders, createAppContext, runProviderPhase } from "./context.ts";
14
14
  export type { AppContext, AppDependencies, AppModule, AppRouteMap, CachedJson, ConfigStore, ModuleRouteContext, MutableAppDependencies, ProviderContext, ServiceFactory, ServiceProvider, } from "./contracts.ts";
15
15
  export { assertAppDependenciesComplete, getRequiredDependency, resolveService, ServiceContainer, } from "./contracts.ts";
@@ -17,6 +17,8 @@ export { createWebRoutes, mergeWebRoutes } from "./createWebRoutes.ts";
17
17
  export { createAppDependencies } from "./dependencies.ts";
18
18
  export type { DiscoverModulesOptions } from "./discoverModules.ts";
19
19
  export { configureModulesDirectory, discoverModules, ensureModulesLoaded, } from "./discoverModules.ts";
20
+ export type { CreateHealthRoutesOptions } from "./health.ts";
21
+ export { checkDatabase, checkRedis, createHealthRoutes, pingDatabase } from "./health.ts";
20
22
  export { type RouteModelAuthorization, securedBindRouteModel, securedBindRouteModelByKey, } from "./http/securedRouteModelBinding.ts";
21
23
  export { createHttpKernel, type HttpKernel, type MiddlewareGroupName } from "./httpKernel.ts";
22
24
  export { resolveMembershipService } from "./membershipService.ts";
@@ -25,4 +27,4 @@ export { coreProviders } from "./providers/index.ts";
25
27
  export { registerDefaultJobs } from "./queue/defaultJobs.ts";
26
28
  export { RouteRegistry, routeRegistry } from "./routeRegistry.ts";
27
29
  export { assertProductionSecrets } from "./secretsGuard.ts";
28
- export { CookieSessionStore, createCsrfProtection, createRouteKernel, createWebServer, type ParsedForm, parseFormBody, routeParams, type SessionUser, slugify, toRouteRequest, type WebServerOptions, wrapSecuredRouteModelByKey, wrapWebLogin, wrapWebRegister, } from "./web/index.ts";
30
+ export { CookieSessionGuard, CookieSessionStore, createCookieSessionAuthManager, createCsrfProtection, createRouteKernel, createWebServer, type MapSessionUser, type ParsedForm, parseFormBody, routeParams, type SessionUser, slugify, toRouteRequest, type WebServerOptions, wrapSecuredRouteModelByKey, wrapWebLogin, wrapWebRegister, } from "./web/index.ts";
@@ -4,5 +4,5 @@
4
4
  export { createCsrfProtection, type ParsedForm, parseFormBody } from "./forms.ts";
5
5
  export { createRouteKernel, routeParams, toRouteRequest, wrapSecuredRouteModelByKey, wrapWebLogin, wrapWebRegister, } from "./routing.ts";
6
6
  export { convertAppRoutesToBunRoutes, createWebServer, type WebServerOptions } from "./server.ts";
7
- export { CookieSessionStore, type SessionUser } from "./session.ts";
7
+ export { CookieSessionGuard, CookieSessionStore, createCookieSessionAuthManager, type MapSessionUser, type SessionUser, } from "./session.ts";
8
8
  export { slugify } from "./slug.ts";
@@ -14,9 +14,9 @@ export declare function wrapSecuredRouteModelByKey<TParams extends Record<string
14
14
  resource: string;
15
15
  action: "view" | "create" | "update" | "delete";
16
16
  }, handler: (request: RouteRequest<TParams>, model: TModel) => Response | Promise<Response>): RouteHandler;
17
- /** Converts kernel login throttle 429 JSON into an HTML response for web forms. */
17
+ /** Web group (CSRF + flash + HTML errors) plus login throttle. Do not wrap with wrapWeb again. */
18
18
  export declare function wrapWebLogin(kernel: HttpKernel, handler: RouteHandler, onThrottled: (request: Request) => Response | Promise<Response>): RouteHandler;
19
- /** Converts kernel register throttle 429 JSON into an HTML response for web forms. */
19
+ /** Web group (CSRF + flash + HTML errors) plus register throttle. Do not wrap with wrapWeb again. */
20
20
  export declare function wrapWebRegister(kernel: HttpKernel, handler: RouteHandler, onThrottled: (request: Request) => Response | Promise<Response>): RouteHandler;
21
21
  /** Convenience alias: HttpKernel is the Laravel-style router middleware wrapper. */
22
22
  export declare function createRouteKernel(dependencies: AppDependencies): HttpKernel;
@@ -1,3 +1,5 @@
1
+ import type { AuthUser } from "@getstrata/core/auth/authContext";
2
+ import { type AuthGuard, AuthManager } from "@getstrata/core/auth/guard";
1
3
  export interface SessionUser {
2
4
  id: number;
3
5
  name: string;
@@ -8,18 +10,38 @@ export interface SessionUser {
8
10
  type SqlClient = {
9
11
  unsafe<T>(query: string, params?: readonly unknown[]): Promise<T[]>;
10
12
  };
13
+ type SqlSource = SqlClient | (() => SqlClient);
14
+ declare function defaultSessionSql(): SqlClient;
15
+ export type MapSessionUser = (user: SessionUser) => AuthUser;
16
+ declare function defaultMapSessionUser(user: SessionUser): AuthUser;
11
17
  export declare class CookieSessionStore {
12
- private readonly sql;
18
+ private readonly sqlSource;
13
19
  private readonly secret;
14
20
  private readonly cookieName;
15
21
  private readonly maxAgeSeconds;
16
- constructor(sql: SqlClient, secret: string, cookieName?: string, maxAgeSeconds?: number);
22
+ constructor(sqlSource: SqlSource, secret: string, cookieName?: string, maxAgeSeconds?: number);
17
23
  cookieHeader(_user: SessionUser, sessionId: string): string;
18
24
  clearCookieHeader(): string;
19
25
  private withSecureFlag;
26
+ private sql;
20
27
  create(user: SessionUser): Promise<string>;
21
28
  destroy(sessionId: string): Promise<void>;
22
29
  read(request: Request): Promise<SessionUser | null>;
23
30
  private sign;
24
31
  }
25
- export {};
32
+ export declare class CookieSessionGuard implements AuthGuard {
33
+ private readonly store;
34
+ private readonly mapUser;
35
+ constructor(store: CookieSessionStore, mapUser?: MapSessionUser);
36
+ resolve(request: Request): Promise<AuthUser | null>;
37
+ }
38
+ export interface CreateCookieSessionAuthManagerOptions {
39
+ store?: CookieSessionStore;
40
+ sql?: SqlSource;
41
+ secret?: string;
42
+ cookieName?: string;
43
+ maxAgeSeconds?: number;
44
+ mapUser?: MapSessionUser;
45
+ }
46
+ declare function createCookieSessionAuthManager(options?: CreateCookieSessionAuthManagerOptions): AuthManager;
47
+ export { createCookieSessionAuthManager, defaultMapSessionUser, defaultSessionSql };
@@ -7,6 +7,7 @@ import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
7
7
 
8
8
  // ../../src/bootstrap/httpKernel.ts
9
9
  import { createMembershipMiddleware } from "@getstrata/core/auth/membershipMiddleware";
10
+ import { resolveAbilityChecker } from "@getstrata/core/contracts/serviceTokens";
10
11
  import { createAuthMiddleware } from "@getstrata/core/http/authMiddleware";
11
12
  import { createAuthorizeMiddleware } from "@getstrata/core/http/authorizeMiddleware";
12
13
  import { createBodySizeLimitMiddleware } from "@getstrata/core/http/bodySizeLimitMiddleware";
@@ -21,6 +22,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
21
22
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
22
23
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
23
24
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
25
+ import { withErrorHandling } from "@getstrata/core/http/response";
24
26
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
25
27
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
26
28
  import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
@@ -66,24 +68,38 @@ function parsePositiveInt(value, fallback) {
66
68
  }
67
69
  return Math.trunc(parsed);
68
70
  }
71
+ function parseWindowSeconds(secondsValue, msValue, fallback) {
72
+ if (secondsValue !== undefined && secondsValue.trim() !== "") {
73
+ return parsePositiveInt(secondsValue, fallback);
74
+ }
75
+ if (msValue !== undefined && msValue.trim() !== "") {
76
+ const parsedMs = Number(msValue);
77
+ if (Number.isFinite(parsedMs) && parsedMs > 0) {
78
+ return Math.max(1, Math.trunc(parsedMs / 1000));
79
+ }
80
+ }
81
+ return fallback;
82
+ }
69
83
  function resolveLoginRateLimit() {
70
84
  const defaults = isLocalAppEnv() ? LOCAL_LOGIN_RATE_LIMIT : PRODUCTION_LOGIN_RATE_LIMIT;
71
85
  return {
72
86
  maxAttempts: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
73
- decaySeconds: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS, defaults.decaySeconds)
87
+ decaySeconds: parseWindowSeconds(process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS, process.env.LOGIN_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
74
88
  };
75
89
  }
76
90
  function resolveRegisterRateLimit() {
77
91
  const defaults = isLocalAppEnv() ? { maxAttempts: 100, decaySeconds: 60 } : { maxAttempts: 10, decaySeconds: 60 };
78
92
  return {
79
93
  maxAttempts: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
80
- decaySeconds: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_WINDOW_SECONDS ?? process.env.REGISTER_RATE_LIMIT_WINDOW_MS ? String(Number(process.env.REGISTER_RATE_LIMIT_WINDOW_MS) / 1000) : undefined, defaults.decaySeconds)
94
+ decaySeconds: parseWindowSeconds(process.env.REGISTER_RATE_LIMIT_WINDOW_SECONDS, process.env.REGISTER_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
81
95
  };
82
96
  }
83
97
 
84
98
  // ../../src/bootstrap/config.ts
85
99
  import {
100
+ CORE_ABILITY_CHECKER_TOKEN,
86
101
  CORE_AUTH_TOKEN,
102
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
87
103
  CORE_CACHE_TOKEN,
88
104
  CORE_CONFIG_TOKEN,
89
105
  CORE_EVENT_BUS_TOKEN,
@@ -173,7 +189,7 @@ class HttpKernel {
173
189
  return this.wrap(["api", "authenticated"], handler);
174
190
  }
175
191
  wrapWeb(handler) {
176
- return this.wrap("web", handler);
192
+ return withErrorHandling(this.wrap("web", handler));
177
193
  }
178
194
  wrapWebPublicRead(handler) {
179
195
  if (isPublicReadsEnabled()) {
@@ -183,19 +199,19 @@ class HttpKernel {
183
199
  }
184
200
  wrapWebAuthenticated(handler) {
185
201
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
186
- return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
202
+ return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
187
203
  }
188
204
  wrapWebAbility(ability, handler) {
189
205
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
190
- const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
206
+ const abilityChecker = resolveAbilityChecker(this.dependencies.container);
191
207
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
192
208
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
193
- return this.wrap("web", withMiddleware(...middleware)(handler));
209
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
194
210
  }
195
211
  wrapWebGlobalAdmin(handler) {
196
212
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
197
213
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
198
- return this.wrap("web", withMiddleware(...middleware)(handler));
214
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
199
215
  }
200
216
  wrapAuthenticated(handler) {
201
217
  return this.wrap("authenticated", handler);
@@ -211,7 +227,7 @@ class HttpKernel {
211
227
  return withMiddleware(...middleware)(handler);
212
228
  }
213
229
  wrapAbility(ability, handler) {
214
- const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
230
+ const abilityChecker = resolveAbilityChecker(this.dependencies.container);
215
231
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
216
232
  const middleware = [...this.group("authenticated"), requireAbility(ability)];
217
233
  return withMiddleware(...middleware)(handler);
@@ -10,6 +10,7 @@ import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
10
10
 
11
11
  // ../../src/bootstrap/httpKernel.ts
12
12
  import { createMembershipMiddleware } from "@getstrata/core/auth/membershipMiddleware";
13
+ import { resolveAbilityChecker } from "@getstrata/core/contracts/serviceTokens";
13
14
  import { createAuthMiddleware } from "@getstrata/core/http/authMiddleware";
14
15
  import { createAuthorizeMiddleware } from "@getstrata/core/http/authorizeMiddleware";
15
16
  import { createBodySizeLimitMiddleware } from "@getstrata/core/http/bodySizeLimitMiddleware";
@@ -24,6 +25,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
24
25
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
25
26
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
26
27
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
28
+ import { withErrorHandling } from "@getstrata/core/http/response";
27
29
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
28
30
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
29
31
  import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
@@ -69,24 +71,38 @@ function parsePositiveInt(value, fallback) {
69
71
  }
70
72
  return Math.trunc(parsed);
71
73
  }
74
+ function parseWindowSeconds(secondsValue, msValue, fallback) {
75
+ if (secondsValue !== undefined && secondsValue.trim() !== "") {
76
+ return parsePositiveInt(secondsValue, fallback);
77
+ }
78
+ if (msValue !== undefined && msValue.trim() !== "") {
79
+ const parsedMs = Number(msValue);
80
+ if (Number.isFinite(parsedMs) && parsedMs > 0) {
81
+ return Math.max(1, Math.trunc(parsedMs / 1000));
82
+ }
83
+ }
84
+ return fallback;
85
+ }
72
86
  function resolveLoginRateLimit() {
73
87
  const defaults = isLocalAppEnv() ? LOCAL_LOGIN_RATE_LIMIT : PRODUCTION_LOGIN_RATE_LIMIT;
74
88
  return {
75
89
  maxAttempts: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
76
- decaySeconds: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS, defaults.decaySeconds)
90
+ decaySeconds: parseWindowSeconds(process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS, process.env.LOGIN_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
77
91
  };
78
92
  }
79
93
  function resolveRegisterRateLimit() {
80
94
  const defaults = isLocalAppEnv() ? { maxAttempts: 100, decaySeconds: 60 } : { maxAttempts: 10, decaySeconds: 60 };
81
95
  return {
82
96
  maxAttempts: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
83
- decaySeconds: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_WINDOW_SECONDS ?? process.env.REGISTER_RATE_LIMIT_WINDOW_MS ? String(Number(process.env.REGISTER_RATE_LIMIT_WINDOW_MS) / 1000) : undefined, defaults.decaySeconds)
97
+ decaySeconds: parseWindowSeconds(process.env.REGISTER_RATE_LIMIT_WINDOW_SECONDS, process.env.REGISTER_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
84
98
  };
85
99
  }
86
100
 
87
101
  // ../../src/bootstrap/config.ts
88
102
  import {
103
+ CORE_ABILITY_CHECKER_TOKEN,
89
104
  CORE_AUTH_TOKEN,
105
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
90
106
  CORE_CACHE_TOKEN,
91
107
  CORE_CONFIG_TOKEN,
92
108
  CORE_EVENT_BUS_TOKEN,
@@ -176,7 +192,7 @@ class HttpKernel {
176
192
  return this.wrap(["api", "authenticated"], handler);
177
193
  }
178
194
  wrapWeb(handler) {
179
- return this.wrap("web", handler);
195
+ return withErrorHandling(this.wrap("web", handler));
180
196
  }
181
197
  wrapWebPublicRead(handler) {
182
198
  if (isPublicReadsEnabled()) {
@@ -186,19 +202,19 @@ class HttpKernel {
186
202
  }
187
203
  wrapWebAuthenticated(handler) {
188
204
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
189
- return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
205
+ return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
190
206
  }
191
207
  wrapWebAbility(ability, handler) {
192
208
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
193
- const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
209
+ const abilityChecker = resolveAbilityChecker(this.dependencies.container);
194
210
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
195
211
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
196
- return this.wrap("web", withMiddleware(...middleware)(handler));
212
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
197
213
  }
198
214
  wrapWebGlobalAdmin(handler) {
199
215
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
200
216
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
201
- return this.wrap("web", withMiddleware(...middleware)(handler));
217
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
202
218
  }
203
219
  wrapAuthenticated(handler) {
204
220
  return this.wrap("authenticated", handler);
@@ -214,7 +230,7 @@ class HttpKernel {
214
230
  return withMiddleware(...middleware)(handler);
215
231
  }
216
232
  wrapAbility(ability, handler) {
217
- const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
233
+ const abilityChecker = resolveAbilityChecker(this.dependencies.container);
218
234
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
219
235
  const middleware = [...this.group("authenticated"), requireAbility(ability)];
220
236
  return withMiddleware(...middleware)(handler);
@@ -3,7 +3,9 @@ var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/config.ts
5
5
  import {
6
+ CORE_ABILITY_CHECKER_TOKEN,
6
7
  CORE_AUTH_TOKEN,
8
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
7
9
  CORE_CACHE_TOKEN,
8
10
  CORE_CONFIG_TOKEN,
9
11
  CORE_EVENT_BUS_TOKEN,
@@ -30,7 +32,9 @@ export {
30
32
  CACHE_DRIVER_CONFIG_KEY,
31
33
  CACHE_MAX_ENTRIES_CONFIG_KEY,
32
34
  CACHE_TTL_MS_CONFIG_KEY,
35
+ CORE_ABILITY_CHECKER_TOKEN,
33
36
  CORE_AUTH_TOKEN,
37
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
34
38
  CORE_CACHE_TOKEN,
35
39
  CORE_CONFIG_TOKEN,
36
40
  CORE_EVENT_BUS_TOKEN,
@@ -107,7 +107,9 @@ var authConfig = {
107
107
 
108
108
  // ../../src/bootstrap/config.ts
109
109
  import {
110
+ CORE_ABILITY_CHECKER_TOKEN,
110
111
  CORE_AUTH_TOKEN,
112
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
111
113
  CORE_CACHE_TOKEN,
112
114
  CORE_CONFIG_TOKEN,
113
115
  CORE_EVENT_BUS_TOKEN,
@@ -506,68 +508,6 @@ var coreProviders = [
506
508
  viewProvider
507
509
  ];
508
510
 
509
- // ../../src/bootstrap/secretsGuard.ts
510
- var DEFAULT_ADMIN_API_TOKEN = "workhub-admin-test-token";
511
- var DEFAULT_MEMBER_API_TOKEN = "workhub-member-test-token";
512
- var DEFAULT_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
513
- var DEFAULT_TOKENS = new Set([DEFAULT_ADMIN_API_TOKEN, DEFAULT_MEMBER_API_TOKEN]);
514
- var DEFAULT_SCIM_TOKENS = new Set([DEFAULT_SCIM_BEARER_TOKEN]);
515
- function isEnabled(value, defaultEnabled) {
516
- if (value === undefined) {
517
- return defaultEnabled;
518
- }
519
- return defaultEnabled ? value !== "false" : value === "true";
520
- }
521
- function assertProductionSecrets(env = process.env) {
522
- const appEnv = env.APP_ENV ?? "local";
523
- if (appEnv !== "production") {
524
- return;
525
- }
526
- const adminToken = env.ADMIN_API_TOKEN ?? DEFAULT_ADMIN_API_TOKEN;
527
- const memberToken = env.MEMBER_API_TOKEN ?? DEFAULT_MEMBER_API_TOKEN;
528
- const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
529
- const encryptionEnabled = isEnabled(env.FEATURE_FIELD_ENCRYPTION, true);
530
- const devHeadersEnabled = isEnabled(env.AUTH_DEV_HEADERS, true);
531
- if (devHeadersEnabled) {
532
- throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
533
- }
534
- if (DEFAULT_TOKENS.has(adminToken) || DEFAULT_TOKENS.has(memberToken)) {
535
- throw new Error("Production startup blocked: rotate ADMIN_API_TOKEN and MEMBER_API_TOKEN away from default WorkHub test values.");
536
- }
537
- if (DEFAULT_SCIM_TOKENS.has(scimToken)) {
538
- throw new Error("Production startup blocked: rotate SCIM_BEARER_TOKEN away from default WorkHub test values.");
539
- }
540
- if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
541
- throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
542
- }
543
- if (!env.SIEM_EXPORT_URL?.trim() && isEnabled(env.FEATURE_SIEM_EXPORT, true)) {
544
- console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
545
- }
546
- if (isEnabled(env.FEATURE_BILLING, true) && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
547
- throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
548
- }
549
- const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
550
- if (corsOrigins.includes("*")) {
551
- throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
552
- }
553
- if (isEnabled(env.FEATURE_PUBLIC_READS, true)) {
554
- throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
555
- }
556
- if (!env.OAUTH_STATE_SECRET?.trim()) {
557
- throw new Error("Production startup blocked: set OAUTH_STATE_SECRET for OAuth CSRF protection.");
558
- }
559
- if (!env.TOKEN_HASH_PEPPER?.trim()) {
560
- throw new Error("Production startup blocked: set TOKEN_HASH_PEPPER for API token hashing.");
561
- }
562
- if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
563
- throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
564
- }
565
- const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
566
- if (frontendMode === "server-htmx" && !env.SESSION_SECRET?.trim()) {
567
- throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx.");
568
- }
569
- }
570
-
571
511
  // ../../src/bootstrap/context.ts
572
512
  function collectProviders(modules = discoverModules()) {
573
513
  return [...coreProviders, ...modules.flatMap((module) => module.providers ?? [])];
@@ -578,7 +518,6 @@ function runProviderPhase(providers, phase, context) {
578
518
  }
579
519
  }
580
520
  function createAppContext() {
581
- assertProductionSecrets();
582
521
  const container = new ServiceContainer;
583
522
  const config = new ConfigStore;
584
523
  const dependencies = {