@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
package/README.md
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
# @dupecom/botcha-cloudflare
|
|
2
2
|
|
|
3
3
|
> **BOTCHA** - Prove you're a bot. Humans need not apply.
|
|
4
|
-
>
|
|
5
|
-
> **Cloudflare Workers Edition v0.
|
|
4
|
+
>
|
|
5
|
+
> **Cloudflare Workers Edition v0.22.0** - Identity layer for AI agents
|
|
6
6
|
|
|
7
7
|
Reverse CAPTCHA that verifies AI agents and blocks humans. Running at the edge.
|
|
8
8
|
|
|
9
|
-
## What's New in v0.
|
|
9
|
+
## What's New in v0.22.0
|
|
10
10
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **Server-side SDKs** - @dupecom/botcha-verify (TS) + botcha-verify (Python)
|
|
11
|
+
- **x402 Payment Gating** — Agents pay $0.001 USDC on Base for a BOTCHA token. No puzzle. (`GET /v1/x402/challenge`)
|
|
12
|
+
- **ANS Integration** — DNS-based agent identity lookup and BOTCHA-issued ownership badges. (`GET /v1/ans/resolve/:name`)
|
|
13
|
+
- **DID/VC Issuer** — BOTCHA issues portable W3C Verifiable Credential JWTs. (`POST /v1/credentials/issue`)
|
|
14
|
+
- **A2A Agent Card Attestation** *(coming soon, PR #26)*
|
|
15
|
+
- **OIDC-A Attestation** *(coming soon, PR #28)*
|
|
17
16
|
|
|
18
17
|
## Features
|
|
19
18
|
|
|
@@ -97,10 +96,76 @@ Rate limit headers:
|
|
|
97
96
|
| `/v1/challenges/:id/verify` | POST | Verify challenge (no JWT) |
|
|
98
97
|
| `/v1/token` | GET | Get challenge for JWT flow |
|
|
99
98
|
| `/v1/token/verify` | POST | Verify challenge → get JWT token |
|
|
99
|
+
| `/v1/token/refresh` | POST | Refresh access token |
|
|
100
|
+
| `/v1/token/revoke` | POST | Revoke token immediately |
|
|
101
|
+
| `/v1/token/validate` | POST | Remote token validation (no shared secret) |
|
|
100
102
|
| `/v1/challenge/stream` | GET | SSE streaming challenge (AI-native) |
|
|
101
103
|
| `/v1/challenge/stream/:session` | POST | SSE action handler (go, solve) |
|
|
102
104
|
| `/agent-only` | GET | Protected endpoint (requires JWT) |
|
|
103
105
|
|
|
106
|
+
### Well-Known Endpoints
|
|
107
|
+
|
|
108
|
+
| Endpoint | Method | Description |
|
|
109
|
+
|----------|--------|-------------|
|
|
110
|
+
| `/.well-known/did.json` | GET | BOTCHA DID Document (`did:web:botcha.ai`) |
|
|
111
|
+
| `/.well-known/jwks` | GET | JWK Set (TAP agent keys + DID signing keys) |
|
|
112
|
+
| `/.well-known/jwks.json` | GET | JWK Set alias |
|
|
113
|
+
| `/.well-known/ai-plugin.json` | GET | ChatGPT plugin manifest |
|
|
114
|
+
|
|
115
|
+
### x402 Payment Gating
|
|
116
|
+
|
|
117
|
+
| Endpoint | Method | Auth | Description |
|
|
118
|
+
|----------|--------|------|-------------|
|
|
119
|
+
| `/v1/x402/info` | GET | public | Payment config discovery |
|
|
120
|
+
| `/v1/x402/challenge` | GET | public / X-Payment | Pay $0.001 USDC → BOTCHA token |
|
|
121
|
+
| `/v1/x402/verify-payment` | POST | Bearer | Verify x402 payment proof |
|
|
122
|
+
| `/v1/x402/webhook` | POST | — | Settlement notifications |
|
|
123
|
+
| `/agent-only/x402` | GET | Bearer + x402 | Demo: requires both BOTCHA token + payment |
|
|
124
|
+
|
|
125
|
+
### ANS (Agent Name Service)
|
|
126
|
+
|
|
127
|
+
| Endpoint | Method | Auth | Description |
|
|
128
|
+
|----------|--------|------|-------------|
|
|
129
|
+
| `/v1/ans/botcha` | GET | public | BOTCHA's ANS identity |
|
|
130
|
+
| `/v1/ans/resolve/:name` | GET | public | DNS-based ANS lookup |
|
|
131
|
+
| `/v1/ans/resolve/lookup` | GET | public | ANS lookup via `?name=` query param |
|
|
132
|
+
| `/v1/ans/discover` | GET | public | List BOTCHA-verified ANS agents |
|
|
133
|
+
| `/v1/ans/nonce/:name` | GET | Bearer | Nonce for ownership proof |
|
|
134
|
+
| `/v1/ans/verify` | POST | Bearer | Verify ANS ownership → BOTCHA badge |
|
|
135
|
+
|
|
136
|
+
### DID/VC Issuer
|
|
137
|
+
|
|
138
|
+
| Endpoint | Method | Auth | Description |
|
|
139
|
+
|----------|--------|------|-------------|
|
|
140
|
+
| `/v1/credentials/issue` | POST | Bearer | Issue W3C VC JWT |
|
|
141
|
+
| `/v1/credentials/verify` | POST | public | Verify any BOTCHA-issued VC JWT |
|
|
142
|
+
| `/v1/dids/:did/resolve` | GET | public | Resolve `did:web` DIDs |
|
|
143
|
+
|
|
144
|
+
### A2A Agent Card Attestation *(coming soon — PR #26)*
|
|
145
|
+
|
|
146
|
+
| Endpoint | Method | Auth | Description |
|
|
147
|
+
|----------|--------|------|-------------|
|
|
148
|
+
| `/.well-known/agent.json` | GET | public | BOTCHA's A2A Agent Card |
|
|
149
|
+
| `/v1/a2a/agent-card` | GET | public | BOTCHA's A2A Agent Card (alias) |
|
|
150
|
+
| `/v1/a2a/attest` | POST | Bearer | Attest an agent's A2A card |
|
|
151
|
+
| `/v1/a2a/verify-card` | POST | public | Verify an attested card |
|
|
152
|
+
| `/v1/a2a/verify-agent` | POST | public | Verify agent by card or `agent_url` |
|
|
153
|
+
| `/v1/a2a/trust-level/:agent_url` | GET | public | Get trust level for agent URL |
|
|
154
|
+
| `/v1/a2a/cards` | GET | public | Registry browse |
|
|
155
|
+
| `/v1/a2a/cards/:id` | GET | public | Get specific card by ID |
|
|
156
|
+
|
|
157
|
+
### OIDC-A Attestation *(coming soon — PR #28)*
|
|
158
|
+
|
|
159
|
+
| Endpoint | Method | Auth | Description |
|
|
160
|
+
|----------|--------|------|-------------|
|
|
161
|
+
| `/.well-known/oauth-authorization-server` | GET | public | OAuth/OIDC-A discovery |
|
|
162
|
+
| `/v1/attestation/eat` | POST | Bearer | Issue Entity Attestation Token (EAT/RFC 9711) |
|
|
163
|
+
| `/v1/attestation/oidc-agent-claims` | POST | Bearer | Issue OIDC-A agent claims block |
|
|
164
|
+
| `/v1/auth/agent-grant` | POST | Bearer | Agent grant flow (OAuth2-style) |
|
|
165
|
+
| `/v1/auth/agent-grant/:id/status` | GET | Bearer | Grant status |
|
|
166
|
+
| `/v1/auth/agent-grant/:id/resolve` | POST | Bearer | Approve/resolve grant |
|
|
167
|
+
| `/v1/oidc/userinfo` | GET | Bearer | OIDC-A UserInfo |
|
|
168
|
+
|
|
104
169
|
### SSE Streaming (AI-Native)
|
|
105
170
|
|
|
106
171
|
For AI agents that prefer conversational flows, BOTCHA offers Server-Sent Events streaming:
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Identity Authentication
|
|
3
|
+
*
|
|
4
|
+
* Allows a registered TAP agent to prove its identity by signing a nonce
|
|
5
|
+
* with its Ed25519 private key. Returns a JWT containing both app_id and
|
|
6
|
+
* agent_id — so callers know exactly which agent they're talking to.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. POST /v1/agents/auth { agent_id }
|
|
10
|
+
* → { challenge_id, nonce, message, expires_in: 60 }
|
|
11
|
+
*
|
|
12
|
+
* 2. Agent signs the nonce bytes with its Ed25519 private key
|
|
13
|
+
*
|
|
14
|
+
* 3. POST /v1/agents/auth/verify { challenge_id, agent_id, signature }
|
|
15
|
+
* → { access_token, agent_id, app_id, expires_in: 3600 }
|
|
16
|
+
*
|
|
17
|
+
* The nonce is a random 32-byte hex string stored in KV with a 60-second TTL.
|
|
18
|
+
* The signature is base64-encoded Ed25519 signature over the raw nonce bytes.
|
|
19
|
+
*
|
|
20
|
+
* Why this matters:
|
|
21
|
+
* A challenge-verified JWT (from /v1/token) only proves "I am an AI agent
|
|
22
|
+
* for app X". An agent-auth JWT proves "I am specifically agent Y for app X".
|
|
23
|
+
* The private key is the agent's persistent credential — the operator stores
|
|
24
|
+
* it and provides it to the agent at the start of each session.
|
|
25
|
+
*/
|
|
26
|
+
import type { Context } from 'hono';
|
|
27
|
+
type KVNamespace = {
|
|
28
|
+
get(key: string, type?: string): Promise<string | null>;
|
|
29
|
+
put(key: string, value: string, opts?: {
|
|
30
|
+
expirationTtl?: number;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
delete(key: string): Promise<void>;
|
|
33
|
+
list(opts?: {
|
|
34
|
+
prefix?: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
keys: {
|
|
37
|
+
name: string;
|
|
38
|
+
}[];
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
type Bindings = {
|
|
42
|
+
AGENTS: KVNamespace;
|
|
43
|
+
CHALLENGES: KVNamespace;
|
|
44
|
+
JWT_SECRET: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* POST /v1/agents/auth/provider
|
|
48
|
+
* Re-identify using the agent's provider API key (Anthropic, OpenAI, etc).
|
|
49
|
+
* The key is never stored — only its SHA-256 hash is compared.
|
|
50
|
+
*
|
|
51
|
+
* Body: { provider: 'anthropic' | 'openai' | ..., api_key: 'sk-ant-...', app_id: '...' }
|
|
52
|
+
*/
|
|
53
|
+
export declare function handleAgentAuthProvider(c: Context<{
|
|
54
|
+
Bindings: Bindings & {
|
|
55
|
+
APP_ID?: string;
|
|
56
|
+
};
|
|
57
|
+
}>): Promise<(Response & import("hono").TypedResponse<{
|
|
58
|
+
success: false;
|
|
59
|
+
error: string;
|
|
60
|
+
message: string;
|
|
61
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
62
|
+
success: false;
|
|
63
|
+
error: string;
|
|
64
|
+
message: string;
|
|
65
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
66
|
+
success: true;
|
|
67
|
+
access_token: string;
|
|
68
|
+
token_type: string;
|
|
69
|
+
agent_id: string;
|
|
70
|
+
app_id: any;
|
|
71
|
+
provider: any;
|
|
72
|
+
expires_in: number;
|
|
73
|
+
message: string;
|
|
74
|
+
usage: {
|
|
75
|
+
header: string;
|
|
76
|
+
};
|
|
77
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
78
|
+
export declare function handleAgentAuthChallenge(c: Context<{
|
|
79
|
+
Bindings: Bindings;
|
|
80
|
+
}>): Promise<(Response & import("hono").TypedResponse<{
|
|
81
|
+
success: false;
|
|
82
|
+
error: string;
|
|
83
|
+
message: string;
|
|
84
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
85
|
+
success: false;
|
|
86
|
+
error: string;
|
|
87
|
+
message: string;
|
|
88
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
89
|
+
success: true;
|
|
90
|
+
challenge_id: string;
|
|
91
|
+
nonce: string;
|
|
92
|
+
agent_id: string;
|
|
93
|
+
expires_in: number;
|
|
94
|
+
message: string;
|
|
95
|
+
instructions: {
|
|
96
|
+
what_to_sign: string;
|
|
97
|
+
signature_format: string;
|
|
98
|
+
next_step: string;
|
|
99
|
+
};
|
|
100
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
101
|
+
export declare function handleAgentAuthVerify(c: Context<{
|
|
102
|
+
Bindings: Bindings;
|
|
103
|
+
}>): Promise<(Response & import("hono").TypedResponse<{
|
|
104
|
+
success: false;
|
|
105
|
+
error: string;
|
|
106
|
+
message: string;
|
|
107
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
108
|
+
success: false;
|
|
109
|
+
error: string;
|
|
110
|
+
message: string;
|
|
111
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
112
|
+
success: false;
|
|
113
|
+
error: string;
|
|
114
|
+
message: string;
|
|
115
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
116
|
+
success: true;
|
|
117
|
+
access_token: string;
|
|
118
|
+
token_type: string;
|
|
119
|
+
agent_id: any;
|
|
120
|
+
app_id: any;
|
|
121
|
+
expires_in: number;
|
|
122
|
+
message: string;
|
|
123
|
+
usage: {
|
|
124
|
+
header: string;
|
|
125
|
+
note: string;
|
|
126
|
+
};
|
|
127
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
128
|
+
export {};
|
|
129
|
+
//# sourceMappingURL=agent-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-auth.d.ts","sourceRoot":"","sources":["../src/agent-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAIpC,KAAK,WAAW,GAAG;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC;AAC/Q,KAAK,QAAQ,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AA0BrF;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;;;;;;;;;;;;;;;;;;;;oEA+CrG;AAID,wBAAsB,wBAAwB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;;;;;;;;;;;;;;;;;;;;oEA0ChF;AAID,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;oEAoD7E"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Identity Authentication
|
|
3
|
+
*
|
|
4
|
+
* Allows a registered TAP agent to prove its identity by signing a nonce
|
|
5
|
+
* with its Ed25519 private key. Returns a JWT containing both app_id and
|
|
6
|
+
* agent_id — so callers know exactly which agent they're talking to.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. POST /v1/agents/auth { agent_id }
|
|
10
|
+
* → { challenge_id, nonce, message, expires_in: 60 }
|
|
11
|
+
*
|
|
12
|
+
* 2. Agent signs the nonce bytes with its Ed25519 private key
|
|
13
|
+
*
|
|
14
|
+
* 3. POST /v1/agents/auth/verify { challenge_id, agent_id, signature }
|
|
15
|
+
* → { access_token, agent_id, app_id, expires_in: 3600 }
|
|
16
|
+
*
|
|
17
|
+
* The nonce is a random 32-byte hex string stored in KV with a 60-second TTL.
|
|
18
|
+
* The signature is base64-encoded Ed25519 signature over the raw nonce bytes.
|
|
19
|
+
*
|
|
20
|
+
* Why this matters:
|
|
21
|
+
* A challenge-verified JWT (from /v1/token) only proves "I am an AI agent
|
|
22
|
+
* for app X". An agent-auth JWT proves "I am specifically agent Y for app X".
|
|
23
|
+
* The private key is the agent's persistent credential — the operator stores
|
|
24
|
+
* it and provides it to the agent at the start of each session.
|
|
25
|
+
*/
|
|
26
|
+
import { SignJWT } from 'jose';
|
|
27
|
+
import { getTAPAgent, listTAPAgents } from './tap-agents';
|
|
28
|
+
const SUPPORTED_PROVIDERS = ['anthropic', 'openai', 'google', 'mistral', 'cohere', 'other'];
|
|
29
|
+
/** SHA-256 hash of a string, returned as hex */
|
|
30
|
+
async function sha256hex(input) {
|
|
31
|
+
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
|
|
32
|
+
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
33
|
+
}
|
|
34
|
+
/** Issue an agent-identity JWT (shared logic) */
|
|
35
|
+
async function issueAgentToken(jwtSecret, agent_id, app_id) {
|
|
36
|
+
const secret = new TextEncoder().encode(jwtSecret);
|
|
37
|
+
return new SignJWT({ type: 'botcha-agent-identity', agent_id, app_id })
|
|
38
|
+
.setProtectedHeader({ alg: 'HS256' })
|
|
39
|
+
.setSubject(agent_id)
|
|
40
|
+
.setIssuer('botcha.ai')
|
|
41
|
+
.setIssuedAt()
|
|
42
|
+
.setExpirationTime('1h')
|
|
43
|
+
.setJti(crypto.randomUUID())
|
|
44
|
+
.sign(secret);
|
|
45
|
+
}
|
|
46
|
+
// ============ PROVIDER KEY AUTH ============
|
|
47
|
+
/**
|
|
48
|
+
* POST /v1/agents/auth/provider
|
|
49
|
+
* Re-identify using the agent's provider API key (Anthropic, OpenAI, etc).
|
|
50
|
+
* The key is never stored — only its SHA-256 hash is compared.
|
|
51
|
+
*
|
|
52
|
+
* Body: { provider: 'anthropic' | 'openai' | ..., api_key: 'sk-ant-...', app_id: '...' }
|
|
53
|
+
*/
|
|
54
|
+
export async function handleAgentAuthProvider(c) {
|
|
55
|
+
const body = await c.req.json().catch(() => ({}));
|
|
56
|
+
const { provider, api_key, app_id } = body ?? {};
|
|
57
|
+
if (!provider || !api_key || !app_id) {
|
|
58
|
+
return c.json({
|
|
59
|
+
success: false,
|
|
60
|
+
error: 'MISSING_FIELDS',
|
|
61
|
+
message: 'provider, api_key, and app_id are required',
|
|
62
|
+
supported_providers: SUPPORTED_PROVIDERS,
|
|
63
|
+
}, 400);
|
|
64
|
+
}
|
|
65
|
+
if (!SUPPORTED_PROVIDERS.includes(provider)) {
|
|
66
|
+
return c.json({ success: false, error: 'UNSUPPORTED_PROVIDER', message: `Unsupported provider. Use one of: ${SUPPORTED_PROVIDERS.join(', ')}` }, 400);
|
|
67
|
+
}
|
|
68
|
+
// Hash the provided key
|
|
69
|
+
const keyHash = await sha256hex(api_key.trim());
|
|
70
|
+
// Scan agents for this app to find a matching hash
|
|
71
|
+
// Uses the app_agents index for efficiency
|
|
72
|
+
const listResult = await listTAPAgents(c.env.AGENTS, app_id);
|
|
73
|
+
const agents = listResult?.agents ?? [];
|
|
74
|
+
const match = agents.find((a) => a.provider === provider && a.provider_key_hash === keyHash);
|
|
75
|
+
if (!match) {
|
|
76
|
+
return c.json({
|
|
77
|
+
success: false,
|
|
78
|
+
error: 'AGENT_NOT_FOUND',
|
|
79
|
+
message: `No agent found for this ${provider} API key on app ${app_id}. Register first via POST /v1/agents/register/tap with provider + api_key.`,
|
|
80
|
+
}, 404);
|
|
81
|
+
}
|
|
82
|
+
const access_token = await issueAgentToken(c.env.JWT_SECRET, match.agent_id, app_id);
|
|
83
|
+
return c.json({
|
|
84
|
+
success: true,
|
|
85
|
+
access_token,
|
|
86
|
+
token_type: 'Bearer',
|
|
87
|
+
agent_id: match.agent_id,
|
|
88
|
+
app_id,
|
|
89
|
+
provider,
|
|
90
|
+
expires_in: 3600,
|
|
91
|
+
message: `Identity verified via ${provider} API key. This token proves you are agent ${match.agent_id}.`,
|
|
92
|
+
usage: { header: 'Authorization: Bearer <access_token>' },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// ============ STEP 1: Issue nonce challenge ============
|
|
96
|
+
export async function handleAgentAuthChallenge(c) {
|
|
97
|
+
const body = await c.req.json().catch(() => ({}));
|
|
98
|
+
const agent_id = body?.agent_id;
|
|
99
|
+
if (!agent_id || typeof agent_id !== 'string') {
|
|
100
|
+
return c.json({ success: false, error: 'MISSING_AGENT_ID', message: 'agent_id is required' }, 400);
|
|
101
|
+
}
|
|
102
|
+
// Look up TAP agent — must have a registered public key
|
|
103
|
+
const result = await getTAPAgent(c.env.AGENTS, agent_id);
|
|
104
|
+
if (!result.success || !result.agent) {
|
|
105
|
+
return c.json({ success: false, error: 'AGENT_NOT_FOUND', message: 'No TAP agent found with that agent_id. Register a keypair first via POST /v1/agents/register/tap' }, 404);
|
|
106
|
+
}
|
|
107
|
+
if (!result.agent.public_key) {
|
|
108
|
+
return c.json({ success: false, error: 'NO_PUBLIC_KEY', message: 'This agent has no registered public key. Re-register via POST /v1/agents/register/tap with a public_key' }, 400);
|
|
109
|
+
}
|
|
110
|
+
// Generate a random nonce
|
|
111
|
+
const nonceBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
112
|
+
const nonce = Array.from(nonceBytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
113
|
+
const challenge_id = `agentauth_${crypto.randomUUID()}`;
|
|
114
|
+
// Store: challenge_id → { nonce, agent_id } with 60s TTL
|
|
115
|
+
await c.env.CHALLENGES.put(`agentauth:${challenge_id}`, JSON.stringify({ nonce, agent_id, created_at: Date.now() }), { expirationTtl: 60 });
|
|
116
|
+
return c.json({
|
|
117
|
+
success: true,
|
|
118
|
+
challenge_id,
|
|
119
|
+
nonce,
|
|
120
|
+
agent_id,
|
|
121
|
+
expires_in: 60,
|
|
122
|
+
message: 'Sign the nonce bytes with your Ed25519 private key. Submit base64-encoded signature to POST /v1/agents/auth/verify',
|
|
123
|
+
instructions: {
|
|
124
|
+
what_to_sign: 'The raw nonce string encoded as UTF-8 bytes',
|
|
125
|
+
signature_format: 'base64-encoded Ed25519 signature',
|
|
126
|
+
next_step: 'POST /v1/agents/auth/verify with { challenge_id, agent_id, signature }',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// ============ STEP 2: Verify signature, issue agent JWT ============
|
|
131
|
+
export async function handleAgentAuthVerify(c) {
|
|
132
|
+
const body = await c.req.json().catch(() => ({}));
|
|
133
|
+
const { challenge_id, agent_id, signature } = body ?? {};
|
|
134
|
+
if (!challenge_id || !agent_id || !signature) {
|
|
135
|
+
return c.json({ success: false, error: 'MISSING_FIELDS', message: 'challenge_id, agent_id, and signature are required' }, 400);
|
|
136
|
+
}
|
|
137
|
+
// Retrieve and immediately delete the challenge (one-shot)
|
|
138
|
+
const raw = await c.env.CHALLENGES.get(`agentauth:${challenge_id}`, 'text');
|
|
139
|
+
if (!raw) {
|
|
140
|
+
return c.json({ success: false, error: 'CHALLENGE_NOT_FOUND', message: 'Challenge not found or expired (60s TTL)' }, 404);
|
|
141
|
+
}
|
|
142
|
+
await c.env.CHALLENGES.delete(`agentauth:${challenge_id}`);
|
|
143
|
+
const stored = JSON.parse(raw);
|
|
144
|
+
// Verify agent_id matches what was challenged
|
|
145
|
+
if (stored.agent_id !== agent_id) {
|
|
146
|
+
return c.json({ success: false, error: 'AGENT_MISMATCH', message: 'agent_id does not match the challenged agent' }, 400);
|
|
147
|
+
}
|
|
148
|
+
// Look up TAP agent and public key
|
|
149
|
+
const result = await getTAPAgent(c.env.AGENTS, agent_id);
|
|
150
|
+
if (!result.success || !result.agent?.public_key) {
|
|
151
|
+
return c.json({ success: false, error: 'AGENT_NOT_FOUND', message: 'Agent not found' }, 404);
|
|
152
|
+
}
|
|
153
|
+
const { public_key, signature_algorithm, app_id } = result.agent;
|
|
154
|
+
// Verify the signature
|
|
155
|
+
const valid = await verifyNonceSignature(stored.nonce, signature, public_key, signature_algorithm ?? 'ed25519');
|
|
156
|
+
if (!valid) {
|
|
157
|
+
return c.json({ success: false, error: 'INVALID_SIGNATURE', message: 'Signature verification failed. Ensure you signed the raw nonce string with your registered private key.' }, 401);
|
|
158
|
+
}
|
|
159
|
+
// Issue agent-identity JWT (1 hour)
|
|
160
|
+
const access_token = await issueAgentToken(c.env.JWT_SECRET, agent_id, app_id);
|
|
161
|
+
return c.json({
|
|
162
|
+
success: true,
|
|
163
|
+
access_token,
|
|
164
|
+
token_type: 'Bearer',
|
|
165
|
+
agent_id,
|
|
166
|
+
app_id,
|
|
167
|
+
expires_in: 3600,
|
|
168
|
+
message: 'Identity verified. This token proves you are specifically this agent.',
|
|
169
|
+
usage: {
|
|
170
|
+
header: 'Authorization: Bearer <access_token>',
|
|
171
|
+
note: 'This token contains your agent_id claim. Services can verify your specific identity without you solving a fresh challenge.',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// ============ Signature verification ============
|
|
176
|
+
async function verifyNonceSignature(nonce, signatureB64, publicKey, algorithm) {
|
|
177
|
+
try {
|
|
178
|
+
const sigBytes = Uint8Array.from(atob(signatureB64), c => c.charCodeAt(0));
|
|
179
|
+
const nonceBytes = new TextEncoder().encode(nonce);
|
|
180
|
+
let keyData;
|
|
181
|
+
if (algorithm === 'ed25519') {
|
|
182
|
+
// Accept raw 32-byte base64 key or SPKI DER base64
|
|
183
|
+
const raw = Uint8Array.from(atob(publicKey), c => c.charCodeAt(0));
|
|
184
|
+
if (raw.length === 32) {
|
|
185
|
+
// Raw key — wrap in SPKI
|
|
186
|
+
const spki = new Uint8Array(44);
|
|
187
|
+
// Ed25519 SPKI header (12 bytes)
|
|
188
|
+
spki.set([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00]);
|
|
189
|
+
spki.set(raw, 12);
|
|
190
|
+
keyData = spki.buffer;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
keyData = raw.buffer;
|
|
194
|
+
}
|
|
195
|
+
const cryptoKey = await crypto.subtle.importKey('spki', keyData, { name: 'Ed25519' }, false, ['verify']);
|
|
196
|
+
return await crypto.subtle.verify({ name: 'Ed25519' }, cryptoKey, sigBytes, nonceBytes);
|
|
197
|
+
}
|
|
198
|
+
// ECDSA P-256
|
|
199
|
+
if (algorithm === 'ecdsa-p256-sha256') {
|
|
200
|
+
const raw = Uint8Array.from(atob(publicKey), c => c.charCodeAt(0));
|
|
201
|
+
const cryptoKey = await crypto.subtle.importKey('spki', raw.buffer, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
|
202
|
+
return await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, cryptoKey, sigBytes, nonceBytes);
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
console.error('Agent auth signature verification error:', e);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
package/dist/agents.d.ts
CHANGED
|
@@ -57,6 +57,16 @@ export declare function createAgent(kv: KVNamespace, app_id: string, data: {
|
|
|
57
57
|
* @returns Agent record or null if not found
|
|
58
58
|
*/
|
|
59
59
|
export declare function getAgent(kv: KVNamespace, agent_id: string): Promise<Agent | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Delete an agent and remove it from the app's agent index.
|
|
62
|
+
* Also removes from the tap-agents app_agents:{appId} index if present.
|
|
63
|
+
*
|
|
64
|
+
* @returns true if deleted, false if not found, null on error
|
|
65
|
+
*/
|
|
66
|
+
export declare function deleteAgent(kv: KVNamespace, agent_id: string, app_id: string): Promise<{
|
|
67
|
+
success: boolean;
|
|
68
|
+
error?: string;
|
|
69
|
+
}>;
|
|
60
70
|
/**
|
|
61
71
|
* List all agents for an app
|
|
62
72
|
*
|
package/dist/agents.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,QAAQ,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACtF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAIF;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAOxC;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,QAAQ,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACtF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAIF;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAOxC;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAiDvB;AAED;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,WAAW,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAcvB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,WAAW,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmC/C;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,EAAE,CAAC,CAsBlB"}
|
package/dist/agents.js
CHANGED
|
@@ -60,10 +60,21 @@ export async function createAgent(kv, app_id, data) {
|
|
|
60
60
|
}
|
|
61
61
|
// Add new agent_id to the list
|
|
62
62
|
agentIds.push(agent_id);
|
|
63
|
-
//
|
|
63
|
+
// Read tap-agents index too, keep both in sync
|
|
64
|
+
let tapAgentIds = [];
|
|
65
|
+
try {
|
|
66
|
+
const tapList = await kv.get(`app_agents:${app_id}`, 'text');
|
|
67
|
+
if (tapList)
|
|
68
|
+
tapAgentIds = JSON.parse(tapList);
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
if (!tapAgentIds.includes(agent_id))
|
|
72
|
+
tapAgentIds.push(agent_id);
|
|
73
|
+
// Store agent record and update both index keys
|
|
64
74
|
await Promise.all([
|
|
65
75
|
kv.put(`agent:${agent_id}`, JSON.stringify(agent)),
|
|
66
76
|
kv.put(`agents:${app_id}`, JSON.stringify(agentIds)),
|
|
77
|
+
kv.put(`app_agents:${app_id}`, JSON.stringify(tapAgentIds)),
|
|
67
78
|
]);
|
|
68
79
|
return agent;
|
|
69
80
|
}
|
|
@@ -94,6 +105,45 @@ export async function getAgent(kv, agent_id) {
|
|
|
94
105
|
return null;
|
|
95
106
|
}
|
|
96
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Delete an agent and remove it from the app's agent index.
|
|
110
|
+
* Also removes from the tap-agents app_agents:{appId} index if present.
|
|
111
|
+
*
|
|
112
|
+
* @returns true if deleted, false if not found, null on error
|
|
113
|
+
*/
|
|
114
|
+
export async function deleteAgent(kv, agent_id, app_id) {
|
|
115
|
+
try {
|
|
116
|
+
// Verify agent exists and belongs to the app
|
|
117
|
+
const existing = await kv.get(`agent:${agent_id}`, 'text');
|
|
118
|
+
if (!existing) {
|
|
119
|
+
return { success: false, error: 'Agent not found' };
|
|
120
|
+
}
|
|
121
|
+
const agent = JSON.parse(existing);
|
|
122
|
+
if (agent.app_id !== app_id) {
|
|
123
|
+
return { success: false, error: 'Agent does not belong to this app' };
|
|
124
|
+
}
|
|
125
|
+
// Remove agent record and update both index keys in parallel
|
|
126
|
+
const [agentsRaw, appAgentsRaw] = await Promise.all([
|
|
127
|
+
kv.get(`agents:${app_id}`, 'text'),
|
|
128
|
+
kv.get(`app_agents:${app_id}`, 'text'),
|
|
129
|
+
]);
|
|
130
|
+
const ops = [kv.delete(`agent:${agent_id}`)];
|
|
131
|
+
if (agentsRaw) {
|
|
132
|
+
const ids = JSON.parse(agentsRaw).filter((id) => id !== agent_id);
|
|
133
|
+
ops.push(kv.put(`agents:${app_id}`, JSON.stringify(ids)));
|
|
134
|
+
}
|
|
135
|
+
if (appAgentsRaw) {
|
|
136
|
+
const ids = JSON.parse(appAgentsRaw).filter((id) => id !== agent_id);
|
|
137
|
+
ops.push(kv.put(`app_agents:${app_id}`, JSON.stringify(ids)));
|
|
138
|
+
}
|
|
139
|
+
await Promise.all(ops);
|
|
140
|
+
return { success: true };
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(`Failed to delete agent ${agent_id}:`, error);
|
|
144
|
+
return { success: false, error: 'Internal server error' };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
97
147
|
/**
|
|
98
148
|
* List all agents for an app
|
|
99
149
|
*
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const APP_GATE_OPEN_PATHS: string[];
|
|
2
|
+
export declare function isAppManagementPath(path: string): boolean;
|
|
3
|
+
export declare function isDashboardAuthedPath(path: string, method: string): boolean;
|
|
4
|
+
export declare function isPublicV1Path(path: string): boolean;
|
|
5
|
+
export declare function shouldBypassAppGate(path: string, method?: string): boolean;
|
|
6
|
+
//# sourceMappingURL=app-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-gate.d.ts","sourceRoot":"","sources":["../src/app-gate.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,UAmC/B,CAAC;AAGF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAGD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAM3E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYpD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAc,GAAG,OAAO,CAEjF"}
|
package/dist/app-gate.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Shared app-gate path rules for /v1/* middleware.
|
|
2
|
+
// App gate open paths that do not require app_id.
|
|
3
|
+
export const APP_GATE_OPEN_PATHS = [
|
|
4
|
+
'/v1/apps', // POST: create app (registration)
|
|
5
|
+
'/v1/auth/recover', // POST: account recovery
|
|
6
|
+
'/v1/token/validate', // POST: public token validation — token is credential
|
|
7
|
+
// x402 endpoints: payment is the credential
|
|
8
|
+
'/v1/x402/challenge',
|
|
9
|
+
'/v1/x402/verify-payment',
|
|
10
|
+
'/v1/x402/webhook',
|
|
11
|
+
'/v1/x402/info',
|
|
12
|
+
// Public ANS + DID/VC endpoints under /v1/*
|
|
13
|
+
'/v1/ans/discover',
|
|
14
|
+
'/v1/ans/botcha',
|
|
15
|
+
'/v1/ans/resolve/lookup',
|
|
16
|
+
'/v1/credentials/verify',
|
|
17
|
+
// OIDC-A UserInfo accepts BOTCHA access tokens OR EAT bearer tokens.
|
|
18
|
+
// EAT tokens are not app-gate tokens, so this route must bypass app-gate
|
|
19
|
+
// and perform its own auth checks in the route handler.
|
|
20
|
+
'/v1/oidc/userinfo',
|
|
21
|
+
// Public A2A verification and discovery endpoints
|
|
22
|
+
'/v1/a2a/agent-card',
|
|
23
|
+
'/v1/a2a/verify-card',
|
|
24
|
+
'/v1/a2a/verify-agent',
|
|
25
|
+
'/v1/a2a/cards',
|
|
26
|
+
// Agent identity auth — keypair, provider key, or OAuth refresh
|
|
27
|
+
'/v1/agents/auth',
|
|
28
|
+
'/v1/agents/auth/verify',
|
|
29
|
+
'/v1/agents/auth/provider',
|
|
30
|
+
'/v1/agents/auth/refresh',
|
|
31
|
+
// OAuth device authorization grant (RFC 8628)
|
|
32
|
+
'/v1/oauth/device',
|
|
33
|
+
'/v1/oauth/token',
|
|
34
|
+
'/v1/oauth/approve',
|
|
35
|
+
'/v1/oauth/revoke',
|
|
36
|
+
'/v1/oauth/lookup',
|
|
37
|
+
'/v1/oauth/status',
|
|
38
|
+
];
|
|
39
|
+
// Pattern-match paths that start with /v1/apps/:id/ (verify-email, resend-verification, etc.)
|
|
40
|
+
export function isAppManagementPath(path) {
|
|
41
|
+
return /^\/v1\/apps\/[^/]+\/(verify-email|resend-verification)$/.test(path);
|
|
42
|
+
}
|
|
43
|
+
// Dashboard-authed paths — use session cookie, not app_id bearer token
|
|
44
|
+
export function isDashboardAuthedPath(path, method) {
|
|
45
|
+
// DELETE /v1/agents/:id — session cookie auth via requireDashboardAuth
|
|
46
|
+
if (method === 'DELETE' && /^\/v1\/agents\/[^/]+$/.test(path))
|
|
47
|
+
return true;
|
|
48
|
+
// /device — OAuth agent approval page
|
|
49
|
+
if (path === '/device')
|
|
50
|
+
return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
export function isPublicV1Path(path) {
|
|
54
|
+
// Public ANS resolution paths: /v1/ans/resolve/:name
|
|
55
|
+
if (path.startsWith('/v1/ans/resolve/'))
|
|
56
|
+
return true;
|
|
57
|
+
// Public DID resolution path: /v1/dids/:did/resolve
|
|
58
|
+
if (/^\/v1\/dids\/[^/]+\/resolve$/.test(path))
|
|
59
|
+
return true;
|
|
60
|
+
// Public A2A routes with dynamic path params
|
|
61
|
+
if (path.startsWith('/v1/a2a/cards/'))
|
|
62
|
+
return true;
|
|
63
|
+
if (path.startsWith('/v1/a2a/trust-level/'))
|
|
64
|
+
return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
export function shouldBypassAppGate(path, method = 'GET') {
|
|
68
|
+
return APP_GATE_OPEN_PATHS.includes(path) || isAppManagementPath(path) || isPublicV1Path(path) || isDashboardAuthedPath(path, method);
|
|
69
|
+
}
|
package/dist/apps.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export interface CreateAppResult {
|
|
|
41
41
|
email: string;
|
|
42
42
|
email_verified: boolean;
|
|
43
43
|
verification_required: boolean;
|
|
44
|
+
verification_code: string;
|
|
44
45
|
}
|
|
45
46
|
/**
|
|
46
47
|
* Public app info returned by getApp (excludes secrets and internal fields)
|
|
@@ -53,6 +54,15 @@ export type PublicAppConfig = {
|
|
|
53
54
|
email: string;
|
|
54
55
|
email_verified: boolean;
|
|
55
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Thrown when attempting to register an app with an email that's already in use.
|
|
59
|
+
* Callers should return a 409 Conflict with recovery instructions.
|
|
60
|
+
*/
|
|
61
|
+
export declare class EmailAlreadyRegisteredError extends Error {
|
|
62
|
+
readonly email: string;
|
|
63
|
+
readonly existing_app_id: string;
|
|
64
|
+
constructor(email: string, existing_app_id: string);
|
|
65
|
+
}
|
|
56
66
|
/**
|
|
57
67
|
* Generate a crypto-random app ID
|
|
58
68
|
* Format: 'app_' + 16 hex chars
|
|
@@ -98,11 +108,10 @@ export declare function generateVerificationCode(): string;
|
|
|
98
108
|
*/
|
|
99
109
|
export declare function createApp(kv: KVNamespace, email: string, name?: string): Promise<CreateAppResult>;
|
|
100
110
|
/**
|
|
101
|
-
*
|
|
111
|
+
* Regenerate a new verification code for an app.
|
|
102
112
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* a new code for resend scenarios.
|
|
113
|
+
* Used for resend scenarios when the user needs a fresh code.
|
|
114
|
+
* Returns null if the app doesn't exist or email is already verified.
|
|
106
115
|
*/
|
|
107
116
|
export declare function regenerateVerificationCode(kv: KVNamespace, app_id: string): Promise<{
|
|
108
117
|
code: string;
|