@synoikia/core 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +8 -0
- package/dist/app.js +16 -4
- package/dist/app.js.map +1 -1
- package/dist/approvals/grants.d.ts +34 -0
- package/dist/approvals/grants.js +79 -0
- package/dist/approvals/grants.js.map +1 -0
- package/dist/approvals/service.d.ts +24 -1
- package/dist/approvals/service.js +24 -2
- package/dist/approvals/service.js.map +1 -1
- package/dist/auth/mcp-auth.d.ts +8 -0
- package/dist/auth/mcp-auth.js +82 -33
- package/dist/auth/mcp-auth.js.map +1 -1
- package/dist/auth/mcp-tokens.d.ts +9 -3
- package/dist/auth/mcp-tokens.js +24 -4
- package/dist/auth/mcp-tokens.js.map +1 -1
- package/dist/auth/oauth.d.ts +5 -1
- package/dist/auth/oauth.js +15 -1
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/oidc.js +3 -2
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/roles.d.ts +43 -0
- package/dist/auth/roles.js +164 -0
- package/dist/auth/roles.js.map +1 -0
- package/dist/auth/sessions.d.ts +7 -0
- package/dist/auth/sessions.js +18 -2
- package/dist/auth/sessions.js.map +1 -1
- package/dist/auth/users.d.ts +26 -1
- package/dist/auth/users.js +109 -12
- package/dist/auth/users.js.map +1 -1
- package/dist/catalog/groups.js +2 -1
- package/dist/catalog/groups.js.map +1 -1
- package/dist/catalog/role-levels.d.ts +67 -0
- package/dist/catalog/role-levels.js +297 -0
- package/dist/catalog/role-levels.js.map +1 -0
- package/dist/catalog/rules.d.ts +33 -9
- package/dist/catalog/rules.js +51 -25
- package/dist/catalog/rules.js.map +1 -1
- package/dist/db/schema.d.ts +566 -0
- package/dist/db/schema.js +64 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/errors.d.ts +5 -2
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -1
- package/dist/gate/access.d.ts +23 -2
- package/dist/gate/access.js +31 -3
- package/dist/gate/access.js.map +1 -1
- package/dist/gate/pipeline.d.ts +25 -5
- package/dist/gate/pipeline.js +67 -6
- package/dist/gate/pipeline.js.map +1 -1
- package/dist/gate/preapproval.d.ts +2 -0
- package/dist/gate/preapproval.js +16 -3
- package/dist/gate/preapproval.js.map +1 -1
- package/dist/http/admin/auth.d.ts +4 -0
- package/dist/http/admin/auth.js +53 -4
- package/dist/http/admin/auth.js.map +1 -1
- package/dist/http/admin/instances.js +19 -1
- package/dist/http/admin/instances.js.map +1 -1
- package/dist/http/admin/me.d.ts +6 -0
- package/dist/http/admin/me.js +189 -0
- package/dist/http/admin/me.js.map +1 -0
- package/dist/http/admin/roles.d.ts +5 -0
- package/dist/http/admin/roles.js +45 -0
- package/dist/http/admin/roles.js.map +1 -0
- package/dist/http/admin/system.js +11 -6
- package/dist/http/admin/system.js.map +1 -1
- package/dist/http/admin-app.js +13 -2
- package/dist/http/admin-app.js.map +1 -1
- package/dist/http/common.d.ts +1 -1
- package/dist/http/mcp/approval-card.d.ts +6 -0
- package/dist/http/mcp/approval-card.js +160 -0
- package/dist/http/mcp/approval-card.js.map +1 -0
- package/dist/http/mcp/approval-routes.d.ts +2 -2
- package/dist/http/mcp/approval-routes.js +40 -4
- package/dist/http/mcp/approval-routes.js.map +1 -1
- package/dist/http/mcp/endpoint.js +104 -10
- package/dist/http/mcp/endpoint.js.map +1 -1
- package/dist/http/mcp/oauth-routes.js +22 -8
- package/dist/http/mcp/oauth-routes.js.map +1 -1
- package/dist/http/mcp/pages.d.ts +8 -1
- package/dist/http/mcp/pages.js +21 -2
- package/dist/http/mcp/pages.js.map +1 -1
- package/dist/instances/manager.d.ts +4 -0
- package/dist/instances/settings.d.ts +2 -0
- package/dist/instances/settings.js +2 -0
- package/dist/instances/settings.js.map +1 -1
- package/dist/runtime/executions.d.ts +68 -0
- package/dist/runtime/executions.js +158 -0
- package/dist/runtime/executions.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +131 -25
- package/dist/runtime/index.js.map +1 -1
- package/dist/settings.d.ts +4 -0
- package/dist/settings.js +12 -0
- package/dist/settings.js.map +1 -1
- package/dist/testing/index.d.ts +11 -1
- package/dist/testing/index.js +23 -7
- package/dist/testing/index.js.map +1 -1
- package/drizzle/0013_user_roles.sql +63 -0
- package/drizzle/0014_user_registration_source.sql +1 -0
- package/drizzle/meta/0013_snapshot.json +2833 -0
- package/drizzle/meta/0014_snapshot.json +2840 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +3 -3
package/dist/app.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SessionGrantService } from './approvals/grants.js';
|
|
1
2
|
import { ApprovalLinkService } from './approvals/links.js';
|
|
2
3
|
import { ApprovalService } from './approvals/service.js';
|
|
3
4
|
import { McpTokenService } from './auth/mcp-tokens.js';
|
|
@@ -5,6 +6,7 @@ import { OAuthService } from './auth/oauth.js';
|
|
|
5
6
|
import { OidcService } from './auth/oidc.js';
|
|
6
7
|
import { SessionService } from './auth/sessions.js';
|
|
7
8
|
import { LoginThrottle } from './auth/throttle.js';
|
|
9
|
+
import { RoleService } from './auth/roles.js';
|
|
8
10
|
import { UserService } from './auth/users.js';
|
|
9
11
|
import type { Config } from './config/env.js';
|
|
10
12
|
import { SecretBox } from './crypto/index.js';
|
|
@@ -20,6 +22,7 @@ import type { FetchLike } from './notify/service.js';
|
|
|
20
22
|
import { syncPluginRegistry } from './plugins/discovery.js';
|
|
21
23
|
import { PluginRepoService } from './plugins/repos.js';
|
|
22
24
|
import type { FetchBytes } from './plugins/repos.js';
|
|
25
|
+
import { ExecutionRegistry } from './runtime/executions.js';
|
|
23
26
|
/** Wires every core service once (design §2): one database, approval service and event bus for
|
|
24
27
|
* both listeners, the scheduler and tests. */
|
|
25
28
|
export interface AppContext {
|
|
@@ -30,6 +33,7 @@ export interface AppContext {
|
|
|
30
33
|
secrets: SecretBox;
|
|
31
34
|
events: CoreEvents;
|
|
32
35
|
users: UserService;
|
|
36
|
+
roles: RoleService;
|
|
33
37
|
sessions: SessionService;
|
|
34
38
|
throttle: LoginThrottle;
|
|
35
39
|
oidc: OidcService;
|
|
@@ -37,6 +41,10 @@ export interface AppContext {
|
|
|
37
41
|
oauth: OAuthService;
|
|
38
42
|
instances: InstanceManager;
|
|
39
43
|
approvals: ApprovalService;
|
|
44
|
+
/** Session grants (design §5.8). */
|
|
45
|
+
grants: SessionGrantService;
|
|
46
|
+
/** Parked executions (design §5.6). */
|
|
47
|
+
executions: ExecutionRegistry;
|
|
40
48
|
limiter: SlidingWindowLimiter;
|
|
41
49
|
links: ApprovalLinkService;
|
|
42
50
|
notifier: NotifierService;
|
package/dist/app.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { SessionGrantService } from './approvals/grants.js';
|
|
2
3
|
import { ApprovalLinkService } from './approvals/links.js';
|
|
3
4
|
import { ApprovalService } from './approvals/service.js';
|
|
4
5
|
import { McpTokenService } from './auth/mcp-tokens.js';
|
|
@@ -6,6 +7,7 @@ import { OAuthService } from './auth/oauth.js';
|
|
|
6
7
|
import { OidcService } from './auth/oidc.js';
|
|
7
8
|
import { SessionService } from './auth/sessions.js';
|
|
8
9
|
import { LoginThrottle } from './auth/throttle.js';
|
|
10
|
+
import { RoleService } from './auth/roles.js';
|
|
9
11
|
import { UserService } from './auth/users.js';
|
|
10
12
|
import { loadMasterKey, SecretBox } from './crypto/index.js';
|
|
11
13
|
import { openDatabase } from './db/index.js';
|
|
@@ -17,6 +19,7 @@ import { runHousekeeping } from './maintenance.js';
|
|
|
17
19
|
import { NotifierService } from './notify/service.js';
|
|
18
20
|
import { discoverPlugins, syncPluginRegistry } from './plugins/discovery.js';
|
|
19
21
|
import { PluginRepoService } from './plugins/repos.js';
|
|
22
|
+
import { ExecutionRegistry } from './runtime/executions.js';
|
|
20
23
|
import { sandboxesRunning } from './runtime/index.js';
|
|
21
24
|
import { normalizeStoredSettings } from './settings.js';
|
|
22
25
|
export async function createAppContext(config, opts = {}) {
|
|
@@ -45,10 +48,15 @@ export async function createAppContext(config, opts = {}) {
|
|
|
45
48
|
warnings.push('PUBLIC_MCP_URL is not set: OAuth is off (bearer tokens still work). Set it to the public address of the MCP port.');
|
|
46
49
|
const links = new ApprovalLinkService(db, now);
|
|
47
50
|
const approvals = new ApprovalService(db, links, now);
|
|
48
|
-
|
|
51
|
+
const grants = new SessionGrantService(db, now);
|
|
52
|
+
const executions = new ExecutionRegistry(() => now().getTime());
|
|
53
|
+
// An approval, a parked run or a grant must not outlive the plugin process and configuration it was given for.
|
|
49
54
|
events.on('instance.status', ({ instanceId, status }) => {
|
|
50
|
-
if (status
|
|
51
|
-
|
|
55
|
+
if (status !== 'stopped')
|
|
56
|
+
return;
|
|
57
|
+
approvals.cancelForInstance(instanceId, 'endpoint_stopped');
|
|
58
|
+
executions.abort(instanceId);
|
|
59
|
+
grants.endForInstance(instanceId, 'endpoint_stopped');
|
|
52
60
|
});
|
|
53
61
|
const keys = {
|
|
54
62
|
attestation: secrets.deriveKey('attestation'),
|
|
@@ -86,6 +94,7 @@ export async function createAppContext(config, opts = {}) {
|
|
|
86
94
|
clearInterval(t);
|
|
87
95
|
// Cancelled approvals end their tool calls first, so closing sessions doesn't cut them off mid-reply.
|
|
88
96
|
approvals.cancelAll('shutdown');
|
|
97
|
+
executions.abort();
|
|
89
98
|
// Give the scripts those calls belonged to a moment to return their result to the client.
|
|
90
99
|
if (sandboxesRunning() > 0) {
|
|
91
100
|
for (const until = Date.now() + 2000; sandboxesRunning() > 0 && Date.now() < until;)
|
|
@@ -102,6 +111,7 @@ export async function createAppContext(config, opts = {}) {
|
|
|
102
111
|
secrets,
|
|
103
112
|
events,
|
|
104
113
|
users,
|
|
114
|
+
roles: new RoleService(db),
|
|
105
115
|
sessions: new SessionService(db, secrets.deriveKey('session-pepper'), now),
|
|
106
116
|
throttle: new LoginThrottle(),
|
|
107
117
|
oidc: new OidcService(db, secrets, keys.state, opts.oidcAllowInsecure ?? false),
|
|
@@ -109,6 +119,8 @@ export async function createAppContext(config, opts = {}) {
|
|
|
109
119
|
oauth: new OAuthService(db, now),
|
|
110
120
|
instances,
|
|
111
121
|
approvals,
|
|
122
|
+
grants,
|
|
123
|
+
executions,
|
|
112
124
|
limiter,
|
|
113
125
|
links,
|
|
114
126
|
notifier,
|
|
@@ -116,7 +128,7 @@ export async function createAppContext(config, opts = {}) {
|
|
|
116
128
|
keys,
|
|
117
129
|
warnings,
|
|
118
130
|
now,
|
|
119
|
-
gateDeps: () => ({ db, approvals, limiter, attestationKey: keys.attestation, now }),
|
|
131
|
+
gateDeps: () => ({ db, approvals, limiter, attestationKey: keys.attestation, now, grants, executions }),
|
|
120
132
|
discoverPlugins: discover,
|
|
121
133
|
async start() {
|
|
122
134
|
await instances.startAll();
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Db } from '../db/index.js';
|
|
2
|
+
/** Session grants (design §5.8): an approver lets one client credential run Ask operations on one endpoint
|
|
3
|
+
* for a while, except locked and typed-confirmation ones. In memory only, so a restart ends them all. */
|
|
4
|
+
export interface SessionGrant {
|
|
5
|
+
id: string;
|
|
6
|
+
instanceId: string;
|
|
7
|
+
/** `principalKey` of the caller the grant covers. */
|
|
8
|
+
principal: string;
|
|
9
|
+
/** The client's audit label, for display. */
|
|
10
|
+
client?: string;
|
|
11
|
+
createdBy: string;
|
|
12
|
+
approvalId: string;
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
expiresAt: Date;
|
|
15
|
+
}
|
|
16
|
+
export declare class SessionGrantService {
|
|
17
|
+
private readonly db;
|
|
18
|
+
private readonly now;
|
|
19
|
+
private readonly grants;
|
|
20
|
+
constructor(db: Db, now?: () => Date);
|
|
21
|
+
create(input: Omit<SessionGrant, 'id' | 'createdAt'>): SessionGrant;
|
|
22
|
+
/** The live grant for this client on this endpoint, if any; expired ones end here. */
|
|
23
|
+
active(instanceId: string, principal: string): SessionGrant | undefined;
|
|
24
|
+
get(id: string): SessionGrant | undefined;
|
|
25
|
+
list(instanceId?: string): SessionGrant[];
|
|
26
|
+
/** Ends a grant early; false if it was not live. */
|
|
27
|
+
revoke(id: string, by: {
|
|
28
|
+
actorKind: 'user' | 'mcp_client';
|
|
29
|
+
actorId?: string;
|
|
30
|
+
}): boolean;
|
|
31
|
+
endForInstance(instanceId: string, reason: string): void;
|
|
32
|
+
private sweep;
|
|
33
|
+
private end;
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { writeAudit } from '../audit.js';
|
|
3
|
+
export class SessionGrantService {
|
|
4
|
+
db;
|
|
5
|
+
now;
|
|
6
|
+
grants = new Map();
|
|
7
|
+
constructor(db, now = () => new Date()) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
this.now = now;
|
|
10
|
+
}
|
|
11
|
+
create(input) {
|
|
12
|
+
// One grant per client and endpoint: a new one replaces the old.
|
|
13
|
+
const old = this.active(input.instanceId, input.principal);
|
|
14
|
+
if (old)
|
|
15
|
+
this.end(old, 'replaced', input.createdBy, 'user');
|
|
16
|
+
const grant = { ...input, id: randomUUID(), createdAt: this.now() };
|
|
17
|
+
this.grants.set(grant.id, grant);
|
|
18
|
+
writeAudit(this.db, {
|
|
19
|
+
kind: 'config',
|
|
20
|
+
instanceId: grant.instanceId,
|
|
21
|
+
decision: 'session_grant.created',
|
|
22
|
+
actorKind: 'user',
|
|
23
|
+
actorId: grant.createdBy,
|
|
24
|
+
detail: {
|
|
25
|
+
grantId: grant.id,
|
|
26
|
+
client: grant.client,
|
|
27
|
+
approvalId: grant.approvalId,
|
|
28
|
+
expiresAt: grant.expiresAt.toISOString(),
|
|
29
|
+
},
|
|
30
|
+
}, grant.createdAt);
|
|
31
|
+
return grant;
|
|
32
|
+
}
|
|
33
|
+
/** The live grant for this client on this endpoint, if any; expired ones end here. */
|
|
34
|
+
active(instanceId, principal) {
|
|
35
|
+
for (const g of this.list(instanceId))
|
|
36
|
+
if (g.principal === principal)
|
|
37
|
+
return g;
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
get(id) {
|
|
41
|
+
this.sweep();
|
|
42
|
+
return this.grants.get(id);
|
|
43
|
+
}
|
|
44
|
+
list(instanceId) {
|
|
45
|
+
this.sweep();
|
|
46
|
+
return [...this.grants.values()].filter((g) => !instanceId || g.instanceId === instanceId);
|
|
47
|
+
}
|
|
48
|
+
/** Ends a grant early; false if it was not live. */
|
|
49
|
+
revoke(id, by) {
|
|
50
|
+
const g = this.get(id);
|
|
51
|
+
if (!g)
|
|
52
|
+
return false;
|
|
53
|
+
this.end(g, 'revoked', by.actorId, by.actorKind);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
endForInstance(instanceId, reason) {
|
|
57
|
+
for (const g of this.list(instanceId))
|
|
58
|
+
this.end(g, reason);
|
|
59
|
+
}
|
|
60
|
+
sweep() {
|
|
61
|
+
const now = this.now().getTime();
|
|
62
|
+
for (const g of [...this.grants.values()])
|
|
63
|
+
if (g.expiresAt.getTime() <= now)
|
|
64
|
+
this.end(g, 'expired');
|
|
65
|
+
}
|
|
66
|
+
end(g, reason, actorId, actorKind = 'system') {
|
|
67
|
+
if (!this.grants.delete(g.id))
|
|
68
|
+
return;
|
|
69
|
+
writeAudit(this.db, {
|
|
70
|
+
kind: 'config',
|
|
71
|
+
instanceId: g.instanceId,
|
|
72
|
+
decision: 'session_grant.ended',
|
|
73
|
+
actorKind: actorId ? actorKind : 'system',
|
|
74
|
+
actorId,
|
|
75
|
+
detail: { grantId: g.id, client: g.client, reason },
|
|
76
|
+
}, this.now());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=grants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.js","sourceRoot":"","sources":["../../src/approvals/grants.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Db } from '../db/index.js';
|
|
2
2
|
import type { ApprovalLinkService } from './links.js';
|
|
3
3
|
/** Human approval (design §5.3): URL mode sends a human to our page to decide; form mode only for opted-in
|
|
4
|
-
* plain writes;
|
|
4
|
+
* plain writes; otherwise the execution parks and hands out the page link (§5.6). Any timeout is denied.
|
|
5
|
+
* A client's answer never approves a risky call. */
|
|
5
6
|
export type DecisionOutcome = 'approved' | 'denied' | 'timed_out' | 'cancelled';
|
|
6
7
|
export type DecisionChannel = 'elicitation' | 'url';
|
|
7
8
|
export interface Decision {
|
|
@@ -9,6 +10,8 @@ export interface Decision {
|
|
|
9
10
|
via?: DecisionChannel;
|
|
10
11
|
decidedBy?: string;
|
|
11
12
|
reason?: string;
|
|
13
|
+
/** The approver also granted this client the endpoint's Ask operations until then (§5.8). */
|
|
14
|
+
sessionGrantUntil?: Date;
|
|
12
15
|
}
|
|
13
16
|
export interface UrlPromptRequest {
|
|
14
17
|
approvalId: string;
|
|
@@ -45,6 +48,21 @@ export interface ClientPrompts {
|
|
|
45
48
|
urlComplete?: (approvalId: string) => void;
|
|
46
49
|
form?: (req: FormPromptRequest) => Promise<FormPromptResponse>;
|
|
47
50
|
}
|
|
51
|
+
/** A parked approval as the execution hands it to the agent: the page link replaces the client prompt. */
|
|
52
|
+
export interface ParkedApproval {
|
|
53
|
+
approvalId: string;
|
|
54
|
+
/** Path of the approval page on the MCP listener; the MCP layer makes it absolute. */
|
|
55
|
+
path: string;
|
|
56
|
+
operationKey: string;
|
|
57
|
+
summary: string;
|
|
58
|
+
expiresAt: Date;
|
|
59
|
+
}
|
|
60
|
+
/** Lets an execution park (design §5.6) when the client has no usable prompt. */
|
|
61
|
+
export interface ParkHook {
|
|
62
|
+
/** Null when the execution may park; otherwise the denial reason. */
|
|
63
|
+
reserve(): string | null;
|
|
64
|
+
offer(approval: ParkedApproval): void;
|
|
65
|
+
}
|
|
48
66
|
export interface ApprovalRequestInput {
|
|
49
67
|
instanceId: string;
|
|
50
68
|
operationId: string;
|
|
@@ -61,11 +79,15 @@ export interface ApprovalRequestInput {
|
|
|
61
79
|
kind: string;
|
|
62
80
|
id?: string;
|
|
63
81
|
};
|
|
82
|
+
/** The user who owns the calling credential; only they decide on the approval page (design §5.3). */
|
|
83
|
+
ownerUserId?: string;
|
|
64
84
|
mcpSessionId?: string;
|
|
65
85
|
timeoutMs: number;
|
|
66
86
|
prompts?: ClientPrompts;
|
|
67
87
|
/** The endpoint lets form prompts approve, and this is a plain write (not locked, no typed confirmation). */
|
|
68
88
|
formApprovals: boolean;
|
|
89
|
+
/** Present when the execution can park; used only when no client prompt applies. */
|
|
90
|
+
park?: ParkHook;
|
|
69
91
|
}
|
|
70
92
|
export declare class ApprovalService {
|
|
71
93
|
private readonly db;
|
|
@@ -87,6 +109,7 @@ export declare class ApprovalService {
|
|
|
87
109
|
approve: boolean;
|
|
88
110
|
confirm?: string;
|
|
89
111
|
decidedBy: string;
|
|
112
|
+
sessionGrantUntil?: Date;
|
|
90
113
|
}): Decision;
|
|
91
114
|
/** Cancels one open request (its execution ended). Returns false if it was already decided. */
|
|
92
115
|
cancel(id: string, reason: string): boolean;
|
|
@@ -49,6 +49,7 @@ export class ApprovalService {
|
|
|
49
49
|
expectedHash: input.expectedHash ?? null,
|
|
50
50
|
clientKind: input.client.kind,
|
|
51
51
|
clientId: input.client.id ?? null,
|
|
52
|
+
ownerUserId: input.ownerUserId ?? null,
|
|
52
53
|
mcpSessionId: input.mcpSessionId ?? null,
|
|
53
54
|
requestedAt: now,
|
|
54
55
|
expiresAt: new Date(now.getTime() + input.timeoutMs),
|
|
@@ -58,10 +59,20 @@ export class ApprovalService {
|
|
|
58
59
|
decidedAt: null,
|
|
59
60
|
};
|
|
60
61
|
const prompts = input.prompts ?? {};
|
|
61
|
-
|
|
62
|
+
let channel = prompts.url
|
|
63
|
+
? 'url'
|
|
64
|
+
: prompts.form && input.formApprovals
|
|
65
|
+
? 'form'
|
|
66
|
+
: null;
|
|
67
|
+
let parkRefused = null;
|
|
68
|
+
if (!channel && input.park) {
|
|
69
|
+
parkRefused = input.park.reserve();
|
|
70
|
+
if (!parkRefused)
|
|
71
|
+
channel = 'link';
|
|
72
|
+
}
|
|
62
73
|
if (!channel) {
|
|
63
74
|
// A form-only client can't carry a human's approval for this call; no prompts at all is worse.
|
|
64
|
-
const reason = prompts.form ? 'client_cannot_approve' : 'no_approval_path';
|
|
75
|
+
const reason = parkRefused ?? (prompts.form ? 'client_cannot_approve' : 'no_approval_path');
|
|
65
76
|
this.db
|
|
66
77
|
.insert(pendingApprovals)
|
|
67
78
|
.values({ ...row, status: 'denied', decidedAt: now })
|
|
@@ -90,6 +101,16 @@ export class ApprovalService {
|
|
|
90
101
|
});
|
|
91
102
|
if (channel === 'url')
|
|
92
103
|
this.offerUrl(id, row.expiresAt, input);
|
|
104
|
+
else if (channel === 'link') {
|
|
105
|
+
const token = this.links.create(id, row.expiresAt);
|
|
106
|
+
input.park.offer({
|
|
107
|
+
approvalId: id,
|
|
108
|
+
path: `/a/${token}`,
|
|
109
|
+
operationKey: input.operationKey,
|
|
110
|
+
summary: input.summary,
|
|
111
|
+
expiresAt: row.expiresAt,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
93
114
|
else
|
|
94
115
|
this.offerForm(id, input);
|
|
95
116
|
return { id, decision };
|
|
@@ -151,6 +172,7 @@ export class ApprovalService {
|
|
|
151
172
|
outcome: input.approve ? 'approved' : 'denied',
|
|
152
173
|
via: 'url',
|
|
153
174
|
decidedBy: input.decidedBy,
|
|
175
|
+
...(input.approve && input.sessionGrantUntil ? { sessionGrantUntil: input.sessionGrantUntil } : {}),
|
|
154
176
|
};
|
|
155
177
|
live.settle(decision);
|
|
156
178
|
return decision;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/approvals/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/approvals/service.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/mcp-auth.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface McpIdentity {
|
|
|
15
15
|
/** Ceiling on what this principal can reach (design §6.3): from OAuth consent or the bearer token;
|
|
16
16
|
* `external` mode is not limited here. */
|
|
17
17
|
access: AccessCeiling;
|
|
18
|
+
/** The user who owns the credential, or whom the proxy named; their role decides the reach (§6.4). */
|
|
19
|
+
userId: string;
|
|
20
|
+
roleId: string;
|
|
18
21
|
}
|
|
19
22
|
export type McpAuthResult = {
|
|
20
23
|
ok: true;
|
|
@@ -44,6 +47,11 @@ export declare function checkCloudflareAccess(teamDomain: string, aud: string, a
|
|
|
44
47
|
}>;
|
|
45
48
|
/** {@link checkCloudflareAccess}, returning who the assertion names, or null. */
|
|
46
49
|
export declare function verifyCloudflareAccess(teamDomain: string, aud: string, assertion: string, keys?: JWTVerifyGetKey): Promise<string | null>;
|
|
50
|
+
/** The owner is still enabled, in the same role, and the role still has the endpoint (parked runs). */
|
|
51
|
+
export declare function ownerLive(ctx: AppContext, identity: McpIdentity, instanceId: string): boolean;
|
|
52
|
+
/** Whether the credential behind a principal still reaches the instance; external identities have no
|
|
53
|
+
* credential to revoke. A parked execution checks this on each call (design §5.6). */
|
|
54
|
+
export declare function principalLive(ctx: AppContext, principal: string, instanceId: string): boolean;
|
|
47
55
|
export declare function authenticateMcp(ctx: AppContext, oauth: OAuthService, c: Context, instance: {
|
|
48
56
|
id: string;
|
|
49
57
|
slug: string;
|
package/dist/auth/mcp-auth.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
1
2
|
import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';
|
|
3
|
+
import { roleHasInstance } from '../catalog/role-levels.js';
|
|
4
|
+
import { users } from '../db/schema.js';
|
|
5
|
+
import { ConflictError, ValidationError } from '../errors.js';
|
|
2
6
|
import { getSettings } from '../settings.js';
|
|
3
7
|
import { McpTokenService, TOKEN_PREFIX } from './mcp-tokens.js';
|
|
4
8
|
import { ACCESS_PREFIX, canonicalResource } from './oauth.js';
|
|
5
|
-
import {
|
|
9
|
+
import { requestOrigin } from '../http/common.js';
|
|
6
10
|
/** The MCP listener's public origin: PUBLIC_MCP_URL, or the request's origin where no security decision
|
|
7
11
|
* depends on it (the approval link back to the same client, sign-in redirects). */
|
|
8
12
|
export function publicMcpBase(ctx, c) {
|
|
@@ -82,6 +86,57 @@ function describeToken(token) {
|
|
|
82
86
|
return `a JWT from another issuer (iss=${claimed.iss ?? '-'})`;
|
|
83
87
|
return 'a token this server did not issue';
|
|
84
88
|
}
|
|
89
|
+
const refuse = (status, error, message, detail) => ({
|
|
90
|
+
ok: false,
|
|
91
|
+
status,
|
|
92
|
+
error,
|
|
93
|
+
message,
|
|
94
|
+
detail,
|
|
95
|
+
});
|
|
96
|
+
/** The credential's owner must be enabled and their role must have this endpoint (design §6.4). */
|
|
97
|
+
function owned(ctx, base, userId, instanceId) {
|
|
98
|
+
const user = userId ? ctx.db.select().from(users).where(eq(users.id, userId)).get() : undefined;
|
|
99
|
+
if (!user || user.disabled)
|
|
100
|
+
return refuse(403, 'forbidden', 'The account behind this credential is disabled', `${base.label}: no enabled owner`);
|
|
101
|
+
if (!roleHasInstance(ctx.db, user.roleId, instanceId))
|
|
102
|
+
return refuse(403, 'insufficient_scope', 'This endpoint is not available to your role', `${base.label}: role of ${user.username} does not include this endpoint`);
|
|
103
|
+
return { ok: true, identity: { ...base, userId: user.id, roleId: user.roleId } };
|
|
104
|
+
}
|
|
105
|
+
/** A user named by the proxy: matched by username, else registered with the default role (design §6.5). */
|
|
106
|
+
async function externalUser(ctx, name) {
|
|
107
|
+
const found = ctx.users.byExternalIdentity(name);
|
|
108
|
+
if (found)
|
|
109
|
+
return found.id;
|
|
110
|
+
if (!ctx.users.registrationOpen())
|
|
111
|
+
return undefined;
|
|
112
|
+
try {
|
|
113
|
+
return (await ctx.users.register({ username: name }, 'external')).id;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
// A concurrent first request registered it; a name taken by a self-registered user, or not a valid
|
|
117
|
+
// username, stays unknown.
|
|
118
|
+
if (err instanceof ConflictError)
|
|
119
|
+
return ctx.users.byExternalIdentity(name)?.id;
|
|
120
|
+
if (err instanceof ValidationError)
|
|
121
|
+
return undefined;
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** The owner is still enabled, in the same role, and the role still has the endpoint (parked runs). */
|
|
126
|
+
export function ownerLive(ctx, identity, instanceId) {
|
|
127
|
+
const user = ctx.db.select().from(users).where(eq(users.id, identity.userId)).get();
|
|
128
|
+
return (!!user && !user.disabled && user.roleId === identity.roleId && roleHasInstance(ctx.db, user.roleId, instanceId));
|
|
129
|
+
}
|
|
130
|
+
/** Whether the credential behind a principal still reaches the instance; external identities have no
|
|
131
|
+
* credential to revoke. A parked execution checks this on each call (design §5.6). */
|
|
132
|
+
export function principalLive(ctx, principal, instanceId) {
|
|
133
|
+
if (principal.startsWith('token:'))
|
|
134
|
+
return ctx.tokens.liveFor(principal.slice('token:'.length), instanceId);
|
|
135
|
+
if (principal.startsWith('grant:'))
|
|
136
|
+
return ctx.oauth.grantLiveFor(principal.slice('grant:'.length), instanceId);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
const unknownUser = (base) => refuse(403, 'forbidden', 'No account matches this identity', `${base.label}: no user has that username and self-registration is off or the name is not a valid username`);
|
|
85
140
|
export async function authenticateMcp(ctx, oauth, c, instance) {
|
|
86
141
|
const mode = effectiveAuthMode(ctx, instance.authMode);
|
|
87
142
|
const settings = getSettings(ctx.db, 'mcp');
|
|
@@ -101,30 +156,30 @@ export async function authenticateMcp(ctx, oauth, c, instance) {
|
|
|
101
156
|
: 'no Cf-Access-Jwt-Assertion header: the request did not come through the Cloudflare Access application',
|
|
102
157
|
};
|
|
103
158
|
const who = checked.who;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
159
|
+
const base = { kind: 'external', principal: `cf:${who}`, label: `external:${who}`, access: 'write' };
|
|
160
|
+
const userId = await externalUser(ctx, who);
|
|
161
|
+
if (!userId)
|
|
162
|
+
return unknownUser(base);
|
|
163
|
+
return owned(ctx, base, userId, instance.id);
|
|
108
164
|
}
|
|
109
165
|
const header = settings.trustedIdentityHeader;
|
|
110
166
|
const named = header && c.req.header(header)?.trim().slice(0, 200);
|
|
111
|
-
if (named)
|
|
112
|
-
|
|
113
|
-
ok: true,
|
|
114
|
-
identity: { kind: 'external', principal: `ext:${named}`, label: `external:${named}`, access: 'write' },
|
|
115
|
-
};
|
|
116
|
-
// Anonymous: without an identity, the client address is what keeps callers' sessions (and rate
|
|
117
|
-
// budgets) apart; otherwise every anonymous client would share one principal.
|
|
118
|
-
const ip = clientIp(c, ctx.config.TRUST_PROXY) ?? 'unknown';
|
|
119
|
-
return {
|
|
120
|
-
ok: true,
|
|
121
|
-
identity: {
|
|
167
|
+
if (named) {
|
|
168
|
+
const base = {
|
|
122
169
|
kind: 'external',
|
|
123
|
-
principal: `ext
|
|
124
|
-
label: `external
|
|
170
|
+
principal: `ext:${named}`,
|
|
171
|
+
label: `external:${named}`,
|
|
125
172
|
access: 'write',
|
|
126
|
-
}
|
|
127
|
-
|
|
173
|
+
};
|
|
174
|
+
const userId = await externalUser(ctx, named);
|
|
175
|
+
if (!userId)
|
|
176
|
+
return unknownUser(base);
|
|
177
|
+
return owned(ctx, base, userId, instance.id);
|
|
178
|
+
}
|
|
179
|
+
// Every call belongs to a user (design §6.4): a caller the proxy didn't name is refused.
|
|
180
|
+
return refuse(401, 'unauthorized', 'The proxy did not identify the caller', header
|
|
181
|
+
? `no ${header} header and no Cloudflare Access assertion: anonymous callers are refused`
|
|
182
|
+
: 'external mode without Cloudflare Access or a trusted identity header: anonymous callers are refused');
|
|
128
183
|
}
|
|
129
184
|
const allowBearer = mode === 'bearer' || mode === 'bearer+oauth';
|
|
130
185
|
// OAuth is off until PUBLIC_MCP_URL is set: its discovery documents must not come from the Host header.
|
|
@@ -174,10 +229,7 @@ export async function authenticateMcp(ctx, oauth, c, instance) {
|
|
|
174
229
|
detail: `bearer token "${row.name}" is not scoped to this endpoint`,
|
|
175
230
|
};
|
|
176
231
|
}
|
|
177
|
-
return {
|
|
178
|
-
ok: true,
|
|
179
|
-
identity: { kind: 'token', principal: `token:${row.id}`, label: `token:${row.name}`, access: row.access },
|
|
180
|
-
};
|
|
232
|
+
return owned(ctx, { kind: 'token', principal: `token:${row.id}`, label: `token:${row.name}`, access: row.access }, row.createdBy, instance.id);
|
|
181
233
|
}
|
|
182
234
|
if (allowOauth && token.startsWith(ACCESS_PREFIX)) {
|
|
183
235
|
const found = oauth.verifyAccess(token, instance.id);
|
|
@@ -199,15 +251,12 @@ export async function authenticateMcp(ctx, oauth, c, instance) {
|
|
|
199
251
|
detail: `OAuth grant of client "${found.client.name}" does not include this endpoint`,
|
|
200
252
|
};
|
|
201
253
|
}
|
|
202
|
-
return {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
access: found.access,
|
|
209
|
-
},
|
|
210
|
-
};
|
|
254
|
+
return owned(ctx, {
|
|
255
|
+
kind: 'oauth',
|
|
256
|
+
principal: `grant:${found.grantId}`,
|
|
257
|
+
label: `oauth:${found.client.name} (${found.user.username})`,
|
|
258
|
+
access: found.access,
|
|
259
|
+
}, found.user.id, instance.id);
|
|
211
260
|
}
|
|
212
261
|
return {
|
|
213
262
|
ok: false,
|