@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.
- package/LICENSE +21 -0
- package/dist/app.d.ts +79 -0
- package/dist/app.js +155 -0
- package/dist/app.js.map +1 -0
- package/dist/approvals/links.d.ts +23 -0
- package/dist/approvals/links.js +49 -0
- package/dist/approvals/links.js.map +1 -0
- package/dist/approvals/service.d.ts +110 -0
- package/dist/approvals/service.js +182 -0
- package/dist/approvals/service.js.map +1 -0
- package/dist/audit-query.d.ts +52 -0
- package/dist/audit-query.js +116 -0
- package/dist/audit-query.js.map +1 -0
- package/dist/audit.d.ts +5 -0
- package/dist/audit.js +8 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth/mcp-auth.d.ts +50 -0
- package/dist/auth/mcp-auth.js +160 -0
- package/dist/auth/mcp-auth.js.map +1 -0
- package/dist/auth/mcp-tokens.d.ts +57 -0
- package/dist/auth/mcp-tokens.js +143 -0
- package/dist/auth/mcp-tokens.js.map +1 -0
- package/dist/auth/oauth.d.ts +188 -0
- package/dist/auth/oauth.js +470 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/oidc.d.ts +110 -0
- package/dist/auth/oidc.js +221 -0
- package/dist/auth/oidc.js.map +1 -0
- package/dist/auth/sessions.d.ts +34 -0
- package/dist/auth/sessions.js +83 -0
- package/dist/auth/sessions.js.map +1 -0
- package/dist/auth/throttle.d.ts +31 -0
- package/dist/auth/throttle.js +52 -0
- package/dist/auth/throttle.js.map +1 -0
- package/dist/auth/tokens.d.ts +7 -0
- package/dist/auth/tokens.js +40 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/auth/totp.d.ts +14 -0
- package/dist/auth/totp.js +81 -0
- package/dist/auth/totp.js.map +1 -0
- package/dist/auth/users.d.ts +70 -0
- package/dist/auth/users.js +296 -0
- package/dist/auth/users.js.map +1 -0
- package/dist/catalog/groups.d.ts +102 -0
- package/dist/catalog/groups.js +363 -0
- package/dist/catalog/groups.js.map +1 -0
- package/dist/catalog/registry.d.ts +25 -0
- package/dist/catalog/registry.js +80 -0
- package/dist/catalog/registry.js.map +1 -0
- package/dist/catalog/rules.d.ts +128 -0
- package/dist/catalog/rules.js +178 -0
- package/dist/catalog/rules.js.map +1 -0
- package/dist/catalog/sync.d.ts +29 -0
- package/dist/catalog/sync.js +222 -0
- package/dist/catalog/sync.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +83 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/env.d.ts +40 -0
- package/dist/config/env.js +71 -0
- package/dist/config/env.js.map +1 -0
- package/dist/crypto/index.d.ts +28 -0
- package/dist/crypto/index.js +88 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/rotate.d.ts +15 -0
- package/dist/crypto/rotate.js +61 -0
- package/dist/crypto/rotate.js.map +1 -0
- package/dist/db/index.d.ts +17 -0
- package/dist/db/index.js +29 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +4389 -0
- package/dist/db/schema.js +372 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/endpoints/slug.d.ts +3 -0
- package/dist/endpoints/slug.js +7 -0
- package/dist/endpoints/slug.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +43 -0
- package/dist/events.js +16 -0
- package/dist/events.js.map +1 -0
- package/dist/gate/access.d.ts +47 -0
- package/dist/gate/access.js +48 -0
- package/dist/gate/access.js.map +1 -0
- package/dist/gate/attestation.d.ts +23 -0
- package/dist/gate/attestation.js +33 -0
- package/dist/gate/attestation.js.map +1 -0
- package/dist/gate/canonical.d.ts +3 -0
- package/dist/gate/canonical.js +11 -0
- package/dist/gate/canonical.js.map +1 -0
- package/dist/gate/match.d.ts +67 -0
- package/dist/gate/match.js +133 -0
- package/dist/gate/match.js.map +1 -0
- package/dist/gate/pipeline.d.ts +61 -0
- package/dist/gate/pipeline.js +284 -0
- package/dist/gate/pipeline.js.map +1 -0
- package/dist/gate/preapproval.d.ts +24 -0
- package/dist/gate/preapproval.js +48 -0
- package/dist/gate/preapproval.js.map +1 -0
- package/dist/gate/rate-limit.d.ts +35 -0
- package/dist/gate/rate-limit.js +82 -0
- package/dist/gate/rate-limit.js.map +1 -0
- package/dist/gate/redact.d.ts +23 -0
- package/dist/gate/redact.js +87 -0
- package/dist/gate/redact.js.map +1 -0
- package/dist/http/admin/auth.d.ts +33 -0
- package/dist/http/admin/auth.js +338 -0
- package/dist/http/admin/auth.js.map +1 -0
- package/dist/http/admin/instances.d.ts +5 -0
- package/dist/http/admin/instances.js +193 -0
- package/dist/http/admin/instances.js.map +1 -0
- package/dist/http/admin/system.d.ts +5 -0
- package/dist/http/admin/system.js +225 -0
- package/dist/http/admin/system.js.map +1 -0
- package/dist/http/admin-app.d.ts +9 -0
- package/dist/http/admin-app.js +51 -0
- package/dist/http/admin-app.js.map +1 -0
- package/dist/http/common.d.ts +32 -0
- package/dist/http/common.js +80 -0
- package/dist/http/common.js.map +1 -0
- package/dist/http/mcp/approval-routes.d.ts +11 -0
- package/dist/http/mcp/approval-routes.js +117 -0
- package/dist/http/mcp/approval-routes.js.map +1 -0
- package/dist/http/mcp/endpoint.d.ts +32 -0
- package/dist/http/mcp/endpoint.js +293 -0
- package/dist/http/mcp/endpoint.js.map +1 -0
- package/dist/http/mcp/oauth-routes.d.ts +29 -0
- package/dist/http/mcp/oauth-routes.js +465 -0
- package/dist/http/mcp/oauth-routes.js.map +1 -0
- package/dist/http/mcp/pages.d.ts +63 -0
- package/dist/http/mcp/pages.js +200 -0
- package/dist/http/mcp/pages.js.map +1 -0
- package/dist/http/mcp-app.d.ts +7 -0
- package/dist/http/mcp-app.js +30 -0
- package/dist/http/mcp-app.js.map +1 -0
- package/dist/instances/connection.d.ts +37 -0
- package/dist/instances/connection.js +109 -0
- package/dist/instances/connection.js.map +1 -0
- package/dist/instances/manager.d.ts +313 -0
- package/dist/instances/manager.js +600 -0
- package/dist/instances/manager.js.map +1 -0
- package/dist/instances/settings.d.ts +42 -0
- package/dist/instances/settings.js +57 -0
- package/dist/instances/settings.js.map +1 -0
- package/dist/lenient.d.ts +16 -0
- package/dist/lenient.js +51 -0
- package/dist/lenient.js.map +1 -0
- package/dist/lock.d.ts +28 -0
- package/dist/lock.js +77 -0
- package/dist/lock.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +38 -0
- package/dist/main.js.map +1 -0
- package/dist/maintenance.d.ts +10 -0
- package/dist/maintenance.js +48 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/notify/service.d.ts +149 -0
- package/dist/notify/service.js +358 -0
- package/dist/notify/service.js.map +1 -0
- package/dist/plugins/discovery.d.ts +39 -0
- package/dist/plugins/discovery.js +157 -0
- package/dist/plugins/discovery.js.map +1 -0
- package/dist/plugins/minisign.d.ts +25 -0
- package/dist/plugins/minisign.js +49 -0
- package/dist/plugins/minisign.js.map +1 -0
- package/dist/plugins/process.d.ts +55 -0
- package/dist/plugins/process.js +162 -0
- package/dist/plugins/process.js.map +1 -0
- package/dist/plugins/repos.d.ts +198 -0
- package/dist/plugins/repos.js +612 -0
- package/dist/plugins/repos.js.map +1 -0
- package/dist/plugins/supervisor.d.ts +50 -0
- package/dist/plugins/supervisor.js +125 -0
- package/dist/plugins/supervisor.js.map +1 -0
- package/dist/runtime/index.d.ts +28 -0
- package/dist/runtime/index.js +248 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +53 -0
- package/dist/sandbox/index.js +197 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +36 -0
- package/dist/server.js.map +1 -0
- package/dist/settings.d.ts +43 -0
- package/dist/settings.js +99 -0
- package/dist/settings.js.map +1 -0
- package/dist/testing/index.d.ts +73 -0
- package/dist/testing/index.js +174 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/drizzle/0000_init.sql +334 -0
- package/drizzle/0001_totp_last_step.sql +1 -0
- package/drizzle/0002_access_levels.sql +16 -0
- package/drizzle/0003_drop_exclusion_flags.sql +2 -0
- package/drizzle/0004_grant_instance_ids.sql +1 -0
- package/drizzle/0005_rule_strict_miss.sql +1 -0
- package/drizzle/0006_attestation_waived.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +2318 -0
- package/drizzle/meta/0001_snapshot.json +2325 -0
- package/drizzle/meta/0002_snapshot.json +2348 -0
- package/drizzle/meta/0003_snapshot.json +2332 -0
- package/drizzle/meta/0004_snapshot.json +2339 -0
- package/drizzle/meta/0005_snapshot.json +2346 -0
- package/drizzle/meta/0006_snapshot.json +2354 -0
- package/drizzle/meta/_journal.json +55 -0
- 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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"}
|
package/dist/audit.d.ts
ADDED
|
@@ -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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"}
|