@dupecom/botcha-cloudflare 0.19.0 → 0.20.1

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":"tap-attestation-routes.d.ts","sourceRoot":"","sources":["../src/tap-attestation-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AA4CpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4GrD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiDnD;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;kBA8DrD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAsEtD;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAmFtD;;;;;;;;AAED,wBAME"}
1
+ {"version":3,"file":"tap-attestation-routes.d.ts","sourceRoot":"","sources":["../src/tap-attestation-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAgEpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4GrD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiDnD;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;kBA8DrD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAsEtD;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAmFtD;;;;;;;;AAED,wBAME"}
@@ -11,25 +11,45 @@
11
11
  * POST /v1/attestations/:id/revoke — Revoke attestation
12
12
  * POST /v1/verify/attestation — Verify attestation + check capability
13
13
  */
14
- import { extractBearerToken, verifyToken } from './auth.js';
14
+ import { extractBearerToken, verifyToken, getSigningPublicKeyJWK } from './auth.js';
15
15
  import { issueAttestation, getAttestation, revokeAttestation, verifyAttestationToken, verifyAndCheckCapability, isValidCapabilityPattern, } from './tap-attestation.js';
16
16
  // ============ VALIDATION HELPERS ============
17
+ function getVerificationPublicKey(env) {
18
+ const rawSigningKey = env?.JWT_SIGNING_KEY;
19
+ if (!rawSigningKey)
20
+ return undefined;
21
+ try {
22
+ const signingKey = JSON.parse(rawSigningKey);
23
+ return getSigningPublicKeyJWK(signingKey);
24
+ }
25
+ catch {
26
+ console.error('Failed to parse JWT_SIGNING_KEY for attestation route verification');
27
+ return undefined;
28
+ }
29
+ }
17
30
  async function validateAppAccess(c, requireAuth = true) {
18
31
  const queryAppId = c.req.query('app_id');
19
- let jwtAppId;
20
32
  const authHeader = c.req.header('authorization');
21
33
  const token = extractBearerToken(authHeader);
22
- if (token) {
23
- const result = await verifyToken(token, c.env.JWT_SECRET, c.env);
24
- if (result.valid && result.payload) {
25
- jwtAppId = result.payload.app_id;
34
+ if (!token) {
35
+ if (!requireAuth) {
36
+ return { valid: true, appId: queryAppId };
26
37
  }
38
+ return { valid: false, error: 'UNAUTHORIZED', status: 401 };
39
+ }
40
+ const publicKey = getVerificationPublicKey(c.env);
41
+ const result = await verifyToken(token, c.env.JWT_SECRET, c.env, undefined, publicKey);
42
+ if (!result.valid || !result.payload) {
43
+ return { valid: false, error: 'INVALID_TOKEN', status: 401 };
44
+ }
45
+ const jwtAppId = result.payload.app_id;
46
+ if (!jwtAppId) {
47
+ return { valid: false, error: 'MISSING_APP_ID', status: 403 };
27
48
  }
28
- const appId = queryAppId || jwtAppId;
29
- if (requireAuth && !appId) {
30
- return { valid: false, error: 'MISSING_APP_ID', status: 401 };
49
+ if (queryAppId && queryAppId !== jwtAppId) {
50
+ return { valid: false, error: 'APP_ID_MISMATCH', status: 403 };
31
51
  }
32
- return { valid: true, appId };
52
+ return { valid: true, appId: jwtAppId };
33
53
  }
34
54
  // ============ ROUTE HANDLERS ============
35
55
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tap-delegation-routes.d.ts","sourceRoot":"","sources":["../src/tap-delegation-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AA4CpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsGrD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAmDlD;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAqEpD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAsErD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkDrD;;;;;;;;AAED,wBAME"}
1
+ {"version":3,"file":"tap-delegation-routes.d.ts","sourceRoot":"","sources":["../src/tap-delegation-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAgEpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsGrD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAmDlD;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAqEpD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAsErD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkDrD;;;;;;;;AAED,wBAME"}
@@ -11,26 +11,46 @@
11
11
  * POST /v1/delegations/:id/revoke — Revoke delegation (cascades)
12
12
  * POST /v1/verify/delegation — Verify delegation chain
13
13
  */
14
- import { extractBearerToken, verifyToken } from './auth.js';
14
+ import { extractBearerToken, verifyToken, getSigningPublicKeyJWK } from './auth.js';
15
15
  import { TAP_VALID_ACTIONS } from './tap-agents.js';
16
16
  import { createDelegation, getDelegation, listDelegations, revokeDelegation, verifyDelegationChain, } from './tap-delegation.js';
17
17
  // ============ VALIDATION HELPERS ============
18
+ function getVerificationPublicKey(env) {
19
+ const rawSigningKey = env?.JWT_SIGNING_KEY;
20
+ if (!rawSigningKey)
21
+ return undefined;
22
+ try {
23
+ const signingKey = JSON.parse(rawSigningKey);
24
+ return getSigningPublicKeyJWK(signingKey);
25
+ }
26
+ catch {
27
+ console.error('Failed to parse JWT_SIGNING_KEY for delegation route verification');
28
+ return undefined;
29
+ }
30
+ }
18
31
  async function validateAppAccess(c, requireAuth = true) {
19
32
  const queryAppId = c.req.query('app_id');
20
- let jwtAppId;
21
33
  const authHeader = c.req.header('authorization');
22
34
  const token = extractBearerToken(authHeader);
23
- if (token) {
24
- const result = await verifyToken(token, c.env.JWT_SECRET, c.env);
25
- if (result.valid && result.payload) {
26
- jwtAppId = result.payload.app_id;
35
+ if (!token) {
36
+ if (!requireAuth) {
37
+ return { valid: true, appId: queryAppId };
27
38
  }
39
+ return { valid: false, error: 'UNAUTHORIZED', status: 401 };
40
+ }
41
+ const publicKey = getVerificationPublicKey(c.env);
42
+ const result = await verifyToken(token, c.env.JWT_SECRET, c.env, undefined, publicKey);
43
+ if (!result.valid || !result.payload) {
44
+ return { valid: false, error: 'INVALID_TOKEN', status: 401 };
45
+ }
46
+ const jwtAppId = result.payload.app_id;
47
+ if (!jwtAppId) {
48
+ return { valid: false, error: 'MISSING_APP_ID', status: 403 };
28
49
  }
29
- const appId = queryAppId || jwtAppId;
30
- if (requireAuth && !appId) {
31
- return { valid: false, error: 'MISSING_APP_ID', status: 401 };
50
+ if (queryAppId && queryAppId !== jwtAppId) {
51
+ return { valid: false, error: 'APP_ID_MISMATCH', status: 403 };
32
52
  }
33
- return { valid: true, appId };
53
+ return { valid: true, appId: jwtAppId };
34
54
  }
35
55
  // ============ ROUTE HANDLERS ============
36
56
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tap-reputation-routes.d.ts","sourceRoot":"","sources":["../src/tap-reputation-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EASL,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAkC7B;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEA6DlD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2H1D;AAED;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEA4EzD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;oEAyDpD;;;;;;;AAED,wBAKE"}
1
+ {"version":3,"file":"tap-reputation-routes.d.ts","sourceRoot":"","sources":["../src/tap-reputation-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EASL,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAsD7B;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEA6DlD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2H1D;AAED;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEA4EzD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;oEAyDpD;;;;;;;AAED,wBAKE"}
@@ -10,25 +10,45 @@
10
10
  * GET /v1/reputation/:agent_id/events — List reputation events
11
11
  * POST /v1/reputation/:agent_id/reset — Reset agent reputation (admin)
12
12
  */
13
- import { extractBearerToken, verifyToken } from './auth.js';
13
+ import { extractBearerToken, verifyToken, getSigningPublicKeyJWK } from './auth.js';
14
14
  import { getReputationScore, recordReputationEvent, listReputationEvents, resetReputation, isValidCategory, isValidAction, isValidCategoryAction, } from './tap-reputation.js';
15
15
  // ============ VALIDATION HELPERS ============
16
+ function getVerificationPublicKey(env) {
17
+ const rawSigningKey = env?.JWT_SIGNING_KEY;
18
+ if (!rawSigningKey)
19
+ return undefined;
20
+ try {
21
+ const signingKey = JSON.parse(rawSigningKey);
22
+ return getSigningPublicKeyJWK(signingKey);
23
+ }
24
+ catch {
25
+ console.error('Failed to parse JWT_SIGNING_KEY for reputation route verification');
26
+ return undefined;
27
+ }
28
+ }
16
29
  async function validateAppAccess(c, requireAuth = true) {
17
30
  const queryAppId = c.req.query('app_id');
18
- let jwtAppId;
19
31
  const authHeader = c.req.header('authorization');
20
32
  const token = extractBearerToken(authHeader);
21
- if (token) {
22
- const result = await verifyToken(token, c.env.JWT_SECRET, c.env);
23
- if (result.valid && result.payload) {
24
- jwtAppId = result.payload.app_id;
33
+ if (!token) {
34
+ if (!requireAuth) {
35
+ return { valid: true, appId: queryAppId };
25
36
  }
37
+ return { valid: false, error: 'UNAUTHORIZED', status: 401 };
38
+ }
39
+ const publicKey = getVerificationPublicKey(c.env);
40
+ const result = await verifyToken(token, c.env.JWT_SECRET, c.env, undefined, publicKey);
41
+ if (!result.valid || !result.payload) {
42
+ return { valid: false, error: 'INVALID_TOKEN', status: 401 };
43
+ }
44
+ const jwtAppId = result.payload.app_id;
45
+ if (!jwtAppId) {
46
+ return { valid: false, error: 'MISSING_APP_ID', status: 403 };
26
47
  }
27
- const appId = queryAppId || jwtAppId;
28
- if (requireAuth && !appId) {
29
- return { valid: false, error: 'MISSING_APP_ID', status: 401 };
48
+ if (queryAppId && queryAppId !== jwtAppId) {
49
+ return { valid: false, error: 'APP_ID_MISMATCH', status: 403 };
30
50
  }
31
- return { valid: true, appId };
51
+ return { valid: true, appId: jwtAppId };
32
52
  }
33
53
  // ============ ROUTE HANDLERS ============
34
54
  /**
@@ -309,13 +309,22 @@ export declare function verifyIOURoute(c: Context): Promise<(Response & import("
309
309
  error: string;
310
310
  message: string;
311
311
  }, 404, "json">) | (Response & import("hono").TypedResponse<{
312
+ success: false;
313
+ error: string;
314
+ message: string;
315
+ }, 403, "json">) | (Response & import("hono").TypedResponse<{
312
316
  success: false;
313
317
  verified: false;
314
318
  error: string | undefined;
315
319
  }, 400, "json">) | (Response & import("hono").TypedResponse<{
320
+ success: false;
321
+ verified: true;
322
+ error: string;
323
+ message: string;
324
+ }, 502, "json">) | (Response & import("hono").TypedResponse<{
316
325
  success: true;
317
326
  verified: true;
318
- access_token: string | undefined;
327
+ access_token: string;
319
328
  expires_at: string | undefined;
320
329
  }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
321
330
  success: false;
@@ -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;AAsIpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwErD;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwDhD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAmDlD;AAID;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsFrD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2ClD;AAID;;;GAGG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4D9C;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;;;;;;;;;;;;;;;;;;;;;kBAwE9C;AAID;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqCnD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsClD;;;;;;;;;;;;;;AAaD,wBAYE"}
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;AAmKpC;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwErD;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwDhD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAmDlD;AAID;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsFrD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2ClD;AAID;;;GAGG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4D9C;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"}
@@ -4,35 +4,64 @@
4
4
  *
5
5
  * Provides backward-compatible endpoints with optional TAP functionality
6
6
  */
7
- import { extractBearerToken, verifyToken } from './auth.js';
7
+ import { extractBearerToken, verifyToken, getSigningPublicKeyJWK } from './auth.js';
8
8
  import { registerTAPAgent, getTAPAgent, listTAPAgents, createTAPSession, getTAPSession, validateCapability, TAP_VALID_ACTIONS } from './tap-agents.js';
9
9
  import { parseTAPIntent } from './tap-verify.js';
10
10
  import { createInvoice, getInvoice, verifyPaymentContainer, verifyBrowsingIOU, fulfillInvoice, parsePaymentContainer } from './tap-payment.js';
11
11
  import { parseAgenticConsumer, verifyAgenticConsumer } from './tap-consumer.js';
12
12
  // ============ VALIDATION HELPERS ============
13
+ function getVerificationPublicKey(env) {
14
+ const rawSigningKey = env?.JWT_SIGNING_KEY;
15
+ if (!rawSigningKey)
16
+ return undefined;
17
+ try {
18
+ const signingKey = JSON.parse(rawSigningKey);
19
+ return getSigningPublicKeyJWK(signingKey);
20
+ }
21
+ catch {
22
+ console.error('Failed to parse JWT_SIGNING_KEY for TAP verification');
23
+ return undefined;
24
+ }
25
+ }
13
26
  async function validateAppAccess(c, requireAuth = true) {
14
- // Extract app_id from query param or JWT
15
27
  const queryAppId = c.req.query('app_id');
16
- // Try to get from JWT Bearer token
17
- let jwtAppId;
18
28
  const authHeader = c.req.header('authorization');
19
29
  const token = extractBearerToken(authHeader);
20
- if (token) {
21
- const result = await verifyToken(token, c.env.JWT_SECRET, c.env);
22
- if (result.valid && result.payload) {
23
- jwtAppId = result.payload.app_id;
30
+ if (!token) {
31
+ if (!requireAuth) {
32
+ return { valid: true, appId: queryAppId };
24
33
  }
34
+ return {
35
+ valid: false,
36
+ error: 'UNAUTHORIZED',
37
+ status: 401
38
+ };
25
39
  }
26
- const appId = queryAppId || jwtAppId;
27
- if (requireAuth && !appId) {
40
+ const publicKey = getVerificationPublicKey(c.env);
41
+ const result = await verifyToken(token, c.env.JWT_SECRET, c.env, undefined, publicKey);
42
+ if (!result.valid || !result.payload) {
28
43
  return {
29
44
  valid: false,
30
- error: 'MISSING_APP_ID',
45
+ error: 'INVALID_TOKEN',
31
46
  status: 401
32
47
  };
33
48
  }
34
- // TODO: Validate app exists (integrate with existing app validation)
35
- return { valid: true, appId };
49
+ const jwtAppId = result.payload.app_id;
50
+ if (!jwtAppId) {
51
+ return {
52
+ valid: false,
53
+ error: 'MISSING_APP_ID',
54
+ status: 403
55
+ };
56
+ }
57
+ if (queryAppId && queryAppId !== jwtAppId) {
58
+ return {
59
+ valid: false,
60
+ error: 'APP_ID_MISMATCH',
61
+ status: 403
62
+ };
63
+ }
64
+ return { valid: true, appId: jwtAppId };
36
65
  }
37
66
  function validateTAPRegistration(body) {
38
67
  if (!body.name || typeof body.name !== 'string') {
@@ -515,33 +544,37 @@ export async function verifyIOURoute(c) {
515
544
  if (!agentKeyId) {
516
545
  return c.json({ success: false, error: 'MISSING_KEY_ID', message: 'browsingIOU must include kid' }, 400);
517
546
  }
518
- // For now, try to find the key in our agent registry
519
- // Future: also check federated sources
520
- let publicKey = null;
521
- const agentIndex = await c.env.AGENTS.get(`app_agents:${invoiceResult.invoice.app_id}`, 'text');
522
- if (agentIndex) {
523
- const agentIds = JSON.parse(agentIndex);
524
- for (const agentId of agentIds) {
525
- const agentData = await c.env.AGENTS.get(`agent:${agentId}`, 'text');
526
- if (agentData) {
527
- const agent = JSON.parse(agentData);
528
- if (agent.public_key) {
529
- publicKey = agent.public_key;
530
- break;
531
- }
532
- }
533
- }
547
+ // Resolve key by kid (agent/key identifier) and enforce same-app ownership.
548
+ const agentData = await c.env.AGENTS.get(`agent:${agentKeyId}`, 'text');
549
+ if (!agentData) {
550
+ return c.json({ success: false, error: 'KEY_NOT_FOUND', message: `No TAP key found for kid: ${agentKeyId}` }, 404);
534
551
  }
535
- if (!publicKey) {
552
+ const agent = JSON.parse(agentData);
553
+ if (agent.app_id !== invoiceResult.invoice.app_id) {
554
+ return c.json({
555
+ success: false,
556
+ error: 'KEY_APP_MISMATCH',
557
+ message: 'Key does not belong to the app that issued this invoice',
558
+ }, 403);
559
+ }
560
+ if (!agent.public_key) {
536
561
  return c.json({ success: false, error: 'KEY_NOT_FOUND', message: 'Could not resolve public key for verification' }, 404);
537
562
  }
538
563
  // Verify the IOU
539
- const iouResult = await verifyBrowsingIOU(body.browsingIOU, invoiceResult.invoice, publicKey, body.browsingIOU.alg || 'ES256');
564
+ const iouResult = await verifyBrowsingIOU(body.browsingIOU, invoiceResult.invoice, agent.public_key, body.browsingIOU.alg || agent.signature_algorithm || 'ES256');
540
565
  if (!iouResult.valid) {
541
566
  return c.json({ success: false, verified: false, error: iouResult.error }, 400);
542
567
  }
543
568
  // Fulfill the invoice
544
569
  const fulfillResult = await fulfillInvoice(c.env.INVOICES, invoiceId, body.browsingIOU);
570
+ if (!fulfillResult.success || !fulfillResult.access_token) {
571
+ return c.json({
572
+ success: false,
573
+ verified: true,
574
+ error: 'INVOICE_FULFILLMENT_FAILED',
575
+ message: fulfillResult.error || 'Invoice could not be fulfilled',
576
+ }, 502);
577
+ }
545
578
  return c.json({
546
579
  success: true,
547
580
  verified: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dupecom/botcha-cloudflare",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
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",