@synoikia/core 0.1.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.
Files changed (209) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app.d.ts +79 -0
  3. package/dist/app.js +155 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/approvals/links.d.ts +23 -0
  6. package/dist/approvals/links.js +49 -0
  7. package/dist/approvals/links.js.map +1 -0
  8. package/dist/approvals/service.d.ts +110 -0
  9. package/dist/approvals/service.js +182 -0
  10. package/dist/approvals/service.js.map +1 -0
  11. package/dist/audit-query.d.ts +52 -0
  12. package/dist/audit-query.js +116 -0
  13. package/dist/audit-query.js.map +1 -0
  14. package/dist/audit.d.ts +5 -0
  15. package/dist/audit.js +8 -0
  16. package/dist/audit.js.map +1 -0
  17. package/dist/auth/mcp-auth.d.ts +50 -0
  18. package/dist/auth/mcp-auth.js +160 -0
  19. package/dist/auth/mcp-auth.js.map +1 -0
  20. package/dist/auth/mcp-tokens.d.ts +57 -0
  21. package/dist/auth/mcp-tokens.js +143 -0
  22. package/dist/auth/mcp-tokens.js.map +1 -0
  23. package/dist/auth/oauth.d.ts +188 -0
  24. package/dist/auth/oauth.js +470 -0
  25. package/dist/auth/oauth.js.map +1 -0
  26. package/dist/auth/oidc.d.ts +110 -0
  27. package/dist/auth/oidc.js +221 -0
  28. package/dist/auth/oidc.js.map +1 -0
  29. package/dist/auth/sessions.d.ts +34 -0
  30. package/dist/auth/sessions.js +83 -0
  31. package/dist/auth/sessions.js.map +1 -0
  32. package/dist/auth/throttle.d.ts +31 -0
  33. package/dist/auth/throttle.js +52 -0
  34. package/dist/auth/throttle.js.map +1 -0
  35. package/dist/auth/tokens.d.ts +7 -0
  36. package/dist/auth/tokens.js +40 -0
  37. package/dist/auth/tokens.js.map +1 -0
  38. package/dist/auth/totp.d.ts +14 -0
  39. package/dist/auth/totp.js +81 -0
  40. package/dist/auth/totp.js.map +1 -0
  41. package/dist/auth/users.d.ts +70 -0
  42. package/dist/auth/users.js +296 -0
  43. package/dist/auth/users.js.map +1 -0
  44. package/dist/catalog/groups.d.ts +102 -0
  45. package/dist/catalog/groups.js +363 -0
  46. package/dist/catalog/groups.js.map +1 -0
  47. package/dist/catalog/registry.d.ts +25 -0
  48. package/dist/catalog/registry.js +80 -0
  49. package/dist/catalog/registry.js.map +1 -0
  50. package/dist/catalog/rules.d.ts +128 -0
  51. package/dist/catalog/rules.js +178 -0
  52. package/dist/catalog/rules.js.map +1 -0
  53. package/dist/catalog/sync.d.ts +29 -0
  54. package/dist/catalog/sync.js +222 -0
  55. package/dist/catalog/sync.js.map +1 -0
  56. package/dist/cli.d.ts +3 -0
  57. package/dist/cli.js +83 -0
  58. package/dist/cli.js.map +1 -0
  59. package/dist/config/env.d.ts +40 -0
  60. package/dist/config/env.js +71 -0
  61. package/dist/config/env.js.map +1 -0
  62. package/dist/crypto/index.d.ts +28 -0
  63. package/dist/crypto/index.js +88 -0
  64. package/dist/crypto/index.js.map +1 -0
  65. package/dist/crypto/rotate.d.ts +15 -0
  66. package/dist/crypto/rotate.js +61 -0
  67. package/dist/crypto/rotate.js.map +1 -0
  68. package/dist/db/index.d.ts +17 -0
  69. package/dist/db/index.js +29 -0
  70. package/dist/db/index.js.map +1 -0
  71. package/dist/db/schema.d.ts +4389 -0
  72. package/dist/db/schema.js +372 -0
  73. package/dist/db/schema.js.map +1 -0
  74. package/dist/endpoints/slug.d.ts +3 -0
  75. package/dist/endpoints/slug.js +7 -0
  76. package/dist/endpoints/slug.js.map +1 -0
  77. package/dist/errors.d.ts +16 -0
  78. package/dist/errors.js +29 -0
  79. package/dist/errors.js.map +1 -0
  80. package/dist/events.d.ts +43 -0
  81. package/dist/events.js +16 -0
  82. package/dist/events.js.map +1 -0
  83. package/dist/gate/access.d.ts +47 -0
  84. package/dist/gate/access.js +48 -0
  85. package/dist/gate/access.js.map +1 -0
  86. package/dist/gate/attestation.d.ts +23 -0
  87. package/dist/gate/attestation.js +33 -0
  88. package/dist/gate/attestation.js.map +1 -0
  89. package/dist/gate/canonical.d.ts +3 -0
  90. package/dist/gate/canonical.js +11 -0
  91. package/dist/gate/canonical.js.map +1 -0
  92. package/dist/gate/match.d.ts +67 -0
  93. package/dist/gate/match.js +133 -0
  94. package/dist/gate/match.js.map +1 -0
  95. package/dist/gate/pipeline.d.ts +61 -0
  96. package/dist/gate/pipeline.js +284 -0
  97. package/dist/gate/pipeline.js.map +1 -0
  98. package/dist/gate/preapproval.d.ts +24 -0
  99. package/dist/gate/preapproval.js +48 -0
  100. package/dist/gate/preapproval.js.map +1 -0
  101. package/dist/gate/rate-limit.d.ts +35 -0
  102. package/dist/gate/rate-limit.js +82 -0
  103. package/dist/gate/rate-limit.js.map +1 -0
  104. package/dist/gate/redact.d.ts +23 -0
  105. package/dist/gate/redact.js +87 -0
  106. package/dist/gate/redact.js.map +1 -0
  107. package/dist/http/admin/auth.d.ts +33 -0
  108. package/dist/http/admin/auth.js +338 -0
  109. package/dist/http/admin/auth.js.map +1 -0
  110. package/dist/http/admin/instances.d.ts +5 -0
  111. package/dist/http/admin/instances.js +193 -0
  112. package/dist/http/admin/instances.js.map +1 -0
  113. package/dist/http/admin/system.d.ts +5 -0
  114. package/dist/http/admin/system.js +225 -0
  115. package/dist/http/admin/system.js.map +1 -0
  116. package/dist/http/admin-app.d.ts +9 -0
  117. package/dist/http/admin-app.js +51 -0
  118. package/dist/http/admin-app.js.map +1 -0
  119. package/dist/http/common.d.ts +32 -0
  120. package/dist/http/common.js +80 -0
  121. package/dist/http/common.js.map +1 -0
  122. package/dist/http/mcp/approval-routes.d.ts +11 -0
  123. package/dist/http/mcp/approval-routes.js +117 -0
  124. package/dist/http/mcp/approval-routes.js.map +1 -0
  125. package/dist/http/mcp/endpoint.d.ts +32 -0
  126. package/dist/http/mcp/endpoint.js +293 -0
  127. package/dist/http/mcp/endpoint.js.map +1 -0
  128. package/dist/http/mcp/oauth-routes.d.ts +29 -0
  129. package/dist/http/mcp/oauth-routes.js +465 -0
  130. package/dist/http/mcp/oauth-routes.js.map +1 -0
  131. package/dist/http/mcp/pages.d.ts +63 -0
  132. package/dist/http/mcp/pages.js +200 -0
  133. package/dist/http/mcp/pages.js.map +1 -0
  134. package/dist/http/mcp-app.d.ts +7 -0
  135. package/dist/http/mcp-app.js +30 -0
  136. package/dist/http/mcp-app.js.map +1 -0
  137. package/dist/instances/connection.d.ts +37 -0
  138. package/dist/instances/connection.js +109 -0
  139. package/dist/instances/connection.js.map +1 -0
  140. package/dist/instances/manager.d.ts +313 -0
  141. package/dist/instances/manager.js +600 -0
  142. package/dist/instances/manager.js.map +1 -0
  143. package/dist/instances/settings.d.ts +42 -0
  144. package/dist/instances/settings.js +57 -0
  145. package/dist/instances/settings.js.map +1 -0
  146. package/dist/lenient.d.ts +16 -0
  147. package/dist/lenient.js +51 -0
  148. package/dist/lenient.js.map +1 -0
  149. package/dist/lock.d.ts +28 -0
  150. package/dist/lock.js +77 -0
  151. package/dist/lock.js.map +1 -0
  152. package/dist/main.d.ts +1 -0
  153. package/dist/main.js +38 -0
  154. package/dist/main.js.map +1 -0
  155. package/dist/maintenance.d.ts +10 -0
  156. package/dist/maintenance.js +48 -0
  157. package/dist/maintenance.js.map +1 -0
  158. package/dist/notify/service.d.ts +149 -0
  159. package/dist/notify/service.js +358 -0
  160. package/dist/notify/service.js.map +1 -0
  161. package/dist/plugins/discovery.d.ts +39 -0
  162. package/dist/plugins/discovery.js +157 -0
  163. package/dist/plugins/discovery.js.map +1 -0
  164. package/dist/plugins/minisign.d.ts +25 -0
  165. package/dist/plugins/minisign.js +49 -0
  166. package/dist/plugins/minisign.js.map +1 -0
  167. package/dist/plugins/process.d.ts +55 -0
  168. package/dist/plugins/process.js +162 -0
  169. package/dist/plugins/process.js.map +1 -0
  170. package/dist/plugins/repos.d.ts +198 -0
  171. package/dist/plugins/repos.js +612 -0
  172. package/dist/plugins/repos.js.map +1 -0
  173. package/dist/plugins/supervisor.d.ts +50 -0
  174. package/dist/plugins/supervisor.js +125 -0
  175. package/dist/plugins/supervisor.js.map +1 -0
  176. package/dist/runtime/index.d.ts +28 -0
  177. package/dist/runtime/index.js +248 -0
  178. package/dist/runtime/index.js.map +1 -0
  179. package/dist/sandbox/index.d.ts +53 -0
  180. package/dist/sandbox/index.js +197 -0
  181. package/dist/sandbox/index.js.map +1 -0
  182. package/dist/server.d.ts +16 -0
  183. package/dist/server.js +36 -0
  184. package/dist/server.js.map +1 -0
  185. package/dist/settings.d.ts +43 -0
  186. package/dist/settings.js +99 -0
  187. package/dist/settings.js.map +1 -0
  188. package/dist/testing/index.d.ts +73 -0
  189. package/dist/testing/index.js +174 -0
  190. package/dist/testing/index.js.map +1 -0
  191. package/dist/version.d.ts +2 -0
  192. package/dist/version.js +4 -0
  193. package/dist/version.js.map +1 -0
  194. package/drizzle/0000_init.sql +334 -0
  195. package/drizzle/0001_totp_last_step.sql +1 -0
  196. package/drizzle/0002_access_levels.sql +16 -0
  197. package/drizzle/0003_drop_exclusion_flags.sql +2 -0
  198. package/drizzle/0004_grant_instance_ids.sql +1 -0
  199. package/drizzle/0005_rule_strict_miss.sql +1 -0
  200. package/drizzle/0006_attestation_waived.sql +1 -0
  201. package/drizzle/meta/0000_snapshot.json +2318 -0
  202. package/drizzle/meta/0001_snapshot.json +2325 -0
  203. package/drizzle/meta/0002_snapshot.json +2348 -0
  204. package/drizzle/meta/0003_snapshot.json +2332 -0
  205. package/drizzle/meta/0004_snapshot.json +2339 -0
  206. package/drizzle/meta/0005_snapshot.json +2346 -0
  207. package/drizzle/meta/0006_snapshot.json +2354 -0
  208. package/drizzle/meta/_journal.json +55 -0
  209. package/package.json +58 -0
