@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,52 @@
1
+ import { z } from 'zod';
2
+ import type { DbLike } from './db/index.js';
3
+ /** Audit log reads for the portal (design §8.2): filterable, paginated, exportable. Never writes. */
4
+ export declare const AuditQuerySchema: z.ZodObject<{
5
+ instance: z.ZodOptional<z.ZodString>;
6
+ kind: z.ZodOptional<z.ZodEnum<{
7
+ search: "search";
8
+ config: "config";
9
+ call: "call";
10
+ auth: "auth";
11
+ plugin: "plugin";
12
+ }>>;
13
+ decision: z.ZodOptional<z.ZodString>;
14
+ operation: z.ZodOptional<z.ZodString>;
15
+ target: z.ZodOptional<z.ZodString>;
16
+ actor: z.ZodOptional<z.ZodString>;
17
+ from: z.ZodOptional<z.ZodCoercedDate<unknown>>;
18
+ to: z.ZodOptional<z.ZodCoercedDate<unknown>>;
19
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
20
+ offset: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
21
+ }, z.core.$strip>;
22
+ export type AuditQuery = z.infer<typeof AuditQuerySchema>;
23
+ export declare function queryAudit(db: DbLike, raw: unknown): {
24
+ rows: {
25
+ id: number;
26
+ at: Date;
27
+ kind: "search" | "config" | "call" | "auth" | "plugin";
28
+ instanceId: string | null;
29
+ operationKey: string | null;
30
+ classification: string | null;
31
+ decision: string | null;
32
+ actorKind: "mcp_client" | "user" | "system";
33
+ actorId: string | null;
34
+ decidedBy: string | null;
35
+ decidedVia: string | null;
36
+ params: unknown;
37
+ resolvedTargets: unknown;
38
+ resultStatus: string | null;
39
+ durationMs: number | null;
40
+ detail: unknown;
41
+ }[];
42
+ total: number;
43
+ limit: number;
44
+ offset: number;
45
+ };
46
+ /**
47
+ * The audit CSV, one page of rows at a time, so an export of a large log never sits in memory at once.
48
+ * Pages continue from the last row seen (keyset), not by offset: rows written during the export
49
+ * neither shift nor repeat what follows.
50
+ */
51
+ export declare function auditCsvChunks(db: DbLike, raw: unknown): Generator<string>;
52
+ export declare function exportAuditCsv(db: DbLike, raw: unknown): string;
@@ -0,0 +1,116 @@
1
+ import { and, desc, eq, gte, lt, lte, or, sql } from 'drizzle-orm';
2
+ import { z } from 'zod';
3
+ import { auditLog } from './db/schema.js';
4
+ /** Audit log reads for the portal (design §8.2): filterable, paginated, exportable. Never writes. */
5
+ export const AuditQuerySchema = z.object({
6
+ instance: z.string().optional(),
7
+ kind: z.enum(['call', 'search', 'config', 'auth', 'plugin']).optional(),
8
+ /** Prefix match, e.g. `auto-approved` or `rejected:`. */
9
+ decision: z.string().optional(),
10
+ operation: z.string().optional(),
11
+ /** Substring of the resolved targets JSON, e.g. an entity id. */
12
+ target: z.string().optional(),
13
+ actor: z.string().optional(),
14
+ from: z.coerce.date().optional(),
15
+ to: z.coerce.date().optional(),
16
+ limit: z.coerce.number().int().min(1).max(1000).default(100),
17
+ offset: z.coerce.number().int().min(0).default(0),
18
+ });
19
+ const escapeLike = (s) => s.replace(/[%_\\]/g, (c) => `\\${c}`);
20
+ function where(q) {
21
+ const c = [];
22
+ if (q.instance)
23
+ c.push(eq(auditLog.instanceId, q.instance));
24
+ if (q.kind)
25
+ c.push(eq(auditLog.kind, q.kind));
26
+ if (q.decision)
27
+ c.push(sql `${auditLog.decision} LIKE ${`${escapeLike(q.decision)}%`} ESCAPE '\\'`);
28
+ if (q.operation)
29
+ c.push(sql `${auditLog.operationKey} LIKE ${`${escapeLike(q.operation)}%`} ESCAPE '\\'`);
30
+ if (q.target)
31
+ c.push(sql `${auditLog.resolvedTargets} LIKE ${`%${escapeLike(q.target)}%`} ESCAPE '\\'`);
32
+ if (q.actor)
33
+ c.push(eq(auditLog.actorId, q.actor));
34
+ if (q.from)
35
+ c.push(gte(auditLog.at, q.from));
36
+ if (q.to)
37
+ c.push(lte(auditLog.at, q.to));
38
+ return c.length ? and(...c) : undefined;
39
+ }
40
+ export function queryAudit(db, raw) {
41
+ const q = AuditQuerySchema.parse(raw);
42
+ const rows = db
43
+ .select()
44
+ .from(auditLog)
45
+ .where(where(q))
46
+ .orderBy(desc(auditLog.at), desc(auditLog.id))
47
+ .limit(q.limit)
48
+ .offset(q.offset)
49
+ .all();
50
+ const total = db
51
+ .select({ n: sql `count(*)` })
52
+ .from(auditLog)
53
+ .where(where(q))
54
+ .get()?.n ?? 0;
55
+ return { rows, total, limit: q.limit, offset: q.offset };
56
+ }
57
+ const CSV_COLUMNS = [
58
+ 'id',
59
+ 'at',
60
+ 'kind',
61
+ 'instanceId',
62
+ 'operationKey',
63
+ 'classification',
64
+ 'decision',
65
+ 'actorKind',
66
+ 'actorId',
67
+ 'decidedBy',
68
+ 'decidedVia',
69
+ 'resultStatus',
70
+ 'durationMs',
71
+ 'params',
72
+ 'resolvedTargets',
73
+ 'detail',
74
+ ];
75
+ function csvCell(v) {
76
+ if (v === null || v === undefined)
77
+ return '';
78
+ let s = v instanceof Date ? v.toISOString() : typeof v === 'object' ? JSON.stringify(v) : String(v);
79
+ // Neutralize spreadsheet formula injection.
80
+ if (/^[=+\-@\t\r]/.test(s))
81
+ s = `'${s}`;
82
+ return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
83
+ }
84
+ const CSV_PAGE = 1000;
85
+ /**
86
+ * The audit CSV, one page of rows at a time, so an export of a large log never sits in memory at once.
87
+ * Pages continue from the last row seen (keyset), not by offset: rows written during the export
88
+ * neither shift nor repeat what follows.
89
+ */
90
+ export function* auditCsvChunks(db, raw) {
91
+ const q = AuditQuerySchema.parse({ ...raw, limit: CSV_PAGE });
92
+ yield `${CSV_COLUMNS.join(',')}\n`;
93
+ let last;
94
+ for (;;) {
95
+ const after = last
96
+ ? or(lt(auditLog.at, last.at), and(eq(auditLog.at, last.at), lt(auditLog.id, last.id)))
97
+ : undefined;
98
+ const rows = db
99
+ .select()
100
+ .from(auditLog)
101
+ .where(and(where(q), after))
102
+ .orderBy(desc(auditLog.at), desc(auditLog.id))
103
+ .limit(CSV_PAGE)
104
+ .all();
105
+ if (rows.length)
106
+ yield rows.map((r) => `${CSV_COLUMNS.map((c) => csvCell(r[c])).join(',')}\n`).join('');
107
+ if (rows.length < CSV_PAGE)
108
+ return;
109
+ const tail = rows.at(-1);
110
+ last = { at: tail.at, id: tail.id };
111
+ }
112
+ }
113
+ export function exportAuditCsv(db, raw) {
114
+ return [...auditCsvChunks(db, raw)].join('');
115
+ }
116
+ //# sourceMappingURL=audit-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-query.js","sourceRoot":"","sources":["../src/audit-query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { DbLike } from './db/index.js';
2
+ import { auditLog } from './db/schema.js';
3
+ export type AuditEvent = Omit<typeof auditLog.$inferInsert, 'id' | 'at'>;
4
+ /** Append-only (design §7.3): this module only ever inserts. */
5
+ export declare function writeAudit(db: DbLike, event: AuditEvent, at?: Date): void;
package/dist/audit.js ADDED
@@ -0,0 +1,8 @@
1
+ import { auditLog } from './db/schema.js';
2
+ /** Append-only (design §7.3): this module only ever inserts. */
3
+ export function writeAudit(db, event, at = new Date()) {
4
+ db.insert(auditLog)
5
+ .values({ ...event, at })
6
+ .run();
7
+ }
8
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI1C,gEAAgE;AAChE,MAAM,UAAU,UAAU,CAAC,EAAU,EAAE,KAAiB,EAAE,KAAW,IAAI,IAAI,EAAE;IAC7E,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC;SAChB,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,CAAC;SACxB,GAAG,EAAE,CAAC;AACX,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { Context } from 'hono';
2
+ import type { JWTVerifyGetKey } from 'jose';
3
+ import type { AppContext } from '../app.js';
4
+ import type { OAuthService } from './oauth.js';
5
+ import type { AccessCeiling } from '../gate/access.js';
6
+ import type { AuthMode } from '../instances/manager.js';
7
+ /**
8
+ * Authentication for `/{slug}` (design §6.2). The mode is the instance override or the global
9
+ * default. Every accepted request yields an identity that is recorded on each audited call.
10
+ */
11
+ export interface McpIdentity {
12
+ kind: 'token' | 'oauth' | 'external';
13
+ /** Stable principal for session binding: a token id, `grant:<id>`, or the external identity. */
14
+ principal: string;
15
+ /** Human-readable, shown in audit and approvals: `token:Claude Code`, `oauth:Claude for admin`. */
16
+ label: string;
17
+ /**
18
+ * Ceiling on what this principal can reach (design §6.3): chosen on the consent page for OAuth,
19
+ * set per bearer token. `external` mode trusts the fronting proxy, so it is not limited here.
20
+ */
21
+ access: AccessCeiling;
22
+ }
23
+ export type McpAuthResult = {
24
+ ok: true;
25
+ identity: McpIdentity;
26
+ } | {
27
+ ok: false;
28
+ status: 401 | 403;
29
+ error: string;
30
+ message: string;
31
+ wwwAuthenticate?: string;
32
+ };
33
+ /**
34
+ * The MCP listener's public origin: PUBLIC_MCP_URL, or — only where no security decision depends on
35
+ * it (the approval-page link sent back to the same client, sign-in redirects) — the request's origin.
36
+ */
37
+ export declare function publicMcpBase(ctx: AppContext, c: Context): string;
38
+ export declare const resourceUrl: (base: string, slug: string) => string;
39
+ export declare function effectiveAuthMode(ctx: AppContext, instanceAuthMode: string | null): AuthMode;
40
+ /**
41
+ * Verifies a `Cf-Access-Jwt-Assertion` against the team's signing keys: signature, issuer (the team
42
+ * domain), audience (the Access application's AUD tag) and expiry. Returns who it names, or null.
43
+ * `keys` replaces the team's published key set (tests).
44
+ */
45
+ export declare function verifyCloudflareAccess(teamDomain: string, aud: string, assertion: string, keys?: JWTVerifyGetKey): Promise<string | null>;
46
+ export declare function authenticateMcp(ctx: AppContext, oauth: OAuthService, c: Context, instance: {
47
+ id: string;
48
+ slug: string;
49
+ authMode: string | null;
50
+ }): Promise<McpAuthResult>;
@@ -0,0 +1,160 @@
1
+ import { createRemoteJWKSet, jwtVerify } from 'jose';
2
+ import { getSettings } from '../settings.js';
3
+ import { McpTokenService, TOKEN_PREFIX } from './mcp-tokens.js';
4
+ import { ACCESS_PREFIX, canonicalResource } from './oauth.js';
5
+ import { clientIp, requestOrigin } from '../http/common.js';
6
+ /**
7
+ * The MCP listener's public origin: PUBLIC_MCP_URL, or — only where no security decision depends on
8
+ * it (the approval-page link sent back to the same client, sign-in redirects) — the request's origin.
9
+ */
10
+ export function publicMcpBase(ctx, c) {
11
+ return (ctx.config.PUBLIC_MCP_URL ?? requestOrigin(c, ctx.config.TRUST_PROXY)).replace(/\/+$/, '');
12
+ }
13
+ export const resourceUrl = (base, slug) => canonicalResource(`${base}/${slug}`);
14
+ export function effectiveAuthMode(ctx, instanceAuthMode) {
15
+ return instanceAuthMode ?? getSettings(ctx.db, 'mcp').defaultAuthMode;
16
+ }
17
+ const jwksCache = new Map();
18
+ /**
19
+ * Verifies a `Cf-Access-Jwt-Assertion` against the team's signing keys: signature, issuer (the team
20
+ * domain), audience (the Access application's AUD tag) and expiry. Returns who it names, or null.
21
+ * `keys` replaces the team's published key set (tests).
22
+ */
23
+ export async function verifyCloudflareAccess(teamDomain, aud, assertion, keys) {
24
+ const domain = teamDomain.replace(/^https?:\/\//, '').replace(/\/+$/, '');
25
+ let jwks = keys ?? jwksCache.get(domain);
26
+ if (!jwks) {
27
+ const remote = createRemoteJWKSet(new URL(`https://${domain}/cdn-cgi/access/certs`));
28
+ jwksCache.set(domain, remote);
29
+ jwks = remote;
30
+ }
31
+ try {
32
+ const { payload } = await jwtVerify(assertion, jwks, { issuer: `https://${domain}`, audience: aud });
33
+ return typeof payload.email === 'string' ? payload.email : (payload.sub ?? 'cf-access');
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ export async function authenticateMcp(ctx, oauth, c, instance) {
40
+ const mode = effectiveAuthMode(ctx, instance.authMode);
41
+ const settings = getSettings(ctx.db, 'mcp');
42
+ if (mode === 'external') {
43
+ const { teamDomain, aud } = settings.cfAccess;
44
+ if (teamDomain && aud) {
45
+ const assertion = c.req.header('cf-access-jwt-assertion');
46
+ const who = assertion ? await verifyCloudflareAccess(teamDomain, aud, assertion) : null;
47
+ if (!who)
48
+ return {
49
+ ok: false,
50
+ status: 401,
51
+ error: 'unauthorized',
52
+ message: 'Cloudflare Access assertion missing or invalid',
53
+ };
54
+ return {
55
+ ok: true,
56
+ identity: { kind: 'external', principal: `cf:${who}`, label: `external:${who}`, access: 'write' },
57
+ };
58
+ }
59
+ const header = settings.trustedIdentityHeader;
60
+ const named = header && c.req.header(header)?.trim().slice(0, 200);
61
+ if (named)
62
+ return {
63
+ ok: true,
64
+ identity: { kind: 'external', principal: `ext:${named}`, label: `external:${named}`, access: 'write' },
65
+ };
66
+ // Anonymous: without an identity, the client address is what keeps callers' sessions (and rate
67
+ // budgets) apart; otherwise every anonymous client would share one principal.
68
+ const ip = clientIp(c, ctx.config.TRUST_PROXY) ?? 'unknown';
69
+ return {
70
+ ok: true,
71
+ identity: {
72
+ kind: 'external',
73
+ principal: `ext-anon:${ip}`,
74
+ label: `external (anonymous, ${ip})`,
75
+ access: 'write',
76
+ },
77
+ };
78
+ }
79
+ const allowBearer = mode === 'bearer' || mode === 'bearer+oauth';
80
+ // OAuth is off until PUBLIC_MCP_URL is set: its discovery documents must not come from the Host header.
81
+ const allowOauth = (mode === 'oauth' || mode === 'bearer+oauth') && !!ctx.config.PUBLIC_MCP_URL;
82
+ const base = publicMcpBase(ctx, c);
83
+ const challenge = (error) => {
84
+ const params = [];
85
+ if (allowOauth)
86
+ params.push(`resource_metadata="${base}/.well-known/oauth-protected-resource/${instance.slug}"`);
87
+ if (error)
88
+ params.push(`error="${error}"`);
89
+ return params.length ? `Bearer ${params.join(', ')}` : 'Bearer';
90
+ };
91
+ const auth = c.req.header('authorization');
92
+ const token = auth?.match(/^Bearer\s+(\S+)$/i)?.[1];
93
+ if (!token)
94
+ return {
95
+ ok: false,
96
+ status: 401,
97
+ error: 'unauthorized',
98
+ message: 'Authentication required',
99
+ wwwAuthenticate: challenge(),
100
+ };
101
+ if (allowBearer && token.startsWith(TOKEN_PREFIX)) {
102
+ const row = ctx.tokens.verify(token);
103
+ if (!row)
104
+ return {
105
+ ok: false,
106
+ status: 401,
107
+ error: 'invalid_token',
108
+ message: 'Invalid or expired token',
109
+ wwwAuthenticate: challenge('invalid_token'),
110
+ };
111
+ if (!McpTokenService.inScope(row, instance.id)) {
112
+ return {
113
+ ok: false,
114
+ status: 403,
115
+ error: 'insufficient_scope',
116
+ message: 'This token is not valid for this endpoint',
117
+ };
118
+ }
119
+ return {
120
+ ok: true,
121
+ identity: { kind: 'token', principal: `token:${row.id}`, label: `token:${row.name}`, access: row.access },
122
+ };
123
+ }
124
+ if (allowOauth && token.startsWith(ACCESS_PREFIX)) {
125
+ const found = oauth.verifyAccess(token, instance.id);
126
+ if (!found)
127
+ return {
128
+ ok: false,
129
+ status: 401,
130
+ error: 'invalid_token',
131
+ message: 'Invalid or expired token',
132
+ wwwAuthenticate: challenge('invalid_token'),
133
+ };
134
+ if (!found.inAudience) {
135
+ return {
136
+ ok: false,
137
+ status: 403,
138
+ error: 'insufficient_scope',
139
+ message: 'This token was not granted for this endpoint',
140
+ };
141
+ }
142
+ return {
143
+ ok: true,
144
+ identity: {
145
+ kind: 'oauth',
146
+ principal: `grant:${found.grantId}`,
147
+ label: `oauth:${found.client.name} (${found.user.username})`,
148
+ access: found.access,
149
+ },
150
+ };
151
+ }
152
+ return {
153
+ ok: false,
154
+ status: 401,
155
+ error: 'invalid_token',
156
+ message: 'This endpoint does not accept that kind of token',
157
+ wwwAuthenticate: challenge('invalid_token'),
158
+ };
159
+ }
160
+ //# sourceMappingURL=mcp-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-auth.js","sourceRoot":"","sources":["../../src/auth/mcp-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ import type { Db } from '../db/index.js';
3
+ import { mcpTokens } from '../db/schema.js';
4
+ /**
5
+ * Static bearer tokens for MCP clients (design §6.2): `syn_…`, shown once, stored as SHA-256,
6
+ * scoped to instance ids or `*`. A token outside its scope gets 403, not 401.
7
+ */
8
+ export declare const TOKEN_PREFIX = "syn_";
9
+ export declare const CreateTokenSchema: z.ZodObject<{
10
+ name: z.ZodString;
11
+ scope: z.ZodArray<z.ZodString>;
12
+ access: z.ZodDefault<z.ZodEnum<{
13
+ read: "read";
14
+ write: "write";
15
+ }>>;
16
+ expiresAt: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
17
+ }, z.core.$strip>;
18
+ type TokenRow = typeof mcpTokens.$inferSelect;
19
+ export declare class McpTokenService {
20
+ private readonly db;
21
+ private readonly now;
22
+ constructor(db: Db, now?: () => Date);
23
+ list(): {
24
+ id: string;
25
+ name: string;
26
+ scope: string[];
27
+ access: "read" | "write";
28
+ createdAt: Date;
29
+ expiresAt: Date | null;
30
+ lastUsedAt: Date | null;
31
+ revokedAt: Date | null;
32
+ }[];
33
+ create(raw: unknown, actor?: {
34
+ userId?: string;
35
+ }): {
36
+ token: string;
37
+ id: string;
38
+ name: string;
39
+ scope: string[];
40
+ access: "read" | "write";
41
+ createdAt: Date;
42
+ expiresAt: Date | null;
43
+ lastUsedAt: Date | null;
44
+ revokedAt: Date | null;
45
+ };
46
+ revoke(id: string, actor?: {
47
+ userId?: string;
48
+ }): void;
49
+ /** Their creator was disabled: bearer tokens they made are revoked (re-enabling revives none). */
50
+ revokeCreatedBy(userId: string, actor?: {
51
+ userId?: string;
52
+ }): number;
53
+ /** Returns the token row if the value is live (not revoked/expired), regardless of scope. */
54
+ verify(value: string): TokenRow | null;
55
+ static inScope(row: Pick<TokenRow, 'scope'>, instanceId: string): boolean;
56
+ }
57
+ export {};
@@ -0,0 +1,143 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { and, asc, eq, inArray, isNull } from 'drizzle-orm';
3
+ import { z } from 'zod';
4
+ import { writeAudit } from '../audit.js';
5
+ import { mcpTokens, pluginInstances } from '../db/schema.js';
6
+ import { NotFoundError, ValidationError } from '../errors.js';
7
+ import { ACCESS_CEILINGS } from '../gate/access.js';
8
+ import { randomToken, sha256 } from './tokens.js';
9
+ /**
10
+ * Static bearer tokens for MCP clients (design §6.2): `syn_…`, shown once, stored as SHA-256,
11
+ * scoped to instance ids or `*`. A token outside its scope gets 403, not 401.
12
+ */
13
+ export const TOKEN_PREFIX = 'syn_';
14
+ export const CreateTokenSchema = z.object({
15
+ name: z.string().trim().min(1).max(100),
16
+ scope: z.array(z.string().min(1)).min(1),
17
+ /** `read` (default) hides and blocks every write, whatever the endpoint's levels allow. */
18
+ access: z.enum(ACCESS_CEILINGS).default('read'),
19
+ expiresAt: z.coerce.date().nullable().optional(),
20
+ });
21
+ const publicToken = (t) => ({
22
+ id: t.id,
23
+ name: t.name,
24
+ scope: t.scope,
25
+ access: t.access,
26
+ createdAt: t.createdAt,
27
+ expiresAt: t.expiresAt,
28
+ lastUsedAt: t.lastUsedAt,
29
+ revokedAt: t.revokedAt,
30
+ });
31
+ export class McpTokenService {
32
+ db;
33
+ now;
34
+ constructor(db, now = () => new Date()) {
35
+ this.db = db;
36
+ this.now = now;
37
+ }
38
+ list() {
39
+ return this.db.select().from(mcpTokens).orderBy(asc(mcpTokens.createdAt)).all().map(publicToken);
40
+ }
41
+ create(raw, actor = {}) {
42
+ const input = CreateTokenSchema.parse(raw);
43
+ if (input.expiresAt && input.expiresAt.getTime() <= this.now().getTime()) {
44
+ throw new ValidationError('invalid_expiry', 'Expiry must be in the future');
45
+ }
46
+ if (!input.scope.includes('*')) {
47
+ const known = new Set(this.db
48
+ .select({ id: pluginInstances.id })
49
+ .from(pluginInstances)
50
+ .all()
51
+ .map((r) => r.id));
52
+ const unknown = input.scope.filter((s) => !known.has(s));
53
+ if (unknown.length)
54
+ throw new ValidationError('unknown_instance', `Unknown endpoint(s) in scope: ${unknown.join(', ')}`);
55
+ }
56
+ const token = `${TOKEN_PREFIX}${randomToken(32)}`;
57
+ const id = randomUUID();
58
+ this.db.transaction((tx) => {
59
+ tx.insert(mcpTokens)
60
+ .values({
61
+ id,
62
+ name: input.name,
63
+ tokenHash: sha256(token),
64
+ scope: input.scope,
65
+ access: input.access,
66
+ createdBy: actor.userId ?? null,
67
+ createdAt: this.now(),
68
+ expiresAt: input.expiresAt ?? null,
69
+ })
70
+ .run();
71
+ writeAudit(tx, {
72
+ kind: 'config',
73
+ decision: 'mcp_token_created',
74
+ actorKind: 'user',
75
+ actorId: actor.userId,
76
+ detail: { id, name: input.name, scope: input.scope, access: input.access, expiresAt: input.expiresAt ?? null },
77
+ });
78
+ });
79
+ return { ...publicToken(this.db.select().from(mcpTokens).where(eq(mcpTokens.id, id)).get()), token };
80
+ }
81
+ revoke(id, actor = {}) {
82
+ const row = this.db.select().from(mcpTokens).where(eq(mcpTokens.id, id)).get();
83
+ if (!row)
84
+ throw new NotFoundError('token_not_found', 'No such token');
85
+ this.db.transaction((tx) => {
86
+ tx.update(mcpTokens).set({ revokedAt: this.now() }).where(eq(mcpTokens.id, id)).run();
87
+ writeAudit(tx, {
88
+ kind: 'config',
89
+ decision: 'mcp_token_revoked',
90
+ actorKind: 'user',
91
+ actorId: actor.userId,
92
+ detail: { id, name: row.name },
93
+ });
94
+ });
95
+ }
96
+ /** Their creator was disabled: bearer tokens they made are revoked (re-enabling revives none). */
97
+ revokeCreatedBy(userId, actor = {}) {
98
+ return this.db.transaction((tx) => {
99
+ const live = tx
100
+ .select({ id: mcpTokens.id, name: mcpTokens.name })
101
+ .from(mcpTokens)
102
+ .where(and(eq(mcpTokens.createdBy, userId), isNull(mcpTokens.revokedAt)))
103
+ .all();
104
+ if (!live.length)
105
+ return 0;
106
+ tx.update(mcpTokens)
107
+ .set({ revokedAt: this.now() })
108
+ .where(inArray(mcpTokens.id, live.map((t) => t.id)))
109
+ .run();
110
+ writeAudit(tx, {
111
+ kind: 'config',
112
+ decision: 'mcp_tokens_revoked',
113
+ actorKind: actor.userId ? 'user' : 'system',
114
+ actorId: actor.userId,
115
+ detail: { createdBy: userId, reason: 'user_disabled', tokens: live.map((t) => t.name) },
116
+ });
117
+ return live.length;
118
+ });
119
+ }
120
+ /** Returns the token row if the value is live (not revoked/expired), regardless of scope. */
121
+ verify(value) {
122
+ if (!value.startsWith(TOKEN_PREFIX) || value.length > 200)
123
+ return null;
124
+ const row = this.db
125
+ .select()
126
+ .from(mcpTokens)
127
+ .where(eq(mcpTokens.tokenHash, sha256(value)))
128
+ .get();
129
+ if (!row || row.revokedAt)
130
+ return null;
131
+ const now = this.now();
132
+ if (row.expiresAt && row.expiresAt.getTime() <= now.getTime())
133
+ return null;
134
+ if (!row.lastUsedAt || now.getTime() - row.lastUsedAt.getTime() > 60_000) {
135
+ this.db.update(mcpTokens).set({ lastUsedAt: now }).where(eq(mcpTokens.id, row.id)).run();
136
+ }
137
+ return row;
138
+ }
139
+ static inScope(row, instanceId) {
140
+ return row.scope.includes('*') || row.scope.includes(instanceId);
141
+ }
142
+ }
143
+ //# sourceMappingURL=mcp-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-tokens.js","sourceRoot":"","sources":["../../src/auth/mcp-tokens.ts"],"names":[],"mappings":"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"}