@dupecom/botcha-cloudflare 0.15.0 → 0.18.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/dist/dashboard/landing.d.ts.map +1 -1
- package/dist/dashboard/landing.js +2 -9
- package/dist/dashboard/layout.d.ts +12 -0
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +12 -5
- package/dist/dashboard/showcase.d.ts +1 -0
- package/dist/dashboard/showcase.d.ts.map +1 -1
- package/dist/dashboard/showcase.js +3 -2
- package/dist/dashboard/whitepaper.d.ts +14 -0
- package/dist/dashboard/whitepaper.d.ts.map +1 -0
- package/dist/dashboard/whitepaper.js +418 -0
- package/dist/email.d.ts.map +1 -1
- package/dist/email.js +5 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -18
- package/dist/og-image.d.ts +2 -0
- package/dist/og-image.d.ts.map +1 -0
- package/dist/og-image.js +2 -0
- package/dist/static.d.ts +871 -2
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +812 -4
- package/dist/tap-agents.d.ts +3 -2
- package/dist/tap-agents.d.ts.map +1 -1
- package/dist/tap-agents.js +19 -6
- package/dist/tap-attestation-routes.d.ts +204 -0
- package/dist/tap-attestation-routes.d.ts.map +1 -0
- package/dist/tap-attestation-routes.js +396 -0
- package/dist/tap-attestation.d.ts +178 -0
- package/dist/tap-attestation.d.ts.map +1 -0
- package/dist/tap-attestation.js +416 -0
- package/dist/tap-consumer.d.ts +151 -0
- package/dist/tap-consumer.d.ts.map +1 -0
- package/dist/tap-consumer.js +346 -0
- package/dist/tap-delegation-routes.d.ts +236 -0
- package/dist/tap-delegation-routes.d.ts.map +1 -0
- package/dist/tap-delegation-routes.js +378 -0
- package/dist/tap-delegation.d.ts +127 -0
- package/dist/tap-delegation.d.ts.map +1 -0
- package/dist/tap-delegation.js +490 -0
- package/dist/tap-edge.d.ts +106 -0
- package/dist/tap-edge.d.ts.map +1 -0
- package/dist/tap-edge.js +487 -0
- package/dist/tap-federation.d.ts +89 -0
- package/dist/tap-federation.d.ts.map +1 -0
- package/dist/tap-federation.js +237 -0
- package/dist/tap-jwks.d.ts +64 -0
- package/dist/tap-jwks.d.ts.map +1 -0
- package/dist/tap-jwks.js +279 -0
- package/dist/tap-payment.d.ts +172 -0
- package/dist/tap-payment.d.ts.map +1 -0
- package/dist/tap-payment.js +425 -0
- package/dist/tap-reputation-routes.d.ts +154 -0
- package/dist/tap-reputation-routes.d.ts.map +1 -0
- package/dist/tap-reputation-routes.js +341 -0
- package/dist/tap-reputation.d.ts +136 -0
- package/dist/tap-reputation.d.ts.map +1 -0
- package/dist/tap-reputation.js +346 -0
- package/dist/tap-routes.d.ts +239 -2
- package/dist/tap-routes.d.ts.map +1 -1
- package/dist/tap-routes.js +279 -4
- package/dist/tap-verify.d.ts +43 -1
- package/dist/tap-verify.d.ts.map +1 -1
- package/dist/tap-verify.js +215 -30
- package/package.json +1 -1
|
@@ -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"}
|
package/dist/tap-edge.js
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
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
|
+
// Headers added to proxied request after verification
|
|
23
|
+
export const TAP_EDGE_HEADERS = {
|
|
24
|
+
VERIFIED: 'X-TAP-Verified', // 'true' or 'false'
|
|
25
|
+
TAG: 'X-TAP-Tag', // 'agent-browser-auth' or 'agent-payer-auth'
|
|
26
|
+
KEY_ID: 'X-TAP-Key-ID', // Which key was used
|
|
27
|
+
AGENT_SOURCE: 'X-TAP-Agent-Source', // 'botcha' | 'visa' | 'static'
|
|
28
|
+
NONCE: 'X-TAP-Nonce', // For body object linking downstream
|
|
29
|
+
TIMESTAMP: 'X-TAP-Timestamp', // Signature creation time
|
|
30
|
+
};
|
|
31
|
+
// ============ MAIN MIDDLEWARE ============
|
|
32
|
+
export function createTAPEdgeMiddleware(options = {}) {
|
|
33
|
+
const { jwksUrls = [], staticKeys = new Map(), allowUnverified = true, requireTag = false, blockOnFailure = true, keyCacheTtl = 3600, onVerified, onFailed, } = options;
|
|
34
|
+
// In-memory key cache (per-isolate in CF Workers)
|
|
35
|
+
const keyCache = new Map();
|
|
36
|
+
return async (c, next) => {
|
|
37
|
+
// 1. Check for TAP signature headers
|
|
38
|
+
const signatureInput = c.req.header('signature-input');
|
|
39
|
+
const signature = c.req.header('signature');
|
|
40
|
+
if (!signatureInput || !signature) {
|
|
41
|
+
if (!allowUnverified) {
|
|
42
|
+
return c.json({ error: 'TAP_REQUIRED', message: 'TAP signature headers required' }, 403);
|
|
43
|
+
}
|
|
44
|
+
c.header(TAP_EDGE_HEADERS.VERIFIED, 'false');
|
|
45
|
+
await next();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// 2. Parse signature input
|
|
49
|
+
const parsed = parseEdgeSignatureInput(signatureInput);
|
|
50
|
+
if (!parsed) {
|
|
51
|
+
const result = { verified: false, error: 'Invalid signature-input format' };
|
|
52
|
+
onFailed?.(result);
|
|
53
|
+
if (blockOnFailure) {
|
|
54
|
+
return c.json({ error: 'TAP_INVALID', message: 'Invalid TAP signature format' }, 403);
|
|
55
|
+
}
|
|
56
|
+
c.header(TAP_EDGE_HEADERS.VERIFIED, 'false');
|
|
57
|
+
await next();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// 3. Check tag requirement
|
|
61
|
+
if (requireTag && !parsed.tag) {
|
|
62
|
+
return c.json({ error: 'TAP_TAG_REQUIRED', message: 'TAP tag required (agent-browser-auth or agent-payer-auth)' }, 403);
|
|
63
|
+
}
|
|
64
|
+
// 4. Resolve public key
|
|
65
|
+
const keyResult = await resolveKey(parsed.keyId, jwksUrls, staticKeys, keyCache, keyCacheTtl);
|
|
66
|
+
if (!keyResult) {
|
|
67
|
+
const result = { verified: false, error: 'Public key not found', agentKeyId: parsed.keyId };
|
|
68
|
+
onFailed?.(result);
|
|
69
|
+
if (blockOnFailure) {
|
|
70
|
+
return c.json({ error: 'TAP_KEY_NOT_FOUND', message: `Public key not found for keyId: ${parsed.keyId}` }, 403);
|
|
71
|
+
}
|
|
72
|
+
c.header(TAP_EDGE_HEADERS.VERIFIED, 'false');
|
|
73
|
+
await next();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// 5. Verify signature
|
|
77
|
+
const verificationResult = await verifyEdgeSignature(c.req, parsed, signature, keyResult.key, parsed.algorithm);
|
|
78
|
+
if (verificationResult.verified) {
|
|
79
|
+
verificationResult.source = keyResult.source;
|
|
80
|
+
onVerified?.(verificationResult);
|
|
81
|
+
// Add verification headers
|
|
82
|
+
c.header(TAP_EDGE_HEADERS.VERIFIED, 'true');
|
|
83
|
+
if (parsed.tag)
|
|
84
|
+
c.header(TAP_EDGE_HEADERS.TAG, parsed.tag);
|
|
85
|
+
c.header(TAP_EDGE_HEADERS.KEY_ID, parsed.keyId);
|
|
86
|
+
c.header(TAP_EDGE_HEADERS.AGENT_SOURCE, keyResult.source);
|
|
87
|
+
if (parsed.nonce)
|
|
88
|
+
c.header(TAP_EDGE_HEADERS.NONCE, parsed.nonce);
|
|
89
|
+
if (parsed.created)
|
|
90
|
+
c.header(TAP_EDGE_HEADERS.TIMESTAMP, String(parsed.created));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
onFailed?.(verificationResult);
|
|
94
|
+
if (blockOnFailure) {
|
|
95
|
+
return c.json({ error: 'TAP_VERIFICATION_FAILED', message: verificationResult.error }, 403);
|
|
96
|
+
}
|
|
97
|
+
c.header(TAP_EDGE_HEADERS.VERIFIED, 'false');
|
|
98
|
+
}
|
|
99
|
+
await next();
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// ============ HELPER FUNCTIONS ============
|
|
103
|
+
/**
|
|
104
|
+
* Parse signature-input header according to RFC 9421
|
|
105
|
+
* Supports BOTH sig1 (BOTCHA) and sig2 (Visa TAP) labels
|
|
106
|
+
*/
|
|
107
|
+
export function parseEdgeSignatureInput(input) {
|
|
108
|
+
try {
|
|
109
|
+
// Match sig1 OR sig2
|
|
110
|
+
const sigMatch = input.match(/(sig[12])=\(([^)]+)\)/);
|
|
111
|
+
if (!sigMatch)
|
|
112
|
+
return null;
|
|
113
|
+
const label = sigMatch[1];
|
|
114
|
+
const components = sigMatch[2]
|
|
115
|
+
.split(' ')
|
|
116
|
+
.map(h => h.replace(/"/g, ''));
|
|
117
|
+
// Extract all params (keyid/keyId, alg, created, expires, nonce, tag)
|
|
118
|
+
const keyIdMatch = input.match(/keyid="([^"]+)"/i);
|
|
119
|
+
const algMatch = input.match(/alg="([^"]+)"/);
|
|
120
|
+
const createdMatch = input.match(/created=(\d+)/);
|
|
121
|
+
const expiresMatch = input.match(/expires=(\d+)/);
|
|
122
|
+
const nonceMatch = input.match(/nonce="([^"]+)"/);
|
|
123
|
+
const tagMatch = input.match(/tag="([^"]+)"/);
|
|
124
|
+
if (!keyIdMatch || !algMatch || !createdMatch)
|
|
125
|
+
return null;
|
|
126
|
+
return {
|
|
127
|
+
label,
|
|
128
|
+
keyId: keyIdMatch[1],
|
|
129
|
+
algorithm: algMatch[1],
|
|
130
|
+
created: parseInt(createdMatch[1]),
|
|
131
|
+
expires: expiresMatch ? parseInt(expiresMatch[1]) : undefined,
|
|
132
|
+
nonce: nonceMatch ? nonceMatch[1] : undefined,
|
|
133
|
+
tag: tagMatch ? tagMatch[1] : undefined,
|
|
134
|
+
components
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve public key from static keys, cache, or JWKS endpoints
|
|
143
|
+
*/
|
|
144
|
+
async function resolveKey(keyId, jwksUrls, staticKeys, cache, cacheTtl) {
|
|
145
|
+
// 1. Check static keys
|
|
146
|
+
if (staticKeys.has(keyId)) {
|
|
147
|
+
return { key: staticKeys.get(keyId), source: 'static' };
|
|
148
|
+
}
|
|
149
|
+
// 2. Check cache
|
|
150
|
+
const cached = cache.get(keyId);
|
|
151
|
+
if (cached && (Date.now() - cached.fetchedAt) / 1000 < cacheTtl) {
|
|
152
|
+
return { key: cached.key, source: cached.source };
|
|
153
|
+
}
|
|
154
|
+
// 3. Fetch from JWKS endpoints
|
|
155
|
+
for (const url of jwksUrls) {
|
|
156
|
+
try {
|
|
157
|
+
const resp = await fetch(url);
|
|
158
|
+
if (!resp.ok)
|
|
159
|
+
continue;
|
|
160
|
+
const jwks = await resp.json();
|
|
161
|
+
const matchingKey = jwks.keys?.find((k) => k.kid === keyId);
|
|
162
|
+
if (matchingKey) {
|
|
163
|
+
const pem = await jwkToPublicKeyPem(matchingKey);
|
|
164
|
+
const source = url.includes('visa.com') ? 'visa' :
|
|
165
|
+
url.includes('botcha') ? 'botcha' : 'unknown';
|
|
166
|
+
cache.set(keyId, { key: pem, fetchedAt: Date.now(), source });
|
|
167
|
+
return { key: pem, source };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
console.error(`Failed to fetch JWKS from ${url}:`, e);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Convert JWK to PEM format using Web Crypto
|
|
178
|
+
*/
|
|
179
|
+
export async function jwkToPublicKeyPem(jwk) {
|
|
180
|
+
const algorithm = jwkAlgToImportParams(jwk.alg || jwk.kty);
|
|
181
|
+
const key = await crypto.subtle.importKey('jwk', jwk, algorithm, true, ['verify']);
|
|
182
|
+
const spki = await crypto.subtle.exportKey('spki', key);
|
|
183
|
+
const base64 = btoa(String.fromCharCode(...new Uint8Array(spki)));
|
|
184
|
+
const lines = base64.match(/.{1,64}/g) || [base64];
|
|
185
|
+
return `-----BEGIN PUBLIC KEY-----\n${lines.join('\n')}\n-----END PUBLIC KEY-----`;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get Web Crypto import parameters for JWK algorithm
|
|
189
|
+
*/
|
|
190
|
+
function jwkAlgToImportParams(alg) {
|
|
191
|
+
switch (alg) {
|
|
192
|
+
case 'ES256':
|
|
193
|
+
return { name: 'ECDSA', namedCurve: 'P-256' };
|
|
194
|
+
case 'PS256':
|
|
195
|
+
return { name: 'RSA-PSS', hash: 'SHA-256' };
|
|
196
|
+
case 'EdDSA':
|
|
197
|
+
return { name: 'Ed25519' };
|
|
198
|
+
case 'EC':
|
|
199
|
+
// Default EC to P-256
|
|
200
|
+
return { name: 'ECDSA', namedCurve: 'P-256' };
|
|
201
|
+
case 'RSA':
|
|
202
|
+
return { name: 'RSA-PSS', hash: 'SHA-256' };
|
|
203
|
+
case 'OKP':
|
|
204
|
+
// Octet Key Pair - Ed25519
|
|
205
|
+
return { name: 'Ed25519' };
|
|
206
|
+
default:
|
|
207
|
+
throw new Error(`Unsupported JWK algorithm: ${alg}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Verify edge signature against signature base
|
|
212
|
+
*/
|
|
213
|
+
export async function verifyEdgeSignature(req, parsed, signature, publicKey, algorithm) {
|
|
214
|
+
try {
|
|
215
|
+
// 1. Validate timestamps
|
|
216
|
+
const timestampValidation = validateEdgeTimestamps(parsed.created, parsed.expires);
|
|
217
|
+
if (!timestampValidation.valid) {
|
|
218
|
+
return { verified: false, error: timestampValidation.error };
|
|
219
|
+
}
|
|
220
|
+
// 2. Get authority from host header
|
|
221
|
+
// Try multiple ways to get the host
|
|
222
|
+
let authority = req.header('host') || req.header(':authority') || '';
|
|
223
|
+
// Fallback: try to extract from URL if available
|
|
224
|
+
if (!authority && 'url' in req && typeof req.url === 'string') {
|
|
225
|
+
try {
|
|
226
|
+
authority = new URL(req.url).hostname;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// ignore URL parse errors
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Get path from req - Hono Request has .path property
|
|
233
|
+
let path;
|
|
234
|
+
if ('path' in req && typeof req.path === 'string') {
|
|
235
|
+
path = req.path;
|
|
236
|
+
}
|
|
237
|
+
else if ('url' in req) {
|
|
238
|
+
path = new URL(req.url).pathname;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
path = '/';
|
|
242
|
+
}
|
|
243
|
+
// 3. Build signature base
|
|
244
|
+
const signatureBase = buildEdgeSignatureBase(authority, path, parsed);
|
|
245
|
+
// 4. Verify cryptographic signature
|
|
246
|
+
const isValid = await verifyCryptoSignature(signatureBase, signature, publicKey, algorithm, parsed.label);
|
|
247
|
+
if (!isValid) {
|
|
248
|
+
return { verified: false, error: 'Signature verification failed' };
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
verified: true,
|
|
252
|
+
tag: parsed.tag,
|
|
253
|
+
agentKeyId: parsed.keyId,
|
|
254
|
+
algorithm: parsed.algorithm,
|
|
255
|
+
nonce: parsed.nonce,
|
|
256
|
+
timestamp: parsed.created,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
return {
|
|
261
|
+
verified: false,
|
|
262
|
+
error: `Verification error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Build signature base string according to RFC 9421 TAP format
|
|
268
|
+
*/
|
|
269
|
+
export function buildEdgeSignatureBase(authority, path, parsed) {
|
|
270
|
+
const lines = [];
|
|
271
|
+
// Add component lines (values are bare, no quotes)
|
|
272
|
+
for (const component of parsed.components) {
|
|
273
|
+
if (component === '@authority') {
|
|
274
|
+
lines.push(`"@authority": ${authority}`);
|
|
275
|
+
}
|
|
276
|
+
else if (component === '@path') {
|
|
277
|
+
lines.push(`"@path": ${path}`);
|
|
278
|
+
}
|
|
279
|
+
else if (component === '@method') {
|
|
280
|
+
lines.push(`"@method": GET`); // Edge typically handles GET requests
|
|
281
|
+
}
|
|
282
|
+
// Note: Other headers would need to be passed in if components include them
|
|
283
|
+
}
|
|
284
|
+
// Build @signature-params line with ALL fields
|
|
285
|
+
const componentsList = parsed.components.map(c => `"${c}"`).join(' ');
|
|
286
|
+
let paramsLine = `"@signature-params": ${parsed.label}=(${componentsList});created=${parsed.created};keyid="${parsed.keyId}";alg="${parsed.algorithm}"`;
|
|
287
|
+
if (parsed.expires !== undefined) {
|
|
288
|
+
paramsLine += `;expires=${parsed.expires}`;
|
|
289
|
+
}
|
|
290
|
+
if (parsed.nonce) {
|
|
291
|
+
paramsLine += `;nonce="${parsed.nonce}"`;
|
|
292
|
+
}
|
|
293
|
+
if (parsed.tag) {
|
|
294
|
+
paramsLine += `;tag="${parsed.tag}"`;
|
|
295
|
+
}
|
|
296
|
+
lines.push(paramsLine);
|
|
297
|
+
return lines.join('\n');
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Validate created/expires timestamps according to TAP spec
|
|
301
|
+
*/
|
|
302
|
+
function validateEdgeTimestamps(created, expires) {
|
|
303
|
+
const now = Math.floor(Date.now() / 1000);
|
|
304
|
+
const clockSkew = 30; // 30 seconds tolerance for clock drift
|
|
305
|
+
// created must be in the past (with clock skew)
|
|
306
|
+
if (created > now + clockSkew) {
|
|
307
|
+
return { valid: false, error: 'Signature timestamp is in the future' };
|
|
308
|
+
}
|
|
309
|
+
// If expires is present, validate it
|
|
310
|
+
if (expires !== undefined) {
|
|
311
|
+
// expires must be in the future
|
|
312
|
+
if (expires < now) {
|
|
313
|
+
return { valid: false, error: 'Signature has expired' };
|
|
314
|
+
}
|
|
315
|
+
// expires - created must be <= 480 seconds (8 minutes per TAP spec)
|
|
316
|
+
const window = expires - created;
|
|
317
|
+
if (window > 480) {
|
|
318
|
+
return { valid: false, error: 'Signature validity window exceeds 8 minutes' };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// No expires - fall back to 5-minute tolerance on created (backward compat)
|
|
323
|
+
const age = now - created;
|
|
324
|
+
if (age > 300) {
|
|
325
|
+
return { valid: false, error: 'Signature timestamp too old' };
|
|
326
|
+
}
|
|
327
|
+
if (age < -clockSkew) {
|
|
328
|
+
return { valid: false, error: 'Signature timestamp too new' };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { valid: true };
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Verify cryptographic signature using Web Crypto API
|
|
335
|
+
*/
|
|
336
|
+
async function verifyCryptoSignature(signatureBase, signature, publicKeyPem, algorithm, label) {
|
|
337
|
+
try {
|
|
338
|
+
// Extract signature bytes using the correct label
|
|
339
|
+
const sigPattern = new RegExp(`${label}=:([^:]+):`);
|
|
340
|
+
const sigMatch = signature.match(sigPattern);
|
|
341
|
+
if (!sigMatch)
|
|
342
|
+
return false;
|
|
343
|
+
const signatureBytes = Uint8Array.from(atob(sigMatch[1]), c => c.charCodeAt(0));
|
|
344
|
+
// Import public key
|
|
345
|
+
const keyData = importPublicKey(publicKeyPem, algorithm);
|
|
346
|
+
const cryptoKey = await crypto.subtle.importKey('spki', keyData, getImportParams(algorithm), false, ['verify']);
|
|
347
|
+
// Verify signature
|
|
348
|
+
const encoder = new TextEncoder();
|
|
349
|
+
const data = encoder.encode(signatureBase);
|
|
350
|
+
return await crypto.subtle.verify(getVerifyParams(algorithm), cryptoKey, signatureBytes, data);
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
console.error('Crypto signature verification error:', error);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Import public key - handles PEM SPKI and raw Ed25519 formats
|
|
359
|
+
*/
|
|
360
|
+
function importPublicKey(key, algorithm) {
|
|
361
|
+
// Check if it's a raw Ed25519 key (32 bytes base64)
|
|
362
|
+
if (algorithm.toLowerCase().includes('ed25519') || algorithm === 'Ed25519') {
|
|
363
|
+
if (isRawEd25519Key(key)) {
|
|
364
|
+
return rawEd25519ToSPKI(key);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Otherwise parse as PEM
|
|
368
|
+
return pemToArrayBuffer(key);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Detect raw Ed25519 public key (32 bytes = 43-44 base64 chars)
|
|
372
|
+
*/
|
|
373
|
+
function isRawEd25519Key(key) {
|
|
374
|
+
const stripped = key.replace(/[\s\n\r-]/g, '').replace(/BEGIN.*?END[^-]*-*/g, '');
|
|
375
|
+
try {
|
|
376
|
+
const decoded = atob(stripped.replace(/-/g, '+').replace(/_/g, '/'));
|
|
377
|
+
return decoded.length === 32;
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Convert raw 32-byte Ed25519 key to SPKI format
|
|
385
|
+
*/
|
|
386
|
+
function rawEd25519ToSPKI(rawKey) {
|
|
387
|
+
const rawBytes = Uint8Array.from(atob(rawKey), c => c.charCodeAt(0));
|
|
388
|
+
if (rawBytes.length !== 32) {
|
|
389
|
+
throw new Error('Invalid Ed25519 key length');
|
|
390
|
+
}
|
|
391
|
+
// SPKI header for Ed25519 (12 bytes)
|
|
392
|
+
const spkiHeader = new Uint8Array([
|
|
393
|
+
0x30, 0x2a, // SEQUENCE (42 bytes)
|
|
394
|
+
0x30, 0x05, // SEQUENCE (5 bytes) - algorithm
|
|
395
|
+
0x06, 0x03, 0x2b, 0x65, 0x70, // OID 1.3.101.112 (Ed25519)
|
|
396
|
+
0x03, 0x21, 0x00 // BIT STRING (33 bytes, 0 unused bits)
|
|
397
|
+
]);
|
|
398
|
+
const spki = new Uint8Array(spkiHeader.length + rawBytes.length);
|
|
399
|
+
spki.set(spkiHeader, 0);
|
|
400
|
+
spki.set(rawBytes, spkiHeader.length);
|
|
401
|
+
return spki.buffer;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Convert PEM public key to ArrayBuffer
|
|
405
|
+
*/
|
|
406
|
+
function pemToArrayBuffer(pem) {
|
|
407
|
+
const base64 = pem
|
|
408
|
+
.replace(/-----BEGIN PUBLIC KEY-----/, '')
|
|
409
|
+
.replace(/-----END PUBLIC KEY-----/, '')
|
|
410
|
+
.replace(/\s/g, '');
|
|
411
|
+
const binary = atob(base64);
|
|
412
|
+
const bytes = new Uint8Array(binary.length);
|
|
413
|
+
for (let i = 0; i < binary.length; i++) {
|
|
414
|
+
bytes[i] = binary.charCodeAt(i);
|
|
415
|
+
}
|
|
416
|
+
return bytes.buffer;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Get Web Crypto API algorithm parameters for key import
|
|
420
|
+
*/
|
|
421
|
+
function getImportParams(algorithm) {
|
|
422
|
+
const alg = algorithm.toLowerCase();
|
|
423
|
+
if (alg.includes('ed25519')) {
|
|
424
|
+
return { name: 'Ed25519' };
|
|
425
|
+
}
|
|
426
|
+
switch (algorithm) {
|
|
427
|
+
case 'ecdsa-p256-sha256':
|
|
428
|
+
return { name: 'ECDSA', namedCurve: 'P-256' };
|
|
429
|
+
case 'rsa-pss-sha256':
|
|
430
|
+
return { name: 'RSA-PSS', hash: 'SHA-256' };
|
|
431
|
+
default:
|
|
432
|
+
throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Get Web Crypto API algorithm parameters for signature verification
|
|
437
|
+
*/
|
|
438
|
+
function getVerifyParams(algorithm) {
|
|
439
|
+
const alg = algorithm.toLowerCase();
|
|
440
|
+
if (alg.includes('ed25519')) {
|
|
441
|
+
return { name: 'Ed25519' };
|
|
442
|
+
}
|
|
443
|
+
switch (algorithm) {
|
|
444
|
+
case 'ecdsa-p256-sha256':
|
|
445
|
+
return { name: 'ECDSA', hash: 'SHA-256' };
|
|
446
|
+
case 'rsa-pss-sha256':
|
|
447
|
+
return { name: 'RSA-PSS', saltLength: 32 };
|
|
448
|
+
default:
|
|
449
|
+
throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// ============ CONVENIENCE PRESETS ============
|
|
453
|
+
/**
|
|
454
|
+
* Strict mode: require TAP on all requests, block failures
|
|
455
|
+
*/
|
|
456
|
+
export const tapEdgeStrict = (jwksUrls) => createTAPEdgeMiddleware({
|
|
457
|
+
jwksUrls,
|
|
458
|
+
allowUnverified: false,
|
|
459
|
+
requireTag: true,
|
|
460
|
+
blockOnFailure: true
|
|
461
|
+
});
|
|
462
|
+
/**
|
|
463
|
+
* Flexible mode: verify if present, pass through if not
|
|
464
|
+
*/
|
|
465
|
+
export const tapEdgeFlexible = (jwksUrls) => createTAPEdgeMiddleware({
|
|
466
|
+
jwksUrls,
|
|
467
|
+
allowUnverified: true,
|
|
468
|
+
requireTag: false,
|
|
469
|
+
blockOnFailure: false
|
|
470
|
+
});
|
|
471
|
+
/**
|
|
472
|
+
* Development mode: log only, never block
|
|
473
|
+
*/
|
|
474
|
+
export const tapEdgeDev = () => createTAPEdgeMiddleware({
|
|
475
|
+
allowUnverified: true,
|
|
476
|
+
blockOnFailure: false,
|
|
477
|
+
onVerified: (r) => console.log('[TAP] Verified:', r),
|
|
478
|
+
onFailed: (r) => console.log('[TAP] Failed:', r),
|
|
479
|
+
});
|
|
480
|
+
// ============ EXPORTS ============
|
|
481
|
+
export default {
|
|
482
|
+
createTAPEdgeMiddleware,
|
|
483
|
+
tapEdgeStrict,
|
|
484
|
+
tapEdgeFlexible,
|
|
485
|
+
tapEdgeDev,
|
|
486
|
+
TAP_EDGE_HEADERS,
|
|
487
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Federation — External JWKS Federation for Cross-Platform Trust
|
|
3
|
+
* Enables BOTCHA to verify agents signed by Visa or other TAP-compatible providers
|
|
4
|
+
* Per Visa TAP spec: https://developer.visa.com/capabilities/trusted-agent-protocol
|
|
5
|
+
*/
|
|
6
|
+
export interface FederatedKeySource {
|
|
7
|
+
url: string;
|
|
8
|
+
name: string;
|
|
9
|
+
trustLevel: 'high' | 'medium' | 'low';
|
|
10
|
+
refreshInterval: number;
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface FederatedKey {
|
|
14
|
+
kid: string;
|
|
15
|
+
kty: string;
|
|
16
|
+
alg: string;
|
|
17
|
+
publicKeyPem: string;
|
|
18
|
+
source: string;
|
|
19
|
+
sourceUrl: string;
|
|
20
|
+
trustLevel: 'high' | 'medium' | 'low';
|
|
21
|
+
fetchedAt: number;
|
|
22
|
+
expiresAt: number;
|
|
23
|
+
x5c?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface FederationConfig {
|
|
26
|
+
sources: FederatedKeySource[];
|
|
27
|
+
kvNamespace?: KVNamespace;
|
|
28
|
+
defaultRefreshInterval?: number;
|
|
29
|
+
maxCacheAge?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface KeyResolutionResult {
|
|
32
|
+
found: boolean;
|
|
33
|
+
key?: FederatedKey;
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
interface KVNamespace {
|
|
37
|
+
get(key: string, type?: string): Promise<string | null>;
|
|
38
|
+
put(key: string, value: string, options?: {
|
|
39
|
+
expirationTtl?: number;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export declare const WELL_KNOWN_SOURCES: FederatedKeySource[];
|
|
43
|
+
/**
|
|
44
|
+
* Fetch JWKS from a URL
|
|
45
|
+
* @throws Error if fetch fails or response is invalid
|
|
46
|
+
*/
|
|
47
|
+
export declare function fetchJWKS(url: string): Promise<{
|
|
48
|
+
keys: any[];
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Convert a JWK from an external source to FederatedKey format
|
|
52
|
+
*/
|
|
53
|
+
export declare function jwkToFederatedKey(jwk: any, source: FederatedKeySource): Promise<FederatedKey>;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve Web Crypto import parameters based on JWK type
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveImportParams(jwk: any): any;
|
|
58
|
+
/**
|
|
59
|
+
* Infer algorithm from JWK if not specified
|
|
60
|
+
*/
|
|
61
|
+
export declare function inferAlgorithm(jwk: any): string;
|
|
62
|
+
export interface FederationResolver {
|
|
63
|
+
resolveKey(kid: string): Promise<KeyResolutionResult>;
|
|
64
|
+
refreshAll(): Promise<{
|
|
65
|
+
refreshed: number;
|
|
66
|
+
errors: string[];
|
|
67
|
+
}>;
|
|
68
|
+
getCachedKeys(): FederatedKey[];
|
|
69
|
+
clearCache(): void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a federation resolver that fetches and caches keys from external sources
|
|
73
|
+
*/
|
|
74
|
+
export declare function createFederationResolver(config: FederationConfig): FederationResolver;
|
|
75
|
+
/**
|
|
76
|
+
* Create a Visa-specific federation resolver
|
|
77
|
+
*/
|
|
78
|
+
export declare function createVisaFederationResolver(kvNamespace?: KVNamespace): FederationResolver;
|
|
79
|
+
declare const _default: {
|
|
80
|
+
fetchJWKS: typeof fetchJWKS;
|
|
81
|
+
jwkToFederatedKey: typeof jwkToFederatedKey;
|
|
82
|
+
createFederationResolver: typeof createFederationResolver;
|
|
83
|
+
createVisaFederationResolver: typeof createVisaFederationResolver;
|
|
84
|
+
resolveImportParams: typeof resolveImportParams;
|
|
85
|
+
inferAlgorithm: typeof inferAlgorithm;
|
|
86
|
+
WELL_KNOWN_SOURCES: FederatedKeySource[];
|
|
87
|
+
};
|
|
88
|
+
export default _default;
|
|
89
|
+
//# sourceMappingURL=tap-federation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-federation.d.ts","sourceRoot":"","sources":["../src/tap-federation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAGD,UAAU,WAAW;IACnB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtF;AAID,eAAO,MAAM,kBAAkB,EAAE,kBAAkB,EASlD,CAAC;AAIF;;;GAGG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,CAiBrE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,YAAY,CAAC,CAuBvB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAcjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAK/C;AAaD,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACtD,UAAU,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC/D,aAAa,IAAI,YAAY,EAAE,CAAC;IAChC,UAAU,IAAI,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,kBAAkB,CAiIrF;AAID;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,kBAAkB,CAO1F;;;;;;;;;;AAID,wBAQE"}
|