@@ -0,0 +1,225 @@
1
+ import { streamSSE } from 'hono/streaming';
2
+ import { asc, count, eq } from 'drizzle-orm';
3
+ import { z } from 'zod';
4
+ import { auditCsvChunks, queryAudit } from '../../audit-query.js';
5
+ import { toPublicUser } from '../../auth/users.js';
6
+ import { pluginInstances, plugins } from '../../db/schema.js';
7
+ import { ConflictError, NotFoundError, ValidationError } from '../../errors.js';
8
+ import { CORE_EVENT_NAMES } from '../../events.js';
9
+ import { getSettings, isSettingsSection, updateSettings } from '../../settings.js';
10
+ import { readJson } from '../common.js';
11
+ import { assertSsoRemains } from './auth.js';
12
+ /** Global Admin API routes (design §8.4): overview, plugins, audit, tokens, users, settings, events. */
13
+ export function registerSystemRoutes(app, ctx) {
14
+ const actor = (c) => ({ userId: c.get('user').id });
15
+ const mcpBase = () => ctx.config.PUBLIC_MCP_URL?.replace(/\/$/, '') ?? '';
16
+ app.get('/api/overview', (c) => {
17
+ const mcpSettings = getSettings(ctx.db, 'mcp');
18
+ const instances = ctx.instances.list().map((i) => ({
19
+ ...i,
20
+ endpointUrl: `${mcpBase()}/${i.slug}`,
21
+ effectiveAuthMode: i.authMode ?? mcpSettings.defaultAuthMode,
22
+ }));
23
+ return c.json({
24
+ instances,
25
+ plugins: ctx.db
26
+ .select()
27
+ .from(plugins)
28
+ .orderBy(asc(plugins.pluginId))
29
+ .all()
30
+ .map((p) => ({
31
+ id: p.id,
32
+ pluginId: p.pluginId,
33
+ status: p.status,
34
+ enabled: p.enabled,
35
+ })),
36
+ warnings: ctx.warnings,
37
+ publicMcpUrl: ctx.config.PUBLIC_MCP_URL ?? null,
38
+ });
39
+ });
40
+ // ── plugins ──
41
+ app.get('/api/plugins', (c) => {
42
+ const counts = new Map(ctx.db
43
+ .select({ pluginId: pluginInstances.pluginId, n: count() })
44
+ .from(pluginInstances)
45
+ .groupBy(pluginInstances.pluginId)
46
+ .all()
47
+ .map((r) => [r.pluginId, r.n]));
48
+ return c.json(ctx.db
49
+ .select()
50
+ .from(plugins)
51
+ .orderBy(asc(plugins.pluginId))
52
+ .all()
53
+ .map((p) => ({ ...p, instances: counts.get(p.id) ?? 0 })));
54
+ });
55
+ app.patch('/api/plugins/:id', async (c) => {
56
+ const { enabled } = await readJson(c, z.object({ enabled: z.boolean() }));
57
+ await ctx.instances.setPluginEnabled(c.req.param('id'), enabled, actor(c));
58
+ return c.json(ctx.db
59
+ .select()
60
+ .from(plugins)
61
+ .where(eq(plugins.id, c.req.param('id')))
62
+ .get() ?? null);
63
+ });
64
+ app.post('/api/plugins/rescan', (c) => c.json(ctx.discoverPlugins()));
65
+ app.post('/api/plugins/install', async (c) => c.json(await ctx.repos.install(await readJson(c, z.unknown()), actor(c)), 201));
66
+ app.delete('/api/plugins/:id', (c) => {
67
+ ctx.repos.uninstall(c.req.param('id'), actor(c));
68
+ return c.body(null, 204);
69
+ });
70
+ // ── plugin repositories (design §4.2–4.3) ──
71
+ app.get('/api/plugin-repos', (c) => c.json(ctx.repos.list()));
72
+ app.get('/api/plugin-repos/available', (c) => c.json(ctx.repos.available()));
73
+ app.post('/api/plugin-repos', async (c) => c.json(await ctx.repos.add(await readJson(c, z.unknown()), actor(c)), 201));
74
+ app.post('/api/plugin-repos/:id/refresh', async (c) => c.json(await ctx.repos.refresh(c.req.param('id'))));
75
+ app.post('/api/plugin-repos/:id/confirm-key', async (c) => {
76
+ const { publicKey } = await readJson(c, z.object({ publicKey: z.string() }));
77
+ return c.json(ctx.repos.confirmKey(c.req.param('id'), publicKey, actor(c)));
78
+ });
79
+ app.delete('/api/plugin-repos/:id', (c) => {
80
+ ctx.repos.remove(c.req.param('id'), actor(c));
81
+ return c.body(null, 204);
82
+ });
83
+ // ── notification channels (design §9) ──
84
+ app.get('/api/notifiers', (c) => c.json(ctx.notifier.list()));
85
+ app.post('/api/notifiers', async (c) => c.json(ctx.notifier.save(await readJson(c, z.unknown()), { actor: actor(c) }), 201));
86
+ app.patch('/api/notifiers/:id', async (c) => c.json(ctx.notifier.save(await readJson(c, z.unknown()), { id: c.req.param('id'), actor: actor(c) })));
87
+ app.delete('/api/notifiers/:id', (c) => {
88
+ ctx.notifier.remove(c.req.param('id'), actor(c));
89
+ return c.body(null, 204);
90
+ });
91
+ app.post('/api/notifiers/:id/test', async (c) => c.json(await ctx.notifier.test(c.req.param('id'))));
92
+ // ── audit ──
93
+ app.get('/api/audit', (c) => c.json(queryAudit(ctx.db, c.req.query())));
94
+ app.get('/api/audit/export.csv', (c) => {
95
+ c.header('content-type', 'text/csv; charset=utf-8');
96
+ c.header('content-disposition', `attachment; filename="audit-${new Date().toISOString().slice(0, 10)}.csv"`);
97
+ const chunks = auditCsvChunks(ctx.db, c.req.query());
98
+ const encoder = new TextEncoder();
99
+ return c.body(new ReadableStream({
100
+ pull(controller) {
101
+ const next = chunks.next();
102
+ if (next.done)
103
+ controller.close();
104
+ else
105
+ controller.enqueue(encoder.encode(next.value));
106
+ },
107
+ cancel() {
108
+ chunks.return(undefined);
109
+ },
110
+ }));
111
+ });
112
+ // ── MCP bearer tokens (design §6.2) ──
113
+ app.get('/api/tokens', (c) => c.json(ctx.tokens.list()));
114
+ app.post('/api/tokens', async (c) => c.json(ctx.tokens.create(await readJson(c, z.unknown()), actor(c)), 201));
115
+ app.delete('/api/tokens/:id', (c) => {
116
+ ctx.tokens.revoke(c.req.param('id'), actor(c));
117
+ return c.body(null, 204);
118
+ });
119
+ // ── OAuth clients & grants (design §6.2) ──
120
+ app.get('/api/oauth/clients', (c) => c.json(ctx.oauth.listClients()));
121
+ app.post('/api/oauth/clients', async (c) => {
122
+ const body = await readJson(c, z.object({
123
+ name: z.string().trim().min(1).max(100),
124
+ redirectUris: z.array(z.string()).min(1).max(10),
125
+ confidential: z.boolean().default(false),
126
+ }));
127
+ return c.json(ctx.oauth.register({
128
+ client_name: body.name,
129
+ redirect_uris: body.redirectUris,
130
+ token_endpoint_auth_method: body.confidential ? 'client_secret_post' : 'none',
131
+ }, 'admin', actor(c)), 201);
132
+ });
133
+ app.delete('/api/oauth/clients/:id', (c) => {
134
+ ctx.oauth.revokeClient(c.req.param('id'), actor(c));
135
+ return c.body(null, 204);
136
+ });
137
+ app.get('/api/oauth/grants', (c) => c.json(ctx.oauth.listGrants()));
138
+ app.delete('/api/oauth/grants/:id', (c) => {
139
+ ctx.oauth.revokeGrant(c.req.param('id'), actor(c));
140
+ return c.body(null, 204);
141
+ });
142
+ // ── users (no roles: every user is an admin) ──
143
+ app.get('/api/users', (c) => c.json(ctx.users.list()));
144
+ app.post('/api/users', async (c) => {
145
+ const body = await readJson(c, z.object({ username: z.string(), password: z.string().optional() }));
146
+ return c.json(await ctx.users.create(body, actor(c)), 201);
147
+ });
148
+ app.patch('/api/users/:id', async (c) => {
149
+ const body = await readJson(c, z.object({ disabled: z.boolean().optional(), password: z.string().optional() }));
150
+ const id = c.req.param('id');
151
+ if (body.disabled !== undefined) {
152
+ if (id === c.get('user').id && body.disabled)
153
+ throw new ConflictError('self_disable', 'You cannot disable yourself');
154
+ if (body.disabled)
155
+ assertSsoRemains(ctx, { losingUser: id });
156
+ ctx.users.setDisabled(id, body.disabled, actor(c));
157
+ if (body.disabled) {
158
+ // Everything the user could still reach MCP with goes too; re-enabling revives none of it.
159
+ ctx.sessions.revokeUser(id);
160
+ ctx.oauth.revokeUserGrants(id, 'user_disabled', actor(c));
161
+ ctx.tokens.revokeCreatedBy(id, actor(c));
162
+ }
163
+ }
164
+ if (body.password !== undefined) {
165
+ await ctx.users.setPassword(id, body.password, { actorId: c.get('user').id });
166
+ ctx.sessions.revokeUser(id);
167
+ ctx.oauth.revokeUserGrants(id, 'password_changed', actor(c));
168
+ }
169
+ return c.json(toPublicUser(ctx.users.get(id)));
170
+ });
171
+ app.post('/api/users/:id/reset-totp', (c) => {
172
+ ctx.users.resetTotp(c.req.param('id'), actor(c));
173
+ return c.json(toPublicUser(ctx.users.get(c.req.param('id'))));
174
+ });
175
+ // ── settings ──
176
+ app.get('/api/settings', (c) => c.json({
177
+ security: getSettings(ctx.db, 'security'),
178
+ mcp: getSettings(ctx.db, 'mcp'),
179
+ audit: getSettings(ctx.db, 'audit'),
180
+ oidc: ctx.oidc.getSettings(),
181
+ forceLocalLogin: ctx.config.ADMIN_FORCE_LOCAL_LOGIN,
182
+ publicMcpUrl: ctx.config.PUBLIC_MCP_URL ?? null,
183
+ publicAdminUrl: ctx.config.PUBLIC_ADMIN_URL ?? null,
184
+ }));
185
+ app.put('/api/settings/oidc', async (c) => {
186
+ const body = await readJson(c, z.record(z.string(), z.unknown()));
187
+ if (body.enabled === false)
188
+ assertSsoRemains(ctx, { oidcOff: true });
189
+ return c.json(ctx.oidc.updateSettings(body, actor(c)));
190
+ });
191
+ app.put('/api/settings/:section', async (c) => {
192
+ const section = c.req.param('section');
193
+ if (!isSettingsSection(section))
194
+ throw new NotFoundError('unknown_section', 'Unknown settings section');
195
+ const body = await readJson(c, z.record(z.string(), z.unknown()));
196
+ // Disabling local login locks everyone out unless SSO works and someone can use it (design §6.1).
197
+ if (section === 'security' && body.disableLocalLogin === true) {
198
+ if (!ctx.oidc.isEnabled() || !ctx.users.hasOidcLinkedUser()) {
199
+ throw new ValidationError('oidc_required', 'Enable OIDC and link at least one user before disabling local login');
200
+ }
201
+ }
202
+ return c.json(updateSettings(ctx.db, section, body, actor(c)));
203
+ });
204
+ // ── live events for the portal (approvals, instance status, sync) ──
205
+ app.get('/api/events', (c) => streamSSE(c, async (stream) => {
206
+ const handlers = CORE_EVENT_NAMES.map((name) => {
207
+ const handler = (payload) => void stream.writeSSE({ event: name, data: JSON.stringify(payload) });
208
+ ctx.events.on(name, handler);
209
+ return [name, handler];
210
+ });
211
+ const heartbeat = setInterval(() => void stream.writeSSE({ event: 'ping', data: '{}' }), 25_000);
212
+ await new Promise((resolve) => {
213
+ stream.onAbort(resolve);
214
+ // Shutdown ends the stream so closing the listener doesn't wait for the browser to leave.
215
+ if (ctx.shutdownSignal.aborted)
216
+ resolve();
217
+ else
218
+ ctx.shutdownSignal.addEventListener('abort', () => resolve(), { once: true });
219
+ });
220
+ clearInterval(heartbeat);
221
+ for (const [name, handler] of handlers)
222
+ ctx.events.off(name, handler);
223
+ }));
224
+ }
225
+ //# sourceMappingURL=system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system.js","sourceRoot":"","sources":["../../../src/http/admin/system.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { Hono } from 'hono';
2
+ import type { AppContext } from '../app.js';
3
+ import type { AdminEnv } from './admin/auth.js';
4
+ export interface AdminAppOptions {
5
+ /** Built admin-ui assets. The SPA is optional so the API can run without a UI build (tests, dev). */
6
+ uiDir?: string;
7
+ }
8
+ /** The LAN-only admin listener (design §2.1): Vue SPA + Admin API + login/OIDC. */
9
+ export declare function createAdminApp(ctx: AppContext, { uiDir }?: AdminAppOptions): Hono<AdminEnv>;
@@ -0,0 +1,51 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { serveStatic } from '@hono/node-server/serve-static';
4
+ import { Hono } from 'hono';
5
+ import { csrfGuard, registerAuthRoutes, registerProfileRoutes, requireUser } from './admin/auth.js';
6
+ import { registerInstanceRoutes } from './admin/instances.js';
7
+ import { registerSystemRoutes } from './admin/system.js';
8
+ import { errorResponse } from './common.js';
9
+ /** Paths the SPA fallback must never swallow, so API/auth typos surface as 404s. */
10
+ const NON_SPA_PREFIXES = ['/api', '/auth', '/.well-known', '/healthz'];
11
+ /** The LAN-only admin listener (design §2.1): Vue SPA + Admin API + login/OIDC. */
12
+ export function createAdminApp(ctx, { uiDir } = {}) {
13
+ const app = new Hono();
14
+ app.onError(errorResponse);
15
+ // The SPA loads only its own bundle; nothing may frame the portal (clickjacking on approvals).
16
+ app.use('*', async (c, next) => {
17
+ await next();
18
+ c.header('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; " +
19
+ "frame-ancestors 'none'; base-uri 'none'; form-action 'self'");
20
+ c.header('X-Frame-Options', 'DENY');
21
+ c.header('X-Content-Type-Options', 'nosniff');
22
+ c.header('Referrer-Policy', 'same-origin');
23
+ });
24
+ app.get('/healthz', (c) => c.json({ status: 'ok' }));
25
+ app.use('/api/*', csrfGuard(ctx));
26
+ app.use('/auth/*', csrfGuard(ctx));
27
+ registerAuthRoutes(app, ctx); // public: session probe, setup, login, OIDC
28
+ // Everything else under /api needs a signed-in user.
29
+ app.use('/api/*', async (c, next) => {
30
+ if (c.req.path === '/api/session' || c.req.path === '/api/setup')
31
+ return next();
32
+ return requireUser(ctx)(c, next);
33
+ });
34
+ registerProfileRoutes(app, ctx);
35
+ registerInstanceRoutes(app, ctx);
36
+ registerSystemRoutes(app, ctx);
37
+ app.all('/api/*', (c) => c.json({ error: 'not_found', message: 'No such API route' }, 404));
38
+ const indexHtml = uiDir ? path.join(uiDir, 'index.html') : undefined;
39
+ if (uiDir && indexHtml && existsSync(indexHtml)) {
40
+ const html = readFileSync(indexHtml, 'utf8');
41
+ app.use('/assets/*', serveStatic({ root: path.relative(process.cwd(), uiDir) }));
42
+ app.get('*', (c, next) => {
43
+ if (NON_SPA_PREFIXES.some((p) => c.req.path === p || c.req.path.startsWith(`${p}/`)))
44
+ return next();
45
+ return c.html(html);
46
+ });
47
+ }
48
+ app.notFound((c) => c.json({ error: 'not_found' }, 404));
49
+ return app;
50
+ }
51
+ //# sourceMappingURL=admin-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-app.js","sourceRoot":"","sources":["../../src/http/admin-app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { Context } from 'hono';
2
+ import { z } from 'zod';
3
+ /** Helpers shared by the admin and MCP listeners. */
4
+ /**
5
+ * The client's IP. Behind `trustProxy` proxies, X-Forwarded-For is read from the right: each trusted
6
+ * proxy appends the address it saw, so the entry `trustProxy` places from the end is the one the
7
+ * outermost proxy recorded. Anything further left was sent by the client and can be forged.
8
+ */
9
+ export declare function clientIp(c: Context, trustProxy: number | boolean): string | undefined;
10
+ /** Whether the client reached us over TLS (directly, or at a trusted reverse proxy). */
11
+ export declare function isSecure(c: Context, trustProxy: number | boolean): boolean;
12
+ /** The origin the client used, honoring X-Forwarded-* only when the proxy is trusted. */
13
+ export declare function requestOrigin(c: Context, trustProxy: number | boolean): string;
14
+ export declare function readJson<S extends z.ZodType>(c: Context, schema: S): Promise<z.infer<S>>;
15
+ /** Like `readJson`, but an empty body is `undefined` rather than `{}`. Malformed JSON is a 400. */
16
+ export declare function readOptionalJson<S extends z.ZodType>(c: Context, schema: S): Promise<z.infer<S> | undefined>;
17
+ /** Maps service errors onto JSON responses; anything unexpected is a 500 without internals. */
18
+ export declare function errorResponse(err: unknown, c: Context): (Response & import("hono").TypedResponse<{
19
+ details?: null | undefined;
20
+ error: string;
21
+ message: string;
22
+ }, 400 | 404 | 409, "json">) | (Response & import("hono").TypedResponse<{
23
+ error: string;
24
+ message: string;
25
+ details: string[];
26
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
27
+ error: string;
28
+ message: string;
29
+ }, 503, "json">) | (Response & import("hono").TypedResponse<{
30
+ error: string;
31
+ message: string;
32
+ }, 500, "json">);
@@ -0,0 +1,80 @@
1
+ import { getConnInfo } from '@hono/node-server/conninfo';
2
+ import { ZodError } from 'zod';
3
+ import { z } from 'zod';
4
+ import { ServiceError, ValidationError } from '../errors.js';
5
+ import { PluginTimeoutError, PluginUnavailableError } from '../plugins/process.js';
6
+ /** Helpers shared by the admin and MCP listeners. */
7
+ /**
8
+ * The client's IP. Behind `trustProxy` proxies, X-Forwarded-For is read from the right: each trusted
9
+ * proxy appends the address it saw, so the entry `trustProxy` places from the end is the one the
10
+ * outermost proxy recorded. Anything further left was sent by the client and can be forged.
11
+ */
12
+ export function clientIp(c, trustProxy) {
13
+ const hops = Number(trustProxy);
14
+ if (hops > 0) {
15
+ const chain = (c.req.header('x-forwarded-for') ?? '')
16
+ .split(',')
17
+ .map((p) => p.trim())
18
+ .filter(Boolean);
19
+ const ip = chain[chain.length - hops];
20
+ if (ip)
21
+ return ip;
22
+ }
23
+ try {
24
+ return getConnInfo(c).remote.address;
25
+ }
26
+ catch {
27
+ return undefined; // app.request() in tests has no socket
28
+ }
29
+ }
30
+ /** Whether the client reached us over TLS (directly, or at a trusted reverse proxy). */
31
+ export function isSecure(c, trustProxy) {
32
+ if (new URL(c.req.url).protocol === 'https:')
33
+ return true;
34
+ return Number(trustProxy) > 0 && c.req.header('x-forwarded-proto')?.split(',')[0]?.trim() === 'https';
35
+ }
36
+ /** The origin the client used, honoring X-Forwarded-* only when the proxy is trusted. */
37
+ export function requestOrigin(c, trustProxy) {
38
+ const url = new URL(c.req.url);
39
+ const proto = isSecure(c, trustProxy) ? 'https' : url.protocol.replace(':', '');
40
+ const host = (Number(trustProxy) > 0 && c.req.header('x-forwarded-host')?.split(',')[0]?.trim()) ||
41
+ c.req.header('host') ||
42
+ url.host;
43
+ return `${proto}://${host}`;
44
+ }
45
+ export async function readJson(c, schema) {
46
+ return schema.parse((await readOptionalJson(c, z.unknown())) ?? {});
47
+ }
48
+ /** Like `readJson`, but an empty body is `undefined` rather than `{}`. Malformed JSON is a 400. */
49
+ export async function readOptionalJson(c, schema) {
50
+ const text = await c.req.text();
51
+ if (!text)
52
+ return undefined;
53
+ let body;
54
+ try {
55
+ body = JSON.parse(text);
56
+ }
57
+ catch {
58
+ throw new ValidationError('invalid_json', 'Request body must be JSON');
59
+ }
60
+ return schema.parse(body);
61
+ }
62
+ /** Maps service errors onto JSON responses; anything unexpected is a 500 without internals. */
63
+ export function errorResponse(err, c) {
64
+ if (err instanceof ServiceError) {
65
+ return c.json({ error: err.code, message: err.message, ...(err.details !== undefined ? { details: err.details } : {}) }, err.status);
66
+ }
67
+ if (err instanceof ZodError) {
68
+ return c.json({
69
+ error: 'invalid_request',
70
+ message: 'Invalid request',
71
+ details: err.issues.map((i) => `${i.path.join('.') || '(body)'}: ${i.message}`),
72
+ }, 400);
73
+ }
74
+ if (err instanceof PluginUnavailableError || err instanceof PluginTimeoutError) {
75
+ return c.json({ error: 'plugin_unavailable', message: err.message }, 503);
76
+ }
77
+ console.error(err);
78
+ return c.json({ error: 'internal', message: 'Internal error' }, 500);
79
+ }
80
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/http/common.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Hono } from 'hono';
2
+ import type { AppContext } from '../../app.js';
3
+ /** A locked operation needs a TOTP code proved this recently (design §5.3). */
4
+ export declare const LOCKED_TOTP_MAX_AGE_MS: number;
5
+ /**
6
+ * The approval page (design §5.3). The MCP client is asked to open it (URL-mode elicitation); the
7
+ * link only opens this page. Deciding needs a signed-in user with TOTP enrolled, a TOTP proof in this
8
+ * session (a fresh one for locked operations) and a CSRF-protected POST, so neither the client that
9
+ * holds the link nor a prefetch can decide anything.
10
+ */
11
+ export declare function registerApprovalRoutes(app: Hono, ctx: AppContext): void;
@@ -0,0 +1,117 @@
1
+ import { eq } from 'drizzle-orm';
2
+ import { operations, pendingApprovals, pluginInstances } from '../../db/schema.js';
3
+ import { ServiceError } from '../../errors.js';
4
+ import { clientIp } from '../common.js';
5
+ import { checkUiCsrf, recordTotpProof, renderLogin, sinceTotpProof, uiCsrf, uiSession } from './oauth-routes.js';
6
+ import { approvalPage, errorPage } from './pages.js';
7
+ /** A locked operation needs a TOTP code proved this recently (design §5.3). */
8
+ export const LOCKED_TOTP_MAX_AGE_MS = 5 * 60_000;
9
+ /**
10
+ * The approval page (design §5.3). The MCP client is asked to open it (URL-mode elicitation); the
11
+ * link only opens this page. Deciding needs a signed-in user with TOTP enrolled, a TOTP proof in this
12
+ * session (a fresh one for locked operations) and a CSRF-protected POST, so neither the client that
13
+ * holds the link nor a prefetch can decide anything.
14
+ */
15
+ export function registerApprovalRoutes(app, ctx) {
16
+ const gone = (c) => errorPage(c, 'Link expired', 'This approval request has expired or was already decided.', 404);
17
+ const load = (c, token) => {
18
+ const link = ctx.links.resolve(token);
19
+ if (!link) {
20
+ // Tokens are unguessable; this only slows down noisy scanners.
21
+ ctx.throttle.allowIp(clientIp(c, ctx.config.TRUST_PROXY));
22
+ return null;
23
+ }
24
+ const row = ctx.db
25
+ .select({ approval: pendingApprovals, op: operations, instance: pluginInstances })
26
+ .from(pendingApprovals)
27
+ .innerJoin(operations, eq(pendingApprovals.operationId, operations.id))
28
+ .innerJoin(pluginInstances, eq(pendingApprovals.instanceId, pluginInstances.id))
29
+ .where(eq(pendingApprovals.id, link.approvalId))
30
+ .get();
31
+ return row ? { link, ...row } : null;
32
+ };
33
+ /** Every approval needs a TOTP proof in this session (OIDC sign-ins have none); locked ops a fresh one. */
34
+ const needsTotp = (session, data) => {
35
+ const since = sinceTotpProof(ctx, session);
36
+ return data.op.locked ? since > LOCKED_TOTP_MAX_AGE_MS : since === Infinity;
37
+ };
38
+ const noTotp = (c) => errorPage(c, 'Two-factor authentication required', 'Approving calls needs an authenticator app on your account. Set it up in the admin portal under Profile, then open this link again.', 403);
39
+ const render = (c, token, data, session, error) => approvalPage(c, {
40
+ token,
41
+ csrf: uiCsrf(ctx, c),
42
+ username: session.user.username,
43
+ slug: data.instance.slug,
44
+ instanceName: data.instance.displayName,
45
+ operationKey: data.op.key,
46
+ locked: data.op.locked,
47
+ summary: data.approval.summary,
48
+ params: data.approval.paramsDisplay,
49
+ targets: data.approval.resolvedTargets,
50
+ diff: data.approval.diff,
51
+ expiresAt: data.approval.expiresAt,
52
+ confirmLiteral: data.approval.confirmLiteral,
53
+ needsTotp: needsTotp(session, data),
54
+ status: data.approval.status,
55
+ error,
56
+ }, error ? 400 : 200);
57
+ app.get('/a/:token', (c) => {
58
+ const token = c.req.param('token');
59
+ const data = load(c, token);
60
+ if (!data)
61
+ return gone(c);
62
+ const session = uiSession(ctx, c, 'approval');
63
+ if (!session)
64
+ return renderLogin(ctx, c, `/a/${token}`, 'Sign in to review this approval request.');
65
+ if (!session.user.totpEnabled)
66
+ return noTotp(c);
67
+ return render(c, token, data, session);
68
+ });
69
+ app.post('/a/:token', async (c) => {
70
+ const token = c.req.param('token');
71
+ const body = await c.req.parseBody();
72
+ const data = load(c, token);
73
+ if (!data)
74
+ return gone(c);
75
+ const session = uiSession(ctx, c, 'approval');
76
+ if (!session)
77
+ return renderLogin(ctx, c, `/a/${token}`, 'Your sign-in expired. Sign in again to decide.');
78
+ if (!session.user.totpEnabled)
79
+ return noTotp(c);
80
+ if (!checkUiCsrf(c, body.csrf))
81
+ return render(c, token, data, session, 'The form expired; try again.');
82
+ const approve = body.decision === 'approve';
83
+ if (!approve && body.decision !== 'deny')
84
+ return render(c, token, data, session, 'Choose approve or deny.');
85
+ if (approve && needsTotp(session, data)) {
86
+ const user = session.user;
87
+ if (ctx.throttle.lockedFor(user.username, 'mcp') > 0)
88
+ return render(c, token, data, session, 'Too many attempts; try again later.');
89
+ // TOTP only: a recovery code typed here must neither count nor be used up.
90
+ if (!ctx.users.verifyTotpCode(user.id, String(body.totp ?? ''))) {
91
+ if (ctx.throttle.fail(user.username, 'mcp'))
92
+ ctx.events.emit('auth.lockout', {
93
+ username: user.username,
94
+ ip: clientIp(c, ctx.config.TRUST_PROXY),
95
+ surface: 'mcp',
96
+ });
97
+ return render(c, token, data, session, 'Enter a valid authenticator code to approve.');
98
+ }
99
+ ctx.throttle.succeed(user.username, 'mcp');
100
+ recordTotpProof(ctx, session.idHash);
101
+ }
102
+ try {
103
+ ctx.approvals.decide(data.approval.id, {
104
+ approve,
105
+ confirm: typeof body.confirm === 'string' ? body.confirm : undefined,
106
+ decidedBy: session.user.username,
107
+ });
108
+ }
109
+ catch (err) {
110
+ if (err instanceof ServiceError)
111
+ return render(c, token, data, session, err.message);
112
+ throw err;
113
+ }
114
+ return errorPage(c, approve ? 'Approved' : 'Denied', `${data.op.key} on /${data.instance.slug} was ${approve ? 'approved' : 'denied'}. You can close this page.`, 200);
115
+ });
116
+ }
117
+ //# sourceMappingURL=approval-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-routes.js","sourceRoot":"","sources":["../../../src/http/mcp/approval-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { Manifest } from '@synoikia/plugin-sdk';
2
+ import type { Context } from 'hono';
3
+ import type { AppContext } from '../../app.js';
4
+ import type { Config } from '../../config/env.js';
5
+ import type { OAuthService } from '../../auth/oauth.js';
6
+ /** Open MCP sessions per authenticated principal; a new one past the cap closes that principal's oldest. */
7
+ export declare const MAX_SESSIONS_PER_PRINCIPAL = 16;
8
+ export declare function describeSearch(manifest: Manifest): string;
9
+ export declare function describeExecute(manifest: Manifest): string;
10
+ /**
11
+ * DNS-rebinding defence (MCP transport spec): a browser page can make its own domain resolve to this
12
+ * server, but it can't change the Host it sends, nor the Origin. Host must be a name this server was
13
+ * configured for (PUBLIC_MCP_URL, MCP_ALLOWED_HOSTS) or something that can't be rebound (localhost, an
14
+ * IP literal); an Origin, when present, must be one of those too.
15
+ */
16
+ export declare function hostAllowed(config: Config, hostHeader: string | undefined): boolean;
17
+ export declare function originAllowed(config: Config, origin: string | undefined): boolean;
18
+ export declare class McpEndpoints {
19
+ private readonly ctx;
20
+ private readonly oauth;
21
+ private readonly sessions;
22
+ private readonly sweeper;
23
+ constructor(ctx: AppContext, oauth: OAuthService);
24
+ get sessionCount(): number;
25
+ private sweep;
26
+ private closeSession;
27
+ closeAll(): Promise<void>;
28
+ private buildServer;
29
+ /** The request carries a bearer or OAuth access token that is live somewhere on this server. */
30
+ private holdsLiveCredential;
31
+ handle(c: Context, slug: string): Promise<Response>;
32
+ }