@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.
@@ -7,7 +7,7 @@ import { jsonResponse as jsonResponse4 } from "@getstrata/core/http/response";
7
7
  import { htmlResponse as htmlResponse2 } from "@getstrata/core/view";
8
8
 
9
9
  // ../../index.html
10
- var strata_default = __jsonParse("{\"index\":\"../../index.html\",\"files\":[{\"input\":\"../../index.html\",\"path\":\"./index-sfreg6q3.js\",\"loader\":\"js\",\"isEntry\":true,\"headers\":{\"etag\":\"Oc_YLpCmV6M\",\"content-type\":\"text/javascript;charset=utf-8\"}},{\"input\":\"../../index.html\",\"path\":\"../../index.html\",\"loader\":\"html\",\"isEntry\":true,\"headers\":{\"etag\":\"rWY90LWIcx8\",\"content-type\":\"text/html;charset=utf-8\"}}]}");
10
+ var strata_default = __jsonParse("{\"index\":\"_.._/_.._/index.html\",\"files\":[{\"input\":\"../../index.html\",\"path\":\"./index-sfreg6q3.js\",\"loader\":\"js\",\"isEntry\":true,\"headers\":{\"etag\":\"Oc_YLpCmV6M\",\"content-type\":\"text/javascript;charset=utf-8\"}},{\"input\":\"../../index.html\",\"path\":\"_.._/_.._/index.html\",\"loader\":\"html\",\"isEntry\":true,\"headers\":{\"etag\":\"rWY90LWIcx8\",\"content-type\":\"text/html;charset=utf-8\"}}]}");
11
11
 
12
12
  // ../../src/config/app.ts
