@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
package/README.md
CHANGED
|
@@ -28,4 +28,10 @@ import {
|
|
|
28
28
|
import { createHttpKernel, createAppContext, coreProviders } from "@getstrata/bootstrap";
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
`createAppContext()` does not call `assertProductionSecrets`. WorkHub calls that from `App.serve()` / `queue:work`. Sibling apps should call it only if they want those WorkHub production gates (API tokens, Stripe, SCIM, CORS).
|
|
32
|
+
|
|
33
|
+
`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.
|
|
34
|
+
|
|
35
|
+
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`).
|
|
36
|
+
|
|
31
37
|
See the [strata](https://github.com/EyK-26/strata) monorepo reference app for full module patterns.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>WorkHub API</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
font-family: sans-serif;
|
|
14
|
+
background-color: #000;
|
|
15
|
+
color: #fff;
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 2rem;
|
|
18
|
+
line-height: 1.5;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
h1,
|
|
22
|
+
h2 {
|
|
23
|
+
margin-top: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
section {
|
|
27
|
+
margin-bottom: 2rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
ul {
|
|
31
|
+
padding-left: 1.25rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
li {
|
|
35
|
+
padding: 0.35rem 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
a {
|
|
39
|
+
color: #fff;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
code,
|
|
43
|
+
input,
|
|
44
|
+
button {
|
|
45
|
+
font: inherit;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
form {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
gap: 0.5rem;
|
|
52
|
+
margin: 0.75rem 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
input {
|
|
56
|
+
border: 1px solid #555;
|
|
57
|
+
background-color: #111;
|
|
58
|
+
color: #fff;
|
|
59
|
+
padding: 0.5rem 0.75rem;
|
|
60
|
+
border-radius: 0.35rem;
|
|
61
|
+
min-width: 14rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
button {
|
|
65
|
+
border: 1px solid #777;
|
|
66
|
+
background-color: #222;
|
|
67
|
+
color: #fff;
|
|
68
|
+
padding: 0.5rem 0.9rem;
|
|
69
|
+
border-radius: 0.35rem;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.hint {
|
|
74
|
+
color: #bbb;
|
|
75
|
+
font-size: 0.95rem;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
78
|
+
<script type="module" crossorigin src="../../index-sfreg6q3.js"></script></head>
|
|
79
|
+
|
|
80
|
+
<body>
|
|
81
|
+
<h1>WorkHub API</h1>
|
|
82
|
+
<p class="hint">
|
|
83
|
+
Framework reference domain for organizations, projects, tasks, and
|
|
84
|
+
comments.
|
|
85
|
+
</p>
|
|
86
|
+
|
|
87
|
+
<section>
|
|
88
|
+
<h2>Collections</h2>
|
|
89
|
+
<ul>
|
|
90
|
+
<li><a href="/organizations">/organizations</a></li>
|
|
91
|
+
<li><a href="/projects?include=organization">/projects</a></li>
|
|
92
|
+
<li><a href="/tasks?include=project">/tasks</a></li>
|
|
93
|
+
<li><a href="/comments">/comments</a></li>
|
|
94
|
+
<li><a href="/reports/summary">/reports/summary</a></li>
|
|
95
|
+
</ul>
|
|
96
|
+
</section>
|
|
97
|
+
|
|
98
|
+
<section>
|
|
99
|
+
<h2>Lookup by ID</h2>
|
|
100
|
+
|
|
101
|
+
<form data-prefix="/organizations/">
|
|
102
|
+
<input
|
|
103
|
+
type="number"
|
|
104
|
+
min="1"
|
|
105
|
+
name="id"
|
|
106
|
+
placeholder="Organization ID"
|
|
107
|
+
required
|
|
108
|
+
/>
|
|
109
|
+
<button type="submit">Open /organizations/:id</button>
|
|
110
|
+
</form>
|
|
111
|
+
|
|
112
|
+
<form data-prefix="/projects/">
|
|
113
|
+
<input
|
|
114
|
+
type="number"
|
|
115
|
+
min="1"
|
|
116
|
+
name="id"
|
|
117
|
+
placeholder="Project ID"
|
|
118
|
+
required
|
|
119
|
+
/>
|
|
120
|
+
<button type="submit">Open /projects/:id</button>
|
|
121
|
+
</form>
|
|
122
|
+
|
|
123
|
+
<form data-prefix="/tasks/">
|
|
124
|
+
<input
|
|
125
|
+
type="number"
|
|
126
|
+
min="1"
|
|
127
|
+
name="id"
|
|
128
|
+
placeholder="Task ID"
|
|
129
|
+
required
|
|
130
|
+
/>
|
|
131
|
+
<button type="submit">Open /tasks/:id</button>
|
|
132
|
+
</form>
|
|
133
|
+
|
|
134
|
+
<form data-prefix="/tasks/" data-suffix="/comments">
|
|
135
|
+
<input
|
|
136
|
+
type="number"
|
|
137
|
+
min="1"
|
|
138
|
+
name="id"
|
|
139
|
+
placeholder="Task ID"
|
|
140
|
+
required
|
|
141
|
+
/>
|
|
142
|
+
<button type="submit">Open /tasks/:id/comments</button>
|
|
143
|
+
</form>
|
|
144
|
+
|
|
145
|
+
<form data-prefix="/reports/organizations/">
|
|
146
|
+
<input
|
|
147
|
+
type="number"
|
|
148
|
+
min="1"
|
|
149
|
+
name="id"
|
|
150
|
+
placeholder="Organization ID"
|
|
151
|
+
required
|
|
152
|
+
/>
|
|
153
|
+
<button type="submit">Open /reports/organizations/:id</button>
|
|
154
|
+
</form>
|
|
155
|
+
</section>
|
|
156
|
+
|
|
157
|
+
<script>
|
|
158
|
+
for (const form of document.querySelectorAll("form[data-prefix]")) {
|
|
159
|
+
form.addEventListener("submit", (event) => {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
const prefix = form.getAttribute("data-prefix");
|
|
162
|
+
const suffix = form.getAttribute("data-suffix") ?? "";
|
|
163
|
+
const input = form.querySelector('input[name="id"]');
|
|
164
|
+
|
|
165
|
+
if (!prefix || !input?.value) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
window.location.href = `${prefix}${input.value}${suffix}`;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
</script>
|
|
173
|
+
</body>
|
|
174
|
+
</html>
|
|
@@ -5,5 +5,6 @@ interface DiscoverModulesOptions {
|
|
|
5
5
|
declare function configureModulesDirectory(modulesDir: string): void;
|
|
6
6
|
declare function ensureModulesLoaded(options?: DiscoverModulesOptions): Promise<AppModule[]>;
|
|
7
7
|
declare function discoverModules(): AppModule[];
|
|
8
|
+
declare function resetDiscoverModulesForTests(): void;
|
|
8
9
|
export type { DiscoverModulesOptions };
|
|
9
|
-
export { configureModulesDirectory, discoverModules, ensureModulesLoaded };
|
|
10
|
+
export { configureModulesDirectory, discoverModules, ensureModulesLoaded, resetDiscoverModulesForTests, };
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export type { ScheduledTask } from "@getstrata/core/scheduler/schedule";
|
|
5
5
|
export { appSchedule, runDueScheduledTasks, Schedule } from "@getstrata/core/scheduler/schedule";
|
|
6
|
-
export { scheduleRunCommand } from "../cli/commands/scheduleRun.ts";
|
|
7
6
|
export { resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationEventBus, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, } from "./applicationRegistry.ts";
|
|
8
7
|
export type { BuildModuleRoutesOptions } from "./buildModuleRoutes.ts";
|
|
9
8
|
export { buildModuleRoutes } from "./buildModuleRoutes.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
|
-
/**
|
|
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
|
-
/**
|
|
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;
|
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
setActiveApplicationContext
|
|
15
15
|
} from "@getstrata/core/runtime/applicationRegistry";
|
|
16
16
|
export {
|
|
17
|
-
|
|
18
|
-
resolveApplicationQueue,
|
|
19
|
-
resolveApplicationPolicyGate,
|
|
20
|
-
resolveApplicationLogger,
|
|
21
|
-
resolveApplicationEventBus,
|
|
22
|
-
resolveApplicationDependencies,
|
|
23
|
-
resolveApplicationConfig,
|
|
17
|
+
resolveApplicationAuth,
|
|
24
18
|
resolveApplicationCache,
|
|
25
|
-
|
|
19
|
+
resolveApplicationConfig,
|
|
20
|
+
resolveApplicationDependencies,
|
|
21
|
+
resolveApplicationEventBus,
|
|
22
|
+
resolveApplicationLogger,
|
|
23
|
+
resolveApplicationPolicyGate,
|
|
24
|
+
resolveApplicationQueue,
|
|
25
|
+
setActiveApplicationContext
|
|
26
26
|
};
|
|
@@ -21,6 +21,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
|
|
|
21
21
|
import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
|
|
22
22
|
import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
|
|
23
23
|
import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
|
|
24
|
+
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
24
25
|
import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
|
|
25
26
|
import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
|
|
26
27
|
import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
|
|
@@ -173,7 +174,7 @@ class HttpKernel {
|
|
|
173
174
|
return this.wrap(["api", "authenticated"], handler);
|
|
174
175
|
}
|
|
175
176
|
wrapWeb(handler) {
|
|
176
|
-
return handler;
|
|
177
|
+
return withErrorHandling(this.wrap("web", handler));
|
|
177
178
|
}
|
|
178
179
|
wrapWebPublicRead(handler) {
|
|
179
180
|
if (isPublicReadsEnabled()) {
|
|
@@ -183,19 +184,19 @@ class HttpKernel {
|
|
|
183
184
|
}
|
|
184
185
|
wrapWebAuthenticated(handler) {
|
|
185
186
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
186
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
187
|
+
return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
187
188
|
}
|
|
188
189
|
wrapWebAbility(ability, handler) {
|
|
189
190
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
190
191
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
191
192
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
192
193
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
193
|
-
return withMiddleware(...middleware)(handler);
|
|
194
|
+
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
194
195
|
}
|
|
195
196
|
wrapWebGlobalAdmin(handler) {
|
|
196
197
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
197
198
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
198
|
-
return withMiddleware(...middleware)(handler);
|
|
199
|
+
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
199
200
|
}
|
|
200
201
|
wrapAuthenticated(handler) {
|
|
201
202
|
return this.wrap("authenticated", handler);
|
|
@@ -294,7 +295,7 @@ function resolveModulesDirectory(options) {
|
|
|
294
295
|
if (state.configuredModulesDir) {
|
|
295
296
|
return state.configuredModulesDir;
|
|
296
297
|
}
|
|
297
|
-
|
|
298
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
298
299
|
}
|
|
299
300
|
async function loadDiscoveredModules(options) {
|
|
300
301
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -328,6 +329,12 @@ async function ensureModulesLoaded(options) {
|
|
|
328
329
|
function discoverModules() {
|
|
329
330
|
return readDiscoverModulesState().appModules;
|
|
330
331
|
}
|
|
332
|
+
function resetDiscoverModulesForTests() {
|
|
333
|
+
const state = readDiscoverModulesState();
|
|
334
|
+
state.configuredModulesDir = undefined;
|
|
335
|
+
state.appModules.length = 0;
|
|
336
|
+
state.modulesReady = undefined;
|
|
337
|
+
}
|
|
331
338
|
// ../../src/bootstrap/prefixRouteMap.ts
|
|
332
339
|
function prefixRouteMap(prefix, routes) {
|
|
333
340
|
const normalizedPrefix = prefix.replace(/\/$/, "");
|
|
@@ -409,6 +416,6 @@ function buildModuleRoutes(dependencies, options = {}) {
|
|
|
409
416
|
return applyMiddlewareToRoutes(registerOpenApiRouteMap(prefixedModuleRoutes, ["global", "api"]), middleware);
|
|
410
417
|
}
|
|
411
418
|
export {
|
|
412
|
-
|
|
413
|
-
|
|
419
|
+
buildModuleRoutes,
|
|
420
|
+
registerOpenApiRouteMap
|
|
414
421
|
};
|
|
@@ -24,6 +24,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
|
|
|
24
24
|
import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
|
|
25
25
|
import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
|
|
26
26
|
import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
|
|
27
|
+
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
27
28
|
import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
|
|
28
29
|
import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
|
|
29
30
|
import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
|
|
@@ -176,7 +177,7 @@ class HttpKernel {
|
|
|
176
177
|
return this.wrap(["api", "authenticated"], handler);
|
|
177
178
|
}
|
|
178
179
|
wrapWeb(handler) {
|
|
179
|
-
return handler;
|
|
180
|
+
return withErrorHandling(this.wrap("web", handler));
|
|
180
181
|
}
|
|
181
182
|
wrapWebPublicRead(handler) {
|
|
182
183
|
if (isPublicReadsEnabled()) {
|
|
@@ -186,19 +187,19 @@ class HttpKernel {
|
|
|
186
187
|
}
|
|
187
188
|
wrapWebAuthenticated(handler) {
|
|
188
189
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
189
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
190
|
+
return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
190
191
|
}
|
|
191
192
|
wrapWebAbility(ability, handler) {
|
|
192
193
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
193
194
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
194
195
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
195
196
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
196
|
-
return withMiddleware(...middleware)(handler);
|
|
197
|
+
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
197
198
|
}
|
|
198
199
|
wrapWebGlobalAdmin(handler) {
|
|
199
200
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
200
201
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
201
|
-
return withMiddleware(...middleware)(handler);
|
|
202
|
+
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
202
203
|
}
|
|
203
204
|
wrapAuthenticated(handler) {
|
|
204
205
|
return this.wrap("authenticated", handler);
|
|
@@ -297,7 +298,7 @@ function resolveModulesDirectory(options) {
|
|
|
297
298
|
if (state.configuredModulesDir) {
|
|
298
299
|
return state.configuredModulesDir;
|
|
299
300
|
}
|
|
300
|
-
|
|
301
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
301
302
|
}
|
|
302
303
|
async function loadDiscoveredModules(options) {
|
|
303
304
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -331,6 +332,12 @@ async function ensureModulesLoaded(options) {
|
|
|
331
332
|
function discoverModules() {
|
|
332
333
|
return readDiscoverModulesState().appModules;
|
|
333
334
|
}
|
|
335
|
+
function resetDiscoverModulesForTests() {
|
|
336
|
+
const state = readDiscoverModulesState();
|
|
337
|
+
state.configuredModulesDir = undefined;
|
|
338
|
+
state.appModules.length = 0;
|
|
339
|
+
state.modulesReady = undefined;
|
|
340
|
+
}
|
|
334
341
|
// ../../src/bootstrap/prefixRouteMap.ts
|
|
335
342
|
function prefixRouteMap(prefix, routes) {
|
|
336
343
|
const normalizedPrefix = prefix.replace(/\/$/, "");
|
|
@@ -419,7 +426,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
|
|
|
419
426
|
routeRegistry.clear();
|
|
420
427
|
}
|
|
421
428
|
const kernel = createHttpKernel(dependencies);
|
|
422
|
-
const middleware =
|
|
429
|
+
const middleware = kernel.globalMiddleware();
|
|
423
430
|
const moduleRoutes = { ...seedRoutes };
|
|
424
431
|
for (const module of modules) {
|
|
425
432
|
if (!module.webRoutes) {
|
|
@@ -26,7 +26,7 @@ function resolveModulesDirectory(options) {
|
|
|
26
26
|
if (state.configuredModulesDir) {
|
|
27
27
|
return state.configuredModulesDir;
|
|
28
28
|
}
|
|
29
|
-
|
|
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,6 +60,12 @@ 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
|
|
|
64
70
|
// ../../src/bootstrap/cache/modelCacheTags.ts
|
|
65
71
|
function cacheTagsForModelWrite(tableName, action) {
|
|
@@ -73,6 +79,6 @@ function discoverModelTableNames() {
|
|
|
73
79
|
return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
|
|
74
80
|
}
|
|
75
81
|
export {
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
cacheTagsForModelWrite,
|
|
83
|
+
discoverModelTableNames
|
|
78
84
|
};
|
package/dist/entries/config.js
CHANGED
|
@@ -25,24 +25,24 @@ var DEFAULT_CACHE_DRIVER = "array";
|
|
|
25
25
|
var DEFAULT_API_TOKEN = "";
|
|
26
26
|
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
27
27
|
export {
|
|
28
|
-
|
|
29
|
-
DEFAULT_QUEUE_DRIVER,
|
|
30
|
-
DEFAULT_CACHE_TTL_MS,
|
|
31
|
-
DEFAULT_CACHE_MAX_ENTRIES,
|
|
32
|
-
DEFAULT_CACHE_DRIVER,
|
|
33
|
-
DEFAULT_APP_PORT,
|
|
34
|
-
DEFAULT_API_TOKEN,
|
|
35
|
-
DATABASE_URL_CONFIG_KEY,
|
|
36
|
-
CORE_TOKEN_SERVICE_TOKEN,
|
|
37
|
-
CORE_QUEUE_TOKEN,
|
|
38
|
-
CORE_POLICY_GATE_TOKEN,
|
|
39
|
-
CORE_EVENT_BUS_TOKEN,
|
|
40
|
-
CORE_CONFIG_TOKEN,
|
|
41
|
-
CORE_CACHE_TOKEN,
|
|
42
|
-
CORE_AUTH_TOKEN,
|
|
43
|
-
CACHE_TTL_MS_CONFIG_KEY,
|
|
44
|
-
CACHE_MAX_ENTRIES_CONFIG_KEY,
|
|
45
|
-
CACHE_DRIVER_CONFIG_KEY,
|
|
28
|
+
APP_PORT_CONFIG_KEY,
|
|
46
29
|
AUTH_DEV_HEADERS_CONFIG_KEY,
|
|
47
|
-
|
|
30
|
+
CACHE_DRIVER_CONFIG_KEY,
|
|
31
|
+
CACHE_MAX_ENTRIES_CONFIG_KEY,
|
|
32
|
+
CACHE_TTL_MS_CONFIG_KEY,
|
|
33
|
+
CORE_AUTH_TOKEN,
|
|
34
|
+
CORE_CACHE_TOKEN,
|
|
35
|
+
CORE_CONFIG_TOKEN,
|
|
36
|
+
CORE_EVENT_BUS_TOKEN,
|
|
37
|
+
CORE_POLICY_GATE_TOKEN,
|
|
38
|
+
CORE_QUEUE_TOKEN,
|
|
39
|
+
CORE_TOKEN_SERVICE_TOKEN,
|
|
40
|
+
DATABASE_URL_CONFIG_KEY,
|
|
41
|
+
DEFAULT_API_TOKEN,
|
|
42
|
+
DEFAULT_APP_PORT,
|
|
43
|
+
DEFAULT_CACHE_DRIVER,
|
|
44
|
+
DEFAULT_CACHE_MAX_ENTRIES,
|
|
45
|
+
DEFAULT_CACHE_TTL_MS,
|
|
46
|
+
DEFAULT_QUEUE_DRIVER,
|
|
47
|
+
REDIS_URL_CONFIG_KEY
|
|
48
48
|
};
|
package/dist/entries/context.js
CHANGED
|
@@ -50,7 +50,7 @@ function resolveModulesDirectory(options) {
|
|
|
50
50
|
if (state.configuredModulesDir) {
|
|
51
51
|
return state.configuredModulesDir;
|
|
52
52
|
}
|
|
53
|
-
|
|
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 = {
|
|
@@ -636,8 +555,8 @@ var appContext = {
|
|
|
636
555
|
}
|
|
637
556
|
};
|
|
638
557
|
export {
|
|
639
|
-
|
|
640
|
-
createAppContext,
|
|
558
|
+
appContext,
|
|
641
559
|
collectProviders,
|
|
642
|
-
|
|
560
|
+
createAppContext,
|
|
561
|
+
runProviderPhase
|
|
643
562
|
};
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
resolveService
|
|
13
13
|
} from "@getstrata/core/contracts/di";
|
|
14
14
|
export {
|
|
15
|
-
|
|
16
|
-
getRequiredDependency,
|
|
17
|
-
assertAppDependenciesComplete,
|
|
15
|
+
ConfigStore,
|
|
18
16
|
ServiceContainer,
|
|
19
|
-
|
|
17
|
+
assertAppDependenciesComplete,
|
|
18
|
+
getRequiredDependency,
|
|
19
|
+
resolveService
|
|
20
20
|
};
|