@dupecom/botcha-cloudflare 0.20.2 → 0.23.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 +74 -9
- package/dist/agent-auth.d.ts +129 -0
- package/dist/agent-auth.d.ts.map +1 -0
- package/dist/agent-auth.js +210 -0
- package/dist/agents.d.ts +10 -0
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +51 -1
- package/dist/app-gate.d.ts +6 -0
- package/dist/app-gate.d.ts.map +1 -0
- package/dist/app-gate.js +69 -0
- package/dist/apps.d.ts +13 -4
- package/dist/apps.d.ts.map +1 -1
- package/dist/apps.js +30 -4
- package/dist/dashboard/account.d.ts +63 -0
- package/dist/dashboard/account.d.ts.map +1 -0
- package/dist/dashboard/account.js +488 -0
- package/dist/dashboard/api.js +15 -68
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +14 -14
- package/dist/dashboard/docs.d.ts.map +1 -1
- package/dist/dashboard/docs.js +146 -3
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +2 -2
- package/dist/dashboard/mcp-setup.d.ts +15 -0
- package/dist/dashboard/mcp-setup.d.ts.map +1 -0
- package/dist/dashboard/mcp-setup.js +391 -0
- package/dist/dashboard/showcase.d.ts +6 -10
- package/dist/dashboard/showcase.d.ts.map +1 -1
- package/dist/dashboard/showcase.js +67 -991
- package/dist/dashboard/whitepaper.d.ts.map +1 -1
- package/dist/dashboard/whitepaper.js +42 -4
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +660 -83
- package/dist/mcp.d.ts +20 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +1290 -0
- package/dist/oauth-agent.d.ts +130 -0
- package/dist/oauth-agent.d.ts.map +1 -0
- package/dist/oauth-agent.js +194 -0
- package/dist/static.d.ts +781 -5
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +790 -111
- package/dist/tap-a2a-routes.d.ts +355 -0
- package/dist/tap-a2a-routes.d.ts.map +1 -0
- package/dist/tap-a2a-routes.js +475 -0
- package/dist/tap-a2a.d.ts +199 -0
- package/dist/tap-a2a.d.ts.map +1 -0
- package/dist/tap-a2a.js +502 -0
- package/dist/tap-agents.d.ts +15 -0
- package/dist/tap-agents.d.ts.map +1 -1
- package/dist/tap-agents.js +31 -1
- package/dist/tap-ans-routes.d.ts +302 -0
- package/dist/tap-ans-routes.d.ts.map +1 -0
- package/dist/tap-ans-routes.js +535 -0
- package/dist/tap-ans.d.ts +241 -0
- package/dist/tap-ans.d.ts.map +1 -0
- package/dist/tap-ans.js +481 -0
- package/dist/tap-delegation-routes.d.ts.map +1 -1
- package/dist/tap-delegation-routes.js +11 -0
- package/dist/tap-did.d.ts +140 -0
- package/dist/tap-did.d.ts.map +1 -0
- package/dist/tap-did.js +262 -0
- package/dist/tap-oidca-routes.d.ts +383 -0
- package/dist/tap-oidca-routes.d.ts.map +1 -0
- package/dist/tap-oidca-routes.js +597 -0
- package/dist/tap-oidca.d.ts +288 -0
- package/dist/tap-oidca.d.ts.map +1 -0
- package/dist/tap-oidca.js +461 -0
- package/dist/tap-routes.d.ts +24 -8
- package/dist/tap-routes.d.ts.map +1 -1
- package/dist/tap-routes.js +169 -23
- package/dist/tap-vc-routes.d.ts +358 -0
- package/dist/tap-vc-routes.d.ts.map +1 -0
- package/dist/tap-vc-routes.js +367 -0
- package/dist/tap-vc.d.ts +125 -0
- package/dist/tap-vc.d.ts.map +1 -0
- package/dist/tap-vc.js +245 -0
- package/dist/tap-x402-routes.d.ts +89 -0
- package/dist/tap-x402-routes.d.ts.map +1 -0
- package/dist/tap-x402-routes.js +579 -0
- package/dist/tap-x402.d.ts +222 -0
- package/dist/tap-x402.d.ts.map +1 -0
- package/dist/tap-x402.js +546 -0
- package/dist/webhooks.d.ts +99 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +642 -0
- package/package.json +3 -1
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOTCHA OIDC-A Attestation Module
|
|
3
|
+
*
|
|
4
|
+
* Implements OIDC-A (OpenID Connect for Agents) attestation:
|
|
5
|
+
* - EAT (Entity Attestation Token) issuance per draft-ietf-rats-eat-25
|
|
6
|
+
* - OIDC-A compatible claims per draft-aap-oauth-profile (Feb 2026)
|
|
7
|
+
* - OAuth 2.0 AS metadata discovery per RFC 8414
|
|
8
|
+
* - Agent Authorization Grant per draft-rosenberg-oauth-aauth
|
|
9
|
+
* - OIDC-A UserInfo endpoint
|
|
10
|
+
*
|
|
11
|
+
* EAT tokens make BOTCHA an `agent_attestation` endpoint that enterprise
|
|
12
|
+
* auth servers embed in OpenID Connect for Agents token chains.
|
|
13
|
+
*/
|
|
14
|
+
import { SignJWT, jwtVerify, importJWK } from 'jose';
|
|
15
|
+
// ============ CONSTANTS ============
|
|
16
|
+
export const BOTCHA_EAT_PROFILE = 'https://botcha.ai/eat-profile/v1';
|
|
17
|
+
export const BOTCHA_ISSUER = 'botcha.ai';
|
|
18
|
+
export const EAT_TOKEN_TTL_SECONDS = 3600; // 1 hour
|
|
19
|
+
export const OIDC_CLAIMS_TTL_SECONDS = 3600; // 1 hour
|
|
20
|
+
export const AGENT_GRANT_TTL_SECONDS = 3600; // 1 hour
|
|
21
|
+
/**
|
|
22
|
+
* Well-known BOTCHA agent capabilities
|
|
23
|
+
* Enterprise auth servers can use these to filter agents by capability.
|
|
24
|
+
*/
|
|
25
|
+
export const BOTCHA_AGENT_CAPABILITIES = [
|
|
26
|
+
'botcha:verified', // Core — agent passed BOTCHA challenge
|
|
27
|
+
'botcha:speed-challenge', // Can solve SHA256 speed challenges
|
|
28
|
+
'botcha:hybrid-challenge', // Can solve hybrid (speed + reasoning) challenges
|
|
29
|
+
'botcha:reasoning-challenge', // Can solve LLM-level reasoning challenges
|
|
30
|
+
'agent:autonomous', // Can operate autonomously
|
|
31
|
+
'agent:tool-use', // Can invoke external tools/APIs
|
|
32
|
+
'agent:multi-step', // Can execute multi-step workflows
|
|
33
|
+
];
|
|
34
|
+
// ============ CORE FUNCTIONS ============
|
|
35
|
+
/**
|
|
36
|
+
* Derive a Universal Entity ID (UEID) from an agent identifier.
|
|
37
|
+
*
|
|
38
|
+
* Per draft-ietf-rats-eat-25 §4.2.1: UEID is a binary identifier
|
|
39
|
+
* unique to the entity. For software agents, we use SHA-256(agent_id)
|
|
40
|
+
* truncated to 33 bytes, base64url-encoded.
|
|
41
|
+
*/
|
|
42
|
+
async function deriveUEID(agentId) {
|
|
43
|
+
const encoder = new TextEncoder();
|
|
44
|
+
const data = encoder.encode(agentId);
|
|
45
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
46
|
+
const hashArray = new Uint8Array(hashBuffer).slice(0, 33); // 33 bytes per spec
|
|
47
|
+
return btoa(String.fromCharCode(...hashArray))
|
|
48
|
+
.replace(/\+/g, '-')
|
|
49
|
+
.replace(/\//g, '_')
|
|
50
|
+
.replace(/=/g, '');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate a cryptographically random nonce for eat_nonce.
|
|
54
|
+
* Returns base64url-encoded 32 bytes.
|
|
55
|
+
*/
|
|
56
|
+
function generateEATNonce() {
|
|
57
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
58
|
+
return btoa(String.fromCharCode(...bytes))
|
|
59
|
+
.replace(/\+/g, '-')
|
|
60
|
+
.replace(/\//g, '_')
|
|
61
|
+
.replace(/=/g, '');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Issue an EAT (Entity Attestation Token) from a valid BOTCHA access token.
|
|
65
|
+
*
|
|
66
|
+
* The EAT proves:
|
|
67
|
+
* 1. The agent solved a computational challenge (proving it is a bot)
|
|
68
|
+
* 2. The challenge was issued by botcha.ai
|
|
69
|
+
* 3. The solution time demonstrates AI-speed computation
|
|
70
|
+
* 4. The agent is registered with a specific app
|
|
71
|
+
*
|
|
72
|
+
* This token can be embedded as `agent_attestation` in OIDC-A tokens.
|
|
73
|
+
*
|
|
74
|
+
* @param botchaPayload - Verified BOTCHA access token payload
|
|
75
|
+
* @param signingKey - ES256 private key for signing (required for EAT)
|
|
76
|
+
* @param options - Optional parameters
|
|
77
|
+
* @returns Signed EAT JWT
|
|
78
|
+
*/
|
|
79
|
+
export async function issueEAT(botchaPayload, signingKey, options) {
|
|
80
|
+
const now = Math.floor(Date.now() / 1000);
|
|
81
|
+
const ttl = options?.ttlSeconds ?? EAT_TOKEN_TTL_SECONDS;
|
|
82
|
+
const agentId = botchaPayload.app_id
|
|
83
|
+
? `${botchaPayload.app_id}:${botchaPayload.sub}`
|
|
84
|
+
: botchaPayload.sub;
|
|
85
|
+
const ueid = await deriveUEID(agentId);
|
|
86
|
+
const eatNonce = options?.nonce ?? generateEATNonce();
|
|
87
|
+
const eatPayload = {
|
|
88
|
+
iss: BOTCHA_ISSUER,
|
|
89
|
+
sub: agentId,
|
|
90
|
+
iat: now,
|
|
91
|
+
exp: now + ttl,
|
|
92
|
+
// EAT standard claims
|
|
93
|
+
eat_profile: BOTCHA_EAT_PROFILE,
|
|
94
|
+
eat_nonce: eatNonce,
|
|
95
|
+
ueid,
|
|
96
|
+
oemid: BOTCHA_ISSUER,
|
|
97
|
+
swname: 'BOTCHA',
|
|
98
|
+
swversion: '0.21.0',
|
|
99
|
+
dbgstat: 'Disabled',
|
|
100
|
+
intuse: 'generic',
|
|
101
|
+
// BOTCHA private claims
|
|
102
|
+
botcha_verified: true,
|
|
103
|
+
botcha_challenge_id: botchaPayload.sub,
|
|
104
|
+
botcha_solve_time_ms: botchaPayload.solveTime,
|
|
105
|
+
botcha_app_id: botchaPayload.app_id,
|
|
106
|
+
botcha_verification_method: options?.verificationMethod ?? 'speed-challenge',
|
|
107
|
+
};
|
|
108
|
+
const cryptoKey = (await importJWK(signingKey, 'ES256'));
|
|
109
|
+
const kid = signingKey.kid || 'botcha-signing-1';
|
|
110
|
+
const token = await new SignJWT(eatPayload)
|
|
111
|
+
.setProtectedHeader({
|
|
112
|
+
alg: 'ES256',
|
|
113
|
+
kid,
|
|
114
|
+
typ: 'JWT+EAT', // RFC 9334 recommends typ claim for EAT
|
|
115
|
+
})
|
|
116
|
+
.sign(cryptoKey);
|
|
117
|
+
return token;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build OIDC-A compatible agent claims block.
|
|
121
|
+
*
|
|
122
|
+
* This is the full claims object that enterprise auth servers embed in their
|
|
123
|
+
* ID tokens for agent grants. It includes the EAT token as `agent_attestation`.
|
|
124
|
+
*
|
|
125
|
+
* @param botchaPayload - Verified BOTCHA access token payload
|
|
126
|
+
* @param eatToken - Signed EAT JWT (from issueEAT)
|
|
127
|
+
* @param signingKey - ES256 signing key
|
|
128
|
+
* @param options - Agent metadata and OIDC-A options
|
|
129
|
+
* @returns Signed OIDC-A claims JWT + plain claims object
|
|
130
|
+
*/
|
|
131
|
+
export async function buildOIDCAgentClaims(botchaPayload, eatToken, signingKey, options) {
|
|
132
|
+
const now = Math.floor(Date.now() / 1000);
|
|
133
|
+
const ttl = options?.ttlSeconds ?? OIDC_CLAIMS_TTL_SECONDS;
|
|
134
|
+
const agentId = botchaPayload.app_id
|
|
135
|
+
? `${botchaPayload.app_id}:${botchaPayload.sub}`
|
|
136
|
+
: botchaPayload.sub;
|
|
137
|
+
// Derive the BOTCHA verification method capability from the actual method used
|
|
138
|
+
const verificationMethod = options?.verificationMethod ?? 'speed-challenge';
|
|
139
|
+
const methodCapability = `botcha:${verificationMethod}`;
|
|
140
|
+
// Build the capability set: always include BOTCHA core + the actual method used
|
|
141
|
+
const capabilities = [
|
|
142
|
+
'botcha:verified',
|
|
143
|
+
methodCapability,
|
|
144
|
+
...(options?.agentCapabilities ?? []).filter(c => c !== methodCapability),
|
|
145
|
+
];
|
|
146
|
+
const claims = {
|
|
147
|
+
// OIDC-A core
|
|
148
|
+
agent_model: options?.agentModel ?? 'botcha-verified-agent',
|
|
149
|
+
agent_version: options?.agentVersion,
|
|
150
|
+
agent_capabilities: capabilities,
|
|
151
|
+
agent_attestation: eatToken,
|
|
152
|
+
delegation_chain: options?.delegationChain ?? [],
|
|
153
|
+
// Identity
|
|
154
|
+
agent_id: agentId,
|
|
155
|
+
agent_operator: options?.agentOperator,
|
|
156
|
+
// Verification metadata — reflect the actual challenge type used
|
|
157
|
+
agent_verification: {
|
|
158
|
+
method: `botcha-${verificationMethod}`,
|
|
159
|
+
solve_time_ms: botchaPayload.solveTime,
|
|
160
|
+
verified_at: new Date(botchaPayload.iat * 1000).toISOString(),
|
|
161
|
+
issuer: BOTCHA_ISSUER,
|
|
162
|
+
challenge_id: botchaPayload.sub,
|
|
163
|
+
},
|
|
164
|
+
// Oversight
|
|
165
|
+
human_oversight_required: options?.humanOversightRequired ?? false,
|
|
166
|
+
oversight_contact: options?.oversightContact,
|
|
167
|
+
// Task binding
|
|
168
|
+
task_id: options?.taskId,
|
|
169
|
+
task_purpose: options?.taskPurpose,
|
|
170
|
+
// Token metadata
|
|
171
|
+
iat: now,
|
|
172
|
+
exp: now + ttl,
|
|
173
|
+
iss: BOTCHA_ISSUER,
|
|
174
|
+
};
|
|
175
|
+
// Remove undefined fields for a clean JWT
|
|
176
|
+
const cleanClaims = JSON.parse(JSON.stringify(claims));
|
|
177
|
+
const cryptoKey = (await importJWK(signingKey, 'ES256'));
|
|
178
|
+
const kid = signingKey.kid || 'botcha-signing-1';
|
|
179
|
+
const claimsJwt = await new SignJWT(cleanClaims)
|
|
180
|
+
.setProtectedHeader({
|
|
181
|
+
alg: 'ES256',
|
|
182
|
+
kid,
|
|
183
|
+
typ: 'JWT+OIDCA', // OIDC-A claims token type
|
|
184
|
+
})
|
|
185
|
+
.sign(cryptoKey);
|
|
186
|
+
return { claims: cleanClaims, claimsJwt };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Issue an Agent Authorization Grant.
|
|
190
|
+
*
|
|
191
|
+
* Implements draft-rosenberg-oauth-aauth "Agent Authorization Grant":
|
|
192
|
+
* - Agent presents BOTCHA access token as credential
|
|
193
|
+
* - Server issues a scoped grant JWT bound to the agent's identity
|
|
194
|
+
* - Optionally queued for human-in-the-loop approval
|
|
195
|
+
*
|
|
196
|
+
* Grant token is a signed JWT with AAP claims (draft-aap-oauth-profile §5).
|
|
197
|
+
*
|
|
198
|
+
* @param botchaPayload - Verified BOTCHA access token payload
|
|
199
|
+
* @param eatToken - EAT JWT from issueEAT
|
|
200
|
+
* @param oidcClaims - OIDC-A claims object from buildOIDCAgentClaims
|
|
201
|
+
* @param signingKey - ES256 signing key
|
|
202
|
+
* @param kv - KV namespace for storing pending grants (for HITL polling)
|
|
203
|
+
* @param options - Grant options
|
|
204
|
+
* @returns AgentGrantResult
|
|
205
|
+
*/
|
|
206
|
+
export async function issueAgentGrant(botchaPayload, eatToken, oidcClaims, signingKey, kv, baseUrl, options) {
|
|
207
|
+
const now = Math.floor(Date.now() / 1000);
|
|
208
|
+
const ttl = options?.ttlSeconds ?? AGENT_GRANT_TTL_SECONDS;
|
|
209
|
+
const scope = options?.scope ?? 'agent:read agent:attest openid';
|
|
210
|
+
const humanOversight = options?.humanOversightRequired ?? false;
|
|
211
|
+
const agentId = botchaPayload.app_id
|
|
212
|
+
? `${botchaPayload.app_id}:${botchaPayload.sub}`
|
|
213
|
+
: botchaPayload.sub;
|
|
214
|
+
// Generate grant ID for tracking
|
|
215
|
+
const grantId = crypto.randomUUID();
|
|
216
|
+
// AAP JWT payload (draft-aap-oauth-profile §5)
|
|
217
|
+
const grantPayload = {
|
|
218
|
+
// Standard JWT
|
|
219
|
+
iss: BOTCHA_ISSUER,
|
|
220
|
+
sub: agentId,
|
|
221
|
+
iat: now,
|
|
222
|
+
exp: now + ttl,
|
|
223
|
+
jti: grantId,
|
|
224
|
+
// OAuth grant type indicator
|
|
225
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:agent_authorization',
|
|
226
|
+
// AAP §5.2 — Agent Identity Section
|
|
227
|
+
agent: {
|
|
228
|
+
id: agentId,
|
|
229
|
+
model: oidcClaims.agent_model,
|
|
230
|
+
version: oidcClaims.agent_version,
|
|
231
|
+
operator: oidcClaims.agent_operator,
|
|
232
|
+
},
|
|
233
|
+
// AAP §5.2 — Capabilities Section
|
|
234
|
+
capabilities: oidcClaims.agent_capabilities,
|
|
235
|
+
scope,
|
|
236
|
+
// AAP §5.2 — Attestation
|
|
237
|
+
attestation: {
|
|
238
|
+
eat_token: eatToken,
|
|
239
|
+
issuer: BOTCHA_ISSUER,
|
|
240
|
+
verified_at: oidcClaims.agent_verification.verified_at,
|
|
241
|
+
method: oidcClaims.agent_verification.method,
|
|
242
|
+
},
|
|
243
|
+
// AAP §5.2 — Oversight
|
|
244
|
+
oversight: {
|
|
245
|
+
human_in_the_loop: humanOversight,
|
|
246
|
+
status: humanOversight ? 'pending' : 'none',
|
|
247
|
+
grant_id: grantId,
|
|
248
|
+
},
|
|
249
|
+
// AAP §5.2 — Task binding (if provided)
|
|
250
|
+
...(options?.taskId && {
|
|
251
|
+
task: {
|
|
252
|
+
id: options.taskId,
|
|
253
|
+
purpose: options.taskPurpose,
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
256
|
+
// AAP §5.2 — Delegation chain
|
|
257
|
+
delegation_chain: oidcClaims.delegation_chain,
|
|
258
|
+
// AAP §5.2 — Contextual constraints
|
|
259
|
+
...(options?.constraints && { constraints: options.constraints }),
|
|
260
|
+
// BOTCHA-specific: embed the full OIDC-A claims
|
|
261
|
+
agent_claims_ref: agentId,
|
|
262
|
+
};
|
|
263
|
+
const cryptoKey = (await importJWK(signingKey, 'ES256'));
|
|
264
|
+
const kid = signingKey.kid || 'botcha-signing-1';
|
|
265
|
+
const grantToken = await new SignJWT(grantPayload)
|
|
266
|
+
.setProtectedHeader({ alg: 'ES256', kid, typ: 'JWT+AGENT-GRANT' })
|
|
267
|
+
.sign(cryptoKey);
|
|
268
|
+
// If HITL required, store the pending grant in KV for polling
|
|
269
|
+
let oversightPollingUrl;
|
|
270
|
+
if (humanOversight) {
|
|
271
|
+
const pendingGrant = {
|
|
272
|
+
grant_id: grantId,
|
|
273
|
+
agent_id: agentId,
|
|
274
|
+
app_id: botchaPayload.app_id,
|
|
275
|
+
scope,
|
|
276
|
+
requested_at: Date.now(),
|
|
277
|
+
status: 'pending',
|
|
278
|
+
};
|
|
279
|
+
await kv.put(`agent_grant:${grantId}`, JSON.stringify(pendingGrant), { expirationTtl: ttl });
|
|
280
|
+
oversightPollingUrl = `${baseUrl}/v1/auth/agent-grant/${grantId}/status`;
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:agent_authorization',
|
|
284
|
+
access_token: grantToken,
|
|
285
|
+
token_type: 'Bearer',
|
|
286
|
+
expires_in: ttl,
|
|
287
|
+
scope,
|
|
288
|
+
agent_id: agentId,
|
|
289
|
+
app_id: botchaPayload.app_id,
|
|
290
|
+
human_oversight_required: humanOversight,
|
|
291
|
+
oversight_status: humanOversight ? 'pending' : 'none',
|
|
292
|
+
oversight_polling_url: oversightPollingUrl,
|
|
293
|
+
agent_claims: oidcClaims,
|
|
294
|
+
eat_token: eatToken,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Build OAuth 2.0 Authorization Server metadata (RFC 8414).
|
|
299
|
+
*
|
|
300
|
+
* This makes BOTCHA discoverable as an OAuth AS by enterprise auth servers
|
|
301
|
+
* that implement RFC 8414 auto-configuration.
|
|
302
|
+
*
|
|
303
|
+
* Extended with OIDC-A specific metadata for agent auth servers.
|
|
304
|
+
*/
|
|
305
|
+
export function buildOAuthASMetadata(baseUrl) {
|
|
306
|
+
return {
|
|
307
|
+
// RFC 8414 §2 — Required
|
|
308
|
+
issuer: baseUrl,
|
|
309
|
+
// Token endpoint (agent grant flow)
|
|
310
|
+
token_endpoint: `${baseUrl}/v1/auth/agent-grant`,
|
|
311
|
+
// JWKS for signature verification
|
|
312
|
+
jwks_uri: `${baseUrl}/.well-known/jwks`,
|
|
313
|
+
// RFC 8414 §2 — Optional but widely expected
|
|
314
|
+
scopes_supported: [
|
|
315
|
+
'openid',
|
|
316
|
+
'profile',
|
|
317
|
+
'agent:read',
|
|
318
|
+
'agent:write',
|
|
319
|
+
'agent:attest',
|
|
320
|
+
'agent:delegate',
|
|
321
|
+
'agent:oversight',
|
|
322
|
+
],
|
|
323
|
+
// Grant types — includes the AAP agent authorization grant
|
|
324
|
+
grant_types_supported: [
|
|
325
|
+
'urn:ietf:params:oauth:grant-type:agent_authorization',
|
|
326
|
+
'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
327
|
+
'client_credentials',
|
|
328
|
+
],
|
|
329
|
+
// Token endpoint auth methods
|
|
330
|
+
token_endpoint_auth_methods_supported: [
|
|
331
|
+
'botcha_token', // BOTCHA-specific: Bearer token from challenge
|
|
332
|
+
'private_key_jwt', // RFC 7523 — for clients with registered keys
|
|
333
|
+
],
|
|
334
|
+
// Response types (if acting as OIDC provider)
|
|
335
|
+
response_types_supported: ['token', 'id_token', 'token id_token'],
|
|
336
|
+
// Subject types
|
|
337
|
+
subject_types_supported: ['public'],
|
|
338
|
+
// ID token signing algorithms
|
|
339
|
+
id_token_signing_alg_values_supported: ['ES256'],
|
|
340
|
+
// Token lifetime
|
|
341
|
+
access_token_lifetime: AGENT_GRANT_TTL_SECONDS,
|
|
342
|
+
// ====== OIDC-A / BOTCHA Extensions ======
|
|
343
|
+
// BOTCHA-specific agent attestation endpoint
|
|
344
|
+
agent_attestation_endpoint: `${baseUrl}/v1/attestation/eat`,
|
|
345
|
+
// OIDC-A enrichment endpoint for auth servers
|
|
346
|
+
oidc_agent_claims_endpoint: `${baseUrl}/v1/attestation/oidc-agent-claims`,
|
|
347
|
+
// UserInfo endpoint (OIDC-A compliant)
|
|
348
|
+
userinfo_endpoint: `${baseUrl}/v1/oidc/userinfo`,
|
|
349
|
+
// EAT profile URI
|
|
350
|
+
eat_profile: BOTCHA_EAT_PROFILE,
|
|
351
|
+
// Well-known agent capabilities this AS can attest
|
|
352
|
+
agent_capabilities_supported: BOTCHA_AGENT_CAPABILITIES,
|
|
353
|
+
// Verification methods BOTCHA uses
|
|
354
|
+
agent_verification_methods_supported: [
|
|
355
|
+
'botcha:speed-challenge',
|
|
356
|
+
'botcha:hybrid-challenge',
|
|
357
|
+
'botcha:reasoning-challenge',
|
|
358
|
+
],
|
|
359
|
+
// Human oversight support
|
|
360
|
+
human_oversight_supported: true,
|
|
361
|
+
oversight_polling_endpoint: `${baseUrl}/v1/auth/agent-grant/{id}/status`,
|
|
362
|
+
// draft-aap-oauth-profile compliance
|
|
363
|
+
aap_version: 'draft-aap-oauth-profile-00',
|
|
364
|
+
// OIDC-A compliance indicator
|
|
365
|
+
oidca_supported: true,
|
|
366
|
+
// Delegation chain support
|
|
367
|
+
delegation_supported: true,
|
|
368
|
+
max_delegation_depth: 5,
|
|
369
|
+
// Integration metadata
|
|
370
|
+
integration: {
|
|
371
|
+
documentation: `${baseUrl}/docs`,
|
|
372
|
+
openapi: `${baseUrl}/openapi.json`,
|
|
373
|
+
ai_txt: `${baseUrl}/ai.txt`,
|
|
374
|
+
whitepaper: `${baseUrl}/whitepaper`,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Verify and decode a BOTCHA EAT token.
|
|
380
|
+
* Used by the UserInfo endpoint to extract agent identity.
|
|
381
|
+
*
|
|
382
|
+
* @param eatJwt - The EAT JWT to verify
|
|
383
|
+
* @param publicKey - ES256 public key JWK
|
|
384
|
+
* @returns Verified EAT payload or null
|
|
385
|
+
*/
|
|
386
|
+
export async function verifyEAT(eatJwt, publicKey) {
|
|
387
|
+
try {
|
|
388
|
+
const cryptoKey = (await importJWK(publicKey, 'ES256'));
|
|
389
|
+
const { payload } = await jwtVerify(eatJwt, cryptoKey, {
|
|
390
|
+
algorithms: ['ES256'],
|
|
391
|
+
issuer: BOTCHA_ISSUER,
|
|
392
|
+
});
|
|
393
|
+
// Validate required EAT claims
|
|
394
|
+
if (!payload.eat_profile || !payload.eat_nonce || !payload.ueid) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
return payload;
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Poll the status of a pending human-in-the-loop grant.
|
|
405
|
+
*
|
|
406
|
+
* @param grantId - The grant ID to poll
|
|
407
|
+
* @param kv - KV namespace
|
|
408
|
+
* @returns Current grant status or null if not found
|
|
409
|
+
*/
|
|
410
|
+
export async function getGrantStatus(grantId, kv) {
|
|
411
|
+
try {
|
|
412
|
+
const data = await kv.get(`agent_grant:${grantId}`);
|
|
413
|
+
if (!data)
|
|
414
|
+
return null;
|
|
415
|
+
return JSON.parse(data);
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Approve or deny a pending agent grant (admin action).
|
|
423
|
+
*
|
|
424
|
+
* @param grantId - The grant ID
|
|
425
|
+
* @param decision - 'approved' or 'denied'
|
|
426
|
+
* @param reason - Optional denial reason
|
|
427
|
+
* @param kv - KV namespace
|
|
428
|
+
*/
|
|
429
|
+
export async function resolveGrant(grantId, decision, reason, kv) {
|
|
430
|
+
const grant = await getGrantStatus(grantId, kv);
|
|
431
|
+
if (!grant) {
|
|
432
|
+
return { success: false, error: 'Grant not found or expired' };
|
|
433
|
+
}
|
|
434
|
+
if (grant.status !== 'pending') {
|
|
435
|
+
return { success: false, error: `Grant is already ${grant.status}` };
|
|
436
|
+
}
|
|
437
|
+
const updated = {
|
|
438
|
+
...grant,
|
|
439
|
+
status: decision,
|
|
440
|
+
approved_at: decision === 'approved' ? Date.now() : undefined,
|
|
441
|
+
denied_at: decision === 'denied' ? Date.now() : undefined,
|
|
442
|
+
denial_reason: decision === 'denied' ? reason : undefined,
|
|
443
|
+
};
|
|
444
|
+
// Preserve the original grant expiry rather than resetting to the full TTL.
|
|
445
|
+
// requested_at is stored in ms; KV expirationTtl is in seconds.
|
|
446
|
+
const elapsedSeconds = Math.floor((Date.now() - grant.requested_at) / 1000);
|
|
447
|
+
const remainingTtl = Math.max(1, AGENT_GRANT_TTL_SECONDS - elapsedSeconds);
|
|
448
|
+
await kv.put(`agent_grant:${grantId}`, JSON.stringify(updated), {
|
|
449
|
+
expirationTtl: remainingTtl,
|
|
450
|
+
});
|
|
451
|
+
return { success: true, grant: updated };
|
|
452
|
+
}
|
|
453
|
+
export default {
|
|
454
|
+
issueEAT,
|
|
455
|
+
buildOIDCAgentClaims,
|
|
456
|
+
issueAgentGrant,
|
|
457
|
+
buildOAuthASMetadata,
|
|
458
|
+
verifyEAT,
|
|
459
|
+
getGrantStatus,
|
|
460
|
+
resolveGrant,
|
|
461
|
+
};
|
package/dist/tap-routes.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export declare function registerTAPAgentRoute(c: Context): Promise<(Response & i
|
|
|
21
21
|
success: false;
|
|
22
22
|
error: string;
|
|
23
23
|
message: string;
|
|
24
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
25
|
+
success: false;
|
|
26
|
+
error: string;
|
|
27
|
+
message: string;
|
|
28
|
+
}, 403, "json">) | (Response & import("hono").TypedResponse<{
|
|
29
|
+
success: false;
|
|
30
|
+
error: string;
|
|
31
|
+
message: string;
|
|
24
32
|
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
25
33
|
success: true;
|
|
26
34
|
agent_id: string;
|
|
@@ -30,7 +38,7 @@ export declare function registerTAPAgentRoute(c: Context): Promise<(Response & i
|
|
|
30
38
|
version: string | undefined;
|
|
31
39
|
created_at: string;
|
|
32
40
|
tap_enabled: boolean | undefined;
|
|
33
|
-
trust_level: "
|
|
41
|
+
trust_level: "basic" | "verified" | "enterprise" | undefined;
|
|
34
42
|
capabilities: {
|
|
35
43
|
action: import("./tap-agents.js").TAPAction;
|
|
36
44
|
scope?: string[] | undefined;
|
|
@@ -40,8 +48,12 @@ export declare function registerTAPAgentRoute(c: Context): Promise<(Response & i
|
|
|
40
48
|
rate_limit?: number | undefined;
|
|
41
49
|
} | undefined;
|
|
42
50
|
}[] | undefined;
|
|
43
|
-
signature_algorithm: "
|
|
51
|
+
signature_algorithm: "ecdsa-p256-sha256" | "rsa-pss-sha256" | "ed25519" | undefined;
|
|
44
52
|
issuer: string | undefined;
|
|
53
|
+
did: string | null;
|
|
54
|
+
ans_name: string | null;
|
|
55
|
+
ans_badge_id: string | null;
|
|
56
|
+
ans_trust_level: "domain-validated" | "key-validated" | "behavior-validated" | null;
|
|
45
57
|
has_public_key: boolean;
|
|
46
58
|
key_fingerprint: string | undefined;
|
|
47
59
|
}, 201, "json">)>;
|
|
@@ -66,7 +78,7 @@ export declare function getTAPAgentRoute(c: Context): Promise<(Response & import
|
|
|
66
78
|
version: string | undefined;
|
|
67
79
|
created_at: string;
|
|
68
80
|
tap_enabled: boolean | undefined;
|
|
69
|
-
trust_level: "
|
|
81
|
+
trust_level: "basic" | "verified" | "enterprise" | undefined;
|
|
70
82
|
capabilities: {
|
|
71
83
|
action: import("./tap-agents.js").TAPAction;
|
|
72
84
|
scope?: string[] | undefined;
|
|
@@ -76,9 +88,13 @@ export declare function getTAPAgentRoute(c: Context): Promise<(Response & import
|
|
|
76
88
|
rate_limit?: number | undefined;
|
|
77
89
|
} | undefined;
|
|
78
90
|
}[] | undefined;
|
|
79
|
-
signature_algorithm: "
|
|
91
|
+
signature_algorithm: "ecdsa-p256-sha256" | "rsa-pss-sha256" | "ed25519" | undefined;
|
|
80
92
|
issuer: string | undefined;
|
|
81
93
|
last_verified_at: string | null;
|
|
94
|
+
ans_name: string | null;
|
|
95
|
+
ans_badge_id: string | null;
|
|
96
|
+
ans_trust_level: "domain-validated" | "key-validated" | "behavior-validated" | null;
|
|
97
|
+
ans_verified_at: string | null;
|
|
82
98
|
has_public_key: boolean;
|
|
83
99
|
key_fingerprint: string | undefined;
|
|
84
100
|
public_key: string | undefined;
|
|
@@ -108,7 +124,7 @@ export declare function listTAPAgentsRoute(c: Context): Promise<(Response & impo
|
|
|
108
124
|
version: string | undefined;
|
|
109
125
|
created_at: string;
|
|
110
126
|
tap_enabled: boolean | undefined;
|
|
111
|
-
trust_level: "
|
|
127
|
+
trust_level: "basic" | "verified" | "enterprise" | undefined;
|
|
112
128
|
capabilities: {
|
|
113
129
|
action: import("./tap-agents.js").TAPAction;
|
|
114
130
|
scope?: string[] | undefined;
|
|
@@ -229,7 +245,7 @@ export declare function rotateKeyRoute(c: Context): Promise<(Response & import("
|
|
|
229
245
|
agent_id: string;
|
|
230
246
|
message: string;
|
|
231
247
|
has_public_key: true;
|
|
232
|
-
signature_algorithm: "
|
|
248
|
+
signature_algorithm: "ecdsa-p256-sha256" | "rsa-pss-sha256" | "ed25519" | undefined;
|
|
233
249
|
key_created_at: string;
|
|
234
250
|
key_expires_at: string | null;
|
|
235
251
|
key_fingerprint: string | undefined;
|
|
@@ -264,7 +280,7 @@ export declare function createInvoiceRoute(c: Context): Promise<(Response & impo
|
|
|
264
280
|
currency?: string | undefined;
|
|
265
281
|
card_acceptor_id?: string | undefined;
|
|
266
282
|
description?: string | undefined;
|
|
267
|
-
status?: "
|
|
283
|
+
status?: "fulfilled" | "pending" | "expired" | undefined;
|
|
268
284
|
success: true;
|
|
269
285
|
}, 201, "json">)>;
|
|
270
286
|
/**
|
|
@@ -289,7 +305,7 @@ export declare function getInvoiceRoute(c: Context): Promise<(Response & import(
|
|
|
289
305
|
currency?: string | undefined;
|
|
290
306
|
card_acceptor_id?: string | undefined;
|
|
291
307
|
description?: string | undefined;
|
|
292
|
-
status?: "
|
|
308
|
+
status?: "fulfilled" | "pending" | "expired" | undefined;
|
|
293
309
|
success: true;
|
|
294
310
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
295
311
|
success: false;
|
package/dist/tap-routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tap-routes.d.ts","sourceRoot":"","sources":["../src/tap-routes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"tap-routes.d.ts","sourceRoot":"","sources":["../src/tap-routes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAoNpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkIrD;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA+DhD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAmDlD;AAID;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAoHrD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2ClD;AAID;;;GAGG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAuF9C;AAID;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;kBAwClD;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;kBAuB/C;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA6E9C;AAID;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqCnD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsClD;;;;;;;;;;;;;;AAaD,wBAYE"}
|