@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.
- package/dist/dashboard/layout.d.ts +12 -0
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +11 -4
- package/dist/dashboard/showcase.d.ts.map +1 -1
- package/dist/dashboard/showcase.js +2 -1
- package/dist/dashboard/whitepaper.d.ts.map +1 -1
- package/dist/dashboard/whitepaper.js +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -1
- package/dist/static.d.ts +541 -1
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +353 -3
- package/dist/tap-attestation-routes.d.ts +204 -0
- package/dist/tap-attestation-routes.d.ts.map +1 -0
- package/dist/tap-attestation-routes.js +396 -0
- package/dist/tap-attestation.d.ts +178 -0
- package/dist/tap-attestation.d.ts.map +1 -0
- package/dist/tap-attestation.js +416 -0
- package/dist/tap-delegation-routes.d.ts +236 -0
- package/dist/tap-delegation-routes.d.ts.map +1 -0
- package/dist/tap-delegation-routes.js +378 -0
- package/dist/tap-delegation.d.ts +127 -0
- package/dist/tap-delegation.d.ts.map +1 -0
- package/dist/tap-delegation.js +490 -0
- package/dist/tap-reputation-routes.d.ts +154 -0
- package/dist/tap-reputation-routes.d.ts.map +1 -0
- package/dist/tap-reputation-routes.js +341 -0
- package/dist/tap-reputation.d.ts +136 -0
- package/dist/tap-reputation.d.ts.map +1 -0
- package/dist/tap-reputation.js +346 -0
- package/package.json +1 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Capability Attestation
|
|
3
|
+
*
|
|
4
|
+
* Signed JWT tokens that cryptographically bind:
|
|
5
|
+
* WHO (agent_id) can do WHAT (can/cannot rules) on WHICH resources,
|
|
6
|
+
* attested by WHOM (app authority), until WHEN (expiration).
|
|
7
|
+
*
|
|
8
|
+
* Permission model: "action:resource" patterns with explicit deny.
|
|
9
|
+
* - Allow: { can: ["read:invoices", "write:orders", "browse:*"] }
|
|
10
|
+
* - Deny: { cannot: ["write:transfers", "purchase:*"] }
|
|
11
|
+
* - Deny takes precedence over allow
|
|
12
|
+
* - Wildcards: "*:*" (all), "read:*" (read anything), "*:invoices" (any action on invoices)
|
|
13
|
+
* - Backward compatible: bare actions like "browse" expand to "browse:*"
|
|
14
|
+
*
|
|
15
|
+
* Attestation tokens are signed JWTs (HS256) with type 'botcha-attestation'.
|
|
16
|
+
* They can be verified offline (signature check) or online (revocation check via KV).
|
|
17
|
+
*/
|
|
18
|
+
import { SignJWT, jwtVerify } from 'jose';
|
|
19
|
+
import { getTAPAgent } from './tap-agents.js';
|
|
20
|
+
// ============ CONSTANTS ============
|
|
21
|
+
const DEFAULT_DURATION = 3600; // 1 hour
|
|
22
|
+
const MAX_DURATION = 86400 * 30; // 30 days
|
|
23
|
+
const MAX_RULES = 100; // max can + cannot entries
|
|
24
|
+
// ============ PERMISSION MATCHING ============
|
|
25
|
+
/**
|
|
26
|
+
* Normalize a capability string to "action:resource" format.
|
|
27
|
+
* Bare actions like "browse" expand to "browse:*".
|
|
28
|
+
*/
|
|
29
|
+
export function normalizeCapability(cap) {
|
|
30
|
+
if (cap.includes(':'))
|
|
31
|
+
return cap;
|
|
32
|
+
return `${cap}:*`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if a pattern matches a target.
|
|
36
|
+
* Supports wildcards: "*:*", "read:*", "*:invoices", "read:invoices"
|
|
37
|
+
*/
|
|
38
|
+
export function matchesPattern(pattern, target) {
|
|
39
|
+
const normalizedPattern = normalizeCapability(pattern);
|
|
40
|
+
const normalizedTarget = normalizeCapability(target);
|
|
41
|
+
const [patAction, patResource] = normalizedPattern.split(':', 2);
|
|
42
|
+
const [tgtAction, tgtResource] = normalizedTarget.split(':', 2);
|
|
43
|
+
const actionMatch = patAction === '*' || patAction === tgtAction;
|
|
44
|
+
const resourceMatch = patResource === '*' || patResource === tgtResource;
|
|
45
|
+
return actionMatch && resourceMatch;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if a specific action:resource is allowed by the can/cannot rules.
|
|
49
|
+
*
|
|
50
|
+
* Rules:
|
|
51
|
+
* 1. Check "cannot" list first — any match means DENIED (deny takes precedence)
|
|
52
|
+
* 2. Check "can" list — any match means ALLOWED
|
|
53
|
+
* 3. If no match in either list — DENIED (default deny)
|
|
54
|
+
*/
|
|
55
|
+
export function checkCapability(can, cannot, action, resource) {
|
|
56
|
+
const target = resource ? `${action}:${resource}` : normalizeCapability(action);
|
|
57
|
+
// Check deny rules first (deny takes precedence)
|
|
58
|
+
for (const rule of cannot) {
|
|
59
|
+
if (matchesPattern(rule, target)) {
|
|
60
|
+
return {
|
|
61
|
+
allowed: false,
|
|
62
|
+
reason: `Explicitly denied by rule: ${rule}`,
|
|
63
|
+
matched_rule: rule,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Check allow rules
|
|
68
|
+
for (const rule of can) {
|
|
69
|
+
if (matchesPattern(rule, target)) {
|
|
70
|
+
return {
|
|
71
|
+
allowed: true,
|
|
72
|
+
reason: `Allowed by rule: ${rule}`,
|
|
73
|
+
matched_rule: rule,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Default deny
|
|
78
|
+
return {
|
|
79
|
+
allowed: false,
|
|
80
|
+
reason: `No matching allow rule for: ${target}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Validate capability pattern syntax.
|
|
85
|
+
* Valid: "action:resource", "action", "*:*", "read:*", "*:invoices"
|
|
86
|
+
*/
|
|
87
|
+
export function isValidCapabilityPattern(pattern) {
|
|
88
|
+
const normalized = normalizeCapability(pattern);
|
|
89
|
+
const parts = normalized.split(':');
|
|
90
|
+
if (parts.length !== 2)
|
|
91
|
+
return false;
|
|
92
|
+
const [action, resource] = parts;
|
|
93
|
+
// Action and resource must be non-empty, alphanumeric + underscore + hyphen + wildcard
|
|
94
|
+
const validPart = /^[a-zA-Z0-9_\-*]+$/;
|
|
95
|
+
return validPart.test(action) && validPart.test(resource);
|
|
96
|
+
}
|
|
97
|
+
// ============ ATTESTATION ISSUANCE ============
|
|
98
|
+
/**
|
|
99
|
+
* Issue a capability attestation token for an agent.
|
|
100
|
+
*
|
|
101
|
+
* Validates:
|
|
102
|
+
* - Agent exists and belongs to the app
|
|
103
|
+
* - All capability patterns are syntactically valid
|
|
104
|
+
* - Total rules don't exceed limit
|
|
105
|
+
*
|
|
106
|
+
* Signs a JWT with the attestation payload.
|
|
107
|
+
*/
|
|
108
|
+
export async function issueAttestation(agents, sessions, appId, secret, options) {
|
|
109
|
+
try {
|
|
110
|
+
// Validate inputs
|
|
111
|
+
if (!options.agent_id) {
|
|
112
|
+
return { success: false, error: 'agent_id is required' };
|
|
113
|
+
}
|
|
114
|
+
if (!options.can || options.can.length === 0) {
|
|
115
|
+
return { success: false, error: 'At least one "can" rule is required' };
|
|
116
|
+
}
|
|
117
|
+
const cannot = options.cannot || [];
|
|
118
|
+
const totalRules = options.can.length + cannot.length;
|
|
119
|
+
if (totalRules > MAX_RULES) {
|
|
120
|
+
return { success: false, error: `Too many rules (${totalRules}). Maximum: ${MAX_RULES}` };
|
|
121
|
+
}
|
|
122
|
+
// Validate all patterns
|
|
123
|
+
for (const rule of options.can) {
|
|
124
|
+
if (!isValidCapabilityPattern(rule)) {
|
|
125
|
+
return { success: false, error: `Invalid capability pattern in "can": ${rule}` };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const rule of cannot) {
|
|
129
|
+
if (!isValidCapabilityPattern(rule)) {
|
|
130
|
+
return { success: false, error: `Invalid capability pattern in "cannot": ${rule}` };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Verify agent exists and belongs to this app
|
|
134
|
+
const agentResult = await getTAPAgent(agents, options.agent_id);
|
|
135
|
+
if (!agentResult.success || !agentResult.agent) {
|
|
136
|
+
return { success: false, error: 'Agent not found' };
|
|
137
|
+
}
|
|
138
|
+
if (agentResult.agent.app_id !== appId) {
|
|
139
|
+
return { success: false, error: 'Agent does not belong to this app' };
|
|
140
|
+
}
|
|
141
|
+
// Calculate expiration
|
|
142
|
+
const durationSeconds = Math.min(options.duration_seconds ?? DEFAULT_DURATION, MAX_DURATION);
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
const expiresAt = now + durationSeconds * 1000;
|
|
145
|
+
// Generate attestation ID
|
|
146
|
+
const attestationId = crypto.randomUUID();
|
|
147
|
+
// Sign the attestation JWT
|
|
148
|
+
const encoder = new TextEncoder();
|
|
149
|
+
const secretKey = encoder.encode(secret);
|
|
150
|
+
const payload = {
|
|
151
|
+
type: 'botcha-attestation',
|
|
152
|
+
can: options.can,
|
|
153
|
+
cannot,
|
|
154
|
+
jti: attestationId,
|
|
155
|
+
};
|
|
156
|
+
if (options.restrictions) {
|
|
157
|
+
payload.restrictions = options.restrictions;
|
|
158
|
+
}
|
|
159
|
+
if (options.delegation_id) {
|
|
160
|
+
payload.delegation_id = options.delegation_id;
|
|
161
|
+
}
|
|
162
|
+
if (options.metadata) {
|
|
163
|
+
payload.metadata = options.metadata;
|
|
164
|
+
}
|
|
165
|
+
const token = await new SignJWT(payload)
|
|
166
|
+
.setProtectedHeader({ alg: 'HS256' })
|
|
167
|
+
.setSubject(options.agent_id)
|
|
168
|
+
.setIssuer(appId)
|
|
169
|
+
.setIssuedAt()
|
|
170
|
+
.setExpirationTime(Math.floor(expiresAt / 1000))
|
|
171
|
+
.sign(secretKey);
|
|
172
|
+
// Build attestation record
|
|
173
|
+
const attestation = {
|
|
174
|
+
attestation_id: attestationId,
|
|
175
|
+
agent_id: options.agent_id,
|
|
176
|
+
app_id: appId,
|
|
177
|
+
can: options.can,
|
|
178
|
+
cannot,
|
|
179
|
+
restrictions: options.restrictions,
|
|
180
|
+
delegation_id: options.delegation_id,
|
|
181
|
+
metadata: options.metadata,
|
|
182
|
+
token,
|
|
183
|
+
created_at: now,
|
|
184
|
+
expires_at: expiresAt,
|
|
185
|
+
revoked: false,
|
|
186
|
+
};
|
|
187
|
+
// Store attestation in KV (for revocation and lookup)
|
|
188
|
+
const ttlSeconds = Math.max(1, Math.floor(durationSeconds));
|
|
189
|
+
await sessions.put(`attestation:${attestationId}`, JSON.stringify(attestation), { expirationTtl: ttlSeconds });
|
|
190
|
+
// Update agent's attestation index
|
|
191
|
+
await updateAttestationIndex(sessions, options.agent_id, attestationId, 'add');
|
|
192
|
+
return { success: true, attestation, token };
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
console.error('Failed to issue attestation:', error);
|
|
196
|
+
return { success: false, error: 'Internal server error' };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get an attestation by ID (from KV, not from JWT)
|
|
201
|
+
*/
|
|
202
|
+
export async function getAttestation(sessions, attestationId) {
|
|
203
|
+
try {
|
|
204
|
+
const data = await sessions.get(`attestation:${attestationId}`, 'text');
|
|
205
|
+
if (!data) {
|
|
206
|
+
return { success: false, error: 'Attestation not found or expired' };
|
|
207
|
+
}
|
|
208
|
+
const attestation = JSON.parse(data);
|
|
209
|
+
return { success: true, attestation };
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.error('Failed to get attestation:', error);
|
|
213
|
+
return { success: false, error: 'Internal server error' };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Revoke an attestation.
|
|
218
|
+
*/
|
|
219
|
+
export async function revokeAttestation(sessions, attestationId, reason) {
|
|
220
|
+
try {
|
|
221
|
+
const result = await getAttestation(sessions, attestationId);
|
|
222
|
+
if (!result.success || !result.attestation) {
|
|
223
|
+
return { success: false, error: 'Attestation not found' };
|
|
224
|
+
}
|
|
225
|
+
const attestation = result.attestation;
|
|
226
|
+
if (attestation.revoked) {
|
|
227
|
+
return { success: true, attestation }; // idempotent
|
|
228
|
+
}
|
|
229
|
+
attestation.revoked = true;
|
|
230
|
+
attestation.revoked_at = Date.now();
|
|
231
|
+
attestation.revocation_reason = reason;
|
|
232
|
+
// Re-store with remaining TTL
|
|
233
|
+
const remainingTtl = Math.max(60, Math.floor((attestation.expires_at - Date.now()) / 1000));
|
|
234
|
+
await sessions.put(`attestation:${attestationId}`, JSON.stringify(attestation), { expirationTtl: remainingTtl });
|
|
235
|
+
// Also store in revocation list (for fast JWT verification without full record lookup)
|
|
236
|
+
await sessions.put(`attestation_revoked:${attestationId}`, JSON.stringify({ revokedAt: attestation.revoked_at, reason }), { expirationTtl: remainingTtl });
|
|
237
|
+
return { success: true, attestation };
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error('Failed to revoke attestation:', error);
|
|
241
|
+
return { success: false, error: 'Internal server error' };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Verify an attestation JWT token.
|
|
246
|
+
*
|
|
247
|
+
* Checks:
|
|
248
|
+
* 1. JWT signature and expiration (cryptographic)
|
|
249
|
+
* 2. Token type is 'botcha-attestation'
|
|
250
|
+
* 3. Revocation status (via KV, fail-open)
|
|
251
|
+
*
|
|
252
|
+
* Returns the parsed attestation payload if valid.
|
|
253
|
+
*/
|
|
254
|
+
export async function verifyAttestationToken(sessions, token, secret) {
|
|
255
|
+
try {
|
|
256
|
+
const encoder = new TextEncoder();
|
|
257
|
+
const secretKey = encoder.encode(secret);
|
|
258
|
+
const { payload } = await jwtVerify(token, secretKey, {
|
|
259
|
+
algorithms: ['HS256'],
|
|
260
|
+
});
|
|
261
|
+
// Check token type
|
|
262
|
+
if (payload.type !== 'botcha-attestation') {
|
|
263
|
+
return { valid: false, error: 'Invalid token type. Expected attestation token.' };
|
|
264
|
+
}
|
|
265
|
+
const jti = payload.jti;
|
|
266
|
+
// Check revocation (fail-open)
|
|
267
|
+
if (jti) {
|
|
268
|
+
try {
|
|
269
|
+
const revoked = await sessions.get(`attestation_revoked:${jti}`);
|
|
270
|
+
if (revoked) {
|
|
271
|
+
return { valid: false, error: 'Attestation has been revoked' };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
console.error('Failed to check attestation revocation:', error);
|
|
276
|
+
// Fail-open
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Build typed payload
|
|
280
|
+
const attestationPayload = {
|
|
281
|
+
sub: payload.sub || '',
|
|
282
|
+
iss: payload.iss || '',
|
|
283
|
+
type: 'botcha-attestation',
|
|
284
|
+
jti: jti || '',
|
|
285
|
+
iat: payload.iat || 0,
|
|
286
|
+
exp: payload.exp || 0,
|
|
287
|
+
can: payload.can || [],
|
|
288
|
+
cannot: payload.cannot || [],
|
|
289
|
+
restrictions: payload.restrictions,
|
|
290
|
+
delegation_id: payload.delegation_id,
|
|
291
|
+
metadata: payload.metadata,
|
|
292
|
+
};
|
|
293
|
+
return { valid: true, payload: attestationPayload };
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
return {
|
|
297
|
+
valid: false,
|
|
298
|
+
error: error instanceof Error ? error.message : 'Invalid attestation token',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Full capability check: verify attestation token + check specific action:resource.
|
|
304
|
+
*
|
|
305
|
+
* Combines token verification with permission checking in one call.
|
|
306
|
+
*/
|
|
307
|
+
export async function verifyAndCheckCapability(sessions, token, secret, action, resource) {
|
|
308
|
+
// First verify the token
|
|
309
|
+
const verification = await verifyAttestationToken(sessions, token, secret);
|
|
310
|
+
if (!verification.valid || !verification.payload) {
|
|
311
|
+
return {
|
|
312
|
+
allowed: false,
|
|
313
|
+
error: verification.error || 'Invalid attestation token',
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const payload = verification.payload;
|
|
317
|
+
// Check restrictions if applicable
|
|
318
|
+
if (payload.restrictions?.rate_limit !== undefined) {
|
|
319
|
+
// Rate limit would need a counter — for now, we just pass the restriction through
|
|
320
|
+
// Future: implement per-attestation rate limit counters in KV
|
|
321
|
+
}
|
|
322
|
+
// Check capability
|
|
323
|
+
const check = checkCapability(payload.can, payload.cannot, action, resource);
|
|
324
|
+
return {
|
|
325
|
+
allowed: check.allowed,
|
|
326
|
+
agent_id: payload.sub,
|
|
327
|
+
reason: check.reason,
|
|
328
|
+
matched_rule: check.matched_rule,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
// ============ ENFORCEMENT MIDDLEWARE ============
|
|
332
|
+
/**
|
|
333
|
+
* Create a Hono middleware that enforces capability attestation.
|
|
334
|
+
*
|
|
335
|
+
* Usage:
|
|
336
|
+
* app.get('/api/invoices', requireCapability('read:invoices'), handler);
|
|
337
|
+
* app.post('/api/transfers', requireCapability('write:transfers'), handler);
|
|
338
|
+
*
|
|
339
|
+
* Extracts attestation token from:
|
|
340
|
+
* 1. X-Botcha-Attestation header
|
|
341
|
+
* 2. Authorization: Bearer header (if token type is attestation)
|
|
342
|
+
*
|
|
343
|
+
* On failure: returns 403 with capability denial details.
|
|
344
|
+
* On missing token: returns 401 requesting attestation.
|
|
345
|
+
*/
|
|
346
|
+
export function requireCapability(capability) {
|
|
347
|
+
return async (c, next) => {
|
|
348
|
+
// Extract attestation token
|
|
349
|
+
const attestationHeader = c.req.header('x-botcha-attestation');
|
|
350
|
+
const authHeader = c.req.header('authorization');
|
|
351
|
+
const token = attestationHeader || extractBearer(authHeader);
|
|
352
|
+
if (!token) {
|
|
353
|
+
return c.json({
|
|
354
|
+
success: false,
|
|
355
|
+
error: 'ATTESTATION_REQUIRED',
|
|
356
|
+
message: 'Capability attestation token required',
|
|
357
|
+
required_capability: capability,
|
|
358
|
+
hint: 'Include X-Botcha-Attestation header or Authorization: Bearer with attestation token',
|
|
359
|
+
}, 401);
|
|
360
|
+
}
|
|
361
|
+
// Verify and check
|
|
362
|
+
const [action, resource] = normalizeCapability(capability).split(':', 2);
|
|
363
|
+
const result = await verifyAndCheckCapability(c.env.SESSIONS, token, c.env.JWT_SECRET, action, resource === '*' ? undefined : resource);
|
|
364
|
+
if (!result.allowed) {
|
|
365
|
+
return c.json({
|
|
366
|
+
success: false,
|
|
367
|
+
error: 'CAPABILITY_DENIED',
|
|
368
|
+
message: result.reason || result.error || 'Capability check failed',
|
|
369
|
+
required_capability: capability,
|
|
370
|
+
agent_id: result.agent_id,
|
|
371
|
+
matched_rule: result.matched_rule,
|
|
372
|
+
}, 403);
|
|
373
|
+
}
|
|
374
|
+
// Attach attestation info to context for downstream handlers
|
|
375
|
+
c.set('attestation_agent_id', result.agent_id);
|
|
376
|
+
c.set('attestation_capability', capability);
|
|
377
|
+
c.set('attestation_matched_rule', result.matched_rule);
|
|
378
|
+
await next();
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
// ============ UTILITY FUNCTIONS ============
|
|
382
|
+
function extractBearer(header) {
|
|
383
|
+
if (!header)
|
|
384
|
+
return null;
|
|
385
|
+
const match = header.match(/^Bearer\s+(.+)$/i);
|
|
386
|
+
return match ? match[1] : null;
|
|
387
|
+
}
|
|
388
|
+
async function updateAttestationIndex(sessions, agentId, attestationId, operation) {
|
|
389
|
+
try {
|
|
390
|
+
const key = `agent_attestations:${agentId}`;
|
|
391
|
+
const data = await sessions.get(key, 'text');
|
|
392
|
+
let ids = data ? JSON.parse(data) : [];
|
|
393
|
+
if (operation === 'add' && !ids.includes(attestationId)) {
|
|
394
|
+
ids.push(attestationId);
|
|
395
|
+
}
|
|
396
|
+
else if (operation === 'remove') {
|
|
397
|
+
ids = ids.filter(id => id !== attestationId);
|
|
398
|
+
}
|
|
399
|
+
await sessions.put(key, JSON.stringify(ids));
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
console.error('Failed to update attestation index:', error);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
export default {
|
|
406
|
+
issueAttestation,
|
|
407
|
+
getAttestation,
|
|
408
|
+
revokeAttestation,
|
|
409
|
+
verifyAttestationToken,
|
|
410
|
+
verifyAndCheckCapability,
|
|
411
|
+
checkCapability,
|
|
412
|
+
matchesPattern,
|
|
413
|
+
normalizeCapability,
|
|
414
|
+
isValidCapabilityPattern,
|
|
415
|
+
requireCapability,
|
|
416
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
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 type { Context } from 'hono';
|
|
15
|
+
/**
|
|
16
|
+
* POST /v1/delegations
|
|
17
|
+
* Create a delegation from one agent to another
|
|
18
|
+
*/
|
|
19
|
+
export declare function createDelegationRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
20
|
+
success: false;
|
|
21
|
+
error: string | undefined;
|
|
22
|
+
message: string;
|
|
23
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
24
|
+
success: false;
|
|
25
|
+
error: string;
|
|
26
|
+
message: string | undefined;
|
|
27
|
+
}, any, "json">) | (Response & import("hono").TypedResponse<{
|
|
28
|
+
success: true;
|
|
29
|
+
delegation_id: string;
|
|
30
|
+
grantor_id: string;
|
|
31
|
+
grantee_id: string;
|
|
32
|
+
app_id: string;
|
|
33
|
+
capabilities: {
|
|
34
|
+
action: import("./tap-agents.js").TAPAction;
|
|
35
|
+
scope?: string[] | undefined;
|
|
36
|
+
restrictions?: {
|
|
37
|
+
[x: string]: any;
|
|
38
|
+
max_amount?: number | undefined;
|
|
39
|
+
rate_limit?: number | undefined;
|
|
40
|
+
} | undefined;
|
|
41
|
+
}[];
|
|
42
|
+
chain: string[];
|
|
43
|
+
depth: number;
|
|
44
|
+
max_depth: number;
|
|
45
|
+
parent_delegation_id: string | null;
|
|
46
|
+
created_at: string;
|
|
47
|
+
expires_at: string;
|
|
48
|
+
metadata: {
|
|
49
|
+
[x: string]: string;
|
|
50
|
+
} | null;
|
|
51
|
+
}, 201, "json">)>;
|
|
52
|
+
/**
|
|
53
|
+
* GET /v1/delegations/:id
|
|
54
|
+
* Get delegation details
|
|
55
|
+
*/
|
|
56
|
+
export declare function getDelegationRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
57
|
+
success: false;
|
|
58
|
+
error: string;
|
|
59
|
+
message: string;
|
|
60
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
61
|
+
success: false;
|
|
62
|
+
error: string;
|
|
63
|
+
message: string;
|
|
64
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
65
|
+
success: true;
|
|
66
|
+
delegation_id: string;
|
|
67
|
+
grantor_id: string;
|
|
68
|
+
grantee_id: string;
|
|
69
|
+
app_id: string;
|
|
70
|
+
capabilities: {
|
|
71
|
+
action: import("./tap-agents.js").TAPAction;
|
|
72
|
+
scope?: string[] | undefined;
|
|
73
|
+
restrictions?: {
|
|
74
|
+
[x: string]: any;
|
|
75
|
+
max_amount?: number | undefined;
|
|
76
|
+
rate_limit?: number | undefined;
|
|
77
|
+
} | undefined;
|
|
78
|
+
}[];
|
|
79
|
+
chain: string[];
|
|
80
|
+
depth: number;
|
|
81
|
+
max_depth: number;
|
|
82
|
+
parent_delegation_id: string | null;
|
|
83
|
+
created_at: string;
|
|
84
|
+
expires_at: string;
|
|
85
|
+
revoked: boolean;
|
|
86
|
+
revoked_at: string | null;
|
|
87
|
+
revocation_reason: string | null;
|
|
88
|
+
metadata: {
|
|
89
|
+
[x: string]: string;
|
|
90
|
+
} | null;
|
|
91
|
+
time_remaining: number;
|
|
92
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
93
|
+
success: false;
|
|
94
|
+
error: string;
|
|
95
|
+
message: string;
|
|
96
|
+
}, 500, "json">)>;
|
|
97
|
+
/**
|
|
98
|
+
* GET /v1/delegations
|
|
99
|
+
* List delegations for an agent
|
|
100
|
+
*
|
|
101
|
+
* Query params:
|
|
102
|
+
* agent_id — required, the agent to list delegations for
|
|
103
|
+
* direction — 'in', 'out', or 'both' (default: 'both')
|
|
104
|
+
* include_revoked — 'true' to include revoked delegations
|
|
105
|
+
* include_expired — 'true' to include expired delegations
|
|
106
|
+
*/
|
|
107
|
+
export declare function listDelegationsRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
108
|
+
success: false;
|
|
109
|
+
error: string | undefined;
|
|
110
|
+
message: string;
|
|
111
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
112
|
+
success: false;
|
|
113
|
+
error: string;
|
|
114
|
+
message: string;
|
|
115
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
116
|
+
success: false;
|
|
117
|
+
error: string;
|
|
118
|
+
message: string;
|
|
119
|
+
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
120
|
+
success: true;
|
|
121
|
+
delegations: {
|
|
122
|
+
delegation_id: string;
|
|
123
|
+
grantor_id: string;
|
|
124
|
+
grantee_id: string;
|
|
125
|
+
capabilities: {
|
|
126
|
+
action: import("./tap-agents.js").TAPAction;
|
|
127
|
+
scope?: string[] | undefined;
|
|
128
|
+
restrictions?: {
|
|
129
|
+
[x: string]: any;
|
|
130
|
+
max_amount?: number | undefined;
|
|
131
|
+
rate_limit?: number | undefined;
|
|
132
|
+
} | undefined;
|
|
133
|
+
}[];
|
|
134
|
+
chain: string[];
|
|
135
|
+
depth: number;
|
|
136
|
+
created_at: string;
|
|
137
|
+
expires_at: string;
|
|
138
|
+
revoked: boolean;
|
|
139
|
+
parent_delegation_id: string | null;
|
|
140
|
+
}[];
|
|
141
|
+
count: number;
|
|
142
|
+
agent_id: string;
|
|
143
|
+
direction: "out" | "in" | "both";
|
|
144
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
145
|
+
/**
|
|
146
|
+
* POST /v1/delegations/:id/revoke
|
|
147
|
+
* Revoke a delegation (cascades to sub-delegations)
|
|
148
|
+
*/
|
|
149
|
+
export declare function revokeDelegationRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
150
|
+
success: false;
|
|
151
|
+
error: string;
|
|
152
|
+
message: string;
|
|
153
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
154
|
+
success: false;
|
|
155
|
+
error: string | undefined;
|
|
156
|
+
message: string;
|
|
157
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
158
|
+
success: false;
|
|
159
|
+
error: string;
|
|
160
|
+
message: string;
|
|
161
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
162
|
+
success: false;
|
|
163
|
+
error: string;
|
|
164
|
+
message: string;
|
|
165
|
+
}, 403, "json">) | (Response & import("hono").TypedResponse<{
|
|
166
|
+
success: false;
|
|
167
|
+
error: string;
|
|
168
|
+
message: string | undefined;
|
|
169
|
+
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
170
|
+
success: true;
|
|
171
|
+
delegation_id: string;
|
|
172
|
+
revoked: true;
|
|
173
|
+
revoked_at: string | null;
|
|
174
|
+
revocation_reason: string | null;
|
|
175
|
+
message: string;
|
|
176
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
177
|
+
/**
|
|
178
|
+
* POST /v1/verify/delegation
|
|
179
|
+
* Verify an entire delegation chain is valid
|
|
180
|
+
*
|
|
181
|
+
* Body: { delegation_id: string }
|
|
182
|
+
*
|
|
183
|
+
* Returns the full chain and effective capabilities if valid.
|
|
184
|
+
*/
|
|
185
|
+
export declare function verifyDelegationRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
186
|
+
success: false;
|
|
187
|
+
error: string;
|
|
188
|
+
message: string;
|
|
189
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
190
|
+
success: false;
|
|
191
|
+
valid: false;
|
|
192
|
+
error: string | undefined;
|
|
193
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
194
|
+
success: true;
|
|
195
|
+
valid: true;
|
|
196
|
+
chain_length: number;
|
|
197
|
+
chain: {
|
|
198
|
+
delegation_id: string;
|
|
199
|
+
grantor_id: string;
|
|
200
|
+
grantee_id: string;
|
|
201
|
+
capabilities: {
|
|
202
|
+
action: import("./tap-agents.js").TAPAction;
|
|
203
|
+
scope?: string[] | undefined;
|
|
204
|
+
restrictions?: {
|
|
205
|
+
[x: string]: any;
|
|
206
|
+
max_amount?: number | undefined;
|
|
207
|
+
rate_limit?: number | undefined;
|
|
208
|
+
} | undefined;
|
|
209
|
+
}[];
|
|
210
|
+
depth: number;
|
|
211
|
+
created_at: string;
|
|
212
|
+
expires_at: string;
|
|
213
|
+
}[];
|
|
214
|
+
effective_capabilities: {
|
|
215
|
+
action: import("./tap-agents.js").TAPAction;
|
|
216
|
+
scope?: string[] | undefined;
|
|
217
|
+
restrictions?: {
|
|
218
|
+
[x: string]: any;
|
|
219
|
+
max_amount?: number | undefined;
|
|
220
|
+
rate_limit?: number | undefined;
|
|
221
|
+
} | undefined;
|
|
222
|
+
}[] | undefined;
|
|
223
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
224
|
+
success: false;
|
|
225
|
+
error: string;
|
|
226
|
+
message: string;
|
|
227
|
+
}, 500, "json">)>;
|
|
228
|
+
declare const _default: {
|
|
229
|
+
createDelegationRoute: typeof createDelegationRoute;
|
|
230
|
+
getDelegationRoute: typeof getDelegationRoute;
|
|
231
|
+
listDelegationsRoute: typeof listDelegationsRoute;
|
|
232
|
+
revokeDelegationRoute: typeof revokeDelegationRoute;
|
|
233
|
+
verifyDelegationRoute: typeof verifyDelegationRoute;
|
|
234
|
+
};
|
|
235
|
+
export default _default;
|
|
236
|
+
//# sourceMappingURL=tap-delegation-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-delegation-routes.d.ts","sourceRoot":"","sources":["../src/tap-delegation-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AA4CpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsGrD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAmDlD;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAqEpD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAsErD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkDrD;;;;;;;;AAED,wBAME"}
|