@dupecom/botcha-cloudflare 0.14.0 → 0.16.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.
Files changed (53) hide show
  1. package/dist/dashboard/auth.d.ts.map +1 -1
  2. package/dist/dashboard/auth.js +1 -1
  3. package/dist/dashboard/landing.d.ts.map +1 -1
  4. package/dist/dashboard/landing.js +3 -10
  5. package/dist/dashboard/layout.d.ts +9 -0
  6. package/dist/dashboard/layout.d.ts.map +1 -1
  7. package/dist/dashboard/layout.js +15 -6
  8. package/dist/dashboard/showcase.d.ts +20 -0
  9. package/dist/dashboard/showcase.d.ts.map +1 -0
  10. package/dist/dashboard/showcase.js +1047 -0
  11. package/dist/dashboard/styles.d.ts +1 -1
  12. package/dist/dashboard/styles.d.ts.map +1 -1
  13. package/dist/dashboard/styles.js +99 -0
  14. package/dist/dashboard/whitepaper.d.ts +14 -0
  15. package/dist/dashboard/whitepaper.d.ts.map +1 -0
  16. package/dist/dashboard/whitepaper.js +418 -0
  17. package/dist/email.d.ts.map +1 -1
  18. package/dist/email.js +5 -1
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +108 -39
  22. package/dist/og-image.d.ts +2 -0
  23. package/dist/og-image.d.ts.map +1 -0
  24. package/dist/og-image.js +2 -0
  25. package/dist/routes/stream.js +1 -1
  26. package/dist/static.d.ts +331 -2
  27. package/dist/static.d.ts.map +1 -1
  28. package/dist/static.js +463 -4
  29. package/dist/tap-agents.d.ts +3 -2
  30. package/dist/tap-agents.d.ts.map +1 -1
  31. package/dist/tap-agents.js +19 -6
  32. package/dist/tap-consumer.d.ts +151 -0
  33. package/dist/tap-consumer.d.ts.map +1 -0
  34. package/dist/tap-consumer.js +346 -0
  35. package/dist/tap-edge.d.ts +106 -0
  36. package/dist/tap-edge.d.ts.map +1 -0
  37. package/dist/tap-edge.js +487 -0
  38. package/dist/tap-federation.d.ts +89 -0
  39. package/dist/tap-federation.d.ts.map +1 -0
  40. package/dist/tap-federation.js +237 -0
  41. package/dist/tap-jwks.d.ts +64 -0
  42. package/dist/tap-jwks.d.ts.map +1 -0
  43. package/dist/tap-jwks.js +279 -0
  44. package/dist/tap-payment.d.ts +172 -0
  45. package/dist/tap-payment.d.ts.map +1 -0
  46. package/dist/tap-payment.js +425 -0
  47. package/dist/tap-routes.d.ts +239 -2
  48. package/dist/tap-routes.d.ts.map +1 -1
  49. package/dist/tap-routes.js +279 -4
  50. package/dist/tap-verify.d.ts +43 -1
  51. package/dist/tap-verify.d.ts.map +1 -1
  52. package/dist/tap-verify.js +215 -30
  53. package/package.json +1 -1
