@getstrata/bootstrap 0.2.47 → 0.2.49

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.
@@ -2,15 +2,41 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/metricsRoutes.ts
5
+ import { timingSafeEqual } from "crypto";
5
6
  import { prometheusRegistry } from "@getstrata/core/metrics/prometheus";
7
+ function tokensMatch(left, right) {
8
+ const leftBuffer = Buffer.from(left);
9
+ const rightBuffer = Buffer.from(right);
10
+ if (leftBuffer.length !== rightBuffer.length) {
11
+ return false;
12
+ }
13
+ return timingSafeEqual(leftBuffer, rightBuffer);
14
+ }
15
+ function authorizeMetrics(request) {
16
+ const expected = process.env.METRICS_TOKEN?.trim();
17
+ const authorization = request.headers.get("authorization") ?? "";
18
+ const presented = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : "";
19
+ if (expected) {
20
+ return presented.length > 0 && tokensMatch(presented, expected);
21
+ }
22
+ if ((process.env.APP_ENV ?? "local") === "production") {
23
+ return false;
24
+ }
25
+ return true;
26
+ }
6
27
  function createMetricsRoutes() {
7
28
  return {
8
- "/metrics": async () => new Response(prometheusRegistry.renderMetrics(), {
9
- status: 200,
10
- headers: {
11
- "content-type": "text/plain; version=0.0.4; charset=utf-8"
29
+ "/metrics": async (request) => {
30
+ if (!authorizeMetrics(request)) {
31
+ return new Response("Not Found", { status: 404 });
12
32
  }
13
- })
33
+ return new Response(prometheusRegistry.renderMetrics(), {
34
+ status: 200,
35
+ headers: {
36
+ "content-type": "text/plain; version=0.0.4; charset=utf-8"
37
+ }
38
+ });
39
+ }
14
40
  };
15
41
  }
16
42
  export {
@@ -38,7 +38,7 @@ var viewProvider = {
38
38
  }
39
39
  };
40
40
  export {
41
- viewProvider,
41
+ CORE_VIEW_TOKEN,
42
42
  VIEW_DIRECTORY_CONFIG_KEY,
43
- CORE_VIEW_TOKEN
43
+ viewProvider
44
44
  };
@@ -293,7 +293,7 @@ function resolveModulesDirectory(options) {
293
293
  if (state.configuredModulesDir) {
294
294
  return state.configuredModulesDir;
295
295
  }
296
- return join2(import.meta.dir, "../modules");
296
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
297
297
  }
298
298
  async function loadDiscoveredModules(options) {
299
299
  const modulesDirectory = resolveModulesDirectory(options);
@@ -327,6 +327,12 @@ async function ensureModulesLoaded(options) {
327
327
  function discoverModules() {
328
328
  return readDiscoverModulesState().appModules;
329
329
  }
330
+ function resetDiscoverModulesForTests() {
331
+ const state = readDiscoverModulesState();
332
+ state.configuredModulesDir = undefined;
333
+ state.appModules.length = 0;
334
+ state.modulesReady = undefined;
335
+ }
330
336
 
331
337
  // ../../src/bootstrap/cache/modelCacheTags.ts
332
338
  function cacheTagsForModelWrite(tableName, action) {
@@ -26,6 +26,6 @@ function readSharedRouteRegistry() {
26
26
  }
27
27
  var routeRegistry = readSharedRouteRegistry();
28
28
  export {
29
- routeRegistry,
30
- RouteRegistry
29
+ RouteRegistry,
30
+ routeRegistry
31
31
  };
@@ -1,64 +1,28 @@
1
1
  // @bun
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
- // ../../src/config/app.ts
5
- var appConfig = {
6
- name: "WorkHub",
7
- env: process.env.APP_ENV ?? "local",
8
- debug: (process.env.APP_DEBUG ?? "true") !== "false",
9
- url: process.env.APP_URL ?? "http://localhost:3000",
10
- apiPrefix: process.env.API_PREFIX ?? "/api/v1"
11
- };
12
-
13
- // ../../src/config/features.ts
14
- function readFeatureFlags() {
15
- return {
16
- webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
17
- fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
18
- auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
19
- oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
20
- samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
21
- scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
22
- billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
23
- siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
24
- publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
25
- emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
26
- mfa: (process.env.FEATURE_MFA ?? "false") === "true"
27
- };
28
- }
29
- var featureFlags = readFeatureFlags();
30
- function isFeatureEnabled(feature) {
31
- return readFeatureFlags()[feature];
32
- }
33
-
34
- // ../../src/domain/auth.ts
35
- var TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
36
- var TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
37
-
38
- // ../../src/domain/scim.ts
39
- var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
40
- var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
41
- var SCIM_SCHEMAS = {
42
- user: "urn:ietf:params:scim:schemas:core:2.0:User",
43
- group: "urn:ietf:params:scim:schemas:core:2.0:Group",
44
- listResponse: "urn:ietf:params:scim:api:messages:2.0:ListResponse",
45
- patchOp: "urn:ietf:params:scim:api:messages:2.0:PatchOp",
46
- serviceProviderConfig: "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
47
- };
48
-
49
4
  // ../../src/bootstrap/secretsGuard.ts
50
- var DEFAULT_TOKENS = new Set([TEST_ADMIN_API_TOKEN, TEST_MEMBER_API_TOKEN]);
51
- var DEFAULT_SCIM_TOKENS = new Set([TEST_SCIM_BEARER_TOKEN, DEFAULT_SCIM_BEARER_TOKEN]);
5
+ var DEFAULT_ADMIN_API_TOKEN = "workhub-admin-test-token";
6
+ var DEFAULT_MEMBER_API_TOKEN = "workhub-member-test-token";
7
+ var DEFAULT_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
8
+ var DEFAULT_TOKENS = new Set([DEFAULT_ADMIN_API_TOKEN, DEFAULT_MEMBER_API_TOKEN]);
9
+ var DEFAULT_SCIM_TOKENS = new Set([DEFAULT_SCIM_BEARER_TOKEN]);
10
+ function isEnabled(value, defaultEnabled) {
11
+ if (value === undefined) {
12
+ return defaultEnabled;
13
+ }
14
+ return defaultEnabled ? value !== "false" : value === "true";
15
+ }
52
16
  function assertProductionSecrets(env = process.env) {
53
- const appEnv = env.APP_ENV ?? appConfig.env;
17
+ const appEnv = env.APP_ENV ?? "local";
54
18
  if (appEnv !== "production") {
55
19
  return;
56
20
  }
57
- const adminToken = env.ADMIN_API_TOKEN ?? TEST_ADMIN_API_TOKEN;
58
- const memberToken = env.MEMBER_API_TOKEN ?? TEST_MEMBER_API_TOKEN;
21
+ const adminToken = env.ADMIN_API_TOKEN ?? DEFAULT_ADMIN_API_TOKEN;
22
+ const memberToken = env.MEMBER_API_TOKEN ?? DEFAULT_MEMBER_API_TOKEN;
59
23
  const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
60
- const encryptionEnabled = env.FEATURE_FIELD_ENCRYPTION !== "false";
61
- const devHeadersEnabled = (env.AUTH_DEV_HEADERS ?? "true") !== "false";
24
+ const encryptionEnabled = isEnabled(env.FEATURE_FIELD_ENCRYPTION, true);
25
+ const devHeadersEnabled = isEnabled(env.AUTH_DEV_HEADERS, true);
62
26
  if (devHeadersEnabled) {
63
27
  throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
64
28
  }
@@ -71,18 +35,17 @@ function assertProductionSecrets(env = process.env) {
71
35
  if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
72
36
  throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
73
37
  }
74
- if (!env.SIEM_EXPORT_URL?.trim() && isFeatureEnabled("siemExport")) {
38
+ if (!env.SIEM_EXPORT_URL?.trim() && isEnabled(env.FEATURE_SIEM_EXPORT, true)) {
75
39
  console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
76
40
  }
77
- const billingEnabled = (env.FEATURE_BILLING ?? "true") !== "false";
78
- if (billingEnabled && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
41
+ if (isEnabled(env.FEATURE_BILLING, true) && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
79
42
  throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
80
43
  }
81
44
  const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
82
45
  if (corsOrigins.includes("*")) {
83
46
  throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
84
47
  }
85
- if ((env.FEATURE_PUBLIC_READS ?? "true") !== "false") {
48
+ if (isEnabled(env.FEATURE_PUBLIC_READS, true)) {
86
49
  throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
87
50
  }
88
51
  if (!env.OAUTH_STATE_SECRET?.trim()) {
@@ -94,6 +57,10 @@ function assertProductionSecrets(env = process.env) {
94
57
  if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
95
58
  throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
96
59
  }
60
+ const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
61
+ if (frontendMode === "server-htmx" && !env.SESSION_SECRET?.trim()) {
62
+ throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx.");
63
+ }
97
64
  }
98
65
  export {
99
66
  assertProductionSecrets
@@ -34,6 +34,6 @@ async function parseFormBody(request) {
34
34
  return { fields, files };
35
35
  }
36
36
  export {
37
- parseFormBody,
38
- createCsrfProtection
37
+ createCsrfProtection,
38
+ parseFormBody
39
39
  };
@@ -2,7 +2,7 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/web/routing.ts
5
- import { withErrorHandling } from "@getstrata/core/http/response";
5
+ import { withErrorHandling as withErrorHandling2 } from "@getstrata/core/http/response";
6
6
 
7
7
  // ../../src/bootstrap/http/securedRouteModelBinding.ts
8
8
  import {
@@ -26,6 +26,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
26
26
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
27
27
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
28
28
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
29
+ import { withErrorHandling } from "@getstrata/core/http/response";
29
30
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
30
31
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
31
32
  import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
@@ -178,7 +179,7 @@ class HttpKernel {
178
179
  return this.wrap(["api", "authenticated"], handler);
179
180
  }
180
181
  wrapWeb(handler) {
181
- return handler;
182
+ return withErrorHandling(this.wrap("web", handler));
182
183
  }
183
184
  wrapWebPublicRead(handler) {
184
185
  if (isPublicReadsEnabled()) {
@@ -188,19 +189,19 @@ class HttpKernel {
188
189
  }
189
190
  wrapWebAuthenticated(handler) {
190
191
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
191
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
192
+ return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
192
193
  }
193
194
  wrapWebAbility(ability, handler) {
194
195
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
195
196
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
196
197
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
197
198
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
198
- return withMiddleware(...middleware)(handler);
199
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
199
200
  }
200
201
  wrapWebGlobalAdmin(handler) {
201
202
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
202
203
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
203
- return withMiddleware(...middleware)(handler);
204
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
204
205
  }
205
206
  wrapAuthenticated(handler) {
206
207
  return this.wrap("authenticated", handler);
@@ -296,14 +297,14 @@ function toRouteRequest(request) {
296
297
  return request;
297
298
  }
298
299
  function wrapSecuredRouteModelByKey(param, resolver, authorization, handler) {
299
- const bound = withErrorHandling(securedBindRouteModelByKey(param, resolver, authorization, handler));
300
+ const bound = withErrorHandling2(securedBindRouteModelByKey(param, resolver, authorization, handler));
300
301
  return async (request) => bound(toRouteRequest(request));
301
302
  }
302
303
  function wrapWebLogin(kernel, handler, onThrottled) {
303
- return wrapWebThrottle(kernel, "login", handler, onThrottled);
304
+ return kernel.wrapWeb(wrapWebThrottle(kernel, "login", handler, onThrottled));
304
305
  }
305
306
  function wrapWebRegister(kernel, handler, onThrottled) {
306
- return wrapWebThrottle(kernel, "register", handler, onThrottled);
307
+ return kernel.wrapWeb(wrapWebThrottle(kernel, "register", handler, onThrottled));
307
308
  }
308
309
  function wrapWebThrottle(kernel, scope, handler, onThrottled) {
309
310
  const throttled = scope === "login" ? kernel.wrapLogin(handler) : kernel.wrapRegister(handler);
@@ -319,10 +320,10 @@ function createRouteKernel(dependencies) {
319
320
  return createHttpKernel(dependencies);
320
321
  }
321
322
  export {
322
- wrapWebRegister,
323
- wrapWebLogin,
324
- wrapSecuredRouteModelByKey,
325
- toRouteRequest,
323
+ createRouteKernel,
326
324
  routeParams,
327
- createRouteKernel
325
+ toRouteRequest,
326
+ wrapSecuredRouteModelByKey,
327
+ wrapWebLogin,
328
+ wrapWebRegister
328
329
  };
@@ -54,6 +54,6 @@ function createWebServer(options) {
54
54
  });
55
55
  }
56
56
  export {
57
- createWebServer,
58
- convertAppRoutesToBunRoutes
57
+ convertAppRoutesToBunRoutes,
58
+ createWebServer
59
59
  };