13
13
  var appConfig = {
@@ -77,6 +77,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
77
77
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
78
78
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
79
79
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
80
+ import { withErrorHandling } from "@getstrata/core/http/response";
80
81
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
81
82
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
82
83
  import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
@@ -211,7 +212,7 @@ class HttpKernel {
211
212
  return this.wrap(["api", "authenticated"], handler);
212
213
  }
213
214
  wrapWeb(handler) {
214
- return handler;
215
+ return withErrorHandling(this.wrap("web", handler));
215
216
  }
216
217
  wrapWebPublicRead(handler) {
217
218
  if (isPublicReadsEnabled()) {
@@ -221,19 +222,19 @@ class HttpKernel {
221
222
  }
222
223
  wrapWebAuthenticated(handler) {
223
224
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
224
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
225
+ return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
225
226
  }
226
227
  wrapWebAbility(ability, handler) {
227
228
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
228
229
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
229
230
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
230
231
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
231
- return withMiddleware(...middleware)(handler);
232
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
232
233
  }
233
234
  wrapWebGlobalAdmin(handler) {
234
235
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
235
236
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
236
- return withMiddleware(...middleware)(handler);
237
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
237
238
  }
238
239
  wrapAuthenticated(handler) {
239
240
  return this.wrap("authenticated", handler);
@@ -332,7 +333,7 @@ function resolveModulesDirectory(options) {
332
333
  if (state.configuredModulesDir) {
333
334
  return state.configuredModulesDir;
334
335
  }
335
- return join(import.meta.dir, "../modules");
336
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
336
337
  }
337
338
  async function loadDiscoveredModules(options) {
338
339
  const modulesDirectory = resolveModulesDirectory(options);
@@ -366,6 +367,12 @@ async function ensureModulesLoaded(options) {
366
367
  function discoverModules() {
367
368
  return readDiscoverModulesState().appModules;
368
369
  }
370
+ function resetDiscoverModulesForTests() {
371
+ const state = readDiscoverModulesState();
372
+ state.configuredModulesDir = undefined;
373
+ state.appModules.length = 0;
374
+ state.modulesReady = undefined;
375
+ }
369
376
  // ../../src/bootstrap/prefixRouteMap.ts
370
377
  function prefixRouteMap(prefix, routes) {
371
378
  const normalizedPrefix = prefix.replace(/\/$/, "");
@@ -496,7 +503,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
496
503
  routeRegistry.clear();
497
504
  }
498
505
  const kernel = createHttpKernel(dependencies);
499
- const middleware = [...kernel.globalMiddleware(), ...kernel.group("web")];
506
+ const middleware = kernel.globalMiddleware();
500
507
  const moduleRoutes = { ...seedRoutes };
501
508
  for (const module of modules) {
502
509
  if (!module.webRoutes) {
@@ -731,15 +738,41 @@ function createHealthRoutes(dependencies) {
731
738
  }
732
739
 
733
740
  // ../../src/bootstrap/metricsRoutes.ts
741
+ import { timingSafeEqual } from "crypto";
734
742
  import { prometheusRegistry } from "@getstrata/core/metrics/prometheus";
743
+ function tokensMatch(left, right) {
744
+ const leftBuffer = Buffer.from(left);
745
+ const rightBuffer = Buffer.from(right);
746
+ if (leftBuffer.length !== rightBuffer.length) {
747
+ return false;
748
+ }
749
+ return timingSafeEqual(leftBuffer, rightBuffer);
750
+ }
751
+ function authorizeMetrics(request) {
752
+ const expected = process.env.METRICS_TOKEN?.trim();
753
+ const authorization = request.headers.get("authorization") ?? "";
754
+ const presented = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : "";
755
+ if (expected) {
756
+ return presented.length > 0 && tokensMatch(presented, expected);
757
+ }
758
+ if ((process.env.APP_ENV ?? "local") === "production") {
759
+ return false;
760
+ }
761
+ return true;
762
+ }
735
763
  function createMetricsRoutes() {
736
764
  return {
737
- "/metrics": async () => new Response(prometheusRegistry.renderMetrics(), {
738
- status: 200,
739
- headers: {
740
- "content-type": "text/plain; version=0.0.4; charset=utf-8"
765
+ "/metrics": async (request) => {
766
+ if (!authorizeMetrics(request)) {
767
+ return new Response("Not Found", { status: 404 });
741
768
  }
742
- })
769
+ return new Response(prometheusRegistry.renderMetrics(), {
770
+ status: 200,
771
+ headers: {
772
+ "content-type": "text/plain; version=0.0.4; charset=utf-8"
773
+ }
774
+ });
775
+ }
743
776
  };
744
777
  }
745
778
 
@@ -749,7 +782,7 @@ import { withMiddleware as withMiddleware2 } from "@getstrata/core/http/routeMid
749
782
  import { createScimThrottleMiddleware } from "@getstrata/core/http/scimThrottleMiddleware";
750
783
 
751
784
  // ../../src/modules/scim/controller.ts
752
- import { withErrorHandling } from "@getstrata/core/http/response";
785
+ import { withErrorHandling as withErrorHandling2 } from "@getstrata/core/http/response";
753
786
 
754
787
  // ../../src/modules/scim/scimResponse.ts
755
788
  import {
@@ -797,8 +830,6 @@ import { NotFoundError as NotFoundError4 } from "@getstrata/core/errors/http";
797
830
  import { currentTenantId as currentTenantId4 } from "@getstrata/core/tenant/tenantContext";
798
831
 
799
832
  // ../../src/domain/scim.ts
800
- var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
801
- var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
802
833
  var SCIM_SCHEMAS = {
803
834
  user: "urn:ietf:params:scim:schemas:core:2.0:User",
804
835
  group: "urn:ietf:params:scim:schemas:core:2.0:Group",
@@ -942,7 +973,7 @@ import { resolveDefaultTokenExpiryDays } from "@getstrata/core/security/tokenExp
942
973
  import { verifyTotp } from "@getstrata/core/security/totp";
943
974
  import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
944
975
 
945
- // ../../src/domain/abilities.ts
976
+ // ../../src/core/auth/abilityCatalog.ts
946
977
  var MEMBER_ABILITIES = [
947
978
  "organizations:read",
948
979
  "projects:read",
@@ -972,107 +1003,6 @@ var ADMIN_ABILITIES = [
972
1003
  "webhooks:write",
973
1004
  "audit:read"
974
1005
  ];
975
- var PLATFORM_ADMIN_ABILITIES = ["*"];
976
- function resolveAbilitiesForRole(role) {
977
- if (role === "admin") {
978
- return [...PLATFORM_ADMIN_ABILITIES];
979
- }
980
- return [...MEMBER_ABILITIES];
981
- }
982
-
983
- // ../../src/modules/user/authService.ts
984
- class AuthService {
985
- users;
986
- tokens;
987
- oauthIdentities;
988
- oauthProviders = new Map;
989
- constructor(users, tokens, oauthIdentities) {
990
- this.users = users;
991
- this.tokens = tokens;
992
- this.oauthIdentities = oauthIdentities;
993
- }
994
- registerOAuthProvider(provider) {
995
- this.oauthProviders.set(provider.name, provider);
996
- }
997
- getOAuthProvider(name) {
998
- return this.oauthProviders.get(name);
999
- }
1000
- async loginWithPassword(email, password, options = {}) {
1001
- const user = await this.users.findByEmail(email);
1002
- if (!user?.password_hash) {
1003
- logSecurityEvent("auth_login_failed", { reason: "unknown_user", email });
1004
- throw new UnauthorizedError("Invalid credentials.");
1005
- }
1006
- const valid = await verifyPassword(password, user.password_hash);
1007
- if (!valid) {
1008
- logSecurityEvent("auth_login_failed", { reason: "invalid_password", user_id: user.id });
1009
- throw new UnauthorizedError("Invalid credentials.");
1010
- }
1011
- if (isFeatureEnabled("emailVerification") && !user.email_verified_at) {
1012
- logSecurityEvent("auth_login_blocked", { reason: "email_unverified", user_id: user.id });
1013
- throw new UnauthorizedError("Email address is not verified.");
1014
- }
1015
- if (isFeatureEnabled("mfa") && user.mfa_enabled) {
1016
- const mfaSecret = revealMfaSecret(user.mfa_secret);
1017
- if (!mfaSecret || !options.mfaCode || !verifyTotp(mfaSecret, options.mfaCode)) {
1018
- logSecurityEvent("auth_login_failed", { reason: "invalid_mfa", user_id: user.id });
1019
- throw new UnauthorizedError("Invalid MFA code.");
1020
- }
1021
- }
1022
- logSecurityEvent("auth_login_success", { user_id: user.id, method: "password" });
1023
- return await this.tokens.createToken(user.id, {
1024
- name: "password-login",
1025
- abilities: resolveAbilitiesForRole(user.role),
1026
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
1027
- });
1028
- }
1029
- async loginWithOAuth(providerName, code) {
1030
- const provider = this.oauthProviders.get(providerName);
1031
- if (!provider) {
1032
- throw new UnauthorizedError("Unsupported OAuth provider.");
1033
- }
1034
- const profile = await provider.exchangeCode(code);
1035
- const user = await this.findOrCreateOAuthUser(providerName, profile);
1036
- logSecurityEvent("auth_login_success", { user_id: user.id, method: `oauth:${providerName}` });
1037
- return await this.tokens.createToken(user.id, {
1038
- name: `${providerName}-oauth`,
1039
- abilities: resolveAbilitiesForRole(user.role),
1040
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
1041
- });
1042
- }
1043
- buildOAuthAuthorizationUrl(providerName, state) {
1044
- const provider = this.oauthProviders.get(providerName);
1045
- if (!provider) {
1046
- throw new UnauthorizedError("Unsupported OAuth provider.");
1047
- }
1048
- return provider.getAuthorizationUrl(state);
1049
- }
1050
- async findOrCreateOAuthUser(providerName, profile) {
1051
- const existingIdentity = await this.oauthIdentities.findByProviderUser(providerName, profile.providerUserId);
1052
- if (existingIdentity) {
1053
- return await this.users.findByIdOrThrow(existingIdentity.user_id);
1054
- }
1055
- const existingUser = await this.users.findByEmail(profile.email);
1056
- const user = existingUser ?? await this.users.create({
1057
- name: profile.name,
1058
- email: profile.email,
1059
- role: "member",
1060
- tenant_id: currentTenantId2(),
1061
- email_verified_at: new Date,
1062
- created_at: new Date,
1063
- updated_at: new Date
1064
- });
1065
- await this.oauthIdentities.create({
1066
- user_id: user.id,
1067
- provider: providerName,
1068
- provider_user_id: profile.providerUserId,
1069
- email: profile.email,
1070
- created_at: new Date
1071
- });
1072
- return user;
1073
- }
1074
- }
1075
-
1076
1006
  // ../../src/modules/user/notificationRepository.ts
1077
1007
  import { BaseRepository as BaseRepository3 } from "@getstrata/core/database/baseRepository";
1078
1008
 
@@ -1317,30 +1247,30 @@ class ScimController {
1317
1247
  constructor(dependencies, service = createScimService(dependencies)) {
1318
1248
  this.service = service;
1319
1249
  }
1320
- serviceProviderConfig = withErrorHandling(async () => {
1250
+ serviceProviderConfig = withErrorHandling2(async () => {
1321
1251
  return scimResponse(this.service.serviceProviderConfig());
1322
1252
  });
1323
- listUsers = withErrorHandling(async (request) => {
1253
+ listUsers = withErrorHandling2(async (request) => {
1324
1254
  const url = new URL(request.url);
1325
1255
  const startIndex = Number.parseInt(url.searchParams.get("startIndex") ?? "1", 10);
1326
1256
  const count = Number.parseInt(url.searchParams.get("count") ?? "100", 10);
1327
1257
  return scimResponse(await this.service.listUsers(startIndex, count));
1328
1258
  });
1329
- createUser = withErrorHandling(async (request) => {
1259
+ createUser = withErrorHandling2(async (request) => {
1330
1260
  const payload = await request.json();
1331
1261
  const user = await this.service.createUser(payload);
1332
1262
  const id = Number.parseInt(user.id, 10);
1333
1263
  const record = await this.service.findUserRecord(id);
1334
1264
  return scimResponse(user, { status: 201, etagSource: record });
1335
1265
  });
1336
- showUser = withErrorHandling(async (request) => {
1266
+ showUser = withErrorHandling2(async (request) => {
1337
1267
  const params = request.params;
1338
1268
  const id = Number.parseInt(params?.id ?? "", 10);
1339
1269
  const record = await this.service.findUserRecord(id);
1340
1270
  const user = await this.service.getUser(id);
1341
1271
  return scimResponse(user, { request, etagSource: record });
1342
1272
  });
1343
- patchUser = withErrorHandling(async (request) => {
1273
+ patchUser = withErrorHandling2(async (request) => {
1344
1274
  const params = request.params;
1345
1275
  const id = Number.parseInt(params?.id ?? "", 10);
1346
1276
  const record = await this.service.findUserRecord(id);
@@ -1350,7 +1280,7 @@ class ScimController {
1350
1280
  const updated = await this.service.findUserRecord(id);
1351
1281
  return scimResponse(user, { etagSource: updated });
1352
1282
  });
1353
- deleteUser = withErrorHandling(async (request) => {
1283
+ deleteUser = withErrorHandling2(async (request) => {
1354
1284
  const params = request.params;
1355
1285
  const id = Number.parseInt(params?.id ?? "", 10);
1356
1286
  const record = await this.service.findUserRecord(id);
@@ -1358,20 +1288,20 @@ class ScimController {
1358
1288
  await this.service.deleteUser(id);
1359
1289
  return new Response(null, { status: 204 });
1360
1290
  });
1361
- listGroups = withErrorHandling(async (request) => {
1291
+ listGroups = withErrorHandling2(async (request) => {
1362
1292
  const url = new URL(request.url);
1363
1293
  const startIndex = Number.parseInt(url.searchParams.get("startIndex") ?? "1", 10);
1364
1294
  const count = Number.parseInt(url.searchParams.get("count") ?? "100", 10);
1365
1295
  return scimResponse(await this.service.listGroups(startIndex, count));
1366
1296
  });
1367
- showGroup = withErrorHandling(async (request) => {
1297
+ showGroup = withErrorHandling2(async (request) => {
1368
1298
  const params = request.params;
1369
1299
  const id = Number.parseInt(params?.id ?? "", 10);
1370
1300
  const record = await this.service.findOrganizationRecord(id);
1371
1301
  const group = await this.service.getGroup(id);
1372
1302
  return scimResponse(group, { request, etagSource: record });
1373
1303
  });
1374
- patchGroup = withErrorHandling(async (request) => {
1304
+ patchGroup = withErrorHandling2(async (request) => {
1375
1305
  const params = request.params;
1376
1306
  const id = Number.parseInt(params?.id ?? "", 10);
1377
1307
  const record = await this.service.findOrganizationRecord(id);
@@ -58,7 +58,7 @@ function mergeSpaRoutes(dependencies, routes) {
58
58
  };
59
59
  }
60
60
  export {
61
- mergeSpaRoutes,
61
+ SPA_DIST_DIRECTORY,
62
62
  createSpaRoutes,
63
- SPA_DIST_DIRECTORY
63
+ mergeSpaRoutes
64
64
  };
@@ -46,6 +46,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
46
46
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
47
47
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
48
48
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
49
+ import { withErrorHandling } from "@getstrata/core/http/response";
49
50
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
50
51
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
51
52
  import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
@@ -180,7 +181,7 @@ class HttpKernel {
180
181
  return this.wrap(["api", "authenticated"], handler);
181
182
  }
182
183
  wrapWeb(handler) {
183
- return handler;
184
+ return withErrorHandling(this.wrap("web", handler));
184
185
  }
185
186
  wrapWebPublicRead(handler) {
186
187
  if (isPublicReadsEnabled()) {
@@ -190,19 +191,19 @@ class HttpKernel {
190
191
  }
191
192
  wrapWebAuthenticated(handler) {
192
193
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
193
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
194
+ return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
194
195
  }
195
196
  wrapWebAbility(ability, handler) {
196
197
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
197
198
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
198
199
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
199
200
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
200
- return withMiddleware(...middleware)(handler);
201
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
201
202
  }
202
203
  wrapWebGlobalAdmin(handler) {
203
204
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
204
205
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
205
- return withMiddleware(...middleware)(handler);
206
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
206
207
  }
207
208
  wrapAuthenticated(handler) {
208
209
  return this.wrap("authenticated", handler);
@@ -301,7 +302,7 @@ function resolveModulesDirectory(options) {
301
302
  if (state.configuredModulesDir) {
302
303
  return state.configuredModulesDir;
303
304
  }
304
- return join(import.meta.dir, "../modules");
305
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
305
306
  }
306
307
  async function loadDiscoveredModules(options) {
307
308
  const modulesDirectory = resolveModulesDirectory(options);
@@ -335,6 +336,12 @@ async function ensureModulesLoaded(options) {
335
336
  function discoverModules() {
336
337
  return readDiscoverModulesState().appModules;
337
338
  }
339
+ function resetDiscoverModulesForTests() {
340
+ const state = readDiscoverModulesState();
341
+ state.configuredModulesDir = undefined;
342
+ state.appModules.length = 0;
343
+ state.modulesReady = undefined;
344
+ }
338
345
  // ../../src/bootstrap/prefixRouteMap.ts
339
346
  function prefixRouteMap(prefix, routes) {
340
347
  const normalizedPrefix = prefix.replace(/\/$/, "");
@@ -423,7 +430,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
423
430
  routeRegistry.clear();
424
431
  }
425
432
  const kernel = createHttpKernel(dependencies);
426
- const middleware = [...kernel.globalMiddleware(), ...kernel.group("web")];
433
+ const middleware = kernel.globalMiddleware();
427
434
  const moduleRoutes = { ...seedRoutes };
428
435
  for (const module of modules) {
429
436
  if (!module.webRoutes) {
@@ -473,6 +480,6 @@ function mergeWebRoutes(dependencies, routes) {
473
480
  };
474
481
  }
475
482
  export {
476
- mergeWebRoutes,
477
- createWebRoutes
483
+ createWebRoutes,
484
+ mergeWebRoutes
478
485
  };
@@ -50,7 +50,7 @@ function resolveModulesDirectory(options) {
50
50
  if (state.configuredModulesDir) {
51
51
  return state.configuredModulesDir;
52
52
  }
53
- return join(import.meta.dir, "../modules");
53
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
54
54
  }
55
55
  async function loadDiscoveredModules(options) {
56
56
  const modulesDirectory = resolveModulesDirectory(options);
@@ -84,6 +84,12 @@ async function ensureModulesLoaded(options) {
84
84
  function discoverModules() {
85
85
  return readDiscoverModulesState().appModules;
86
86
  }
87
+ function resetDiscoverModulesForTests() {
88
+ const state = readDiscoverModulesState();
89
+ state.configuredModulesDir = undefined;
90
+ state.appModules.length = 0;
91
+ state.modulesReady = undefined;
92
+ }
87
93
  // ../../src/bootstrap/providers/auth.ts
88
94
  import {
89
95
  AuthManager,
@@ -500,92 +506,6 @@ var coreProviders = [
500
506
  viewProvider
501
507
  ];
502
508
 
503
- // ../../src/config/features.ts
504
- function readFeatureFlags() {
505
- return {
506
- webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
507
- fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
508
- auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
509
- oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
510
- samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
511
- scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
512
- billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
513
- siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
514
- publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
515
- emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
516
- mfa: (process.env.FEATURE_MFA ?? "false") === "true"
517
- };
518
- }
519
- var featureFlags = readFeatureFlags();
520
- function isFeatureEnabled(feature) {
521
- return readFeatureFlags()[feature];
522
- }
523
-
524
- // ../../src/domain/auth.ts
525
- var TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
526
- var TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
527
-
528
- // ../../src/domain/scim.ts
529
- var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
530
- var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
531
- var SCIM_SCHEMAS = {
532
- user: "urn:ietf:params:scim:schemas:core:2.0:User",
533
- group: "urn:ietf:params:scim:schemas:core:2.0:Group",
534
- listResponse: "urn:ietf:params:scim:api:messages:2.0:ListResponse",
535
- patchOp: "urn:ietf:params:scim:api:messages:2.0:PatchOp",
536
- serviceProviderConfig: "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
537
- };
538
-
539
- // ../../src/bootstrap/secretsGuard.ts
540
- var DEFAULT_TOKENS = new Set([TEST_ADMIN_API_TOKEN, TEST_MEMBER_API_TOKEN]);
541
- var DEFAULT_SCIM_TOKENS = new Set([TEST_SCIM_BEARER_TOKEN, DEFAULT_SCIM_BEARER_TOKEN]);
542
- function assertProductionSecrets(env = process.env) {
543
- const appEnv = env.APP_ENV ?? appConfig.env;
544
- if (appEnv !== "production") {
545
- return;
546
- }
547
- const adminToken = env.ADMIN_API_TOKEN ?? TEST_ADMIN_API_TOKEN;
548
- const memberToken = env.MEMBER_API_TOKEN ?? TEST_MEMBER_API_TOKEN;
549
- const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
550
- const encryptionEnabled = env.FEATURE_FIELD_ENCRYPTION !== "false";
551
- const devHeadersEnabled = (env.AUTH_DEV_HEADERS ?? "true") !== "false";
552
- if (devHeadersEnabled) {
553
- throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
554
- }
555
- if (DEFAULT_TOKENS.has(adminToken) || DEFAULT_TOKENS.has(memberToken)) {
556
- throw new Error("Production startup blocked: rotate ADMIN_API_TOKEN and MEMBER_API_TOKEN away from default WorkHub test values.");
557
- }
558
- if (DEFAULT_SCIM_TOKENS.has(scimToken)) {
559
- throw new Error("Production startup blocked: rotate SCIM_BEARER_TOKEN away from default WorkHub test values.");
560
- }
561
- if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
562
- throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
563
- }
564
- if (!env.SIEM_EXPORT_URL?.trim() && isFeatureEnabled("siemExport")) {
565
- console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
566
- }
567
- const billingEnabled = (env.FEATURE_BILLING ?? "true") !== "false";
568
- if (billingEnabled && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
569
- throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
570
- }
571
- const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
572
- if (corsOrigins.includes("*")) {
573
- throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
574
- }
575
- if ((env.FEATURE_PUBLIC_READS ?? "true") !== "false") {
576
- throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
577
- }
578
- if (!env.OAUTH_STATE_SECRET?.trim()) {
579
- throw new Error("Production startup blocked: set OAUTH_STATE_SECRET for OAuth CSRF protection.");
580
- }
581
- if (!env.TOKEN_HASH_PEPPER?.trim()) {
582
- throw new Error("Production startup blocked: set TOKEN_HASH_PEPPER for API token hashing.");
583
- }
584
- if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
585
- throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
586
- }
587
- }
588
-
589
509
  // ../../src/bootstrap/context.ts
590
510
  function collectProviders(modules = discoverModules()) {
591
511
  return [...coreProviders, ...modules.flatMap((module) => module.providers ?? [])];
@@ -596,7 +516,6 @@ function runProviderPhase(providers, phase, context) {
596
516
  }
597
517
  }
598
518
  function createAppContext() {
599
- assertProductionSecrets();
600
519
  const container = new ServiceContainer;
601
520
  const config = new ConfigStore;
602
521
  const dependencies = {
@@ -641,6 +560,6 @@ function createAppDependencies() {
641
560
  return createAppContext().dependencies;
642
561
  }
643
562
  export {
644
- createAppDependencies,
645
- createAppContext
563
+ createAppContext,
564
+ createAppDependencies
646
565
  };
@@ -26,7 +26,7 @@ function resolveModulesDirectory(options) {
26
26
  if (state.configuredModulesDir) {
27
27
  return state.configuredModulesDir;
28
28
  }
29
- return join(import.meta.dir, "../modules");
29
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
30
30
  }
31
31
  async function loadDiscoveredModules(options) {
32
32
  const modulesDirectory = resolveModulesDirectory(options);
@@ -60,8 +60,15 @@ async function ensureModulesLoaded(options) {
60
60
  function discoverModules() {
61
61
  return readDiscoverModulesState().appModules;
62
62
  }
63
+ function resetDiscoverModulesForTests() {
64
+ const state = readDiscoverModulesState();
65
+ state.configuredModulesDir = undefined;
66
+ state.appModules.length = 0;
67
+ state.modulesReady = undefined;
68
+ }
63
69
  export {
64
- ensureModulesLoaded,
70
+ configureModulesDirectory,
65
71
  discoverModules,
66
- configureModulesDirectory
72
+ ensureModulesLoaded,
73
+ resetDiscoverModulesForTests
67
74
  };
@@ -209,7 +209,7 @@ function createHealthRoutes(dependencies) {
209
209
  };
210
210
  }
211
211
  export {
212
- createHealthRoutes,
212
+ checkDatabase,
213
213
  checkRedis,
214
- checkDatabase
214
+ createHealthRoutes
215
215
  };
@@ -7,6 +7,6 @@ import {
7
7
  securedBindRouteModelByKey
8
8
  } from "@getstrata/core/http/securedRouteModelBinding";
9
9
  export {
10
- securedBindRouteModelByKey,
11
- securedBindRouteModel
10
+ securedBindRouteModel,
11
+ securedBindRouteModelByKey
12
12
  };
@@ -17,6 +17,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
17
17
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
18
18
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
19
19
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
20
+ import { withErrorHandling } from "@getstrata/core/http/response";
20
21
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
21
22
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
22
23
  import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
@@ -169,7 +170,7 @@ class HttpKernel {
169
170
  return this.wrap(["api", "authenticated"], handler);
170
171
  }
171
172
  wrapWeb(handler) {
172
- return handler;
173
+ return withErrorHandling(this.wrap("web", handler));
173
174
  }
174
175
  wrapWebPublicRead(handler) {
175
176
  if (isPublicReadsEnabled()) {
@@ -179,19 +180,19 @@ class HttpKernel {
179
180
  }
180
181
  wrapWebAuthenticated(handler) {
181
182
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
182
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
183
+ return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
183
184
  }
184
185
  wrapWebAbility(ability, handler) {
185
186
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
186
187
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
187
188
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
188
189
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
189
- return withMiddleware(...middleware)(handler);
190
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
190
191
  }
191
192
  wrapWebGlobalAdmin(handler) {
192
193
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
193
194
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
194
- return withMiddleware(...middleware)(handler);
195
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
195
196
  }
196
197
  wrapAuthenticated(handler) {
197
198
  return this.wrap("authenticated", handler);
@@ -265,6 +266,6 @@ function createHttpKernel(dependencies) {
265
266
  return new HttpKernel(dependencies);
266
267
  }
267
268
  export {
268
- createHttpKernel,
269
- HttpKernel
269
+ HttpKernel,
270
+ createHttpKernel
270
271
  };
@@ -44,7 +44,7 @@ function resolveModulesDirectory(options) {
44
44
  if (state.configuredModulesDir) {
45
45
  return state.configuredModulesDir;
46
46
  }
47
- return join(import.meta.dir, "../modules");
47
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
48
48
  }
49
49
  async function loadDiscoveredModules(options) {
50
50
  const modulesDirectory = resolveModulesDirectory(options);
@@ -78,6 +78,12 @@ async function ensureModulesLoaded(options) {
78
78
  function discoverModules() {
79
79
  return readDiscoverModulesState().appModules;
80
80
  }
81
+ function resetDiscoverModulesForTests() {
82
+ const state = readDiscoverModulesState();
83
+ state.configuredModulesDir = undefined;
84
+ state.appModules.length = 0;
85
+ state.modulesReady = undefined;
86
+ }
81
87
 
82
88
  // ../../src/bootstrap/cache/modelCacheTags.ts
83
89
  function cacheTagsForModelWrite(tableName, action) {