@getstrata/bootstrap 0.2.46 → 0.2.48
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/README.md +4 -0
- package/dist/_.._/_.._/index.html +174 -0
- package/dist/bootstrap/discoverModules.d.ts +2 -1
- package/dist/bootstrap/inProcessCron.d.ts +4 -0
- package/dist/bootstrap/metricsRoutes.d.ts +1 -1
- package/dist/bootstrap/public-api.d.ts +0 -1
- package/dist/entries/applicationRegistry.js +8 -8
- package/dist/entries/buildModuleRoutes.js +13 -7
- package/dist/entries/buildWebModuleRoutes.js +12 -6
- package/dist/entries/cache/modelCacheTags.js +9 -3
- package/dist/entries/config.js +19 -19
- package/dist/entries/context.js +33 -51
- package/dist/entries/contracts.js +4 -4
- package/dist/entries/createRoutes.js +50 -121
- package/dist/entries/createSpaRoutes.js +3 -3
- package/dist/entries/createWebRoutes.js +14 -8
- package/dist/entries/dependencies.js +32 -50
- package/dist/entries/discoverModules.js +10 -3
- package/dist/entries/health.js +3 -3
- package/dist/entries/http/securedRouteModelBinding.js +2 -2
- package/dist/entries/httpKernel.js +6 -6
- package/dist/entries/listeners/invalidateCacheOnModelWrite.js +7 -1
- package/dist/entries/metricsRoutes.js +31 -5
- package/dist/entries/providers/view.js +2 -2
- package/dist/entries/providers.js +7 -1
- package/dist/entries/routeRegistry.js +2 -2
- package/dist/entries/secretsGuard.js +23 -56
- package/dist/entries/web/forms.js +2 -2
- package/dist/entries/web/routing.js +10 -10
- package/dist/entries/web/server.js +2 -2
- package/dist/index.js +91 -177
- package/package.json +3 -3
|
@@ -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 () =>
|
|
9
|
-
|
|
10
|
-
|
|
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 {
|
|
@@ -293,7 +293,7 @@ function resolveModulesDirectory(options) {
|
|
|
293
293
|
if (state.configuredModulesDir) {
|
|
294
294
|
return state.configuredModulesDir;
|
|
295
295
|
}
|
|
296
|
-
|
|
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) {
|
|
@@ -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
|
|
51
|
-
var
|
|
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 ??
|
|
17
|
+
const appEnv = env.APP_ENV ?? "local";
|
|
54
18
|
if (appEnv !== "production") {
|
|
55
19
|
return;
|
|
56
20
|
}
|
|
57
|
-
const adminToken = env.ADMIN_API_TOKEN ??
|
|
58
|
-
const memberToken = env.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
|
|
61
|
-
const devHeadersEnabled = (env.AUTH_DEV_HEADERS
|
|
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() &&
|
|
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
|
-
|
|
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
|
|
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
|
|
@@ -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";
|
|
5
|
+
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
6
6
|
|
|
7
7
|
// ../../src/bootstrap/http/securedRouteModelBinding.ts
|
|
8
8
|
import {
|
|
@@ -178,7 +178,7 @@ class HttpKernel {
|
|
|
178
178
|
return this.wrap(["api", "authenticated"], handler);
|
|
179
179
|
}
|
|
180
180
|
wrapWeb(handler) {
|
|
181
|
-
return handler;
|
|
181
|
+
return this.wrap("web", handler);
|
|
182
182
|
}
|
|
183
183
|
wrapWebPublicRead(handler) {
|
|
184
184
|
if (isPublicReadsEnabled()) {
|
|
@@ -188,19 +188,19 @@ class HttpKernel {
|
|
|
188
188
|
}
|
|
189
189
|
wrapWebAuthenticated(handler) {
|
|
190
190
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
191
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
191
|
+
return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
192
192
|
}
|
|
193
193
|
wrapWebAbility(ability, handler) {
|
|
194
194
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
195
195
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
196
196
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
197
197
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
198
|
-
return withMiddleware(...middleware)(handler);
|
|
198
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
199
199
|
}
|
|
200
200
|
wrapWebGlobalAdmin(handler) {
|
|
201
201
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
202
202
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
203
|
-
return withMiddleware(...middleware)(handler);
|
|
203
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
204
204
|
}
|
|
205
205
|
wrapAuthenticated(handler) {
|
|
206
206
|
return this.wrap("authenticated", handler);
|
|
@@ -319,10 +319,10 @@ function createRouteKernel(dependencies) {
|
|
|
319
319
|
return createHttpKernel(dependencies);
|
|
320
320
|
}
|
|
321
321
|
export {
|
|
322
|
-
|
|
323
|
-
wrapWebLogin,
|
|
324
|
-
wrapSecuredRouteModelByKey,
|
|
325
|
-
toRouteRequest,
|
|
322
|
+
createRouteKernel,
|
|
326
323
|
routeParams,
|
|
327
|
-
|
|
324
|
+
toRouteRequest,
|
|
325
|
+
wrapSecuredRouteModelByKey,
|
|
326
|
+
wrapWebLogin,
|
|
327
|
+
wrapWebRegister
|
|
328
328
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,96 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/bootstrap/public-api.ts
|
|
3
|
-
import { appSchedule
|
|
3
|
+
import { appSchedule, runDueScheduledTasks, Schedule } from "@getstrata/core/scheduler/schedule";
|
|
4
4
|
|
|
5
|
-
// ../../src/core/scheduler/schedule.ts
|
|
6
|
-
class Schedule {
|
|
7
|
-
tasks = [];
|
|
8
|
-
command(expression, name, run) {
|
|
9
|
-
this.tasks.push({ expression, name, run });
|
|
10
|
-
return this;
|
|
11
|
-
}
|
|
12
|
-
dueTasks(now = new Date) {
|
|
13
|
-
const minute = now.getMinutes();
|
|
14
|
-
return this.tasks.filter((task) => {
|
|
15
|
-
if (task.expression === "* * * * *") {
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
if (task.expression.startsWith("*/")) {
|
|
19
|
-
const interval = Number.parseInt(task.expression.slice(2), 10);
|
|
20
|
-
return Number.isInteger(interval) && interval > 0 && minute % interval === 0;
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
tasksList() {
|
|
26
|
-
return [...this.tasks];
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
var appSchedule = new Schedule;
|
|
30
|
-
async function runDueScheduledTasks(schedule = appSchedule, now = new Date) {
|
|
31
|
-
const due = schedule.dueTasks(now);
|
|
32
|
-
for (const task of due) {
|
|
33
|
-
await task.run();
|
|
34
|
-
}
|
|
35
|
-
return due.length;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ../../src/bootstrap/schedule.ts
|
|
39
|
-
import { exportPendingAuditLogs } from "@getstrata/core/audit/exportAuditLogs";
|
|
40
|
-
import { appLogger } from "@getstrata/core/logging/logger";
|
|
41
|
-
import { appSchedule as appSchedule2 } from "@getstrata/core/scheduler/schedule";
|
|
42
|
-
|
|
43
|
-
// ../../src/config/features.ts
|
|
44
|
-
function readFeatureFlags() {
|
|
45
|
-
return {
|
|
46
|
-
webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
|
|
47
|
-
fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
|
|
48
|
-
auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
|
|
49
|
-
oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
|
|
50
|
-
samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
|
|
51
|
-
scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
|
|
52
|
-
billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
|
|
53
|
-
siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
|
|
54
|
-
publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
|
|
55
|
-
emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
|
|
56
|
-
mfa: (process.env.FEATURE_MFA ?? "false") === "true"
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
var featureFlags = readFeatureFlags();
|
|
60
|
-
function isFeatureEnabled(feature) {
|
|
61
|
-
return readFeatureFlags()[feature];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ../../src/bootstrap/schedule.ts
|
|
65
|
-
appSchedule2.command("* * * * *", "heartbeat", () => {
|
|
66
|
-
appLogger.debug("Scheduler heartbeat");
|
|
67
|
-
});
|
|
68
|
-
appSchedule2.command("* * * * *", "audit-export", async () => {
|
|
69
|
-
if (!isFeatureEnabled("siemExport")) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const exported = await exportPendingAuditLogs();
|
|
74
|
-
if (exported > 0) {
|
|
75
|
-
appLogger.info(`Exported ${exported} audit log entries to SIEM.`);
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
appLogger.error("Audit export failed.", { error: String(error) });
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// ../../src/cli/commands/scheduleRun.ts
|
|
83
|
-
async function scheduleRunCommand() {
|
|
84
|
-
const due = appSchedule.dueTasks();
|
|
85
|
-
if (due.length === 0) {
|
|
86
|
-
console.log("No scheduled tasks due.");
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
for (const task of due) {
|
|
90
|
-
console.log(`Running scheduled task: ${task.name}`);
|
|
91
|
-
}
|
|
92
|
-
await runDueScheduledTasks(appSchedule);
|
|
93
|
-
}
|
|
94
5
|
// ../../src/bootstrap/applicationRegistry.ts
|
|
95
6
|
import {
|
|
96
7
|
resolveApplicationAuth,
|
|
@@ -271,7 +182,7 @@ class HttpKernel {
|
|
|
271
182
|
return this.wrap(["api", "authenticated"], handler);
|
|
272
183
|
}
|
|
273
184
|
wrapWeb(handler) {
|
|
274
|
-
return handler;
|
|
185
|
+
return this.wrap("web", handler);
|
|
275
186
|
}
|
|
276
187
|
wrapWebPublicRead(handler) {
|
|
277
188
|
if (isPublicReadsEnabled()) {
|
|
@@ -281,19 +192,19 @@ class HttpKernel {
|
|
|
281
192
|
}
|
|
282
193
|
wrapWebAuthenticated(handler) {
|
|
283
194
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
284
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
195
|
+
return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
285
196
|
}
|
|
286
197
|
wrapWebAbility(ability, handler) {
|
|
287
198
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
288
199
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
289
200
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
290
201
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
291
|
-
return withMiddleware(...middleware)(handler);
|
|
202
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
292
203
|
}
|
|
293
204
|
wrapWebGlobalAdmin(handler) {
|
|
294
205
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
295
206
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
296
|
-
return withMiddleware(...middleware)(handler);
|
|
207
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
297
208
|
}
|
|
298
209
|
wrapAuthenticated(handler) {
|
|
299
210
|
return this.wrap("authenticated", handler);
|
|
@@ -392,7 +303,7 @@ function resolveModulesDirectory(options) {
|
|
|
392
303
|
if (state.configuredModulesDir) {
|
|
393
304
|
return state.configuredModulesDir;
|
|
394
305
|
}
|
|
395
|
-
|
|
306
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
396
307
|
}
|
|
397
308
|
async function loadDiscoveredModules(options) {
|
|
398
309
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -514,7 +425,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
|
|
|
514
425
|
routeRegistry.clear();
|
|
515
426
|
}
|
|
516
427
|
const kernel = createHttpKernel(dependencies);
|
|
517
|
-
const middleware =
|
|
428
|
+
const middleware = kernel.globalMiddleware();
|
|
518
429
|
const moduleRoutes = { ...seedRoutes };
|
|
519
430
|
for (const module of modules) {
|
|
520
431
|
if (!module.webRoutes) {
|
|
@@ -907,27 +818,28 @@ var coreProviders = [
|
|
|
907
818
|
viewProvider
|
|
908
819
|
];
|
|
909
820
|
|
|
910
|
-
// ../../src/domain/auth.ts
|
|
911
|
-
var TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
|
|
912
|
-
var TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
|
|
913
|
-
|
|
914
|
-
// ../../src/domain/scim.ts
|
|
915
|
-
var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
|
|
916
|
-
var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
|
|
917
|
-
|
|
918
821
|
// ../../src/bootstrap/secretsGuard.ts
|
|
919
|
-
var
|
|
920
|
-
var
|
|
822
|
+
var DEFAULT_ADMIN_API_TOKEN = "workhub-admin-test-token";
|
|
823
|
+
var DEFAULT_MEMBER_API_TOKEN = "workhub-member-test-token";
|
|
824
|
+
var DEFAULT_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
|
|
825
|
+
var DEFAULT_TOKENS = new Set([DEFAULT_ADMIN_API_TOKEN, DEFAULT_MEMBER_API_TOKEN]);
|
|
826
|
+
var DEFAULT_SCIM_TOKENS = new Set([DEFAULT_SCIM_BEARER_TOKEN]);
|
|
827
|
+
function isEnabled(value, defaultEnabled) {
|
|
828
|
+
if (value === undefined) {
|
|
829
|
+
return defaultEnabled;
|
|
830
|
+
}
|
|
831
|
+
return defaultEnabled ? value !== "false" : value === "true";
|
|
832
|
+
}
|
|
921
833
|
function assertProductionSecrets(env = process.env) {
|
|
922
|
-
const appEnv = env.APP_ENV ??
|
|
834
|
+
const appEnv = env.APP_ENV ?? "local";
|
|
923
835
|
if (appEnv !== "production") {
|
|
924
836
|
return;
|
|
925
837
|
}
|
|
926
|
-
const adminToken = env.ADMIN_API_TOKEN ??
|
|
927
|
-
const memberToken = env.MEMBER_API_TOKEN ??
|
|
838
|
+
const adminToken = env.ADMIN_API_TOKEN ?? DEFAULT_ADMIN_API_TOKEN;
|
|
839
|
+
const memberToken = env.MEMBER_API_TOKEN ?? DEFAULT_MEMBER_API_TOKEN;
|
|
928
840
|
const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
|
|
929
|
-
const encryptionEnabled = env.FEATURE_FIELD_ENCRYPTION
|
|
930
|
-
const devHeadersEnabled = (env.AUTH_DEV_HEADERS
|
|
841
|
+
const encryptionEnabled = isEnabled(env.FEATURE_FIELD_ENCRYPTION, true);
|
|
842
|
+
const devHeadersEnabled = isEnabled(env.AUTH_DEV_HEADERS, true);
|
|
931
843
|
if (devHeadersEnabled) {
|
|
932
844
|
throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
|
|
933
845
|
}
|
|
@@ -940,18 +852,17 @@ function assertProductionSecrets(env = process.env) {
|
|
|
940
852
|
if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
|
|
941
853
|
throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
|
|
942
854
|
}
|
|
943
|
-
if (!env.SIEM_EXPORT_URL?.trim() &&
|
|
855
|
+
if (!env.SIEM_EXPORT_URL?.trim() && isEnabled(env.FEATURE_SIEM_EXPORT, true)) {
|
|
944
856
|
console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
|
|
945
857
|
}
|
|
946
|
-
|
|
947
|
-
if (billingEnabled && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
|
|
858
|
+
if (isEnabled(env.FEATURE_BILLING, true) && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
|
|
948
859
|
throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
|
|
949
860
|
}
|
|
950
861
|
const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
|
|
951
862
|
if (corsOrigins.includes("*")) {
|
|
952
863
|
throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
|
|
953
864
|
}
|
|
954
|
-
if ((env.FEATURE_PUBLIC_READS
|
|
865
|
+
if (isEnabled(env.FEATURE_PUBLIC_READS, true)) {
|
|
955
866
|
throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
|
|
956
867
|
}
|
|
957
868
|
if (!env.OAUTH_STATE_SECRET?.trim()) {
|
|
@@ -963,6 +874,10 @@ function assertProductionSecrets(env = process.env) {
|
|
|
963
874
|
if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
|
|
964
875
|
throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
|
|
965
876
|
}
|
|
877
|
+
const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
|
|
878
|
+
if (frontendMode === "server-htmx" && !env.SESSION_SECRET?.trim()) {
|
|
879
|
+
throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx.");
|
|
880
|
+
}
|
|
966
881
|
}
|
|
967
882
|
|
|
968
883
|
// ../../src/bootstrap/context.ts
|
|
@@ -1078,7 +993,7 @@ async function parseFormBody(request) {
|
|
|
1078
993
|
return { fields, files };
|
|
1079
994
|
}
|
|
1080
995
|
// ../../src/bootstrap/web/routing.ts
|
|
1081
|
-
import { withErrorHandling } from "@getstrata/core/http";
|
|
996
|
+
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
1082
997
|
function routeParams(request) {
|
|
1083
998
|
const normalized = {};
|
|
1084
999
|
const raw = request.params;
|
|
@@ -1249,66 +1164,65 @@ function slugify(value) {
|
|
|
1249
1164
|
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
1250
1165
|
}
|
|
1251
1166
|
export {
|
|
1252
|
-
|
|
1253
|
-
wrapWebLogin,
|
|
1254
|
-
wrapSecuredRouteModelByKey,
|
|
1255
|
-
toRouteRequest,
|
|
1256
|
-
slugify,
|
|
1257
|
-
setActiveApplicationContext,
|
|
1258
|
-
securedBindRouteModelByKey,
|
|
1259
|
-
securedBindRouteModel,
|
|
1260
|
-
scheduleRunCommand,
|
|
1261
|
-
runProviderPhase,
|
|
1262
|
-
runDueScheduledTasks2 as runDueScheduledTasks,
|
|
1263
|
-
routeRegistry,
|
|
1264
|
-
routeParams,
|
|
1265
|
-
resolveService,
|
|
1266
|
-
resolveMembershipService,
|
|
1267
|
-
resolveApplicationQueue,
|
|
1268
|
-
resolveApplicationPolicyGate,
|
|
1269
|
-
resolveApplicationLogger,
|
|
1270
|
-
resolveApplicationEventBus,
|
|
1271
|
-
resolveApplicationDependencies,
|
|
1272
|
-
resolveApplicationConfig,
|
|
1273
|
-
resolveApplicationCache,
|
|
1274
|
-
resolveApplicationAuth,
|
|
1275
|
-
registerDefaultJobs,
|
|
1276
|
-
prefixRouteMap,
|
|
1277
|
-
parseFormBody,
|
|
1278
|
-
mergeWebRoutes,
|
|
1279
|
-
getRequiredDependency,
|
|
1280
|
-
ensureModulesLoaded,
|
|
1281
|
-
discoverModules,
|
|
1282
|
-
discoverModelTableNames,
|
|
1283
|
-
createWebServer,
|
|
1284
|
-
createWebRoutes,
|
|
1285
|
-
createRouteKernel,
|
|
1286
|
-
createHttpKernel,
|
|
1287
|
-
createCsrfProtection,
|
|
1288
|
-
createAppDependencies,
|
|
1289
|
-
createAppContext,
|
|
1290
|
-
coreProviders,
|
|
1291
|
-
configureModulesDirectory,
|
|
1292
|
-
collectProviders,
|
|
1293
|
-
cacheTagsForModelWrite,
|
|
1294
|
-
buildWebModuleRoutes,
|
|
1295
|
-
buildModuleRoutes,
|
|
1296
|
-
assertProductionSecrets,
|
|
1297
|
-
assertAppDependenciesComplete,
|
|
1298
|
-
appSchedule3 as appSchedule,
|
|
1299
|
-
ServiceContainer,
|
|
1300
|
-
Schedule2 as Schedule,
|
|
1301
|
-
RouteRegistry,
|
|
1302
|
-
REDIS_URL_CONFIG_KEY,
|
|
1303
|
-
DEFAULT_APP_PORT,
|
|
1304
|
-
DATABASE_URL_CONFIG_KEY,
|
|
1305
|
-
CookieSessionStore,
|
|
1306
|
-
CORE_TOKEN_SERVICE_TOKEN,
|
|
1307
|
-
CORE_QUEUE_TOKEN,
|
|
1308
|
-
CORE_POLICY_GATE_TOKEN,
|
|
1309
|
-
CORE_EVENT_BUS_TOKEN,
|
|
1310
|
-
CORE_CONFIG_TOKEN,
|
|
1311
|
-
CORE_CACHE_TOKEN,
|
|
1167
|
+
APP_PORT_CONFIG_KEY,
|
|
1312
1168
|
CORE_AUTH_TOKEN,
|
|
1313
|
-
|
|
1169
|
+
CORE_CACHE_TOKEN,
|
|
1170
|
+
CORE_CONFIG_TOKEN,
|
|
1171
|
+
CORE_EVENT_BUS_TOKEN,
|
|
1172
|
+
CORE_POLICY_GATE_TOKEN,
|
|
1173
|
+
CORE_QUEUE_TOKEN,
|
|
1174
|
+
CORE_TOKEN_SERVICE_TOKEN,
|
|
1175
|
+
CookieSessionStore,
|
|
1176
|
+
DATABASE_URL_CONFIG_KEY,
|
|
1177
|
+
DEFAULT_APP_PORT,
|
|
1178
|
+
REDIS_URL_CONFIG_KEY,
|
|
1179
|
+
RouteRegistry,
|
|
1180
|
+
Schedule,
|
|
1181
|
+
ServiceContainer,
|
|
1182
|
+
appSchedule,
|
|
1183
|
+
assertAppDependenciesComplete,
|
|
1184
|
+
assertProductionSecrets,
|
|
1185
|
+
buildModuleRoutes,
|
|
1186
|
+
buildWebModuleRoutes,
|
|
1187
|
+
cacheTagsForModelWrite,
|
|
1188
|
+
collectProviders,
|
|
1189
|
+
configureModulesDirectory,
|
|
1190
|
+
coreProviders,
|
|
1191
|
+
createAppContext,
|
|
1192
|
+
createAppDependencies,
|
|
1193
|
+
createCsrfProtection,
|
|
1194
|
+
createHttpKernel,
|
|
1195
|
+
createRouteKernel,
|
|
1196
|
+
createWebRoutes,
|
|
1197
|
+
createWebServer,
|
|
1198
|
+
discoverModelTableNames,
|
|
1199
|
+
discoverModules,
|
|
1200
|
+
ensureModulesLoaded,
|
|
1201
|
+
getRequiredDependency,
|
|
1202
|
+
mergeWebRoutes,
|
|
1203
|
+
parseFormBody,
|
|
1204
|
+
prefixRouteMap,
|
|
1205
|
+
registerDefaultJobs,
|
|
1206
|
+
resolveApplicationAuth,
|
|
1207
|
+
resolveApplicationCache,
|
|
1208
|
+
resolveApplicationConfig,
|
|
1209
|
+
resolveApplicationDependencies,
|
|
1210
|
+
resolveApplicationEventBus,
|
|
1211
|
+
resolveApplicationLogger,
|
|
1212
|
+
resolveApplicationPolicyGate,
|
|
1213
|
+
resolveApplicationQueue,
|
|
1214
|
+
resolveMembershipService,
|
|
1215
|
+
resolveService,
|
|
1216
|
+
routeParams,
|
|
1217
|
+
routeRegistry,
|
|
1218
|
+
runDueScheduledTasks,
|
|
1219
|
+
runProviderPhase,
|
|
1220
|
+
securedBindRouteModel,
|
|
1221
|
+
securedBindRouteModelByKey,
|
|
1222
|
+
setActiveApplicationContext,
|
|
1223
|
+
slugify,
|
|
1224
|
+
toRouteRequest,
|
|
1225
|
+
wrapSecuredRouteModelByKey,
|
|
1226
|
+
wrapWebLogin,
|
|
1227
|
+
wrapWebRegister
|
|
1314
1228
|
};
|