@@ -0,0 +1,151 @@
1
+ /**
2
+ * TAP Consumer Recognition — Agentic Consumer Recognition Object
3
+ * Visa TAP Layer 2: Consumer identity verification linked to message signature
4
+ *
5
+ * Implements nonce-linked signature chains where:
6
+ * 1. HTTP message signature (Layer 1) contains a nonce
7
+ * 2. Consumer object contains the same nonce + consumer data
8
+ * 3. Consumer object is signed with same key
9
+ *
10
+ * This proves the agent had authorization from the consumer at signature time.
11
+ */
12
+ /**
13
+ * Agentic Consumer Recognition Object
14
+ * JSON structure in request body proving consumer authorization
15
+ */
16
+ export interface AgenticConsumer {
17
+ nonce: string;
18
+ idToken?: IDTokenJWT | string;
19
+ contextualData?: ContextualData;
20
+ kid: string;
21
+ alg: string;
22
+ signature: string;
23
+ }
24
+ /**
25
+ * Parsed ID Token (OIDC-compatible JWT)
26
+ */
27
+ export interface IDTokenJWT {
28
+ header: {
29
+ typ?: string;
30
+ alg: string;
31
+ kid?: string;
32
+ };
33
+ payload: IDTokenClaims;
34
+ signature: string;
35
+ }
36
+ /**
37
+ * ID Token claims (OIDC-compatible)
38
+ */
39
+ export interface IDTokenClaims {
40
+ iss: string;
41
+ sub: string;
42
+ aud: string | string[];
43
+ exp: number;
44
+ iat: number;
45
+ jti?: string;
46
+ auth_time?: number;
47
+ amr?: string[];
48
+ phone_number?: string;
49
+ phone_number_verified?: boolean;
50
+ email?: string;
51
+ email_verified?: boolean;
52
+ phone_number_mask?: string;
53
+ email_mask?: string;
54
+ [key: string]: any;
55
+ }
56
+ /**
57
+ * Contextual data about consumer device/location
58
+ */
59
+ export interface ContextualData {
60
+ countryCode?: string;
61
+ zip?: string;
62
+ ipAddress?: string;
63
+ deviceData?: DeviceData;
64
+ }
65
+ /**
66
+ * Device fingerprint data
67
+ */
68
+ export interface DeviceData {
69
+ userAgent?: string;
70
+ screenResolution?: string;
71
+ language?: string;
72
+ timezone?: string;
73
+ [key: string]: any;
74
+ }
75
+ /**
76
+ * Result of consumer verification
77
+ */
78
+ export interface ConsumerVerificationResult {
79
+ verified: boolean;
80
+ nonceLinked: boolean;
81
+ signatureValid: boolean;
82
+ idTokenValid?: boolean;
83
+ idTokenClaims?: IDTokenClaims;
84
+ contextualData?: ContextualData;
85
+ error?: string;
86
+ }
87
+ /**
88
+ * JWKS (JSON Web Key Set) structure
89
+ */
90
+ export interface JWKS {
91
+ keys: JWK[];
92
+ }
93
+ /**
94
+ * JSON Web Key
95
+ */
96
+ export interface JWK {
97
+ kid: string;
98
+ kty: string;
99
+ alg?: string;
100
+ use?: string;
101
+ n?: string;
102
+ e?: string;
103
+ crv?: string;
104
+ x?: string;
105
+ y?: string;
106
+ [key: string]: any;
107
+ }
108
+ /**
109
+ * Parse AgenticConsumer from request body
110
+ * Handles both nested { agenticConsumer: {...} } and top-level formats
111
+ */
112
+ export declare function parseAgenticConsumer(body: any): AgenticConsumer | null;
113
+ /**
114
+ * Parse ID Token JWT string into structured format
115
+ * Does NOT verify signature - just parses and validates structure
116
+ */
117
+ export declare function parseIDToken(tokenString: string): IDTokenClaims | null;
118
+ /**
119
+ * Verify ID Token signature by fetching JWKS from issuer
120
+ * Full cryptographic verification of the JWT
121
+ */
122
+ export declare function verifyIDTokenSignature(tokenString: string, jwksUrl?: string): Promise<{
123
+ valid: boolean;
124
+ claims?: IDTokenClaims;
125
+ error?: string;
126
+ }>;
127
+ /**
128
+ * Build canonical signature base string for consumer object
129
+ * Per TAP spec: all fields in order except 'signature' itself
130
+ */
131
+ export declare function buildConsumerSignatureBase(consumer: AgenticConsumer): string;
132
+ /**
133
+ * Verify AgenticConsumer object signature and nonce linkage
134
+ * Main verification function for TAP Layer 2
135
+ */
136
+ export declare function verifyAgenticConsumer(consumer: AgenticConsumer, headerNonce: string, publicKey: string, algorithm?: string): Promise<ConsumerVerificationResult>;
137
+ /**
138
+ * Hash-match obfuscated identity with cleartext
139
+ * Merchants maintain mapping tables with hashed values
140
+ */
141
+ export declare function hashMatchIdentity(obfuscated: string, cleartext: string, method?: 'sha256'): Promise<boolean>;
142
+ declare const _default: {
143
+ parseAgenticConsumer: typeof parseAgenticConsumer;
144
+ verifyAgenticConsumer: typeof verifyAgenticConsumer;
145
+ parseIDToken: typeof parseIDToken;
146
+ verifyIDTokenSignature: typeof verifyIDTokenSignature;
147
+ hashMatchIdentity: typeof hashMatchIdentity;
148
+ buildConsumerSignatureBase: typeof buildConsumerSignatureBase;
149
+ };
150
+ export default _default;
151
+ //# sourceMappingURL=tap-consumer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tap-consumer.d.ts","sourceRoot":"","sources":["../src/tap-consumer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,346 @@
1
+ /**
2
+ * TAP Consumer Recognition — Agentic Consumer Recognition Object
3
+ * Visa TAP Layer 2: Consumer identity verification linked to message signature
4
+ *
5
+ * Implements nonce-linked signature chains where:
6
+ * 1. HTTP message signature (Layer 1) contains a nonce
7
+ * 2. Consumer object contains the same nonce + consumer data
8
+ * 3. Consumer object is signed with same key
9
+ *
10
+ * This proves the agent had authorization from the consumer at signature time.
11
+ */
12
+ // ============ PARSING ============
13
+ /**
14
+ * Parse AgenticConsumer from request body
15
+ * Handles both nested { agenticConsumer: {...} } and top-level formats
16
+ */
17
+ export function parseAgenticConsumer(body) {
18
+ try {
19
+ // Try nested format first
20
+ let consumer = body?.agenticConsumer;
21
+ // If not nested, check if body itself is the consumer object
22
+ if (!consumer && body?.nonce && body?.kid && body?.alg && body?.signature) {
23
+ consumer = body;
24
+ }
25
+ if (!consumer) {
26
+ return null;
27
+ }
28
+ // Validate required fields
29
+ if (typeof consumer.nonce !== 'string' ||
30
+ typeof consumer.kid !== 'string' ||
31
+ typeof consumer.alg !== 'string' ||
32
+ typeof consumer.signature !== 'string') {
33
+ return null;
34
+ }
35
+ return {
36
+ nonce: consumer.nonce,
37
+ idToken: consumer.idToken,
38
+ contextualData: consumer.contextualData,
39
+ kid: consumer.kid,
40
+ alg: consumer.alg,
41
+ signature: consumer.signature
42
+ };
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * Parse ID Token JWT string into structured format
50
+ * Does NOT verify signature - just parses and validates structure
51
+ */
52
+ export function parseIDToken(tokenString) {
53
+ try {
54
+ const parts = tokenString.split('.');
55
+ if (parts.length !== 3) {
56
+ return null;
57
+ }
58
+ // Base64url decode header
59
+ const headerJson = atob(parts[0].replace(/-/g, '+').replace(/_/g, '/'));
60
+ const header = JSON.parse(headerJson);
61
+ // Base64url decode payload
62
+ const payloadJson = atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'));
63
+ const claims = JSON.parse(payloadJson);
64
+ // Validate required claims
65
+ if (typeof claims.iss !== 'string' ||
66
+ typeof claims.sub !== 'string' ||
67
+ !claims.aud ||
68
+ typeof claims.exp !== 'number' ||
69
+ typeof claims.iat !== 'number') {
70
+ return null;
71
+ }
72
+ // Check expiration
73
+ const now = Math.floor(Date.now() / 1000);
74
+ if (claims.exp <= now) {
75
+ return null; // Token expired
76
+ }
77
+ return claims;
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ /**
84
+ * Verify ID Token signature by fetching JWKS from issuer
85
+ * Full cryptographic verification of the JWT
86
+ */
87
+ export async function verifyIDTokenSignature(tokenString, jwksUrl) {
88
+ try {
89
+ const parts = tokenString.split('.');
90
+ if (parts.length !== 3) {
91
+ return { valid: false, error: 'Invalid JWT format' };
92
+ }
93
+ // Parse header and payload
94
+ const headerJson = atob(parts[0].replace(/-/g, '+').replace(/_/g, '/'));
95
+ const header = JSON.parse(headerJson);
96
+ const payloadJson = atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'));
97
+ const claims = JSON.parse(payloadJson);
98
+ // If no JWKS URL provided, just parse without verification
99
+ if (!jwksUrl) {
100
+ // Check expiration at least
101
+ const now = Math.floor(Date.now() / 1000);
102
+ if (claims.exp && claims.exp <= now) {
103
+ return { valid: false, error: 'Token expired', claims };
104
+ }
105
+ return { valid: true, claims }; // Unverified but parsed
106
+ }
107
+ // Fetch JWKS
108
+ const jwksResponse = await fetch(jwksUrl);
109
+ if (!jwksResponse.ok) {
110
+ return { valid: false, error: 'Failed to fetch JWKS' };
111
+ }
112
+ const jwks = await jwksResponse.json();
113
+ // Find matching key by kid
114
+ const key = jwks.keys.find(k => k.kid === header.kid);
115
+ if (!key) {
116
+ return { valid: false, error: 'Key not found in JWKS' };
117
+ }
118
+ // Build signature base (header.payload)
119
+ const signatureBase = `${parts[0]}.${parts[1]}`;
120
+ const signatureBytes = base64UrlToArrayBuffer(parts[2]);
121
+ // Import public key from JWK
122
+ const cryptoKey = await jwkToCryptoKey(key, header.alg);
123
+ // Verify signature
124
+ const encoder = new TextEncoder();
125
+ const data = encoder.encode(signatureBase);
126
+ const { importParams, verifyParams } = jwkAlgToWebCrypto(header.alg);
127
+ const isValid = await crypto.subtle.verify(verifyParams, cryptoKey, signatureBytes, data);
128
+ if (!isValid) {
129
+ return { valid: false, error: 'Signature verification failed' };
130
+ }
131
+ // Check expiration
132
+ const now = Math.floor(Date.now() / 1000);
133
+ if (claims.exp && claims.exp <= now) {
134
+ return { valid: false, error: 'Token expired', claims };
135
+ }
136
+ return { valid: true, claims };
137
+ }
138
+ catch (error) {
139
+ return {
140
+ valid: false,
141
+ error: `Verification error: ${error instanceof Error ? error.message : 'Unknown'}`
142
+ };
143
+ }
144
+ }
145
+ // ============ SIGNATURE VERIFICATION ============
146
+ /**
147
+ * Build canonical signature base string for consumer object
148
+ * Per TAP spec: all fields in order except 'signature' itself
149
+ */
150
+ export function buildConsumerSignatureBase(consumer) {
151
+ const fields = [];
152
+ // Add fields IN ORDER, excluding 'signature'
153
+ if (consumer.nonce) {
154
+ fields.push(`"nonce": "${consumer.nonce}"`);
155
+ }
156
+ if (consumer.idToken) {
157
+ const idTokenStr = typeof consumer.idToken === 'string'
158
+ ? `"${consumer.idToken}"`
159
+ : JSON.stringify(consumer.idToken);
160
+ fields.push(`"idToken": ${idTokenStr}`);
161
+ }
162
+ if (consumer.contextualData) {
163
+ fields.push(`"contextualData": ${JSON.stringify(consumer.contextualData)}`);
164
+ }
165
+ if (consumer.kid) {
166
+ fields.push(`"kid": "${consumer.kid}"`);
167
+ }
168
+ if (consumer.alg) {
169
+ fields.push(`"alg": "${consumer.alg}"`);
170
+ }
171
+ return fields.join('\n');
172
+ }
173
+ /**
174
+ * Verify AgenticConsumer object signature and nonce linkage
175
+ * Main verification function for TAP Layer 2
176
+ */
177
+ export async function verifyAgenticConsumer(consumer, headerNonce, publicKey, algorithm) {
178
+ try {
179
+ // Step 1: Check nonce linkage
180
+ const nonceLinked = consumer.nonce === headerNonce;
181
+ // Step 2: Build signature base
182
+ const signatureBase = buildConsumerSignatureBase(consumer);
183
+ // Step 3: Verify signature
184
+ const alg = algorithm || consumer.alg;
185
+ const signatureValid = await verifyCryptoSignature(signatureBase, consumer.signature, publicKey, alg);
186
+ // Step 4: Parse and validate ID token if present
187
+ let idTokenValid;
188
+ let idTokenClaims;
189
+ if (consumer.idToken) {
190
+ const tokenString = typeof consumer.idToken === 'string'
191
+ ? consumer.idToken
192
+ : consumer.idToken.signature; // If already parsed
193
+ if (tokenString) {
194
+ const parsedClaims = parseIDToken(tokenString);
195
+ idTokenClaims = parsedClaims ?? undefined;
196
+ idTokenValid = parsedClaims !== null;
197
+ }
198
+ }
199
+ const verified = nonceLinked && signatureValid;
200
+ return {
201
+ verified,
202
+ nonceLinked,
203
+ signatureValid,
204
+ idTokenValid,
205
+ idTokenClaims,
206
+ contextualData: consumer.contextualData,
207
+ error: verified ? undefined : 'Verification failed'
208
+ };
209
+ }
210
+ catch (error) {
211
+ return {
212
+ verified: false,
213
+ nonceLinked: false,
214
+ signatureValid: false,
215
+ error: `Verification error: ${error instanceof Error ? error.message : 'Unknown'}`
216
+ };
217
+ }
218
+ }
219
+ /**
220
+ * Verify cryptographic signature using Web Crypto API
221
+ */
222
+ async function verifyCryptoSignature(signatureBase, signature, publicKeyPem, algorithm) {
223
+ try {
224
+ // Decode signature from base64
225
+ const signatureBytes = Uint8Array.from(atob(signature), c => c.charCodeAt(0));
226
+ // Import public key
227
+ const keyData = pemToArrayBuffer(publicKeyPem);
228
+ const { importParams, verifyParams } = jwkAlgToWebCrypto(algorithm);
229
+ const cryptoKey = await crypto.subtle.importKey('spki', keyData, importParams, false, ['verify']);
230
+ // Verify signature
231
+ const encoder = new TextEncoder();
232
+ const data = encoder.encode(signatureBase);
233
+ return await crypto.subtle.verify(verifyParams, cryptoKey, signatureBytes, data);
234
+ }
235
+ catch (error) {
236
+ console.error('Consumer signature verification error:', error);
237
+ return false;
238
+ }
239
+ }
240
+ // ============ IDENTITY MATCHING ============
241
+ /**
242
+ * Hash-match obfuscated identity with cleartext
243
+ * Merchants maintain mapping tables with hashed values
244
+ */
245
+ export async function hashMatchIdentity(obfuscated, cleartext, method = 'sha256') {
246
+ try {
247
+ const encoder = new TextEncoder();
248
+ const data = encoder.encode(cleartext);
249
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
250
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
251
+ const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
252
+ // Compare with obfuscated value (assume it's hex-encoded hash)
253
+ return hashHex === obfuscated.toLowerCase();
254
+ }
255
+ catch {
256
+ return false;
257
+ }
258
+ }
259
+ // ============ CRYPTO HELPERS ============
260
+ /**
261
+ * Convert PEM public key to ArrayBuffer
262
+ */
263
+ function pemToArrayBuffer(pem) {
264
+ const base64 = pem
265
+ .replace(/-----BEGIN PUBLIC KEY-----/, '')
266
+ .replace(/-----END PUBLIC KEY-----/, '')
267
+ .replace(/\s/g, '');
268
+ const binary = atob(base64);
269
+ const bytes = new Uint8Array(binary.length);
270
+ for (let i = 0; i < binary.length; i++) {
271
+ bytes[i] = binary.charCodeAt(i);
272
+ }
273
+ return bytes.buffer;
274
+ }
275
+ /**
276
+ * Convert base64url string to ArrayBuffer
277
+ */
278
+ function base64UrlToArrayBuffer(base64url) {
279
+ const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
280
+ const binary = atob(base64);
281
+ const bytes = new Uint8Array(binary.length);
282
+ for (let i = 0; i < binary.length; i++) {
283
+ bytes[i] = binary.charCodeAt(i);
284
+ }
285
+ return bytes.buffer;
286
+ }
287
+ /**
288
+ * Convert JWK algorithm name to Web Crypto API parameters
289
+ */
290
+ function jwkAlgToWebCrypto(alg) {
291
+ switch (alg) {
292
+ case 'PS256':
293
+ return {
294
+ importParams: { name: 'RSA-PSS', hash: 'SHA-256' },
295
+ verifyParams: { name: 'RSA-PSS', saltLength: 32 }
296
+ };
297
+ case 'ES256':
298
+ return {
299
+ importParams: { name: 'ECDSA', namedCurve: 'P-256' },
300
+ verifyParams: { name: 'ECDSA', hash: 'SHA-256' }
301
+ };
302
+ case 'EdDSA':
303
+ return {
304
+ importParams: { name: 'Ed25519' },
305
+ verifyParams: { name: 'Ed25519' }
306
+ };
307
+ case 'RS256':
308
+ return {
309
+ importParams: { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
310
+ verifyParams: { name: 'RSASSA-PKCS1-v1_5' }
311
+ };
312
+ default:
313
+ throw new Error(`Unsupported algorithm: ${alg}`);
314
+ }
315
+ }
316
+ /**
317
+ * Convert JWK to CryptoKey
318
+ */
319
+ async function jwkToCryptoKey(jwk, alg) {
320
+ const { importParams } = jwkAlgToWebCrypto(alg);
321
+ // Build JWK for import
322
+ const keyData = {
323
+ kty: jwk.kty,
324
+ alg: alg,
325
+ ext: true
326
+ };
327
+ if (jwk.kty === 'RSA') {
328
+ keyData.n = jwk.n;
329
+ keyData.e = jwk.e;
330
+ }
331
+ else if (jwk.kty === 'EC') {
332
+ keyData.crv = jwk.crv;
333
+ keyData.x = jwk.x;
334
+ keyData.y = jwk.y;
335
+ }
336
+ return await crypto.subtle.importKey('jwk', keyData, importParams, true, ['verify']);
337
+ }
338
+ // ============ EXPORTS ============
339
+ export default {
340
+ parseAgenticConsumer,
341
+ verifyAgenticConsumer,
342
+ parseIDToken,
343
+ verifyIDTokenSignature,
344
+ hashMatchIdentity,
345
+ buildConsumerSignatureBase
346
+ };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * TAP Edge Verification — CDN-layer TAP signature verification
3
+ *
4
+ * Drop-in Hono middleware for Cloudflare Workers that:
5
+ * 1. Intercepts requests with TAP signature headers
6
+ * 2. Fetches agent public keys from BOTCHA or Visa JWKS
7
+ * 3. Verifies RFC 9421 signatures
8
+ * 4. Adds verification result headers to the proxied request
9
+ * 5. Passes through non-TAP requests unmodified
10
+ *
11
+ * Usage:
12
+ * ```typescript
13
+ * import { createTAPEdgeMiddleware } from '@dupecom/botcha/edge';
14
+ *
15
+ * app.use('*', createTAPEdgeMiddleware({
16
+ * jwksUrls: ['https://botcha.ai/.well-known/jwks?app_id=YOUR_APP'],
17
+ * allowUnverified: false, // Block unsigned requests
18
+ * requireTag: true, // Require agent-browser-auth or agent-payer-auth tag
19
+ * }));
20
+ * ```
21
+ */
22
+ import type { MiddlewareHandler } from 'hono';
23
+ export interface TAPEdgeOptions {
24
+ jwksUrls?: string[];
25
+ staticKeys?: Map<string, string>;
26
+ allowUnverified?: boolean;
27
+ requireTag?: boolean;
28
+ blockOnFailure?: boolean;
29
+ keyCacheTtl?: number;
30
+ onVerified?: (result: EdgeVerificationResult) => void;
31
+ onFailed?: (result: EdgeVerificationResult) => void;
32
+ }
33
+ export interface EdgeVerificationResult {
34
+ verified: boolean;
35
+ tag?: string;
36
+ agentKeyId?: string;
37
+ algorithm?: string;
38
+ nonce?: string;
39
+ timestamp?: number;
40
+ error?: string;
41
+ source?: 'botcha' | 'visa' | 'static' | 'unknown';
42
+ }
43
+ export interface ParsedEdgeSignatureInput {
44
+ label: string;
45
+ components: string[];
46
+ created: number;
47
+ expires?: number;
48
+ keyId: string;
49
+ algorithm: string;
50
+ nonce?: string;
51
+ tag?: string;
52
+ }
53
+ export declare const TAP_EDGE_HEADERS: {
54
+ readonly VERIFIED: "X-TAP-Verified";
55
+ readonly TAG: "X-TAP-Tag";
56
+ readonly KEY_ID: "X-TAP-Key-ID";
57
+ readonly AGENT_SOURCE: "X-TAP-Agent-Source";
58
+ readonly NONCE: "X-TAP-Nonce";
59
+ readonly TIMESTAMP: "X-TAP-Timestamp";
60
+ };
61
+ export declare function createTAPEdgeMiddleware(options?: TAPEdgeOptions): MiddlewareHandler;
62
+ /**
63
+ * Parse signature-input header according to RFC 9421
64
+ * Supports BOTH sig1 (BOTCHA) and sig2 (Visa TAP) labels
65
+ */
66
+ export declare function parseEdgeSignatureInput(input: string): ParsedEdgeSignatureInput | null;
67
+ /**
68
+ * Convert JWK to PEM format using Web Crypto
69
+ */
70
+ export declare function jwkToPublicKeyPem(jwk: any): Promise<string>;
71
+ /**
72
+ * Verify edge signature against signature base
73
+ */
74
+ export declare function verifyEdgeSignature(req: any, parsed: ParsedEdgeSignatureInput, signature: string, publicKey: string, algorithm: string): Promise<EdgeVerificationResult>;
75
+ /**
76
+ * Build signature base string according to RFC 9421 TAP format
77
+ */
78
+ export declare function buildEdgeSignatureBase(authority: string, path: string, parsed: ParsedEdgeSignatureInput): string;
79
+ /**
80
+ * Strict mode: require TAP on all requests, block failures
81
+ */
82
+ export declare const tapEdgeStrict: (jwksUrls: string[]) => MiddlewareHandler;
83
+ /**
84
+ * Flexible mode: verify if present, pass through if not
85
+ */
86
+ export declare const tapEdgeFlexible: (jwksUrls: string[]) => MiddlewareHandler;
87
+ /**
88
+ * Development mode: log only, never block
89
+ */
90
+ export declare const tapEdgeDev: () => MiddlewareHandler;
91
+ declare const _default: {
92
+ createTAPEdgeMiddleware: typeof createTAPEdgeMiddleware;
93
+ tapEdgeStrict: (jwksUrls: string[]) => MiddlewareHandler;
94
+ tapEdgeFlexible: (jwksUrls: string[]) => MiddlewareHandler;
95
+ tapEdgeDev: () => MiddlewareHandler;
96
+ TAP_EDGE_HEADERS: {
97
+ readonly VERIFIED: "X-TAP-Verified";
98
+ readonly TAG: "X-TAP-Tag";
99
+ readonly KEY_ID: "X-TAP-Key-ID";
100
+ readonly AGENT_SOURCE: "X-TAP-Agent-Source";
101
+ readonly NONCE: "X-TAP-Nonce";
102
+ readonly TIMESTAMP: "X-TAP-Timestamp";
103
+ };
104
+ };
105
+ export default _default;
106
+ //# sourceMappingURL=tap-edge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tap-edge.d.ts","sourceRoot":"","sources":["../src/tap-edge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIvD,MAAM,WAAW,cAAc;IAE7B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAGjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IAGzB,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACtD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CACnD;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAcD,eAAO,MAAM,gBAAgB;;;;;;;CAOnB,CAAC;AAIX,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CAoFvF;AAID;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAkCtF;AA6CD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAOjE;AA0BD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,wBAAwB,EAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,CAAC,CA8DjC;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,wBAAwB,GAC/B,MAAM,CAgCR;AAsMD;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,EAAE,sBAK9C,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,EAAE,sBAKhD,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,UAAU,yBAKrB,CAAC;;;8BAzBqC,MAAM,EAAE;gCAUN,MAAM,EAAE;;;;;;;;;;;AAmBlD,wBAME"}