@dupecom/botcha-cloudflare 0.18.0 → 0.20.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 +2 -2
- package/dist/apps.d.ts +6 -2
- package/dist/apps.d.ts.map +1 -1
- package/dist/apps.js +6 -2
- package/dist/auth.d.ts +51 -6
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +107 -30
- 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.map +1 -1
- package/dist/dashboard/layout.js +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 +100 -20
- package/dist/static.d.ts +61 -2
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +86 -21
- 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/package.json +1 -1
package/dist/static.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0OzD;AAED,eAAO,MAAM,UAAU,85CAuDtB,CAAC;AAEF,eAAO,MAAM,MAAM,kzlBA0QlB,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;CAsB1B,CAAC;AAEF,eAAO,MAAM,WAAW,+3BAiCvB,CAAC;AAGF,wBAAgB,qBAAqB,IAAI,MAAM,CAiJ9C;AAGD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAq5C7C"}
|
package/dist/static.js
CHANGED
|
@@ -51,7 +51,7 @@ curl https://botcha.ai/agent-only -H "Authorization: Bearer <token>"
|
|
|
51
51
|
|
|
52
52
|
| Method | Path | Description |
|
|
53
53
|
|--------|------|-------------|
|
|
54
|
-
| \`POST\` | \`/v1/apps\` | Create app (email required) → app_id + app_secret |
|
|
54
|
+
| \`POST\` | \`/v1/apps\` | Create app (email required, name optional) → app_id + app_secret |
|
|
55
55
|
| \`POST\` | \`/v1/agents/register\` | Register agent identity → agent_id |
|
|
56
56
|
| \`GET\` | \`/v1/challenges\` | Get a challenge (hybrid by default) |
|
|
57
57
|
| \`POST\` | \`/v1/challenges/:id/verify\` | Submit solution → JWT token |
|
|
@@ -63,7 +63,7 @@ curl https://botcha.ai/agent-only -H "Authorization: Bearer <token>"
|
|
|
63
63
|
|
|
64
64
|
| Method | Path | Description |
|
|
65
65
|
|--------|------|-------------|
|
|
66
|
-
| \`POST\` | \`/v1/apps\` | Create app (email required,
|
|
66
|
+
| \`POST\` | \`/v1/apps\` | Create app (email required, name optional) → app_id + name + app_secret |
|
|
67
67
|
| \`GET\` | \`/v1/apps/:id\` | Get app info |
|
|
68
68
|
| \`POST\` | \`/v1/apps/:id/verify-email\` | Verify email with 6-digit code |
|
|
69
69
|
| \`POST\` | \`/v1/apps/:id/resend-verification\` | Resend verification email |
|
|
@@ -154,9 +154,10 @@ curl https://botcha.ai/agent-only -H "Authorization: Bearer <token>"
|
|
|
154
154
|
| Method | Path | Description |
|
|
155
155
|
|--------|------|-------------|
|
|
156
156
|
| \`GET\` | \`/v1/token\` | Get challenge for JWT token flow |
|
|
157
|
-
| \`POST\` | \`/v1/token/verify\` | Submit solution → access_token (
|
|
157
|
+
| \`POST\` | \`/v1/token/verify\` | Submit solution → access_token (1hr) + refresh_token (1hr) |
|
|
158
158
|
| \`POST\` | \`/v1/token/refresh\` | Refresh access token |
|
|
159
159
|
| \`POST\` | \`/v1/token/revoke\` | Revoke a token |
|
|
160
|
+
| \`POST\` | \`/v1/token/validate\` | Validate a token remotely (no shared secret needed) |
|
|
160
161
|
|
|
161
162
|
### Dashboard & Auth
|
|
162
163
|
|
|
@@ -180,9 +181,19 @@ curl https://botcha.ai/agent-only -H "Authorization: Bearer <token>"
|
|
|
180
181
|
3. \`POST /v1/token/verify\` — submit solution, receive JWT
|
|
181
182
|
4. Use \`Authorization: Bearer <token>\` on protected endpoints
|
|
182
183
|
|
|
183
|
-
**Token lifetimes:** access_token =
|
|
184
|
+
**Token lifetimes:** access_token = 1 hour, refresh_token = 1 hour
|
|
184
185
|
|
|
185
|
-
**
|
|
186
|
+
**Token signing:** ES256 (ECDSA P-256) asymmetric signing. HS256 supported for backward compatibility.
|
|
187
|
+
|
|
188
|
+
**Features:** audience claims, client IP binding, token revocation, refresh tokens, JWKS public key discovery
|
|
189
|
+
|
|
190
|
+
## Token Verification (for API providers)
|
|
191
|
+
|
|
192
|
+
Three ways to verify incoming BOTCHA tokens:
|
|
193
|
+
|
|
194
|
+
1. **JWKS (Recommended)** — Fetch public keys from \`GET /.well-known/jwks\` and verify ES256 signatures locally. No shared secret needed.
|
|
195
|
+
2. **Remote Validation** — \`POST /v1/token/validate\` with \`{"token": "..."}\`. Simplest approach, no SDK needed.
|
|
196
|
+
3. **Shared Secret (Legacy)** — Verify HS256 tokens with \`BOTCHA_SECRET\`. Requires secret sharing.
|
|
186
197
|
|
|
187
198
|
## RTT-Aware Challenges
|
|
188
199
|
|
|
@@ -200,7 +211,7 @@ Formula: \`timeout = 500ms + (2 × RTT) + 100ms buffer\`
|
|
|
200
211
|
|----------|---------|---------|
|
|
201
212
|
| npm | \`@dupecom/botcha\` | \`npm install @dupecom/botcha\` |
|
|
202
213
|
| PyPI | \`botcha\` | \`pip install botcha\` |
|
|
203
|
-
| Verify (TS) | \`@botcha
|
|
214
|
+
| Verify (TS) | \`@dupecom/botcha-verify\` | \`npm install @dupecom/botcha-verify\` |
|
|
204
215
|
| Verify (Python) | \`botcha-verify\` | \`pip install botcha-verify\` |
|
|
205
216
|
| TAP middleware | \`@dupecom/botcha/middleware\` | \`import { createTAPVerifyMiddleware } from '@dupecom/botcha/middleware'\` |
|
|
206
217
|
|
|
@@ -315,6 +326,7 @@ API-Format: OpenAPI 3.1.0
|
|
|
315
326
|
|
|
316
327
|
# Documentation
|
|
317
328
|
Docs: https://botcha.ai
|
|
329
|
+
Docs: https://botcha.ai/docs
|
|
318
330
|
Docs: https://botcha.ai/whitepaper
|
|
319
331
|
Docs: https://github.com/dupe-com/botcha#readme
|
|
320
332
|
Docs: https://www.npmjs.com/package/@dupecom/botcha
|
|
@@ -327,11 +339,11 @@ Feature: Standard Challenge (5s time limit)
|
|
|
327
339
|
Feature: Hybrid Challenge (speed + reasoning combined)
|
|
328
340
|
Feature: Reasoning Challenge (LLM-only questions, 30s limit)
|
|
329
341
|
Feature: RTT-Aware Fairness (automatic network latency compensation)
|
|
330
|
-
Feature: Token Rotation (
|
|
342
|
+
Feature: Token Rotation (1-hour access tokens + 1-hour refresh tokens)
|
|
331
343
|
Feature: Audience Claims (tokens scoped to specific services)
|
|
332
344
|
Feature: Client IP Binding (optional token-to-IP binding)
|
|
333
345
|
Feature: Token Revocation (invalidate tokens before expiry)
|
|
334
|
-
Feature: Server-Side Verification SDK (@botcha
|
|
346
|
+
Feature: Server-Side Verification SDK (@dupecom/botcha-verify for TS, botcha-verify for Python)
|
|
335
347
|
Feature: Multi-Tenant API Keys (per-app isolation, rate limiting, and token scoping)
|
|
336
348
|
Feature: Per-App Metrics Dashboard (server-rendered at /dashboard, htmx-powered)
|
|
337
349
|
Feature: Email-Tied App Creation (email required, 6-digit verification, account recovery)
|
|
@@ -343,6 +355,9 @@ Feature: TAP Capabilities (action + resource scoping for agent sessions)
|
|
|
343
355
|
Feature: TAP Trust Levels (basic, verified, enterprise)
|
|
344
356
|
Feature: TAP Showcase Homepage (botcha.ai — one of the first services to implement Visa's Trusted Agent Protocol)
|
|
345
357
|
Feature: TAP Full Spec v0.16.0 — Ed25519, RFC 9421 full compliance, JWKS infrastructure, Layer 2 Consumer Recognition, Layer 3 Payment Container, 402 micropayments, CDN edge verification, Visa key federation
|
|
358
|
+
Feature: ES256 Asymmetric JWT Signing v0.19.0 — tokens signed with ES256 (ECDSA P-256), public key discovery via JWKS, HS256 still supported for backward compatibility
|
|
359
|
+
Feature: Remote Token Validation v0.19.0 — POST /v1/token/validate for third-party token verification without shared secrets
|
|
360
|
+
Feature: JWKS Public Key Discovery v0.19.0 — GET /.well-known/jwks exposes BOTCHA signing public keys for offline token verification
|
|
346
361
|
|
|
347
362
|
# Endpoints
|
|
348
363
|
# Challenge Endpoints
|
|
@@ -358,9 +373,10 @@ Endpoint: GET https://botcha.ai/v1/token - Get challenge for JWT token flow
|
|
|
358
373
|
Endpoint: POST https://botcha.ai/v1/token/verify - Verify challenge and receive JWT token
|
|
359
374
|
Endpoint: POST https://botcha.ai/v1/token/refresh - Refresh access token using refresh token
|
|
360
375
|
Endpoint: POST https://botcha.ai/v1/token/revoke - Revoke a token (access or refresh)
|
|
376
|
+
Endpoint: POST https://botcha.ai/v1/token/validate - Validate a BOTCHA token remotely (no shared secret needed)
|
|
361
377
|
|
|
362
378
|
# Multi-Tenant Endpoints
|
|
363
|
-
Endpoint: POST https://botcha.ai/v1/apps - Create new app (email required,
|
|
379
|
+
Endpoint: POST https://botcha.ai/v1/apps - Create new app (email required, name optional) → app_id + name + app_secret
|
|
364
380
|
Endpoint: GET https://botcha.ai/v1/apps/:id - Get app info (with email + verification status)
|
|
365
381
|
Endpoint: POST https://botcha.ai/v1/apps/:id/verify-email - Verify email with 6-digit code
|
|
366
382
|
Endpoint: POST https://botcha.ai/v1/apps/:id/resend-verification - Resend verification email
|
|
@@ -444,7 +460,7 @@ Endpoint: GET https://botcha.ai/agent-only - Protected AI-only resource
|
|
|
444
460
|
# Usage
|
|
445
461
|
Install-NPM: npm install @dupecom/botcha
|
|
446
462
|
Install-Python: pip install botcha
|
|
447
|
-
Verify-NPM: npm install @botcha
|
|
463
|
+
Verify-NPM: npm install @dupecom/botcha-verify
|
|
448
464
|
Verify-Python: pip install botcha-verify
|
|
449
465
|
License: MIT
|
|
450
466
|
|
|
@@ -467,10 +483,14 @@ Content-Negotiation-Example: curl https://botcha.ai -H "Accept: text/markdown"
|
|
|
467
483
|
Content-Negotiation-Benefit: 80% fewer tokens vs HTML — ideal for LLM context windows
|
|
468
484
|
|
|
469
485
|
# JWT TOKEN SECURITY
|
|
486
|
+
Token-Signing: ES256 (ECDSA P-256) asymmetric signing by default. HS256 still supported for backward compatibility.
|
|
487
|
+
Token-JWKS: GET /.well-known/jwks — public keys for offline token verification (no shared secret needed)
|
|
488
|
+
Token-Validate: POST /v1/token/validate with {"token": "<token>"} — remote validation without shared secret
|
|
489
|
+
Token-Verify-Modes: 1. JWKS (recommended, offline) 2. Remote validation (/v1/token/validate) 3. Shared secret (legacy HS256)
|
|
470
490
|
Token-Flow: 1. GET /v1/token (get challenge) → 2. Solve → 3. POST /v1/token/verify (get tokens + human_link)
|
|
471
491
|
Token-Human-Link: /v1/token/verify response includes human_link — give this URL to your human for one-click browser access
|
|
472
|
-
Token-Access-Expiry:
|
|
473
|
-
Token-Refresh-Expiry: 1 hour (use to get new access tokens)
|
|
492
|
+
Token-Access-Expiry: 1 hour
|
|
493
|
+
Token-Refresh-Expiry: 1 hour (use to get new access tokens without re-solving challenges)
|
|
474
494
|
Token-Refresh: POST /v1/token/refresh with {"refresh_token": "<token>"}
|
|
475
495
|
Token-Revoke: POST /v1/token/revoke with {"token": "<token>"}
|
|
476
496
|
Token-Audience: Include {"audience": "<service-url>"} in /v1/token/verify to scope token
|
|
@@ -593,6 +613,11 @@ export const SITEMAP_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
593
613
|
<changefreq>monthly</changefreq>
|
|
594
614
|
<priority>0.9</priority>
|
|
595
615
|
</url>
|
|
616
|
+
<url>
|
|
617
|
+
<loc>https://botcha.ai/docs</loc>
|
|
618
|
+
<changefreq>weekly</changefreq>
|
|
619
|
+
<priority>0.9</priority>
|
|
620
|
+
</url>
|
|
596
621
|
</urlset>
|
|
597
622
|
`;
|
|
598
623
|
// Whitepaper markdown — served at /whitepaper with Accept: text/markdown
|
|
@@ -681,7 +706,7 @@ Inspired by Visa's Trusted Agent Protocol (https://developer.visa.com/capabiliti
|
|
|
681
706
|
|
|
682
707
|
- **Runtime:** Cloudflare Workers (300+ edge locations)
|
|
683
708
|
- **Storage:** Workers KV with TTLs
|
|
684
|
-
- **Tokens:** HMAC-SHA256 JWTs (
|
|
709
|
+
- **Tokens:** HMAC-SHA256 JWTs (1-hr access, 1-hr refresh)
|
|
685
710
|
- **TAP Signatures:** ECDSA P-256 or RSA-PSS SHA-256
|
|
686
711
|
- **Rate Limits:** 100 challenges/hour/app (fail-open)
|
|
687
712
|
|
|
@@ -703,7 +728,7 @@ async with BotchaClient() as client:
|
|
|
703
728
|
|
|
704
729
|
### Server-side Verification
|
|
705
730
|
|
|
706
|
-
Express: \`@botcha
|
|
731
|
+
Express: \`@dupecom/botcha-verify\` · FastAPI/Django: \`botcha-verify\` · Hono middleware included.
|
|
707
732
|
|
|
708
733
|
### CLI
|
|
709
734
|
|
|
@@ -761,7 +786,7 @@ export function getOpenApiSpec(version) {
|
|
|
761
786
|
"x-sdk": {
|
|
762
787
|
npm: "@dupecom/botcha",
|
|
763
788
|
python: "botcha (pip install botcha)",
|
|
764
|
-
verify_npm: "@botcha
|
|
789
|
+
verify_npm: "@dupecom/botcha-verify (server-side verification)",
|
|
765
790
|
verify_python: "botcha-verify (pip install botcha-verify)"
|
|
766
791
|
}
|
|
767
792
|
},
|
|
@@ -935,7 +960,7 @@ export function getOpenApiSpec(version) {
|
|
|
935
960
|
"/v1/token/refresh": {
|
|
936
961
|
post: {
|
|
937
962
|
summary: "Refresh access token",
|
|
938
|
-
description: "Exchange a refresh token for a new
|
|
963
|
+
description: "Exchange a refresh token for a new access token (1 hour). Avoids solving a new challenge.",
|
|
939
964
|
operationId: "refreshToken",
|
|
940
965
|
requestBody: {
|
|
941
966
|
required: true,
|
|
@@ -961,7 +986,7 @@ export function getOpenApiSpec(version) {
|
|
|
961
986
|
properties: {
|
|
962
987
|
"success": { type: "boolean" },
|
|
963
988
|
"access_token": { type: "string" },
|
|
964
|
-
"expires_in": { type: "integer", description: "Token lifetime in seconds (
|
|
989
|
+
"expires_in": { type: "integer", description: "Token lifetime in seconds (3600 = 1 hour)" },
|
|
965
990
|
"token_type": { type: "string", enum: ["Bearer"] }
|
|
966
991
|
}
|
|
967
992
|
}
|
|
@@ -997,6 +1022,44 @@ export function getOpenApiSpec(version) {
|
|
|
997
1022
|
}
|
|
998
1023
|
}
|
|
999
1024
|
},
|
|
1025
|
+
"/v1/token/validate": {
|
|
1026
|
+
post: {
|
|
1027
|
+
summary: "Validate a BOTCHA token remotely",
|
|
1028
|
+
description: "Validate a BOTCHA token without needing the signing secret. Returns the token validity and decoded payload. Supports both ES256 and HS256 tokens.",
|
|
1029
|
+
operationId: "validateToken",
|
|
1030
|
+
requestBody: {
|
|
1031
|
+
required: true,
|
|
1032
|
+
content: {
|
|
1033
|
+
"application/json": {
|
|
1034
|
+
schema: {
|
|
1035
|
+
type: "object",
|
|
1036
|
+
required: ["token"],
|
|
1037
|
+
properties: {
|
|
1038
|
+
"token": { type: "string", description: "The JWT token to validate" }
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
},
|
|
1044
|
+
responses: {
|
|
1045
|
+
"200": {
|
|
1046
|
+
description: "Token validation result",
|
|
1047
|
+
content: {
|
|
1048
|
+
"application/json": {
|
|
1049
|
+
schema: {
|
|
1050
|
+
type: "object",
|
|
1051
|
+
properties: {
|
|
1052
|
+
"valid": { type: "boolean", description: "Whether the token is valid" },
|
|
1053
|
+
"payload": { type: "object", description: "Decoded token payload (if valid)" },
|
|
1054
|
+
"error": { type: "string", description: "Error message (if invalid)" }
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1000
1063
|
"/v1/hybrid": {
|
|
1001
1064
|
get: {
|
|
1002
1065
|
summary: "Get hybrid challenge",
|
|
@@ -1094,8 +1157,8 @@ export function getOpenApiSpec(version) {
|
|
|
1094
1157
|
},
|
|
1095
1158
|
"/v1/apps": {
|
|
1096
1159
|
post: {
|
|
1097
|
-
summary: "Create a new multi-tenant app
|
|
1098
|
-
description: "Create a new app with unique app_id and app_secret. Email is required for account recovery. A 6-digit verification code is sent to the provided email.",
|
|
1160
|
+
summary: "Create a new multi-tenant app",
|
|
1161
|
+
description: "Create a new app with unique app_id and app_secret. Email is required for account recovery. Name is optional but recommended for identification. A 6-digit verification code is sent to the provided email.",
|
|
1099
1162
|
operationId: "createApp",
|
|
1100
1163
|
requestBody: {
|
|
1101
1164
|
required: true,
|
|
@@ -1105,7 +1168,8 @@ export function getOpenApiSpec(version) {
|
|
|
1105
1168
|
type: "object",
|
|
1106
1169
|
required: ["email"],
|
|
1107
1170
|
properties: {
|
|
1108
|
-
"email": { type: "string", format: "email", description: "Owner email (required for recovery)" }
|
|
1171
|
+
"email": { type: "string", format: "email", description: "Owner email (required for recovery)" },
|
|
1172
|
+
"name": { type: "string", maxLength: 100, description: "Human-readable app label (optional, e.g. 'My Shopping App')" }
|
|
1109
1173
|
}
|
|
1110
1174
|
}
|
|
1111
1175
|
}
|
|
@@ -1120,6 +1184,7 @@ export function getOpenApiSpec(version) {
|
|
|
1120
1184
|
type: "object",
|
|
1121
1185
|
properties: {
|
|
1122
1186
|
"app_id": { type: "string", description: "Unique app identifier" },
|
|
1187
|
+
"name": { type: "string", description: "Human-readable app label" },
|
|
1123
1188
|
"app_secret": { type: "string", description: "Secret key (only shown once!)" },
|
|
1124
1189
|
"email": { type: "string" },
|
|
1125
1190
|
"email_verified": { type: "boolean" },
|
|
@@ -1130,7 +1195,7 @@ export function getOpenApiSpec(version) {
|
|
|
1130
1195
|
}
|
|
1131
1196
|
}
|
|
1132
1197
|
},
|
|
1133
|
-
"400": { description: "Missing or invalid email" }
|
|
1198
|
+
"400": { description: "Missing or invalid email, or invalid name" }
|
|
1134
1199
|
}
|
|
1135
1200
|
}
|
|
1136
1201
|
},
|
package/dist/tap-jwks.d.ts
CHANGED
|
@@ -39,7 +39,8 @@ export declare function jwkToPem(jwk: JWK): Promise<string>;
|
|
|
39
39
|
export declare function algToJWKAlg(algorithm: string): string;
|
|
40
40
|
/**
|
|
41
41
|
* GET /.well-known/jwks
|
|
42
|
-
* Returns JWK Set for app's TAP-enabled agents
|
|
42
|
+
* Returns JWK Set for app's TAP-enabled agents.
|
|
43
|
+
* Also includes BOTCHA's own signing public key when JWT_SIGNING_KEY is configured.
|
|
43
44
|
*/
|
|
44
45
|
export declare function jwksRoute(c: Context): Promise<Response>;
|
|
45
46
|
/**
|
package/dist/tap-jwks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tap-jwks.d.ts","sourceRoot":"","sources":["../src/tap-jwks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"tap-jwks.d.ts","sourceRoot":"","sources":["../src/tap-jwks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAOpC,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IAEZ,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IAEX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IAEX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;CACb;AAID;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,GAAG,CAAC,CAed;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAKxD;AAID;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAWrD;AAiFD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoG7D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2D/D;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuBjE;;;;;;;;;AAED,wBAOE"}
|
package/dist/tap-jwks.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Implements .well-known/jwks for TAP agent public key discovery
|
|
4
4
|
* Per Visa TAP spec: https://developer.visa.com/capabilities/trusted-agent-protocol
|
|
5
5
|
*/
|
|
6
|
+
import { getSigningPublicKeyJWK } from './auth.js';
|
|
6
7
|
// ============ PEM <-> JWK CONVERSION ============
|
|
7
8
|
/**
|
|
8
9
|
* Convert PEM public key to JWK format
|
|
@@ -122,27 +123,49 @@ function arrayBufferToPem(buffer) {
|
|
|
122
123
|
// ============ JWKS ENDPOINT HANDLERS ============
|
|
123
124
|
/**
|
|
124
125
|
* GET /.well-known/jwks
|
|
125
|
-
* Returns JWK Set for app's TAP-enabled agents
|
|
126
|
+
* Returns JWK Set for app's TAP-enabled agents.
|
|
127
|
+
* Also includes BOTCHA's own signing public key when JWT_SIGNING_KEY is configured.
|
|
126
128
|
*/
|
|
127
129
|
export async function jwksRoute(c) {
|
|
128
130
|
try {
|
|
131
|
+
const allKeys = [];
|
|
132
|
+
// Always include BOTCHA's own signing public key if configured
|
|
133
|
+
const jwtSigningKeyEnv = c.env.JWT_SIGNING_KEY;
|
|
134
|
+
if (jwtSigningKeyEnv) {
|
|
135
|
+
try {
|
|
136
|
+
const privateKeyJwk = JSON.parse(jwtSigningKeyEnv);
|
|
137
|
+
const publicKeyJwk = getSigningPublicKeyJWK(privateKeyJwk);
|
|
138
|
+
allKeys.push({
|
|
139
|
+
kty: publicKeyJwk.kty,
|
|
140
|
+
crv: publicKeyJwk.crv,
|
|
141
|
+
x: publicKeyJwk.x,
|
|
142
|
+
y: publicKeyJwk.y,
|
|
143
|
+
kid: 'botcha-signing-1',
|
|
144
|
+
use: 'sig',
|
|
145
|
+
alg: 'ES256',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error('Failed to derive BOTCHA signing public key for JWKS:', error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
129
152
|
const appId = c.req.query('app_id');
|
|
130
|
-
//
|
|
153
|
+
// If no app_id, return just the BOTCHA signing key (if any)
|
|
131
154
|
if (!appId) {
|
|
132
|
-
return c.json({ keys:
|
|
155
|
+
return c.json({ keys: allKeys }, 200, {
|
|
133
156
|
'Cache-Control': 'public, max-age=3600',
|
|
134
157
|
});
|
|
135
158
|
}
|
|
136
159
|
const agents = c.env.AGENTS;
|
|
137
160
|
if (!agents) {
|
|
138
161
|
console.error('AGENTS KV namespace not available');
|
|
139
|
-
return c.json({ keys:
|
|
162
|
+
return c.json({ keys: allKeys }, 200);
|
|
140
163
|
}
|
|
141
164
|
// Get agent list for this app
|
|
142
165
|
const agentIndexKey = `app_agents:${appId}`;
|
|
143
166
|
const agentIdsData = await agents.get(agentIndexKey, 'text');
|
|
144
167
|
if (!agentIdsData) {
|
|
145
|
-
return c.json({ keys:
|
|
168
|
+
return c.json({ keys: allKeys }, 200, {
|
|
146
169
|
'Cache-Control': 'public, max-age=3600',
|
|
147
170
|
});
|
|
148
171
|
}
|
|
@@ -174,8 +197,9 @@ export async function jwksRoute(c) {
|
|
|
174
197
|
return null;
|
|
175
198
|
}
|
|
176
199
|
});
|
|
177
|
-
const
|
|
178
|
-
|
|
200
|
+
const agentJwks = (await Promise.all(jwkPromises)).filter((jwk) => jwk !== null);
|
|
201
|
+
allKeys.push(...agentJwks);
|
|
202
|
+
return c.json({ keys: allKeys }, 200, {
|
|
179
203
|
'Cache-Control': 'public, max-age=3600',
|
|
180
204
|
});
|
|
181
205
|
}
|