@dupecom/botcha-cloudflare 0.16.0 → 0.19.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/README.md +1 -1
- package/dist/auth.d.ts +48 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +89 -21
- package/dist/dashboard/docs.d.ts +15 -0
- package/dist/dashboard/docs.d.ts.map +1 -0
- package/dist/dashboard/docs.js +556 -0
- package/dist/dashboard/layout.d.ts +12 -0
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +12 -5
- 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 +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -13
- package/dist/static.d.ts +592 -2
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +422 -9
- 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-jwks.d.ts +2 -1
- package/dist/tap-jwks.d.ts.map +1 -1
- package/dist/tap-jwks.js +31 -7
- 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,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Capability Attestation API Routes
|
|
3
|
+
*
|
|
4
|
+
* Endpoints for issuing, retrieving, revoking, and verifying
|
|
5
|
+
* capability attestation tokens for TAP agents.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* POST /v1/attestations — Issue attestation token
|
|
9
|
+
* GET /v1/attestations/:id — Get attestation details
|
|
10
|
+
* GET /v1/attestations — List attestations for agent
|
|
11
|
+
* POST /v1/attestations/:id/revoke — Revoke attestation
|
|
12
|
+
* POST /v1/verify/attestation — Verify attestation + check capability
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from 'hono';
|
|
15
|
+
/**
|
|
16
|
+
* POST /v1/attestations
|
|
17
|
+
* Issue a capability attestation token for an agent
|
|
18
|
+
*/
|
|
19
|
+
export declare function issueAttestationRoute(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
|
+
attestation_id: string;
|
|
30
|
+
agent_id: string;
|
|
31
|
+
app_id: string;
|
|
32
|
+
token: string;
|
|
33
|
+
can: string[];
|
|
34
|
+
cannot: string[];
|
|
35
|
+
restrictions: {
|
|
36
|
+
[x: string]: any;
|
|
37
|
+
max_amount?: number | undefined;
|
|
38
|
+
rate_limit?: number | undefined;
|
|
39
|
+
} | null;
|
|
40
|
+
delegation_id: string | null;
|
|
41
|
+
metadata: {
|
|
42
|
+
[x: string]: string;
|
|
43
|
+
} | null;
|
|
44
|
+
created_at: string;
|
|
45
|
+
expires_at: string;
|
|
46
|
+
}, 201, "json">)>;
|
|
47
|
+
/**
|
|
48
|
+
* GET /v1/attestations/:id
|
|
49
|
+
* Get attestation details
|
|
50
|
+
*/
|
|
51
|
+
export declare function getAttestationRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
52
|
+
success: false;
|
|
53
|
+
error: string;
|
|
54
|
+
message: string;
|
|
55
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
56
|
+
success: false;
|
|
57
|
+
error: string;
|
|
58
|
+
message: string;
|
|
59
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
60
|
+
success: true;
|
|
61
|
+
attestation_id: string;
|
|
62
|
+
agent_id: string;
|
|
63
|
+
app_id: string;
|
|
64
|
+
can: string[];
|
|
65
|
+
cannot: string[];
|
|
66
|
+
restrictions: {
|
|
67
|
+
[x: string]: any;
|
|
68
|
+
max_amount?: number | undefined;
|
|
69
|
+
rate_limit?: number | undefined;
|
|
70
|
+
} | null;
|
|
71
|
+
delegation_id: string | null;
|
|
72
|
+
metadata: {
|
|
73
|
+
[x: string]: string;
|
|
74
|
+
} | null;
|
|
75
|
+
created_at: string;
|
|
76
|
+
expires_at: string;
|
|
77
|
+
revoked: boolean;
|
|
78
|
+
revoked_at: string | null;
|
|
79
|
+
revocation_reason: string | null;
|
|
80
|
+
time_remaining: number;
|
|
81
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
82
|
+
success: false;
|
|
83
|
+
error: string;
|
|
84
|
+
message: string;
|
|
85
|
+
}, 500, "json">)>;
|
|
86
|
+
/**
|
|
87
|
+
* GET /v1/attestations
|
|
88
|
+
* List attestations for an agent
|
|
89
|
+
*
|
|
90
|
+
* Query params:
|
|
91
|
+
* agent_id — required, the agent to list attestations for
|
|
92
|
+
*/
|
|
93
|
+
export declare function listAttestationsRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
94
|
+
success: false;
|
|
95
|
+
error: string | undefined;
|
|
96
|
+
message: string;
|
|
97
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
98
|
+
success: false;
|
|
99
|
+
error: string;
|
|
100
|
+
message: string;
|
|
101
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
102
|
+
success: true;
|
|
103
|
+
attestations: any[];
|
|
104
|
+
count: number;
|
|
105
|
+
agent_id: string;
|
|
106
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
107
|
+
success: false;
|
|
108
|
+
error: string;
|
|
109
|
+
message: string;
|
|
110
|
+
}, 500, "json">)>;
|
|
111
|
+
/**
|
|
112
|
+
* POST /v1/attestations/:id/revoke
|
|
113
|
+
* Revoke an attestation
|
|
114
|
+
*/
|
|
115
|
+
export declare function revokeAttestationRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
116
|
+
success: false;
|
|
117
|
+
error: string;
|
|
118
|
+
message: string;
|
|
119
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
120
|
+
success: false;
|
|
121
|
+
error: string | undefined;
|
|
122
|
+
message: string;
|
|
123
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
124
|
+
success: false;
|
|
125
|
+
error: string;
|
|
126
|
+
message: string;
|
|
127
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
128
|
+
success: false;
|
|
129
|
+
error: string;
|
|
130
|
+
message: string;
|
|
131
|
+
}, 403, "json">) | (Response & import("hono").TypedResponse<{
|
|
132
|
+
success: false;
|
|
133
|
+
error: string;
|
|
134
|
+
message: string | undefined;
|
|
135
|
+
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
136
|
+
success: true;
|
|
137
|
+
attestation_id: string;
|
|
138
|
+
revoked: true;
|
|
139
|
+
revoked_at: string | null;
|
|
140
|
+
revocation_reason: string | null;
|
|
141
|
+
message: string;
|
|
142
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
143
|
+
/**
|
|
144
|
+
* POST /v1/verify/attestation
|
|
145
|
+
* Verify an attestation token and optionally check a specific capability
|
|
146
|
+
*
|
|
147
|
+
* Body:
|
|
148
|
+
* token — required, the attestation JWT token
|
|
149
|
+
* action — optional, capability action to check (e.g. "read")
|
|
150
|
+
* resource — optional, capability resource to check (e.g. "invoices")
|
|
151
|
+
*/
|
|
152
|
+
export declare function verifyAttestationRoute(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
153
|
+
success: false;
|
|
154
|
+
error: string;
|
|
155
|
+
message: string;
|
|
156
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
157
|
+
success: false;
|
|
158
|
+
valid: false;
|
|
159
|
+
allowed: false;
|
|
160
|
+
agent_id: string | null;
|
|
161
|
+
error: string | undefined;
|
|
162
|
+
matched_rule: string | null;
|
|
163
|
+
checked_capability: any;
|
|
164
|
+
}, 401 | 403, "json">) | (Response & import("hono").TypedResponse<{
|
|
165
|
+
success: true;
|
|
166
|
+
valid: true;
|
|
167
|
+
allowed: true;
|
|
168
|
+
agent_id: string | undefined;
|
|
169
|
+
reason: string | undefined;
|
|
170
|
+
matched_rule: string | undefined;
|
|
171
|
+
checked_capability: any;
|
|
172
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
173
|
+
success: false;
|
|
174
|
+
valid: false;
|
|
175
|
+
error: string | undefined;
|
|
176
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
177
|
+
success: true;
|
|
178
|
+
valid: true;
|
|
179
|
+
agent_id: string;
|
|
180
|
+
issuer: string;
|
|
181
|
+
can: string[];
|
|
182
|
+
cannot: string[];
|
|
183
|
+
restrictions: {
|
|
184
|
+
[x: string]: any;
|
|
185
|
+
max_amount?: number | undefined;
|
|
186
|
+
rate_limit?: number | undefined;
|
|
187
|
+
} | null;
|
|
188
|
+
delegation_id: string | null;
|
|
189
|
+
issued_at: string;
|
|
190
|
+
expires_at: string;
|
|
191
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
192
|
+
success: false;
|
|
193
|
+
error: string;
|
|
194
|
+
message: string;
|
|
195
|
+
}, 500, "json">)>;
|
|
196
|
+
declare const _default: {
|
|
197
|
+
issueAttestationRoute: typeof issueAttestationRoute;
|
|
198
|
+
getAttestationRoute: typeof getAttestationRoute;
|
|
199
|
+
listAttestationsRoute: typeof listAttestationsRoute;
|
|
200
|
+
revokeAttestationRoute: typeof revokeAttestationRoute;
|
|
201
|
+
verifyAttestationRoute: typeof verifyAttestationRoute;
|
|
202
|
+
};
|
|
203
|
+
export default _default;
|
|
204
|
+
//# sourceMappingURL=tap-attestation-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-attestation-routes.d.ts","sourceRoot":"","sources":["../src/tap-attestation-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AA4CpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4GrD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiDnD;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;kBA8DrD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAsEtD;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAmFtD;;;;;;;;AAED,wBAME"}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Capability Attestation API Routes
|
|
3
|
+
*
|
|
4
|
+
* Endpoints for issuing, retrieving, revoking, and verifying
|
|
5
|
+
* capability attestation tokens for TAP agents.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* POST /v1/attestations — Issue attestation token
|
|
9
|
+
* GET /v1/attestations/:id — Get attestation details
|
|
10
|
+
* GET /v1/attestations — List attestations for agent
|
|
11
|
+
* POST /v1/attestations/:id/revoke — Revoke attestation
|
|
12
|
+
* POST /v1/verify/attestation — Verify attestation + check capability
|
|
13
|
+
*/
|
|
14
|
+
import { extractBearerToken, verifyToken } from './auth.js';
|
|
15
|
+
import { issueAttestation, getAttestation, revokeAttestation, verifyAttestationToken, verifyAndCheckCapability, isValidCapabilityPattern, } from './tap-attestation.js';
|
|
16
|
+
// ============ VALIDATION HELPERS ============
|
|
17
|
+
async function validateAppAccess(c, requireAuth = true) {
|
|
18
|
+
const queryAppId = c.req.query('app_id');
|
|
19
|
+
let jwtAppId;
|
|
20
|
+
const authHeader = c.req.header('authorization');
|
|
21
|
+
const token = extractBearerToken(authHeader);
|
|
22
|
+
if (token) {
|
|
23
|
+
const result = await verifyToken(token, c.env.JWT_SECRET, c.env);
|
|
24
|
+
if (result.valid && result.payload) {
|
|
25
|
+
jwtAppId = result.payload.app_id;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const appId = queryAppId || jwtAppId;
|
|
29
|
+
if (requireAuth && !appId) {
|
|
30
|
+
return { valid: false, error: 'MISSING_APP_ID', status: 401 };
|
|
31
|
+
}
|
|
32
|
+
return { valid: true, appId };
|
|
33
|
+
}
|
|
34
|
+
// ============ ROUTE HANDLERS ============
|
|
35
|
+
/**
|
|
36
|
+
* POST /v1/attestations
|
|
37
|
+
* Issue a capability attestation token for an agent
|
|
38
|
+
*/
|
|
39
|
+
export async function issueAttestationRoute(c) {
|
|
40
|
+
try {
|
|
41
|
+
const appAccess = await validateAppAccess(c, true);
|
|
42
|
+
if (!appAccess.valid) {
|
|
43
|
+
return c.json({
|
|
44
|
+
success: false,
|
|
45
|
+
error: appAccess.error,
|
|
46
|
+
message: 'Authentication required'
|
|
47
|
+
}, (appAccess.status || 401));
|
|
48
|
+
}
|
|
49
|
+
const body = await c.req.json().catch(() => ({}));
|
|
50
|
+
// Validate required fields
|
|
51
|
+
if (!body.agent_id) {
|
|
52
|
+
return c.json({
|
|
53
|
+
success: false,
|
|
54
|
+
error: 'MISSING_AGENT_ID',
|
|
55
|
+
message: 'agent_id is required'
|
|
56
|
+
}, 400);
|
|
57
|
+
}
|
|
58
|
+
if (!body.can || !Array.isArray(body.can) || body.can.length === 0) {
|
|
59
|
+
return c.json({
|
|
60
|
+
success: false,
|
|
61
|
+
error: 'MISSING_CAPABILITIES',
|
|
62
|
+
message: 'At least one "can" rule is required (e.g. ["read:invoices", "browse:*"])'
|
|
63
|
+
}, 400);
|
|
64
|
+
}
|
|
65
|
+
// Validate capability patterns
|
|
66
|
+
for (const rule of body.can) {
|
|
67
|
+
if (!isValidCapabilityPattern(rule)) {
|
|
68
|
+
return c.json({
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'INVALID_CAPABILITY_PATTERN',
|
|
71
|
+
message: `Invalid capability pattern in "can": ${rule}. Use "action:resource" format.`
|
|
72
|
+
}, 400);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (body.cannot && Array.isArray(body.cannot)) {
|
|
76
|
+
for (const rule of body.cannot) {
|
|
77
|
+
if (!isValidCapabilityPattern(rule)) {
|
|
78
|
+
return c.json({
|
|
79
|
+
success: false,
|
|
80
|
+
error: 'INVALID_CAPABILITY_PATTERN',
|
|
81
|
+
message: `Invalid capability pattern in "cannot": ${rule}. Use "action:resource" format.`
|
|
82
|
+
}, 400);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const options = {
|
|
87
|
+
agent_id: body.agent_id,
|
|
88
|
+
can: body.can,
|
|
89
|
+
cannot: body.cannot,
|
|
90
|
+
restrictions: body.restrictions,
|
|
91
|
+
duration_seconds: body.duration_seconds,
|
|
92
|
+
delegation_id: body.delegation_id,
|
|
93
|
+
metadata: body.metadata,
|
|
94
|
+
};
|
|
95
|
+
const result = await issueAttestation(c.env.AGENTS, c.env.SESSIONS, appAccess.appId, c.env.JWT_SECRET, options);
|
|
96
|
+
if (!result.success) {
|
|
97
|
+
const status = result.error?.includes('not found') ? 404
|
|
98
|
+
: result.error?.includes('does not belong') ? 403
|
|
99
|
+
: result.error?.includes('Too many rules') ? 400
|
|
100
|
+
: 400;
|
|
101
|
+
return c.json({
|
|
102
|
+
success: false,
|
|
103
|
+
error: 'ATTESTATION_ISSUANCE_FAILED',
|
|
104
|
+
message: result.error
|
|
105
|
+
}, status);
|
|
106
|
+
}
|
|
107
|
+
const att = result.attestation;
|
|
108
|
+
return c.json({
|
|
109
|
+
success: true,
|
|
110
|
+
attestation_id: att.attestation_id,
|
|
111
|
+
agent_id: att.agent_id,
|
|
112
|
+
app_id: att.app_id,
|
|
113
|
+
token: att.token,
|
|
114
|
+
can: att.can,
|
|
115
|
+
cannot: att.cannot,
|
|
116
|
+
restrictions: att.restrictions || null,
|
|
117
|
+
delegation_id: att.delegation_id || null,
|
|
118
|
+
metadata: att.metadata || null,
|
|
119
|
+
created_at: new Date(att.created_at).toISOString(),
|
|
120
|
+
expires_at: new Date(att.expires_at).toISOString(),
|
|
121
|
+
}, 201);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error('Attestation issuance error:', error);
|
|
125
|
+
return c.json({
|
|
126
|
+
success: false,
|
|
127
|
+
error: 'INTERNAL_ERROR',
|
|
128
|
+
message: 'Internal server error'
|
|
129
|
+
}, 500);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* GET /v1/attestations/:id
|
|
134
|
+
* Get attestation details
|
|
135
|
+
*/
|
|
136
|
+
export async function getAttestationRoute(c) {
|
|
137
|
+
try {
|
|
138
|
+
const attestationId = c.req.param('id');
|
|
139
|
+
if (!attestationId) {
|
|
140
|
+
return c.json({
|
|
141
|
+
success: false,
|
|
142
|
+
error: 'MISSING_ATTESTATION_ID',
|
|
143
|
+
message: 'Attestation ID is required'
|
|
144
|
+
}, 400);
|
|
145
|
+
}
|
|
146
|
+
const result = await getAttestation(c.env.SESSIONS, attestationId);
|
|
147
|
+
if (!result.success || !result.attestation) {
|
|
148
|
+
return c.json({
|
|
149
|
+
success: false,
|
|
150
|
+
error: 'ATTESTATION_NOT_FOUND',
|
|
151
|
+
message: result.error || 'Attestation not found or expired'
|
|
152
|
+
}, 404);
|
|
153
|
+
}
|
|
154
|
+
const att = result.attestation;
|
|
155
|
+
return c.json({
|
|
156
|
+
success: true,
|
|
157
|
+
attestation_id: att.attestation_id,
|
|
158
|
+
agent_id: att.agent_id,
|
|
159
|
+
app_id: att.app_id,
|
|
160
|
+
can: att.can,
|
|
161
|
+
cannot: att.cannot,
|
|
162
|
+
restrictions: att.restrictions || null,
|
|
163
|
+
delegation_id: att.delegation_id || null,
|
|
164
|
+
metadata: att.metadata || null,
|
|
165
|
+
created_at: new Date(att.created_at).toISOString(),
|
|
166
|
+
expires_at: new Date(att.expires_at).toISOString(),
|
|
167
|
+
revoked: att.revoked,
|
|
168
|
+
revoked_at: att.revoked_at ? new Date(att.revoked_at).toISOString() : null,
|
|
169
|
+
revocation_reason: att.revocation_reason || null,
|
|
170
|
+
time_remaining: Math.max(0, att.expires_at - Date.now()),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.error('Attestation retrieval error:', error);
|
|
175
|
+
return c.json({
|
|
176
|
+
success: false,
|
|
177
|
+
error: 'INTERNAL_ERROR',
|
|
178
|
+
message: 'Internal server error'
|
|
179
|
+
}, 500);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* GET /v1/attestations
|
|
184
|
+
* List attestations for an agent
|
|
185
|
+
*
|
|
186
|
+
* Query params:
|
|
187
|
+
* agent_id — required, the agent to list attestations for
|
|
188
|
+
*/
|
|
189
|
+
export async function listAttestationsRoute(c) {
|
|
190
|
+
try {
|
|
191
|
+
const appAccess = await validateAppAccess(c, true);
|
|
192
|
+
if (!appAccess.valid) {
|
|
193
|
+
return c.json({
|
|
194
|
+
success: false,
|
|
195
|
+
error: appAccess.error,
|
|
196
|
+
message: 'Authentication required'
|
|
197
|
+
}, (appAccess.status || 401));
|
|
198
|
+
}
|
|
199
|
+
const agentId = c.req.query('agent_id');
|
|
200
|
+
if (!agentId) {
|
|
201
|
+
return c.json({
|
|
202
|
+
success: false,
|
|
203
|
+
error: 'MISSING_AGENT_ID',
|
|
204
|
+
message: 'agent_id query parameter is required'
|
|
205
|
+
}, 400);
|
|
206
|
+
}
|
|
207
|
+
// Get the attestation index for this agent
|
|
208
|
+
const indexKey = `agent_attestations:${agentId}`;
|
|
209
|
+
const indexData = await c.env.SESSIONS.get(indexKey, 'text');
|
|
210
|
+
const attestationIds = indexData ? JSON.parse(indexData) : [];
|
|
211
|
+
// Fetch each attestation (filter out expired/missing)
|
|
212
|
+
const attestations = [];
|
|
213
|
+
for (const id of attestationIds) {
|
|
214
|
+
const result = await getAttestation(c.env.SESSIONS, id);
|
|
215
|
+
if (result.success && result.attestation) {
|
|
216
|
+
const att = result.attestation;
|
|
217
|
+
// Only include attestations for this app
|
|
218
|
+
if (att.app_id === appAccess.appId) {
|
|
219
|
+
attestations.push({
|
|
220
|
+
attestation_id: att.attestation_id,
|
|
221
|
+
agent_id: att.agent_id,
|
|
222
|
+
can: att.can,
|
|
223
|
+
cannot: att.cannot,
|
|
224
|
+
created_at: new Date(att.created_at).toISOString(),
|
|
225
|
+
expires_at: new Date(att.expires_at).toISOString(),
|
|
226
|
+
revoked: att.revoked,
|
|
227
|
+
delegation_id: att.delegation_id || null,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return c.json({
|
|
233
|
+
success: true,
|
|
234
|
+
attestations,
|
|
235
|
+
count: attestations.length,
|
|
236
|
+
agent_id: agentId,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error('Attestation listing error:', error);
|
|
241
|
+
return c.json({
|
|
242
|
+
success: false,
|
|
243
|
+
error: 'INTERNAL_ERROR',
|
|
244
|
+
message: 'Internal server error'
|
|
245
|
+
}, 500);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* POST /v1/attestations/:id/revoke
|
|
250
|
+
* Revoke an attestation
|
|
251
|
+
*/
|
|
252
|
+
export async function revokeAttestationRoute(c) {
|
|
253
|
+
try {
|
|
254
|
+
const attestationId = c.req.param('id');
|
|
255
|
+
if (!attestationId) {
|
|
256
|
+
return c.json({
|
|
257
|
+
success: false,
|
|
258
|
+
error: 'MISSING_ATTESTATION_ID',
|
|
259
|
+
message: 'Attestation ID is required'
|
|
260
|
+
}, 400);
|
|
261
|
+
}
|
|
262
|
+
const appAccess = await validateAppAccess(c, true);
|
|
263
|
+
if (!appAccess.valid) {
|
|
264
|
+
return c.json({
|
|
265
|
+
success: false,
|
|
266
|
+
error: appAccess.error,
|
|
267
|
+
message: 'Authentication required'
|
|
268
|
+
}, (appAccess.status || 401));
|
|
269
|
+
}
|
|
270
|
+
// Verify attestation exists and belongs to this app
|
|
271
|
+
const existing = await getAttestation(c.env.SESSIONS, attestationId);
|
|
272
|
+
if (!existing.success || !existing.attestation) {
|
|
273
|
+
return c.json({
|
|
274
|
+
success: false,
|
|
275
|
+
error: 'ATTESTATION_NOT_FOUND',
|
|
276
|
+
message: 'Attestation not found or expired'
|
|
277
|
+
}, 404);
|
|
278
|
+
}
|
|
279
|
+
if (existing.attestation.app_id !== appAccess.appId) {
|
|
280
|
+
return c.json({
|
|
281
|
+
success: false,
|
|
282
|
+
error: 'UNAUTHORIZED',
|
|
283
|
+
message: 'Attestation does not belong to this app'
|
|
284
|
+
}, 403);
|
|
285
|
+
}
|
|
286
|
+
const body = await c.req.json().catch(() => ({}));
|
|
287
|
+
const reason = body.reason || undefined;
|
|
288
|
+
const result = await revokeAttestation(c.env.SESSIONS, attestationId, reason);
|
|
289
|
+
if (!result.success) {
|
|
290
|
+
return c.json({
|
|
291
|
+
success: false,
|
|
292
|
+
error: 'REVOCATION_FAILED',
|
|
293
|
+
message: result.error
|
|
294
|
+
}, 500);
|
|
295
|
+
}
|
|
296
|
+
const att = result.attestation;
|
|
297
|
+
return c.json({
|
|
298
|
+
success: true,
|
|
299
|
+
attestation_id: att.attestation_id,
|
|
300
|
+
revoked: true,
|
|
301
|
+
revoked_at: att.revoked_at ? new Date(att.revoked_at).toISOString() : null,
|
|
302
|
+
revocation_reason: att.revocation_reason || null,
|
|
303
|
+
message: 'Attestation revoked. Token will be rejected on verification.',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
console.error('Attestation revocation error:', error);
|
|
308
|
+
return c.json({
|
|
309
|
+
success: false,
|
|
310
|
+
error: 'INTERNAL_ERROR',
|
|
311
|
+
message: 'Internal server error'
|
|
312
|
+
}, 500);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* POST /v1/verify/attestation
|
|
317
|
+
* Verify an attestation token and optionally check a specific capability
|
|
318
|
+
*
|
|
319
|
+
* Body:
|
|
320
|
+
* token — required, the attestation JWT token
|
|
321
|
+
* action — optional, capability action to check (e.g. "read")
|
|
322
|
+
* resource — optional, capability resource to check (e.g. "invoices")
|
|
323
|
+
*/
|
|
324
|
+
export async function verifyAttestationRoute(c) {
|
|
325
|
+
try {
|
|
326
|
+
const body = await c.req.json().catch(() => ({}));
|
|
327
|
+
if (!body.token) {
|
|
328
|
+
return c.json({
|
|
329
|
+
success: false,
|
|
330
|
+
error: 'MISSING_TOKEN',
|
|
331
|
+
message: 'Attestation token is required'
|
|
332
|
+
}, 400);
|
|
333
|
+
}
|
|
334
|
+
// If action specified, do full verify+check
|
|
335
|
+
if (body.action) {
|
|
336
|
+
const result = await verifyAndCheckCapability(c.env.SESSIONS, body.token, c.env.JWT_SECRET, body.action, body.resource);
|
|
337
|
+
if (!result.allowed) {
|
|
338
|
+
return c.json({
|
|
339
|
+
success: false,
|
|
340
|
+
valid: false,
|
|
341
|
+
allowed: false,
|
|
342
|
+
agent_id: result.agent_id || null,
|
|
343
|
+
error: result.error || result.reason,
|
|
344
|
+
matched_rule: result.matched_rule || null,
|
|
345
|
+
checked_capability: body.resource ? `${body.action}:${body.resource}` : body.action,
|
|
346
|
+
}, result.error ? 401 : 403);
|
|
347
|
+
}
|
|
348
|
+
return c.json({
|
|
349
|
+
success: true,
|
|
350
|
+
valid: true,
|
|
351
|
+
allowed: true,
|
|
352
|
+
agent_id: result.agent_id,
|
|
353
|
+
reason: result.reason,
|
|
354
|
+
matched_rule: result.matched_rule,
|
|
355
|
+
checked_capability: body.resource ? `${body.action}:${body.resource}` : body.action,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
// Otherwise just verify the token
|
|
359
|
+
const verification = await verifyAttestationToken(c.env.SESSIONS, body.token, c.env.JWT_SECRET);
|
|
360
|
+
if (!verification.valid || !verification.payload) {
|
|
361
|
+
return c.json({
|
|
362
|
+
success: false,
|
|
363
|
+
valid: false,
|
|
364
|
+
error: verification.error,
|
|
365
|
+
}, 401);
|
|
366
|
+
}
|
|
367
|
+
const payload = verification.payload;
|
|
368
|
+
return c.json({
|
|
369
|
+
success: true,
|
|
370
|
+
valid: true,
|
|
371
|
+
agent_id: payload.sub,
|
|
372
|
+
issuer: payload.iss,
|
|
373
|
+
can: payload.can,
|
|
374
|
+
cannot: payload.cannot,
|
|
375
|
+
restrictions: payload.restrictions || null,
|
|
376
|
+
delegation_id: payload.delegation_id || null,
|
|
377
|
+
issued_at: new Date(payload.iat * 1000).toISOString(),
|
|
378
|
+
expires_at: new Date(payload.exp * 1000).toISOString(),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.error('Attestation verification error:', error);
|
|
383
|
+
return c.json({
|
|
384
|
+
success: false,
|
|
385
|
+
error: 'INTERNAL_ERROR',
|
|
386
|
+
message: 'Internal server error'
|
|
387
|
+
}, 500);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
export default {
|
|
391
|
+
issueAttestationRoute,
|
|
392
|
+
getAttestationRoute,
|
|
393
|
+
listAttestationsRoute,
|
|
394
|
+
revokeAttestationRoute,
|
|
395
|
+
verifyAttestationRoute,
|
|
396
|
+
};
|