@dupecom/botcha-cloudflare 0.16.0 → 0.18.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.
@@ -0,0 +1,378 @@
1
+ /**
2
+ * TAP Delegation Chain API Routes
3
+ *
4
+ * Endpoints for creating, querying, revoking, and verifying
5
+ * delegation chains between TAP agents.
6
+ *
7
+ * Routes:
8
+ * POST /v1/delegations — Create delegation
9
+ * GET /v1/delegations/:id — Get delegation details
10
+ * GET /v1/delegations — List delegations (by agent)
11
+ * POST /v1/delegations/:id/revoke — Revoke delegation (cascades)
12
+ * POST /v1/verify/delegation — Verify delegation chain
13
+ */
14
+ import { extractBearerToken, verifyToken } from './auth.js';
15
+ import { TAP_VALID_ACTIONS } from './tap-agents.js';
16
+ import { createDelegation, getDelegation, listDelegations, revokeDelegation, verifyDelegationChain, } from './tap-delegation.js';
17
+ // ============ VALIDATION HELPERS ============
18
+ async function validateAppAccess(c, requireAuth = true) {
19
+ const queryAppId = c.req.query('app_id');
20
+ let jwtAppId;
21
+ const authHeader = c.req.header('authorization');
22
+ const token = extractBearerToken(authHeader);
23
+ if (token) {
24
+ const result = await verifyToken(token, c.env.JWT_SECRET, c.env);
25
+ if (result.valid && result.payload) {
26
+ jwtAppId = result.payload.app_id;
27
+ }
28
+ }
29
+ const appId = queryAppId || jwtAppId;
30
+ if (requireAuth && !appId) {
31
+ return { valid: false, error: 'MISSING_APP_ID', status: 401 };
32
+ }
33
+ return { valid: true, appId };
34
+ }
35
+ // ============ ROUTE HANDLERS ============
36
+ /**
37
+ * POST /v1/delegations
38
+ * Create a delegation from one agent to another
39
+ */
40
+ export async function createDelegationRoute(c) {
41
+ try {
42
+ const appAccess = await validateAppAccess(c, true);
43
+ if (!appAccess.valid) {
44
+ return c.json({
45
+ success: false,
46
+ error: appAccess.error,
47
+ message: 'Authentication required'
48
+ }, (appAccess.status || 401));
49
+ }
50
+ const body = await c.req.json().catch(() => ({}));
51
+ // Validate required fields
52
+ if (!body.grantor_id || !body.grantee_id) {
53
+ return c.json({
54
+ success: false,
55
+ error: 'MISSING_REQUIRED_FIELDS',
56
+ message: 'grantor_id and grantee_id are required'
57
+ }, 400);
58
+ }
59
+ if (!body.capabilities || !Array.isArray(body.capabilities) || body.capabilities.length === 0) {
60
+ return c.json({
61
+ success: false,
62
+ error: 'MISSING_CAPABILITIES',
63
+ message: 'At least one capability is required'
64
+ }, 400);
65
+ }
66
+ // Validate capability actions
67
+ for (const cap of body.capabilities) {
68
+ if (!cap.action || !TAP_VALID_ACTIONS.includes(cap.action)) {
69
+ return c.json({
70
+ success: false,
71
+ error: 'INVALID_CAPABILITY',
72
+ message: `Invalid capability action. Valid: ${TAP_VALID_ACTIONS.join(', ')}`
73
+ }, 400);
74
+ }
75
+ }
76
+ const options = {
77
+ grantor_id: body.grantor_id,
78
+ grantee_id: body.grantee_id,
79
+ capabilities: body.capabilities,
80
+ duration_seconds: body.duration_seconds,
81
+ max_depth: body.max_depth,
82
+ parent_delegation_id: body.parent_delegation_id,
83
+ metadata: body.metadata,
84
+ };
85
+ const result = await createDelegation(c.env.AGENTS, c.env.SESSIONS, appAccess.appId, options);
86
+ if (!result.success) {
87
+ // Determine appropriate status code
88
+ const status = result.error?.includes('not found') ? 404
89
+ : result.error?.includes('does not belong') ? 403
90
+ : result.error?.includes('Cannot delegate') ? 403
91
+ : result.error?.includes('depth limit') ? 403
92
+ : result.error?.includes('cycle') ? 409
93
+ : result.error?.includes('revoked') ? 410
94
+ : result.error?.includes('expired') ? 410
95
+ : 400;
96
+ return c.json({
97
+ success: false,
98
+ error: 'DELEGATION_CREATION_FAILED',
99
+ message: result.error
100
+ }, status);
101
+ }
102
+ const del = result.delegation;
103
+ return c.json({
104
+ success: true,
105
+ delegation_id: del.delegation_id,
106
+ grantor_id: del.grantor_id,
107
+ grantee_id: del.grantee_id,
108
+ app_id: del.app_id,
109
+ capabilities: del.capabilities,
110
+ chain: del.chain,
111
+ depth: del.depth,
112
+ max_depth: del.max_depth,
113
+ parent_delegation_id: del.parent_delegation_id || null,
114
+ created_at: new Date(del.created_at).toISOString(),
115
+ expires_at: new Date(del.expires_at).toISOString(),
116
+ metadata: del.metadata || null,
117
+ }, 201);
118
+ }
119
+ catch (error) {
120
+ console.error('Delegation creation error:', error);
121
+ return c.json({
122
+ success: false,
123
+ error: 'INTERNAL_ERROR',
124
+ message: 'Internal server error'
125
+ }, 500);
126
+ }
127
+ }
128
+ /**
129
+ * GET /v1/delegations/:id
130
+ * Get delegation details
131
+ */
132
+ export async function getDelegationRoute(c) {
133
+ try {
134
+ const delegationId = c.req.param('id');
135
+ if (!delegationId) {
136
+ return c.json({
137
+ success: false,
138
+ error: 'MISSING_DELEGATION_ID',
139
+ message: 'Delegation ID is required'
140
+ }, 400);
141
+ }
142
+ const result = await getDelegation(c.env.SESSIONS, delegationId);
143
+ if (!result.success || !result.delegation) {
144
+ return c.json({
145
+ success: false,
146
+ error: 'DELEGATION_NOT_FOUND',
147
+ message: result.error || 'Delegation not found or expired'
148
+ }, 404);
149
+ }
150
+ const del = result.delegation;
151
+ return c.json({
152
+ success: true,
153
+ delegation_id: del.delegation_id,
154
+ grantor_id: del.grantor_id,
155
+ grantee_id: del.grantee_id,
156
+ app_id: del.app_id,
157
+ capabilities: del.capabilities,
158
+ chain: del.chain,
159
+ depth: del.depth,
160
+ max_depth: del.max_depth,
161
+ parent_delegation_id: del.parent_delegation_id || null,
162
+ created_at: new Date(del.created_at).toISOString(),
163
+ expires_at: new Date(del.expires_at).toISOString(),
164
+ revoked: del.revoked,
165
+ revoked_at: del.revoked_at ? new Date(del.revoked_at).toISOString() : null,
166
+ revocation_reason: del.revocation_reason || null,
167
+ metadata: del.metadata || null,
168
+ time_remaining: Math.max(0, del.expires_at - Date.now()),
169
+ });
170
+ }
171
+ catch (error) {
172
+ console.error('Delegation retrieval error:', error);
173
+ return c.json({
174
+ success: false,
175
+ error: 'INTERNAL_ERROR',
176
+ message: 'Internal server error'
177
+ }, 500);
178
+ }
179
+ }
180
+ /**
181
+ * GET /v1/delegations
182
+ * List delegations for an agent
183
+ *
184
+ * Query params:
185
+ * agent_id — required, the agent to list delegations for
186
+ * direction — 'in', 'out', or 'both' (default: 'both')
187
+ * include_revoked — 'true' to include revoked delegations
188
+ * include_expired — 'true' to include expired delegations
189
+ */
190
+ export async function listDelegationsRoute(c) {
191
+ try {
192
+ const appAccess = await validateAppAccess(c, true);
193
+ if (!appAccess.valid) {
194
+ return c.json({
195
+ success: false,
196
+ error: appAccess.error,
197
+ message: 'Authentication required'
198
+ }, (appAccess.status || 401));
199
+ }
200
+ const agentId = c.req.query('agent_id');
201
+ if (!agentId) {
202
+ return c.json({
203
+ success: false,
204
+ error: 'MISSING_AGENT_ID',
205
+ message: 'agent_id query parameter is required'
206
+ }, 400);
207
+ }
208
+ const direction = (c.req.query('direction') || 'both');
209
+ const includeRevoked = c.req.query('include_revoked') === 'true';
210
+ const includeExpired = c.req.query('include_expired') === 'true';
211
+ const result = await listDelegations(c.env.SESSIONS, {
212
+ agent_id: agentId,
213
+ app_id: appAccess.appId,
214
+ direction,
215
+ include_revoked: includeRevoked,
216
+ include_expired: includeExpired,
217
+ });
218
+ if (!result.success) {
219
+ return c.json({
220
+ success: false,
221
+ error: 'LIST_FAILED',
222
+ message: result.error || 'Failed to list delegations'
223
+ }, 500);
224
+ }
225
+ const delegations = result.delegations.map(del => ({
226
+ delegation_id: del.delegation_id,
227
+ grantor_id: del.grantor_id,
228
+ grantee_id: del.grantee_id,
229
+ capabilities: del.capabilities,
230
+ chain: del.chain,
231
+ depth: del.depth,
232
+ created_at: new Date(del.created_at).toISOString(),
233
+ expires_at: new Date(del.expires_at).toISOString(),
234
+ revoked: del.revoked,
235
+ parent_delegation_id: del.parent_delegation_id || null,
236
+ }));
237
+ return c.json({
238
+ success: true,
239
+ delegations,
240
+ count: delegations.length,
241
+ agent_id: agentId,
242
+ direction,
243
+ });
244
+ }
245
+ catch (error) {
246
+ console.error('Delegation listing error:', error);
247
+ return c.json({
248
+ success: false,
249
+ error: 'INTERNAL_ERROR',
250
+ message: 'Internal server error'
251
+ }, 500);
252
+ }
253
+ }
254
+ /**
255
+ * POST /v1/delegations/:id/revoke
256
+ * Revoke a delegation (cascades to sub-delegations)
257
+ */
258
+ export async function revokeDelegationRoute(c) {
259
+ try {
260
+ const delegationId = c.req.param('id');
261
+ if (!delegationId) {
262
+ return c.json({
263
+ success: false,
264
+ error: 'MISSING_DELEGATION_ID',
265
+ message: 'Delegation ID is required'
266
+ }, 400);
267
+ }
268
+ const appAccess = await validateAppAccess(c, true);
269
+ if (!appAccess.valid) {
270
+ return c.json({
271
+ success: false,
272
+ error: appAccess.error,
273
+ message: 'Authentication required'
274
+ }, (appAccess.status || 401));
275
+ }
276
+ // Verify delegation exists and belongs to this app
277
+ const existing = await getDelegation(c.env.SESSIONS, delegationId);
278
+ if (!existing.success || !existing.delegation) {
279
+ return c.json({
280
+ success: false,
281
+ error: 'DELEGATION_NOT_FOUND',
282
+ message: 'Delegation not found or expired'
283
+ }, 404);
284
+ }
285
+ if (existing.delegation.app_id !== appAccess.appId) {
286
+ return c.json({
287
+ success: false,
288
+ error: 'UNAUTHORIZED',
289
+ message: 'Delegation does not belong to this app'
290
+ }, 403);
291
+ }
292
+ const body = await c.req.json().catch(() => ({}));
293
+ const reason = body.reason || undefined;
294
+ const result = await revokeDelegation(c.env.SESSIONS, delegationId, reason);
295
+ if (!result.success) {
296
+ return c.json({
297
+ success: false,
298
+ error: 'REVOCATION_FAILED',
299
+ message: result.error
300
+ }, 500);
301
+ }
302
+ const del = result.delegation;
303
+ return c.json({
304
+ success: true,
305
+ delegation_id: del.delegation_id,
306
+ revoked: true,
307
+ revoked_at: del.revoked_at ? new Date(del.revoked_at).toISOString() : null,
308
+ revocation_reason: del.revocation_reason || null,
309
+ message: 'Delegation revoked. Sub-delegations have been cascaded.',
310
+ });
311
+ }
312
+ catch (error) {
313
+ console.error('Delegation revocation error:', error);
314
+ return c.json({
315
+ success: false,
316
+ error: 'INTERNAL_ERROR',
317
+ message: 'Internal server error'
318
+ }, 500);
319
+ }
320
+ }
321
+ /**
322
+ * POST /v1/verify/delegation
323
+ * Verify an entire delegation chain is valid
324
+ *
325
+ * Body: { delegation_id: string }
326
+ *
327
+ * Returns the full chain and effective capabilities if valid.
328
+ */
329
+ export async function verifyDelegationRoute(c) {
330
+ try {
331
+ const body = await c.req.json().catch(() => ({}));
332
+ if (!body.delegation_id) {
333
+ return c.json({
334
+ success: false,
335
+ error: 'MISSING_DELEGATION_ID',
336
+ message: 'delegation_id is required'
337
+ }, 400);
338
+ }
339
+ const result = await verifyDelegationChain(c.env.AGENTS, c.env.SESSIONS, body.delegation_id);
340
+ if (!result.valid) {
341
+ return c.json({
342
+ success: false,
343
+ valid: false,
344
+ error: result.error,
345
+ }, 400);
346
+ }
347
+ return c.json({
348
+ success: true,
349
+ valid: true,
350
+ chain_length: result.chain.length,
351
+ chain: result.chain.map(del => ({
352
+ delegation_id: del.delegation_id,
353
+ grantor_id: del.grantor_id,
354
+ grantee_id: del.grantee_id,
355
+ capabilities: del.capabilities,
356
+ depth: del.depth,
357
+ created_at: new Date(del.created_at).toISOString(),
358
+ expires_at: new Date(del.expires_at).toISOString(),
359
+ })),
360
+ effective_capabilities: result.effective_capabilities,
361
+ });
362
+ }
363
+ catch (error) {
364
+ console.error('Delegation verification error:', error);
365
+ return c.json({
366
+ success: false,
367
+ error: 'INTERNAL_ERROR',
368
+ message: 'Internal server error'
369
+ }, 500);
370
+ }
371
+ }
372
+ export default {
373
+ createDelegationRoute,
374
+ getDelegationRoute,
375
+ listDelegationsRoute,
376
+ revokeDelegationRoute,
377
+ verifyDelegationRoute,
378
+ };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * TAP Delegation Chains
3
+ *
4
+ * "User X authorized Agent Y to do Z until time T."
5
+ *
6
+ * Signed, auditable chains of trust between TAP agents. A delegation grants
7
+ * a subset of one agent's capabilities to another agent, with time bounds
8
+ * and depth limits. Delegations can be chained (A→B→C) with each link
9
+ * only narrowing capabilities, never expanding them.
10
+ *
11
+ * Key invariants:
12
+ * - Capabilities can only be narrowed (subset enforcement)
13
+ * - Chain depth is capped (default max: 3)
14
+ * - Revoking a delegation cascades to all sub-delegations
15
+ * - Expired delegations are automatically invalid
16
+ * - Both grantor and grantee must belong to the same app
17
+ */
18
+ import type { KVNamespace } from './agents.js';
19
+ import { TAPCapability } from './tap-agents.js';
20
+ export interface Delegation {
21
+ delegation_id: string;
22
+ grantor_id: string;
23
+ grantee_id: string;
24
+ app_id: string;
25
+ capabilities: TAPCapability[];
26
+ parent_delegation_id?: string;
27
+ chain: string[];
28
+ depth: number;
29
+ max_depth: number;
30
+ created_at: number;
31
+ expires_at: number;
32
+ revoked: boolean;
33
+ revoked_at?: number;
34
+ revocation_reason?: string;
35
+ metadata?: Record<string, string>;
36
+ }
37
+ export interface CreateDelegationOptions {
38
+ grantor_id: string;
39
+ grantee_id: string;
40
+ capabilities: TAPCapability[];
41
+ duration_seconds?: number;
42
+ max_depth?: number;
43
+ parent_delegation_id?: string;
44
+ metadata?: Record<string, string>;
45
+ }
46
+ export interface DelegationResult {
47
+ success: boolean;
48
+ delegation?: Delegation;
49
+ error?: string;
50
+ }
51
+ export interface DelegationListResult {
52
+ success: boolean;
53
+ delegations?: Delegation[];
54
+ error?: string;
55
+ }
56
+ export interface DelegationVerifyResult {
57
+ valid: boolean;
58
+ chain?: Delegation[];
59
+ effective_capabilities?: TAPCapability[];
60
+ error?: string;
61
+ }
62
+ /**
63
+ * Check if capabilitiesB is a subset of capabilitiesA.
64
+ *
65
+ * A capability in B is valid if there exists a capability in A with the same
66
+ * action, and B's scope is a subset of A's scope (or A has wildcard scope).
67
+ * B's restrictions must be equal or stricter.
68
+ */
69
+ export declare function isCapabilitySubset(parent: TAPCapability[], child: TAPCapability[]): {
70
+ valid: boolean;
71
+ error?: string;
72
+ };
73
+ /**
74
+ * Create a delegation from one agent to another.
75
+ *
76
+ * Validates:
77
+ * - Both agents exist and belong to the same app
78
+ * - Grantor has the capabilities being delegated
79
+ * - Capabilities are a valid subset (never expanded)
80
+ * - Chain depth is within limits
81
+ * - Parent delegation (if sub-delegation) is valid and not revoked/expired
82
+ */
83
+ export declare function createDelegation(agents: KVNamespace, sessions: KVNamespace, appId: string, options: CreateDelegationOptions): Promise<DelegationResult>;
84
+ /**
85
+ * Get a delegation by ID
86
+ */
87
+ export declare function getDelegation(sessions: KVNamespace, delegationId: string): Promise<DelegationResult>;
88
+ /**
89
+ * List delegations for an agent (inbound, outbound, or both)
90
+ */
91
+ export declare function listDelegations(sessions: KVNamespace, options: {
92
+ agent_id?: string;
93
+ app_id?: string;
94
+ direction?: 'in' | 'out' | 'both';
95
+ include_revoked?: boolean;
96
+ include_expired?: boolean;
97
+ }): Promise<DelegationListResult>;
98
+ /**
99
+ * Revoke a delegation and cascade to all sub-delegations.
100
+ *
101
+ * When a delegation is revoked, all delegations that have it as a parent
102
+ * (directly or transitively) are also revoked. This is enforced by marking
103
+ * each delegation record as revoked.
104
+ */
105
+ export declare function revokeDelegation(sessions: KVNamespace, delegationId: string, reason?: string): Promise<DelegationResult>;
106
+ /**
107
+ * Verify an entire delegation chain is valid.
108
+ *
109
+ * Walks from the leaf delegation up through parent delegations to the root,
110
+ * verifying each link is:
111
+ * - Not revoked
112
+ * - Not expired
113
+ * - Capabilities are valid subsets
114
+ *
115
+ * Returns the full chain and effective (intersected) capabilities.
116
+ */
117
+ export declare function verifyDelegationChain(agents: KVNamespace, sessions: KVNamespace, delegationId: string): Promise<DelegationVerifyResult>;
118
+ declare const _default: {
119
+ createDelegation: typeof createDelegation;
120
+ getDelegation: typeof getDelegation;
121
+ listDelegations: typeof listDelegations;
122
+ revokeDelegation: typeof revokeDelegation;
123
+ verifyDelegationChain: typeof verifyDelegationChain;
124
+ isCapabilitySubset: typeof isCapabilitySubset;
125
+ };
126
+ export default _default;
127
+ //# sourceMappingURL=tap-delegation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tap-delegation.d.ts","sourceRoot":"","sources":["../src/tap-delegation.ts"],"names":[],"mappings":"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"}