@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.
- package/README.md +6 -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/bootstrap/web/routing.d.ts +2 -2
- package/dist/entries/applicationRegistry.js +8 -8
- package/dist/entries/buildModuleRoutes.js +14 -7
- package/dist/entries/buildWebModuleRoutes.js +13 -6
- package/dist/entries/cache/modelCacheTags.js +9 -3
- package/dist/entries/config.js +19 -19
- package/dist/entries/context.js +10 -91
- package/dist/entries/contracts.js +4 -4
- package/dist/entries/createRoutes.js +56 -126
- package/dist/entries/createSpaRoutes.js +2 -2
- package/dist/entries/createWebRoutes.js +15 -8
- package/dist/entries/dependencies.js +9 -90
- package/dist/entries/discoverModules.js +10 -3
- package/dist/entries/health.js +2 -2
- package/dist/entries/http/securedRouteModelBinding.js +2 -2
- package/dist/entries/httpKernel.js +7 -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 +14 -13
- package/dist/entries/web/server.js +2 -2
- package/dist/index.js +133 -220
- 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/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 =
|
|
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
|
-
|
|
323
|
-
wrapWebLogin,
|
|
324
|
-
wrapSecuredRouteModelByKey,
|
|
325
|
-
toRouteRequest,
|
|
323
|
+
createRouteKernel,
|
|
326
324
|
routeParams,
|
|
327
|
-
|
|
325
|
+
toRouteRequest,
|
|
326
|
+
wrapSecuredRouteModelByKey,
|
|
327
|
+
wrapWebLogin,
|
|
328
|
+
wrapWebRegister
|
|
328
329
|
};
|