@bevel-software/platform-core-backend 0.1.1 → 0.2.0
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/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +13 -0
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +23 -2
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core-config.d.ts +36 -1
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +56 -2
- package/dist/core-config.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts +17 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +14 -1
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +17 -0
- package/dist/modules/auth/account.routes.d.ts.map +1 -0
- package/dist/modules/auth/account.routes.js +47 -0
- package/dist/modules/auth/account.routes.js.map +1 -0
- package/dist/modules/auth/auth.routes.d.ts +13 -7
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +51 -3
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +44 -8
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +129 -32
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts +51 -0
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -0
- package/dist/modules/auth/oidc-auth-provider.js +189 -0
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -0
- package/dist/modules/auth/password-hash.d.ts +12 -0
- package/dist/modules/auth/password-hash.d.ts.map +1 -0
- package/dist/modules/auth/password-hash.js +67 -0
- package/dist/modules/auth/password-hash.js.map +1 -0
- package/dist/modules/auth/rate-limit.d.ts +32 -0
- package/dist/modules/auth/rate-limit.d.ts.map +1 -0
- package/dist/modules/auth/rate-limit.js +49 -0
- package/dist/modules/auth/rate-limit.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +17 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +6 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/migrations/0001_user_password_hash.sql +4 -0
- package/migrations/meta/_journal.json +20 -13
- package/package.json +2 -2
- package/src/core/create-core-server.ts +352 -338
- package/src/core/create-core-services.ts +542 -514
- package/src/core-config.ts +55 -2
- package/src/modules/admin/admin-access.service.ts +10 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +98 -0
- package/src/modules/auth/__tests__/auth.routes.test.ts +91 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +217 -0
- package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +174 -0
- package/src/modules/auth/__tests__/password-hash.test.ts +53 -0
- package/src/modules/auth/__tests__/rate-limit.test.ts +36 -0
- package/src/modules/auth/account.routes.ts +59 -0
- package/src/modules/auth/auth.routes.ts +68 -10
- package/src/modules/auth/auth.service.ts +151 -32
- package/src/modules/auth/oidc-auth-provider.ts +229 -0
- package/src/modules/auth/password-hash.ts +80 -0
- package/src/modules/auth/rate-limit.ts +45 -0
- package/src/modules/database/core-schema.ts +437 -431
|
@@ -1,338 +1,352 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import cors from 'cors';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import type { Router, RequestHandler } from 'express';
|
|
5
|
-
import { createAuthRoutes } from '../modules/auth/auth.routes.js';
|
|
6
|
-
import { createWorkspaceRoutes } from '../modules/workspace/workspace.routes.js';
|
|
7
|
-
import { createDiffRoutes } from '../modules/diff/diff.routes.js';
|
|
8
|
-
import { createWorkflowRoutes } from '../modules/workflow/workflow.routes.js';
|
|
9
|
-
import { createEventsRoutes } from '../modules/workflow/events.routes.js';
|
|
10
|
-
import { createAccessRoutes } from '../modules/access/access.routes.js';
|
|
11
|
-
import { createMcpRoutes } from '../modules/mcp/mcp.routes.js';
|
|
12
|
-
import { createOAuthConsentRoutes } from '../modules/mcp/oauth/oauth-consent.routes.js';
|
|
13
|
-
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
|
|
14
|
-
import { createManualRoutes } from '../modules/tool-registry/manual.routes.js';
|
|
15
|
-
import {
|
|
16
|
-
createToolManualsAgentRoutes,
|
|
17
|
-
createToolManualsBrowserRoutes,
|
|
18
|
-
registerToolManualsTools,
|
|
19
|
-
} from '../modules/tool-manuals/index.js';
|
|
20
|
-
import { registerWorkflowTools } from '../modules/workflow/agent-tools/workflow.tools.js';
|
|
21
|
-
import { registerWorkspaceTools } from '../modules/workspace/workspace.tools.js';
|
|
22
|
-
import { RECOVERY_BOT_EMAIL } from '../modules/workflow/recovery-bot.js';
|
|
23
|
-
import { registerSkillsTools, createSkillsRoutes } from '../modules/skills/index.js';
|
|
24
|
-
import type { SessionOntologyGate } from '../modules/workspace/session-ontology.gate.js';
|
|
25
|
-
import {
|
|
26
|
-
createSecretsVaultRoutes,
|
|
27
|
-
createSecretsVaultPublicRoutes,
|
|
28
|
-
} from '../modules/secrets-vault/index.js';
|
|
29
|
-
import { createAdminAccessRoutes } from '../modules/admin/admin-access.routes.js';
|
|
30
|
-
import {
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// surface
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
core,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
core.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
core
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
core.
|
|
261
|
-
core.authService,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
app.use('/api', core.authMiddleware,
|
|
272
|
-
core.
|
|
273
|
-
core.
|
|
274
|
-
core.
|
|
275
|
-
core.eventBus,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
app.use('/api', core.authMiddleware,
|
|
284
|
-
core.
|
|
285
|
-
core.
|
|
286
|
-
core.
|
|
287
|
-
core.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
core.
|
|
297
|
-
core.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
app.use('/api', core.authMiddleware,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import type { Router, RequestHandler } from 'express';
|
|
5
|
+
import { createAuthRoutes } from '../modules/auth/auth.routes.js';
|
|
6
|
+
import { createWorkspaceRoutes } from '../modules/workspace/workspace.routes.js';
|
|
7
|
+
import { createDiffRoutes } from '../modules/diff/diff.routes.js';
|
|
8
|
+
import { createWorkflowRoutes } from '../modules/workflow/workflow.routes.js';
|
|
9
|
+
import { createEventsRoutes } from '../modules/workflow/events.routes.js';
|
|
10
|
+
import { createAccessRoutes } from '../modules/access/access.routes.js';
|
|
11
|
+
import { createMcpRoutes } from '../modules/mcp/mcp.routes.js';
|
|
12
|
+
import { createOAuthConsentRoutes } from '../modules/mcp/oauth/oauth-consent.routes.js';
|
|
13
|
+
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
|
|
14
|
+
import { createManualRoutes } from '../modules/tool-registry/manual.routes.js';
|
|
15
|
+
import {
|
|
16
|
+
createToolManualsAgentRoutes,
|
|
17
|
+
createToolManualsBrowserRoutes,
|
|
18
|
+
registerToolManualsTools,
|
|
19
|
+
} from '../modules/tool-manuals/index.js';
|
|
20
|
+
import { registerWorkflowTools } from '../modules/workflow/agent-tools/workflow.tools.js';
|
|
21
|
+
import { registerWorkspaceTools } from '../modules/workspace/workspace.tools.js';
|
|
22
|
+
import { RECOVERY_BOT_EMAIL } from '../modules/workflow/recovery-bot.js';
|
|
23
|
+
import { registerSkillsTools, createSkillsRoutes } from '../modules/skills/index.js';
|
|
24
|
+
import type { SessionOntologyGate } from '../modules/workspace/session-ontology.gate.js';
|
|
25
|
+
import {
|
|
26
|
+
createSecretsVaultRoutes,
|
|
27
|
+
createSecretsVaultPublicRoutes,
|
|
28
|
+
} from '../modules/secrets-vault/index.js';
|
|
29
|
+
import { createAdminAccessRoutes } from '../modules/admin/admin-access.routes.js';
|
|
30
|
+
import { createAccountRoutes } from '../modules/auth/account.routes.js';
|
|
31
|
+
import { GIT_SHA } from '../version.js';
|
|
32
|
+
import type { CoreServices } from './create-core-services.js';
|
|
33
|
+
|
|
34
|
+
type ExpressApp = ReturnType<typeof express>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The context handed to {@link ServerExtensions.tools}: everything an overlay
|
|
38
|
+
* needs to register its own tool defs + endpoint routes on the unified tool
|
|
39
|
+
* surface, exactly like the core modules do.
|
|
40
|
+
*/
|
|
41
|
+
export interface ToolSurfaceCtx {
|
|
42
|
+
registry: CoreServices['toolRegistry'];
|
|
43
|
+
router: Router;
|
|
44
|
+
toolAuth: RequestHandler;
|
|
45
|
+
toolHandler: CoreServices['toolHandlerFactory'];
|
|
46
|
+
/** Shared ontology-session boundary gate config (file tools + graph tools). */
|
|
47
|
+
sessionOntologyGate: SessionOntologyGate;
|
|
48
|
+
core: CoreServices;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The hook points an overlay (the enterprise app) uses to add its surfaces to
|
|
53
|
+
* the core server. Each hook maps to a fixed slot in the LOAD-BEARING mount
|
|
54
|
+
* order encoded in {@link createCoreServer} — the order comments there explain
|
|
55
|
+
* why each slot sits where it does (Express 5 fires outer middleware before
|
|
56
|
+
* route matching, so pre-JWT surfaces MUST mount before the JWT `/api` mounts).
|
|
57
|
+
*/
|
|
58
|
+
export interface ServerExtensions {
|
|
59
|
+
/**
|
|
60
|
+
* Request paths the global 10 MB JSON body parser must skip because the
|
|
61
|
+
* overlay's own router installs a larger parser for them (body-parser
|
|
62
|
+
* ignores a second parse once the first has run — see the comment at the
|
|
63
|
+
* parser below).
|
|
64
|
+
*/
|
|
65
|
+
jsonParserExemptPaths?: string[];
|
|
66
|
+
/**
|
|
67
|
+
* Boot-time side effects that belong to the server lifecycle (startup
|
|
68
|
+
* reconciles, periodic sweeps). Runs after health/cors/json are installed,
|
|
69
|
+
* before any routes mount.
|
|
70
|
+
*/
|
|
71
|
+
onBoot?(core: CoreServices): Promise<void> | void;
|
|
72
|
+
/** Un-authed overlay surfaces (e.g. OAuth callbacks hit by provider redirects). */
|
|
73
|
+
preAuth?(app: ExpressApp, core: CoreServices): void;
|
|
74
|
+
/**
|
|
75
|
+
* Overlay tool registrations on the unified tool surface. Runs after the
|
|
76
|
+
* core registrations and BEFORE the manual routes mount, preserving the
|
|
77
|
+
* "manual snapshot stays complete" invariant.
|
|
78
|
+
*/
|
|
79
|
+
tools?(ctx: ToolSurfaceCtx): void;
|
|
80
|
+
/**
|
|
81
|
+
* Non-JWT overlay surfaces that historically mount AFTER the tools router
|
|
82
|
+
* but before the JWT-protected `/api` routes (LLM proxy, embed, upload).
|
|
83
|
+
* A separate phase from `preAuth` purely to preserve today's exact order.
|
|
84
|
+
*/
|
|
85
|
+
postTools?(app: ExpressApp, core: CoreServices): void;
|
|
86
|
+
/** JWT-protected overlay routes; mounted after every core authed route. */
|
|
87
|
+
authed?(app: ExpressApp, core: CoreServices): void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function createCoreServer(
|
|
91
|
+
core: CoreServices,
|
|
92
|
+
ext: ServerExtensions = {},
|
|
93
|
+
opts: { staticDir?: string } = {},
|
|
94
|
+
): Promise<ExpressApp> {
|
|
95
|
+
const app = express();
|
|
96
|
+
|
|
97
|
+
// Honor forwarded client addresses ONLY when the deployment declares its
|
|
98
|
+
// proxy topology (TRUST_PROXY = hop count or CIDR list — see CoreConfig).
|
|
99
|
+
// Without it, `req.ip` is the socket peer: safe when directly exposed, but
|
|
100
|
+
// behind a proxy every client would share the proxy's address (pooling the
|
|
101
|
+
// per-IP login rate limit). A number is the hop count; anything else is
|
|
102
|
+
// passed through as Express's address/CIDR list form.
|
|
103
|
+
if (core.config.trustProxy) {
|
|
104
|
+
const raw = core.config.trustProxy;
|
|
105
|
+
app.set('trust proxy', /^\d+$/.test(raw) ? Number(raw) : raw);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// `credentials: true` so EventSource (`withCredentials: true`) can carry
|
|
109
|
+
// the `bevel_token` cookie set at login — the only auth path the
|
|
110
|
+
// EventSource API supports. `origin: true` reflects the request origin
|
|
111
|
+
// back in `Access-Control-Allow-Origin`, which the spec requires when
|
|
112
|
+
// credentials are in play (the wildcard `*` is refused alongside
|
|
113
|
+
// credentials). Cookie itself is `SameSite=Lax`, so a hostile cross-site
|
|
114
|
+
// page still can't read events on the user's behalf.
|
|
115
|
+
//
|
|
116
|
+
// `exposedHeaders` lets a BROWSER-based MCP client (e.g. MCP Inspector) read
|
|
117
|
+
// the Streamable-HTTP session header off the `initialize` response — custom
|
|
118
|
+
// response headers are hidden from browser JS unless exposed, so without this
|
|
119
|
+
// the client can't send `Mcp-Session-Id` back and every follow-up 400s with
|
|
120
|
+
// "session id does not match any active session". `WWW-Authenticate` is
|
|
121
|
+
// exposed so a browser client can read the 401 challenge and start the OAuth
|
|
122
|
+
// discovery flow. (Native clients like Claude Code aren't subject to CORS.)
|
|
123
|
+
app.use(
|
|
124
|
+
cors({
|
|
125
|
+
origin: true,
|
|
126
|
+
credentials: true,
|
|
127
|
+
exposedHeaders: ['Mcp-Session-Id', 'Mcp-Protocol-Version', 'WWW-Authenticate'],
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
// Global JSON body parser. Some overlay routes carry a whole document dump
|
|
131
|
+
// (e.g. the onboarding import), so their routers install their own larger
|
|
132
|
+
// parser — skip the global one for those paths (body-parser ignores a second
|
|
133
|
+
// parse once the first has run, so without this the 10 MB global limit would
|
|
134
|
+
// shadow the route's larger limit and 413 a large-but-valid upload before it
|
|
135
|
+
// ever reaches the route).
|
|
136
|
+
const jsonExemptPaths = new Set(ext.jsonParserExemptPaths ?? []);
|
|
137
|
+
const globalJson = express.json({ limit: '10mb' });
|
|
138
|
+
app.use((req, res, next) => {
|
|
139
|
+
if (jsonExemptPaths.has(req.path)) return next();
|
|
140
|
+
return globalJson(req, res, next);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Health check. `sha` is the git commit this build was produced from
|
|
144
|
+
// (see version.ts) so the deploy pipeline can confirm a staging/production
|
|
145
|
+
// rollout matches the merged commit before smoke-testing it.
|
|
146
|
+
app.get('/api/health', (_req, res) => {
|
|
147
|
+
res.json({ status: 'ok', sha: GIT_SHA, timestamp: Date.now() });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Overlay boot-time side effects (startup reconciles, periodic sweeps).
|
|
151
|
+
await ext.onBoot?.(core);
|
|
152
|
+
|
|
153
|
+
// Auth routes (unprotected — login endpoint must be accessible)
|
|
154
|
+
app.use(
|
|
155
|
+
'/api',
|
|
156
|
+
createAuthRoutes(
|
|
157
|
+
core.authService,
|
|
158
|
+
core.authMiddleware,
|
|
159
|
+
core.authProviders,
|
|
160
|
+
core.config.loginPasswordEnabled,
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// MCP routes must mount BEFORE every `app.use('/api', authMiddleware, …)`
|
|
165
|
+
// below: Express 5 fires the outer JWT middleware before deciding whether
|
|
166
|
+
// its router has a matching route, so a request to `POST /api/mcp` carrying
|
|
167
|
+
// a connection-key (non-JWT) bearer would otherwise be 401'd by the first
|
|
168
|
+
// protected mount before this router ever saw it.
|
|
169
|
+
app.use('/api', createMcpRoutes(
|
|
170
|
+
core.mcpService,
|
|
171
|
+
core.externalApiKeyService,
|
|
172
|
+
core.mcpAuthMiddleware,
|
|
173
|
+
core.authMiddleware,
|
|
174
|
+
core.usageMeter,
|
|
175
|
+
));
|
|
176
|
+
|
|
177
|
+
// MCP OAuth 2.1 authorization server: /authorize, /token, /register,
|
|
178
|
+
// /revoke + the /.well-known metadata documents (the SDK requires an
|
|
179
|
+
// app-root mount for those paths). Un-authed by design — these endpoints
|
|
180
|
+
// are hit by MCP clients with no credentials yet and by bare browser
|
|
181
|
+
// navigations; the user attaches to the flow at the JWT/cookie-authed
|
|
182
|
+
// consent routes below. Must sit before the prod SPA catch-all or the
|
|
183
|
+
// .well-known documents would be swallowed by index.html.
|
|
184
|
+
app.use(mcpAuthRouter({
|
|
185
|
+
provider: core.mcpOAuthProvider,
|
|
186
|
+
issuerUrl: new URL(core.config.publicBackendUrl),
|
|
187
|
+
resourceServerUrl: new URL('/api/mcp', core.config.publicBackendUrl),
|
|
188
|
+
scopesSupported: ['mcp'],
|
|
189
|
+
resourceName: 'Bevel MCP',
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
// Un-authed overlay surfaces (e.g. connector OAuth callbacks hit by a
|
|
193
|
+
// provider's browser redirect with no Authorization header).
|
|
194
|
+
ext.preAuth?.(app, core);
|
|
195
|
+
|
|
196
|
+
// Secrets Vault OAuth callback — also hit by a provider browser redirect with
|
|
197
|
+
// no Authorization header, so it mounts UN-AUTHED here, recovering the caller
|
|
198
|
+
// from the signed `state`. The authed CRUD/start routes mount later.
|
|
199
|
+
const secretsVaultRoutesDeps = {
|
|
200
|
+
secretsVault: core.secretsVaultService,
|
|
201
|
+
toolManualService: core.toolManualService,
|
|
202
|
+
accessControl: core.accessControl,
|
|
203
|
+
stateSecret: core.config.jwtSecret,
|
|
204
|
+
publicBackendUrl: core.config.publicBackendUrl,
|
|
205
|
+
publicFrontendUrl: core.config.publicFrontendUrl,
|
|
206
|
+
};
|
|
207
|
+
app.use('/api', createSecretsVaultPublicRoutes(secretsVaultRoutesDeps));
|
|
208
|
+
|
|
209
|
+
// Unified tool surface — the ONE catalog, served as two UTCP manuals + each
|
|
210
|
+
// tool's module-hosted endpoint, all behind the shared `toolAuth` (connection
|
|
211
|
+
// key OR internal token). Each module registers its tool defs + hosts its
|
|
212
|
+
// routes on this one router. Mount BEFORE the JWT `/api` mounts — connection-
|
|
213
|
+
// key / internal-token bearers would be 401'd by the outer JWT middleware
|
|
214
|
+
// otherwise. Manuals are added LAST so they snapshot the full catalog:
|
|
215
|
+
// core registrations first, then the overlay's (`ext.tools`), then the
|
|
216
|
+
// manual routes.
|
|
217
|
+
const toolsRouter = express.Router();
|
|
218
|
+
const ta = core.toolAuthMiddleware;
|
|
219
|
+
const th = core.toolHandlerFactory;
|
|
220
|
+
// Shared ontology-session boundary gate config, consumed by every tool
|
|
221
|
+
// surface that touches the KB (file tools + graph tools). The gate's
|
|
222
|
+
// blocking decision runs through the workflow hooks: core registers none
|
|
223
|
+
// (tracking only); the enterprise root registers the ontology block on
|
|
224
|
+
// `workflowService.hooks` before this server is built.
|
|
225
|
+
const sessionOntologyGate = {
|
|
226
|
+
service: core.sessionOntologyService,
|
|
227
|
+
enabled: core.config.ontologySessionBlock,
|
|
228
|
+
kbDirName: core.config.kbDirName,
|
|
229
|
+
recoveryBotEmail: RECOVERY_BOT_EMAIL,
|
|
230
|
+
hooks: core.workflowService.hooks,
|
|
231
|
+
};
|
|
232
|
+
registerWorkflowTools(core.toolRegistry, toolsRouter, ta, th);
|
|
233
|
+
registerWorkspaceTools(core.toolRegistry, toolsRouter, ta, th, core.spillStore, core.accessControl, core.config.kbDirName, sessionOntologyGate, core.routineWritePolicy, core.sessionSink);
|
|
234
|
+
registerSkillsTools(core.toolRegistry, toolsRouter, ta, th, core.skillService);
|
|
235
|
+
registerToolManualsTools(core.toolRegistry, toolsRouter, ta, th, core.toolManualService, {
|
|
236
|
+
accessControl: core.accessControl,
|
|
237
|
+
// The vault satisfies the module's local VariableStatusPort — `list_tool_setup`
|
|
238
|
+
// reports configuration booleans only; secret values never ride through tools.
|
|
239
|
+
variableStatus: core.secretsVaultService,
|
|
240
|
+
});
|
|
241
|
+
// Overlay tool registrations (defs + module-hosted endpoints).
|
|
242
|
+
ext.tools?.({
|
|
243
|
+
registry: core.toolRegistry,
|
|
244
|
+
router: toolsRouter,
|
|
245
|
+
toolAuth: ta,
|
|
246
|
+
toolHandler: th,
|
|
247
|
+
sessionOntologyGate,
|
|
248
|
+
core,
|
|
249
|
+
});
|
|
250
|
+
toolsRouter.use(createManualRoutes(
|
|
251
|
+
core.toolRegistry,
|
|
252
|
+
core.manualAuthMiddleware,
|
|
253
|
+
async (userId) => (await core.authService.getUserById(userId))?.email,
|
|
254
|
+
));
|
|
255
|
+
// The aggregated manual list (KB + the caller's accessible `.tool`s) + inline
|
|
256
|
+
// `.tool` sub-manuals. Mounted on the tools router so they share `manualAuth`
|
|
257
|
+
// and sit before the JWT `/api` mounts.
|
|
258
|
+
toolsRouter.use(createToolManualsAgentRoutes(
|
|
259
|
+
core.toolManualService,
|
|
260
|
+
core.manualAuthMiddleware,
|
|
261
|
+
async (userId) => (await core.authService.getUserById(userId))?.email,
|
|
262
|
+
));
|
|
263
|
+
app.use('/api', toolsRouter);
|
|
264
|
+
|
|
265
|
+
// Non-JWT overlay surfaces that sit between the tools router and the
|
|
266
|
+
// JWT-protected `/api` routes (LLM proxy, embed, upload — see the phase
|
|
267
|
+
// doc on ServerExtensions.postTools).
|
|
268
|
+
ext.postTools?.(app, core);
|
|
269
|
+
|
|
270
|
+
// Protected routes
|
|
271
|
+
app.use('/api', core.authMiddleware, createWorkspaceRoutes(
|
|
272
|
+
core.workspaceService,
|
|
273
|
+
core.authService,
|
|
274
|
+
core.workflowService,
|
|
275
|
+
core.eventBus,
|
|
276
|
+
core.accessControl,
|
|
277
|
+
core.config.kbDirName,
|
|
278
|
+
core.creatorAccess,
|
|
279
|
+
));
|
|
280
|
+
// Workflow is the only branches / changes / change-request surface. The
|
|
281
|
+
// former /git/*, /pr/*, /pr/:n/* routes are gone — every consumer goes
|
|
282
|
+
// through /workflow/* now.
|
|
283
|
+
app.use('/api', core.authMiddleware, createWorkflowRoutes(
|
|
284
|
+
core.workflowService,
|
|
285
|
+
core.workspaceService,
|
|
286
|
+
core.authService,
|
|
287
|
+
core.eventBus,
|
|
288
|
+
));
|
|
289
|
+
// SSE event-bus surface. The route owns its own auth gating via the
|
|
290
|
+
// injected middleware (which accepts both Bearer and the `bevel_token`
|
|
291
|
+
// cookie — the cookie path is what makes EventSource work, since the
|
|
292
|
+
// browser API can't set headers). Mounted as its own app.use so it
|
|
293
|
+
// doesn't share an outer middleware chain with the JSON routes above.
|
|
294
|
+
app.use('/api', createEventsRoutes(core.eventBus, core.authMiddleware));
|
|
295
|
+
app.use('/api', core.authMiddleware, createDiffRoutes(
|
|
296
|
+
core.diffService,
|
|
297
|
+
core.authService,
|
|
298
|
+
core.workflowService,
|
|
299
|
+
core.accessControl,
|
|
300
|
+
core.config.kbDirName,
|
|
301
|
+
));
|
|
302
|
+
app.use('/api', core.authMiddleware, createAccessRoutes(
|
|
303
|
+
core.accessControl,
|
|
304
|
+
core.workspaceService,
|
|
305
|
+
core.authService,
|
|
306
|
+
core.workflowService,
|
|
307
|
+
core.eventBus,
|
|
308
|
+
core.db,
|
|
309
|
+
core.config.kbDirName,
|
|
310
|
+
));
|
|
311
|
+
app.use('/api', core.authMiddleware, createSkillsRoutes(core.skillService));
|
|
312
|
+
// Admin-status resolver (CORE — see the note in admin-access.routes.ts;
|
|
313
|
+
// the full admin router is an enterprise `ext.authed` extension).
|
|
314
|
+
app.use('/api', core.authMiddleware, createAdminAccessRoutes(core.adminAccess));
|
|
315
|
+
// Account management (list/create password accounts) — admin-gated inside.
|
|
316
|
+
app.use('/api', core.authMiddleware, createAccountRoutes(core.authService, core.adminAccess));
|
|
317
|
+
app.use('/api', core.authMiddleware, createToolManualsBrowserRoutes(core.toolManualService));
|
|
318
|
+
app.use('/api', core.authMiddleware, createSecretsVaultRoutes(secretsVaultRoutesDeps));
|
|
319
|
+
// The authed tail of the MCP OAuth flow: /connect calls these to describe
|
|
320
|
+
// the pending authorization and, on Finish, to mint the one-time code. The
|
|
321
|
+
// browser arrives via redirect with no Authorization header, so the JWT
|
|
322
|
+
// middleware's bevel_token-cookie fallback is what identifies the user.
|
|
323
|
+
app.use('/api', core.authMiddleware, createOAuthConsentRoutes({
|
|
324
|
+
provider: core.mcpOAuthProvider,
|
|
325
|
+
stateSecret: core.config.jwtSecret,
|
|
326
|
+
}));
|
|
327
|
+
|
|
328
|
+
// JWT-protected overlay routes.
|
|
329
|
+
ext.authed?.(app, core);
|
|
330
|
+
|
|
331
|
+
// In production, serve the frontend static build
|
|
332
|
+
if (opts.staticDir) {
|
|
333
|
+
const frontendDist = opts.staticDir;
|
|
334
|
+
app.use(express.static(frontendDist));
|
|
335
|
+
app.get('{*path}', (req, res) => {
|
|
336
|
+
// Non-SPA namespaces must NOT resolve to index.html. In particular, an
|
|
337
|
+
// OAuth well-known metadata URL the mcpAuthRouter doesn't serve (a variant
|
|
338
|
+
// the client probes, e.g. the root protected-resource or a path-suffixed
|
|
339
|
+
// authorization-server doc) would otherwise return `200 + HTML`, which an
|
|
340
|
+
// MCP client parses as JSON → "expected object, received undefined" and
|
|
341
|
+
// the whole connection fails. A clean 404 lets discovery fall through to
|
|
342
|
+
// the variant we do serve. Same reasoning for stray `/api/*` gets.
|
|
343
|
+
if (req.path.startsWith('/.well-known/') || req.path.startsWith('/api/')) {
|
|
344
|
+
res.status(404).json({ error: 'Not found' });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
res.sendFile(path.join(frontendDist, 'index.html'));
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return app;
|
|
352
|
+
}
|