@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.
@@ -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,CA+NzD;AAED,eAAO,MAAM,UAAU,85CAuDtB,CAAC;AAEF,eAAO,MAAM,MAAM,6ujBAiQlB,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;CAsB1B,CAAC;AAEF,eAAO,MAAM,WAAW,kwBA4BvB,CAAC;AAGF,wBAAgB,qBAAqB,IAAI,MAAM,CAiJ9C;AAGD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA62C7C"}
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, returns app_id + app_secret) |
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 (5min) + refresh_token (1hr) |
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 = 5 minutes, refresh_token = 1 hour
184
+ **Token lifetimes:** access_token = 1 hour, refresh_token = 1 hour
184
185
 
185
- **Features:** audience claims, client IP binding, token revocation, refresh tokens
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/verify\` | \`npm install @botcha/verify\` |
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 (5-minute access tokens + 1-hour refresh tokens)
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/verify for TS, botcha-verify for Python)
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, returns app_id + app_secret)
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/verify
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: 5 minutes (short-lived for security)
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 (5-min access, 1-hr refresh)
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/verify\` · FastAPI/Django: \`botcha-verify\` · Hono middleware included.
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/verify (server-side verification)",
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 short-lived access token (5 minutes). Avoids solving a new challenge.",
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 (300 = 5 minutes)" },
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 (email required)",
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
  },
@@ -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
  /**
@@ -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;AAMpC,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;;;GAGG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6E7D;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"}
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
- // Security: Don't expose all keys globally
153
+ // If no app_id, return just the BOTCHA signing key (if any)
131
154
  if (!appId) {
132
- return c.json({ keys: [] }, 200, {
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: [] }, 200);
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: [] }, 200, {
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 jwks = (await Promise.all(jwkPromises)).filter((jwk) => jwk !== null);
178
- return c.json({ keys: jwks }, 200, {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dupecom/botcha-cloudflare",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "BOTCHA for Cloudflare Workers - Prove you're a bot. Humans need not apply.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",