@fragno-dev/api-fragment 0.0.0-stage → 0.0.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.
Files changed (79) hide show
  1. package/LICENSE.md +16 -0
  2. package/README.md +249 -2
  3. package/dist/browser/client/client-3ZV9sw3i.js +2580 -0
  4. package/dist/browser/client/client-3ZV9sw3i.js.map +1 -0
  5. package/dist/browser/client/client-DPjmoaJQ.d.ts +711 -0
  6. package/dist/browser/client/client-DPjmoaJQ.d.ts.map +1 -0
  7. package/dist/browser/client/client.d.ts +1645 -0
  8. package/dist/browser/client/client.d.ts.map +1 -0
  9. package/dist/browser/client/client.js +2 -0
  10. package/dist/browser/client/react.d.ts +1665 -0
  11. package/dist/browser/client/react.d.ts.map +1 -0
  12. package/dist/browser/client/react.js +194 -0
  13. package/dist/browser/client/react.js.map +1 -0
  14. package/dist/browser/client/solid.d.ts +1668 -0
  15. package/dist/browser/client/solid.d.ts.map +1 -0
  16. package/dist/browser/client/solid.js +129 -0
  17. package/dist/browser/client/solid.js.map +1 -0
  18. package/dist/browser/client/svelte.d.ts +1668 -0
  19. package/dist/browser/client/svelte.d.ts.map +1 -0
  20. package/dist/browser/client/svelte.js +131 -0
  21. package/dist/browser/client/svelte.js.map +1 -0
  22. package/dist/browser/client/vanilla.d.ts +1695 -0
  23. package/dist/browser/client/vanilla.d.ts.map +1 -0
  24. package/dist/browser/client/vanilla.js +159 -0
  25. package/dist/browser/client/vanilla.js.map +1 -0
  26. package/dist/browser/client/vue.d.ts +1668 -0
  27. package/dist/browser/client/vue.d.ts.map +1 -0
  28. package/dist/browser/client/vue.js +127 -0
  29. package/dist/browser/client/vue.js.map +1 -0
  30. package/dist/browser/client-D4NnY-zV.d.ts +713 -0
  31. package/dist/browser/client-D4NnY-zV.d.ts.map +1 -0
  32. package/dist/browser/client-DQE0c7GI.d.ts +711 -0
  33. package/dist/browser/client-DQE0c7GI.d.ts.map +1 -0
  34. package/dist/browser/index.d.ts +3130 -0
  35. package/dist/browser/index.d.ts.map +1 -0
  36. package/dist/browser/index.js +2 -0
  37. package/dist/browser/src-BrYPVbAN.js +2404 -0
  38. package/dist/browser/src-BrYPVbAN.js.map +1 -0
  39. package/dist/browser/src-t-4wp9-f.js +2400 -0
  40. package/dist/browser/src-t-4wp9-f.js.map +1 -0
  41. package/dist/node/api-types.d.ts +731 -0
  42. package/dist/node/api-types.d.ts.map +1 -0
  43. package/dist/node/api-types.js +281 -0
  44. package/dist/node/api-types.js.map +1 -0
  45. package/dist/node/crypto.d.ts +6 -0
  46. package/dist/node/crypto.d.ts.map +1 -0
  47. package/dist/node/crypto.js +103 -0
  48. package/dist/node/crypto.js.map +1 -0
  49. package/dist/node/definition.d.ts +638 -0
  50. package/dist/node/definition.d.ts.map +1 -0
  51. package/dist/node/definition.js +315 -0
  52. package/dist/node/definition.js.map +1 -0
  53. package/dist/node/oauth.js +124 -0
  54. package/dist/node/oauth.js.map +1 -0
  55. package/dist/node/routes.d.ts +1585 -0
  56. package/dist/node/routes.d.ts.map +1 -0
  57. package/dist/node/routes.js +1100 -0
  58. package/dist/node/routes.js.map +1 -0
  59. package/dist/node/schema.d.ts +99 -0
  60. package/dist/node/schema.d.ts.map +1 -0
  61. package/dist/node/schema.js +16 -0
  62. package/dist/node/schema.js.map +1 -0
  63. package/dist/node/server.d.ts +1585 -0
  64. package/dist/node/server.d.ts.map +1 -0
  65. package/dist/node/server.js +12 -0
  66. package/dist/node/server.js.map +1 -0
  67. package/dist/node/services.d.ts +10 -0
  68. package/dist/node/services.d.ts.map +1 -0
  69. package/dist/node/services.js +304 -0
  70. package/dist/node/services.js.map +1 -0
  71. package/dist/node/webhooks/auth.d.ts +133 -0
  72. package/dist/node/webhooks/auth.d.ts.map +1 -0
  73. package/dist/node/webhooks/auth.js +268 -0
  74. package/dist/node/webhooks/auth.js.map +1 -0
  75. package/dist/node/webhooks/verification.d.ts +102 -0
  76. package/dist/node/webhooks/verification.d.ts.map +1 -0
  77. package/dist/node/webhooks/verification.js +119 -0
  78. package/dist/node/webhooks/verification.js.map +1 -0
  79. package/package.json +136 -3
@@ -0,0 +1,268 @@
1
+ import { decodeBase64, decodeBase64ToText, decodeHex, timingSafeEqualBytes, timingSafeEqualText, utf8Bytes, verifyHmacSignature } from "../crypto.js";
2
+ import { z } from "zod";
3
+ //#region src/webhooks/auth.ts
4
+ const secretRefSchema = z.string().trim().min(1);
5
+ const requestValueNameSchema = z.string().trim().min(1);
6
+ const webhookAuthConfigSchema = z.discriminatedUnion("type", [
7
+ z.object({ type: z.literal("none") }),
8
+ z.object({
9
+ type: z.literal("bearer"),
10
+ tokenRef: secretRefSchema
11
+ }),
12
+ z.object({
13
+ type: z.literal("apiKey"),
14
+ location: z.enum(["header", "query"]),
15
+ name: requestValueNameSchema,
16
+ secretRef: secretRefSchema
17
+ }),
18
+ z.object({
19
+ type: z.literal("basic"),
20
+ usernameRef: secretRefSchema,
21
+ passwordRef: secretRefSchema
22
+ }),
23
+ z.object({
24
+ type: z.literal("hmac"),
25
+ secretRef: secretRefSchema,
26
+ algorithm: z.enum([
27
+ "sha1",
28
+ "sha256",
29
+ "sha512"
30
+ ]),
31
+ signature: z.object({
32
+ location: z.enum(["header", "query"]),
33
+ name: requestValueNameSchema,
34
+ encoding: z.enum([
35
+ "hex",
36
+ "base64",
37
+ "base64url"
38
+ ]),
39
+ prefix: z.string().optional()
40
+ }),
41
+ signedPayload: z.discriminatedUnion("type", [z.object({ type: z.literal("rawBody") }), z.object({
42
+ type: z.literal("timestampedBody"),
43
+ prefix: z.string(),
44
+ timestampHeader: requestValueNameSchema,
45
+ delimiter: z.string(),
46
+ toleranceSeconds: z.number().int().positive()
47
+ })])
48
+ })
49
+ ]);
50
+ const getHeaderAuthorizationCredential = (request, scheme) => {
51
+ const header = request.headers.get("authorization");
52
+ if (!header) return null;
53
+ const [headerScheme, ...rest] = header.split(/\s+/);
54
+ if (headerScheme?.toLowerCase() !== scheme || rest.length === 0) return null;
55
+ return rest.join(" ");
56
+ };
57
+ const getRequestValue = (request, location, name) => {
58
+ if (location === "header") return request.headers.get(name);
59
+ return new URL(request.url).searchParams.get(name);
60
+ };
61
+ const requireSecret = async (context, ref) => {
62
+ const secret = await context.secrets.get(ref);
63
+ if (!secret) return {
64
+ ok: false,
65
+ reason: "missing_secret"
66
+ };
67
+ return {
68
+ ok: true,
69
+ secret
70
+ };
71
+ };
72
+ const webhookAuthVerifiers = {
73
+ none: {
74
+ type: "none",
75
+ requiredSecretRefs: () => [],
76
+ sensitiveRequestValues: () => [],
77
+ verify: async () => ({ ok: true })
78
+ },
79
+ bearer: {
80
+ type: "bearer",
81
+ requiredSecretRefs: (config) => [config.tokenRef],
82
+ sensitiveRequestValues: () => [{
83
+ location: "header",
84
+ name: "authorization"
85
+ }],
86
+ verify: async (config, context) => {
87
+ const secret = await requireSecret(context, config.tokenRef);
88
+ if (!secret.ok) return secret;
89
+ const token = getHeaderAuthorizationCredential(context.request, "bearer");
90
+ if (!token) return {
91
+ ok: false,
92
+ reason: "missing_credential"
93
+ };
94
+ return await timingSafeEqualText(token, secret.secret) ? { ok: true } : {
95
+ ok: false,
96
+ reason: "invalid_credential"
97
+ };
98
+ }
99
+ },
100
+ apiKey: {
101
+ type: "apiKey",
102
+ requiredSecretRefs: (config) => [config.secretRef],
103
+ sensitiveRequestValues: (config) => [{
104
+ location: config.location,
105
+ name: config.name
106
+ }],
107
+ verify: async (config, context) => {
108
+ const secret = await requireSecret(context, config.secretRef);
109
+ if (!secret.ok) return secret;
110
+ const credential = getRequestValue(context.request, config.location, config.name);
111
+ if (!credential) return {
112
+ ok: false,
113
+ reason: "missing_credential"
114
+ };
115
+ return await timingSafeEqualText(credential, secret.secret) ? { ok: true } : {
116
+ ok: false,
117
+ reason: "invalid_credential"
118
+ };
119
+ }
120
+ },
121
+ basic: {
122
+ type: "basic",
123
+ requiredSecretRefs: (config) => [config.usernameRef, config.passwordRef],
124
+ sensitiveRequestValues: () => [{
125
+ location: "header",
126
+ name: "authorization"
127
+ }],
128
+ verify: async (config, context) => {
129
+ const username = await requireSecret(context, config.usernameRef);
130
+ if (!username.ok) return username;
131
+ const password = await requireSecret(context, config.passwordRef);
132
+ if (!password.ok) return password;
133
+ const encodedCredential = getHeaderAuthorizationCredential(context.request, "basic");
134
+ if (!encodedCredential) return {
135
+ ok: false,
136
+ reason: "missing_credential"
137
+ };
138
+ const decodedCredential = decodeBase64ToText(encodedCredential, "base64");
139
+ if (decodedCredential === null) return {
140
+ ok: false,
141
+ reason: "invalid_credential"
142
+ };
143
+ const separatorIndex = decodedCredential.indexOf(":");
144
+ if (separatorIndex === -1) return {
145
+ ok: false,
146
+ reason: "invalid_credential"
147
+ };
148
+ const actualUsername = decodedCredential.slice(0, separatorIndex);
149
+ const actualPassword = decodedCredential.slice(separatorIndex + 1);
150
+ return await timingSafeEqualText(actualUsername, username.secret) && await timingSafeEqualText(actualPassword, password.secret) ? { ok: true } : {
151
+ ok: false,
152
+ reason: "invalid_credential"
153
+ };
154
+ }
155
+ },
156
+ hmac: {
157
+ type: "hmac",
158
+ requiredSecretRefs: (config) => [config.secretRef],
159
+ sensitiveRequestValues: (config) => [{
160
+ location: config.signature.location,
161
+ name: config.signature.name
162
+ }],
163
+ verify: async (config, context) => {
164
+ const secret = await requireSecret(context, config.secretRef);
165
+ if (!secret.ok) return secret;
166
+ const signatureValue = getRequestValue(context.request, config.signature.location, config.signature.name);
167
+ if (!signatureValue) return {
168
+ ok: false,
169
+ reason: "missing_credential"
170
+ };
171
+ const normalizedSignature = normalizeSignature(signatureValue, config.signature.prefix);
172
+ if (normalizedSignature === null) return {
173
+ ok: false,
174
+ reason: "invalid_credential"
175
+ };
176
+ const signedPayload = await buildSignedPayload(config, context);
177
+ if (!signedPayload.ok) return signedPayload;
178
+ const providedSignature = decodeSignature(normalizedSignature, config.signature.encoding);
179
+ if (!providedSignature) return {
180
+ ok: false,
181
+ reason: "invalid_credential"
182
+ };
183
+ return await verifyHmacSignature({
184
+ algorithm: config.algorithm,
185
+ secret: secret.secret,
186
+ payload: signedPayload.payload,
187
+ signature: providedSignature
188
+ }) ? { ok: true } : {
189
+ ok: false,
190
+ reason: "invalid_credential"
191
+ };
192
+ }
193
+ }
194
+ };
195
+ async function verifyWebhookAuth({ config, request, secrets, now = /* @__PURE__ */ new Date() }) {
196
+ const verifier = getWebhookAuthVerifier(config);
197
+ let rawBodyBytes = null;
198
+ return await verifier.verify(config, {
199
+ request,
200
+ now,
201
+ secrets,
202
+ rawBodyBytes: async () => {
203
+ rawBodyBytes ??= new Uint8Array(await request.clone().arrayBuffer());
204
+ return rawBodyBytes;
205
+ }
206
+ });
207
+ }
208
+ function getWebhookAuthSecretRefs(config) {
209
+ return getWebhookAuthVerifier(config).requiredSecretRefs(config);
210
+ }
211
+ function getSensitiveWebhookAuthValues(config) {
212
+ return getWebhookAuthVerifier(config).sensitiveRequestValues(config);
213
+ }
214
+ function getWebhookAuthVerifier(config) {
215
+ return webhookAuthVerifiers[config.type];
216
+ }
217
+ async function buildSignedPayload(config, context) {
218
+ if (config.signedPayload.type === "rawBody") return {
219
+ ok: true,
220
+ payload: await context.rawBodyBytes()
221
+ };
222
+ const timestamp = context.request.headers.get(config.signedPayload.timestampHeader);
223
+ if (!timestamp) return {
224
+ ok: false,
225
+ reason: "missing_credential"
226
+ };
227
+ const timestampMs = parseWebhookTimestampMs(timestamp);
228
+ if (timestampMs === null) return {
229
+ ok: false,
230
+ reason: "invalid_credential"
231
+ };
232
+ const toleranceMs = config.signedPayload.toleranceSeconds * 1e3;
233
+ if (Math.abs(context.now.getTime() - timestampMs) > toleranceMs) return {
234
+ ok: false,
235
+ reason: "timestamp_out_of_range"
236
+ };
237
+ return {
238
+ ok: true,
239
+ payload: concatBytes(utf8Bytes(`${config.signedPayload.prefix}${timestamp}${config.signedPayload.delimiter}`), await context.rawBodyBytes())
240
+ };
241
+ }
242
+ function normalizeSignature(signature, prefix) {
243
+ const trimmed = signature.trim();
244
+ if (!prefix) return trimmed || null;
245
+ if (!trimmed.startsWith(prefix)) return null;
246
+ return trimmed.slice(prefix.length).trim() || null;
247
+ }
248
+ function decodeSignature(signature, encoding) {
249
+ if (encoding === "hex") return decodeHex(signature);
250
+ return decodeBase64(signature, encoding);
251
+ }
252
+ function parseWebhookTimestampMs(value) {
253
+ const normalized = value.trim();
254
+ if (!/^\d+$/.test(normalized)) return null;
255
+ const numeric = Number(normalized);
256
+ if (!Number.isSafeInteger(numeric) || numeric <= 0) return null;
257
+ return numeric < 0xe8d4a51000 ? numeric * 1e3 : numeric;
258
+ }
259
+ function concatBytes(first, second) {
260
+ const output = new Uint8Array(first.length + second.length);
261
+ output.set(first, 0);
262
+ output.set(second, first.length);
263
+ return output;
264
+ }
265
+ //#endregion
266
+ export { getSensitiveWebhookAuthValues, getWebhookAuthSecretRefs, timingSafeEqualBytes, timingSafeEqualText, verifyWebhookAuth, webhookAuthConfigSchema, webhookAuthVerifiers };
267
+
268
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","names":["verifySecretText"],"sources":["../../../src/webhooks/auth.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport {\n decodeBase64,\n decodeBase64ToText,\n decodeHex,\n timingSafeEqualText as verifySecretText,\n utf8Bytes,\n verifyHmacSignature,\n} from \"../crypto\";\n\nconst secretRefSchema = z.string().trim().min(1);\nconst requestValueNameSchema = z.string().trim().min(1);\n\nexport const webhookAuthConfigSchema = z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"none\") }),\n z.object({ type: z.literal(\"bearer\"), tokenRef: secretRefSchema }),\n z.object({\n type: z.literal(\"apiKey\"),\n location: z.enum([\"header\", \"query\"]),\n name: requestValueNameSchema,\n secretRef: secretRefSchema,\n }),\n z.object({\n type: z.literal(\"basic\"),\n usernameRef: secretRefSchema,\n passwordRef: secretRefSchema,\n }),\n z.object({\n type: z.literal(\"hmac\"),\n secretRef: secretRefSchema,\n algorithm: z.enum([\"sha1\", \"sha256\", \"sha512\"]),\n signature: z.object({\n location: z.enum([\"header\", \"query\"]),\n name: requestValueNameSchema,\n encoding: z.enum([\"hex\", \"base64\", \"base64url\"]),\n prefix: z.string().optional(),\n }),\n signedPayload: z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"rawBody\") }),\n z.object({\n type: z.literal(\"timestampedBody\"),\n prefix: z.string(),\n timestampHeader: requestValueNameSchema,\n delimiter: z.string(),\n toleranceSeconds: z.number().int().positive(),\n }),\n ]),\n }),\n]);\n\nexport type WebhookAuthConfig = z.infer<typeof webhookAuthConfigSchema>;\nexport type WebhookAuthType = WebhookAuthConfig[\"type\"];\nexport type WebhookAuthSecretRef = string;\n\nexport type WebhookSecretResolver = {\n get(ref: WebhookAuthSecretRef): string | undefined | Promise<string | undefined>;\n};\n\nexport type WebhookAuthFailureReason =\n | \"missing_secret\"\n | \"missing_credential\"\n | \"invalid_credential\"\n | \"timestamp_out_of_range\"\n | \"bad_config\";\n\nexport type WebhookAuthResult = { ok: true } | { ok: false; reason: WebhookAuthFailureReason };\n\nexport type SensitiveRequestValue = {\n location: \"header\" | \"query\";\n name: string;\n};\n\nexport type WebhookAuthVerificationContext = {\n request: Request;\n now: Date;\n secrets: WebhookSecretResolver;\n rawBodyBytes(): Promise<Uint8Array>;\n};\n\nexport interface WebhookAuthVerifier<TConfig extends WebhookAuthConfig> {\n type: TConfig[\"type\"];\n requiredSecretRefs(config: TConfig): readonly WebhookAuthSecretRef[];\n sensitiveRequestValues(config: TConfig): readonly SensitiveRequestValue[];\n verify(config: TConfig, context: WebhookAuthVerificationContext): Promise<WebhookAuthResult>;\n}\n\ntype WebhookAuthVerifierRegistry = {\n [TType in WebhookAuthType]: WebhookAuthVerifier<Extract<WebhookAuthConfig, { type: TType }>>;\n};\n\nexport type VerifyWebhookAuthInput = {\n config: WebhookAuthConfig;\n /** Pass the original web-standard Request or a clone. The body is read from request.clone(). */\n request: Request;\n secrets: WebhookSecretResolver;\n now?: Date;\n};\n\nconst getHeaderAuthorizationCredential = (request: Request, scheme: \"basic\" | \"bearer\") => {\n const header = request.headers.get(\"authorization\");\n if (!header) {\n return null;\n }\n\n const [headerScheme, ...rest] = header.split(/\\s+/);\n if (headerScheme?.toLowerCase() !== scheme || rest.length === 0) {\n return null;\n }\n\n return rest.join(\" \");\n};\n\nconst getRequestValue = (\n request: Request,\n location: \"header\" | \"query\",\n name: string,\n): string | null => {\n if (location === \"header\") {\n return request.headers.get(name);\n }\n\n return new URL(request.url).searchParams.get(name);\n};\n\ntype WebhookAuthFailure = Extract<WebhookAuthResult, { ok: false }>;\n\ntype ResolvedSecret = WebhookAuthFailure | { ok: true; secret: string };\n\nconst requireSecret = async (\n context: WebhookAuthVerificationContext,\n ref: WebhookAuthSecretRef,\n): Promise<ResolvedSecret> => {\n const secret = await context.secrets.get(ref);\n if (!secret) {\n return { ok: false, reason: \"missing_secret\" };\n }\n\n return { ok: true, secret };\n};\n\nconst noneVerifier: WebhookAuthVerifier<Extract<WebhookAuthConfig, { type: \"none\" }>> = {\n type: \"none\",\n requiredSecretRefs: () => [],\n sensitiveRequestValues: () => [],\n verify: async () => ({ ok: true }),\n};\n\nconst bearerVerifier: WebhookAuthVerifier<Extract<WebhookAuthConfig, { type: \"bearer\" }>> = {\n type: \"bearer\",\n requiredSecretRefs: (config) => [config.tokenRef],\n sensitiveRequestValues: () => [{ location: \"header\", name: \"authorization\" }],\n verify: async (config, context) => {\n const secret = await requireSecret(context, config.tokenRef);\n if (!secret.ok) {\n return secret;\n }\n\n const token = getHeaderAuthorizationCredential(context.request, \"bearer\");\n if (!token) {\n return { ok: false, reason: \"missing_credential\" };\n }\n\n return (await verifySecretText(token, secret.secret))\n ? { ok: true }\n : { ok: false, reason: \"invalid_credential\" };\n },\n};\n\nconst apiKeyVerifier: WebhookAuthVerifier<Extract<WebhookAuthConfig, { type: \"apiKey\" }>> = {\n type: \"apiKey\",\n requiredSecretRefs: (config) => [config.secretRef],\n sensitiveRequestValues: (config) => [{ location: config.location, name: config.name }],\n verify: async (config, context) => {\n const secret = await requireSecret(context, config.secretRef);\n if (!secret.ok) {\n return secret;\n }\n\n const credential = getRequestValue(context.request, config.location, config.name);\n if (!credential) {\n return { ok: false, reason: \"missing_credential\" };\n }\n\n return (await verifySecretText(credential, secret.secret))\n ? { ok: true }\n : { ok: false, reason: \"invalid_credential\" };\n },\n};\n\nconst basicVerifier: WebhookAuthVerifier<Extract<WebhookAuthConfig, { type: \"basic\" }>> = {\n type: \"basic\",\n requiredSecretRefs: (config) => [config.usernameRef, config.passwordRef],\n sensitiveRequestValues: () => [{ location: \"header\", name: \"authorization\" }],\n verify: async (config, context) => {\n const username = await requireSecret(context, config.usernameRef);\n if (!username.ok) {\n return username;\n }\n const password = await requireSecret(context, config.passwordRef);\n if (!password.ok) {\n return password;\n }\n\n const encodedCredential = getHeaderAuthorizationCredential(context.request, \"basic\");\n if (!encodedCredential) {\n return { ok: false, reason: \"missing_credential\" };\n }\n\n const decodedCredential = decodeBase64ToText(encodedCredential, \"base64\");\n if (decodedCredential === null) {\n return { ok: false, reason: \"invalid_credential\" };\n }\n\n const separatorIndex = decodedCredential.indexOf(\":\");\n if (separatorIndex === -1) {\n return { ok: false, reason: \"invalid_credential\" };\n }\n\n const actualUsername = decodedCredential.slice(0, separatorIndex);\n const actualPassword = decodedCredential.slice(separatorIndex + 1);\n\n return (await verifySecretText(actualUsername, username.secret)) &&\n (await verifySecretText(actualPassword, password.secret))\n ? { ok: true }\n : { ok: false, reason: \"invalid_credential\" };\n },\n};\n\nconst hmacVerifier: WebhookAuthVerifier<Extract<WebhookAuthConfig, { type: \"hmac\" }>> = {\n type: \"hmac\",\n requiredSecretRefs: (config) => [config.secretRef],\n sensitiveRequestValues: (config) => [\n { location: config.signature.location, name: config.signature.name },\n ],\n verify: async (config, context) => {\n const secret = await requireSecret(context, config.secretRef);\n if (!secret.ok) {\n return secret;\n }\n\n const signatureValue = getRequestValue(\n context.request,\n config.signature.location,\n config.signature.name,\n );\n if (!signatureValue) {\n return { ok: false, reason: \"missing_credential\" };\n }\n\n const normalizedSignature = normalizeSignature(signatureValue, config.signature.prefix);\n if (normalizedSignature === null) {\n return { ok: false, reason: \"invalid_credential\" };\n }\n\n const signedPayload = await buildSignedPayload(config, context);\n if (!signedPayload.ok) {\n return signedPayload;\n }\n\n const providedSignature = decodeSignature(normalizedSignature, config.signature.encoding);\n if (!providedSignature) {\n return { ok: false, reason: \"invalid_credential\" };\n }\n\n return (await verifyHmacSignature({\n algorithm: config.algorithm,\n secret: secret.secret,\n payload: signedPayload.payload,\n signature: providedSignature,\n }))\n ? { ok: true }\n : { ok: false, reason: \"invalid_credential\" };\n },\n};\n\nexport const webhookAuthVerifiers = {\n none: noneVerifier,\n bearer: bearerVerifier,\n apiKey: apiKeyVerifier,\n basic: basicVerifier,\n hmac: hmacVerifier,\n} satisfies WebhookAuthVerifierRegistry;\n\nexport async function verifyWebhookAuth({\n config,\n request,\n secrets,\n now = new Date(),\n}: VerifyWebhookAuthInput): Promise<WebhookAuthResult> {\n const verifier = getWebhookAuthVerifier(config);\n let rawBodyBytes: Uint8Array | null = null;\n\n return await verifier.verify(config, {\n request,\n now,\n secrets,\n rawBodyBytes: async () => {\n rawBodyBytes ??= new Uint8Array(await request.clone().arrayBuffer());\n return rawBodyBytes;\n },\n });\n}\n\nexport function getWebhookAuthSecretRefs(\n config: WebhookAuthConfig,\n): readonly WebhookAuthSecretRef[] {\n return getWebhookAuthVerifier(config).requiredSecretRefs(config);\n}\n\nexport function getSensitiveWebhookAuthValues(\n config: WebhookAuthConfig,\n): readonly SensitiveRequestValue[] {\n return getWebhookAuthVerifier(config).sensitiveRequestValues(config);\n}\n\nfunction getWebhookAuthVerifier<TConfig extends WebhookAuthConfig>(\n config: TConfig,\n): WebhookAuthVerifier<TConfig> {\n return webhookAuthVerifiers[config.type] as WebhookAuthVerifier<TConfig>;\n}\n\nasync function buildSignedPayload(\n config: Extract<WebhookAuthConfig, { type: \"hmac\" }>,\n context: WebhookAuthVerificationContext,\n): Promise<{ ok: true; payload: Uint8Array } | WebhookAuthFailure> {\n if (config.signedPayload.type === \"rawBody\") {\n return { ok: true, payload: await context.rawBodyBytes() };\n }\n\n const timestamp = context.request.headers.get(config.signedPayload.timestampHeader);\n if (!timestamp) {\n return { ok: false, reason: \"missing_credential\" };\n }\n\n const timestampMs = parseWebhookTimestampMs(timestamp);\n if (timestampMs === null) {\n return { ok: false, reason: \"invalid_credential\" };\n }\n\n const toleranceMs = config.signedPayload.toleranceSeconds * 1000;\n if (Math.abs(context.now.getTime() - timestampMs) > toleranceMs) {\n return { ok: false, reason: \"timestamp_out_of_range\" };\n }\n\n return {\n ok: true,\n payload: concatBytes(\n utf8Bytes(`${config.signedPayload.prefix}${timestamp}${config.signedPayload.delimiter}`),\n await context.rawBodyBytes(),\n ),\n };\n}\n\nfunction normalizeSignature(signature: string, prefix: string | undefined) {\n const trimmed = signature.trim();\n if (!prefix) {\n return trimmed || null;\n }\n\n if (!trimmed.startsWith(prefix)) {\n return null;\n }\n\n const withoutPrefix = trimmed.slice(prefix.length).trim();\n return withoutPrefix || null;\n}\n\nfunction decodeSignature(signature: string, encoding: \"hex\" | \"base64\" | \"base64url\") {\n if (encoding === \"hex\") {\n return decodeHex(signature);\n }\n\n return decodeBase64(signature, encoding);\n}\n\nfunction parseWebhookTimestampMs(value: string) {\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n return null;\n }\n\n const numeric = Number(normalized);\n if (!Number.isSafeInteger(numeric) || numeric <= 0) {\n return null;\n }\n\n return numeric < 1_000_000_000_000 ? numeric * 1000 : numeric;\n}\n\nfunction concatBytes(first: Uint8Array, second: Uint8Array) {\n const output = new Uint8Array(first.length + second.length);\n output.set(first, 0);\n output.set(second, first.length);\n return output;\n}\n\nexport { timingSafeEqualBytes, timingSafeEqualText } from \"../crypto\";\n"],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ import { z } from "zod";
2
+ //#region src/webhooks/verification.d.ts
3
+ declare const webhookRequestValueSourceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
+ type: z.ZodLiteral<"header">;
5
+ name: z.ZodString;
6
+ }, z.core.$strip>, z.ZodObject<{
7
+ type: z.ZodLiteral<"query">;
8
+ name: z.ZodString;
9
+ }, z.core.$strip>, z.ZodObject<{
10
+ type: z.ZodLiteral<"jsonBodyPath">;
11
+ path: z.ZodArray<z.ZodString>;
12
+ }, z.core.$strip>], "type">;
13
+ declare const webhookVerificationConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
14
+ type: z.ZodLiteral<"none">;
15
+ }, z.core.$strip>, z.ZodObject<{
16
+ type: z.ZodLiteral<"challenge">;
17
+ method: z.ZodEnum<{
18
+ GET: "GET";
19
+ POST: "POST";
20
+ }>;
21
+ when: z.ZodDiscriminatedUnion<[z.ZodObject<{
22
+ type: z.ZodLiteral<"present">;
23
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
24
+ type: z.ZodLiteral<"header">;
25
+ name: z.ZodString;
26
+ }, z.core.$strip>, z.ZodObject<{
27
+ type: z.ZodLiteral<"query">;
28
+ name: z.ZodString;
29
+ }, z.core.$strip>, z.ZodObject<{
30
+ type: z.ZodLiteral<"jsonBodyPath">;
31
+ path: z.ZodArray<z.ZodString>;
32
+ }, z.core.$strip>], "type">;
33
+ }, z.core.$strip>, z.ZodObject<{
34
+ type: z.ZodLiteral<"equals">;
35
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
36
+ type: z.ZodLiteral<"header">;
37
+ name: z.ZodString;
38
+ }, z.core.$strip>, z.ZodObject<{
39
+ type: z.ZodLiteral<"query">;
40
+ name: z.ZodString;
41
+ }, z.core.$strip>, z.ZodObject<{
42
+ type: z.ZodLiteral<"jsonBodyPath">;
43
+ path: z.ZodArray<z.ZodString>;
44
+ }, z.core.$strip>], "type">;
45
+ value: z.ZodString;
46
+ }, z.core.$strip>], "type">;
47
+ response: z.ZodObject<{
48
+ type: z.ZodLiteral<"echoText">;
49
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
50
+ type: z.ZodLiteral<"header">;
51
+ name: z.ZodString;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ type: z.ZodLiteral<"query">;
54
+ name: z.ZodString;
55
+ }, z.core.$strip>, z.ZodObject<{
56
+ type: z.ZodLiteral<"jsonBodyPath">;
57
+ path: z.ZodArray<z.ZodString>;
58
+ }, z.core.$strip>], "type">;
59
+ }, z.core.$strip>;
60
+ }, z.core.$strip>], "type">;
61
+ type WebhookRequestValueSource = z.infer<typeof webhookRequestValueSourceSchema>;
62
+ type WebhookVerificationConfig = z.infer<typeof webhookVerificationConfigSchema>;
63
+ type WebhookJsonBodyResult = {
64
+ ok: true;
65
+ body: Record<string, unknown>;
66
+ } | {
67
+ ok: false;
68
+ reason: "invalid_json" | "invalid_value";
69
+ };
70
+ type WebhookRequestValueResult = {
71
+ ok: true;
72
+ value: string;
73
+ } | {
74
+ ok: false;
75
+ reason: "missing" | "invalid_json" | "invalid_value";
76
+ };
77
+ type WebhookRequestValues = {
78
+ jsonBody(): WebhookJsonBodyResult;
79
+ read(source: WebhookRequestValueSource): WebhookRequestValueResult;
80
+ };
81
+ type WebhookVerificationResult = {
82
+ type: "not_verification";
83
+ } | {
84
+ type: "response";
85
+ body: string;
86
+ } | {
87
+ type: "invalid_response";
88
+ reason: "missing" | "invalid_json" | "invalid_value";
89
+ };
90
+ declare function createWebhookRequestValues(input: {
91
+ headers: Headers;
92
+ query: URLSearchParams;
93
+ rawBody: string | undefined;
94
+ }): WebhookRequestValues;
95
+ declare function evaluateWebhookVerification(input: {
96
+ config: WebhookVerificationConfig;
97
+ method: "GET" | "POST";
98
+ requestValues: WebhookRequestValues;
99
+ }): WebhookVerificationResult;
100
+ //#endregion
101
+ export { WebhookJsonBodyResult, WebhookRequestValueResult, WebhookRequestValueSource, WebhookRequestValues, WebhookVerificationConfig, WebhookVerificationResult, createWebhookRequestValues, evaluateWebhookVerification, webhookRequestValueSourceSchema, webhookVerificationConfigSchema };
102
+ //# sourceMappingURL=verification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification.d.ts","names":[],"sources":["../../../src/webhooks/verification.ts"],"mappings":";;cAIa,iCAA+B,EAAA,uBAAA,EAAA;;;;;;;;;;cAqB/B,iCAA+B,EAAA,uBAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAahC,4BAA4B,EAAE,aAAa;KAC3C,4BAA4B,EAAE,aAAa;KAE3C;EACN;EAAU,MAAM;;EAChB;EAAW;;KAEL;EACN;EAAU;;EACV;EAAW;;KAEL;EACV,YAAY;EACZ,KAAK,QAAQ,4BAA4B;;KAG/B;EACN;;EACA;EAAkB;;EAElB;EACA;;iBAGU,2BAA2B;EACzC,SAAS;EACT,OAAO;EACP;IACE;iBAgDY,4BAA4B;EAC1C,QAAQ;EACR;EACA,eAAe;IACb"}
@@ -0,0 +1,119 @@
1
+ import { z } from "zod";
2
+ //#region src/webhooks/verification.ts
3
+ const webhookRequestValueNameSchema = z.string().trim().min(1);
4
+ const webhookRequestValueSourceSchema = z.discriminatedUnion("type", [
5
+ z.object({
6
+ type: z.literal("header"),
7
+ name: webhookRequestValueNameSchema
8
+ }),
9
+ z.object({
10
+ type: z.literal("query"),
11
+ name: webhookRequestValueNameSchema
12
+ }),
13
+ z.object({
14
+ type: z.literal("jsonBodyPath"),
15
+ path: z.array(z.string().trim().min(1)).min(1)
16
+ })
17
+ ]);
18
+ const webhookVerificationPredicateSchema = z.discriminatedUnion("type", [z.object({
19
+ type: z.literal("present"),
20
+ source: webhookRequestValueSourceSchema
21
+ }), z.object({
22
+ type: z.literal("equals"),
23
+ source: webhookRequestValueSourceSchema,
24
+ value: z.string().min(1)
25
+ })]);
26
+ const webhookVerificationConfigSchema = z.discriminatedUnion("type", [z.object({ type: z.literal("none") }), z.object({
27
+ type: z.literal("challenge"),
28
+ method: z.enum(["GET", "POST"]),
29
+ when: webhookVerificationPredicateSchema,
30
+ response: z.object({
31
+ type: z.literal("echoText"),
32
+ source: webhookRequestValueSourceSchema
33
+ })
34
+ })]);
35
+ function createWebhookRequestValues(input) {
36
+ let parsedJsonBody;
37
+ function jsonBody() {
38
+ if (parsedJsonBody) return parsedJsonBody;
39
+ let value;
40
+ try {
41
+ value = JSON.parse(input.rawBody ?? "");
42
+ } catch {
43
+ parsedJsonBody = {
44
+ ok: false,
45
+ reason: "invalid_json"
46
+ };
47
+ return parsedJsonBody;
48
+ }
49
+ parsedJsonBody = isJsonObject(value) ? {
50
+ ok: true,
51
+ body: value
52
+ } : {
53
+ ok: false,
54
+ reason: "invalid_value"
55
+ };
56
+ return parsedJsonBody;
57
+ }
58
+ function read(source) {
59
+ if (source.type === "header") return requestValue(input.headers.get(source.name));
60
+ if (source.type === "query") return requestValue(input.query.get(source.name));
61
+ const body = jsonBody();
62
+ if (!body.ok) return body;
63
+ let value = body.body;
64
+ for (const segment of source.path) {
65
+ if (!isJsonObject(value) || !(segment in value)) return {
66
+ ok: false,
67
+ reason: "missing"
68
+ };
69
+ value = value[segment];
70
+ }
71
+ return requestValue(value);
72
+ }
73
+ return {
74
+ jsonBody,
75
+ read
76
+ };
77
+ }
78
+ function evaluateWebhookVerification(input) {
79
+ if (input.config.type === "none" || input.config.method !== input.method) return { type: "not_verification" };
80
+ const predicateValue = input.requestValues.read(input.config.when.source);
81
+ if (!predicateValue.ok) return { type: "not_verification" };
82
+ if (!(input.config.when.type === "present" || predicateValue.value === input.config.when.value)) return { type: "not_verification" };
83
+ const responseValue = input.requestValues.read(input.config.response.source);
84
+ if (!responseValue.ok) return {
85
+ type: "invalid_response",
86
+ reason: responseValue.reason
87
+ };
88
+ return {
89
+ type: "response",
90
+ body: responseValue.value
91
+ };
92
+ }
93
+ function requestValue(value) {
94
+ if (typeof value === "string") return value.length > 0 ? {
95
+ ok: true,
96
+ value
97
+ } : {
98
+ ok: false,
99
+ reason: "missing"
100
+ };
101
+ if (typeof value === "number") return {
102
+ ok: true,
103
+ value: `${value}`
104
+ };
105
+ return value == null ? {
106
+ ok: false,
107
+ reason: "missing"
108
+ } : {
109
+ ok: false,
110
+ reason: "invalid_value"
111
+ };
112
+ }
113
+ function isJsonObject(value) {
114
+ return typeof value === "object" && value !== null && !Array.isArray(value);
115
+ }
116
+ //#endregion
117
+ export { createWebhookRequestValues, evaluateWebhookVerification, webhookRequestValueSourceSchema, webhookVerificationConfigSchema };
118
+
119
+ //# sourceMappingURL=verification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification.js","names":[],"sources":["../../../src/webhooks/verification.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst webhookRequestValueNameSchema = z.string().trim().min(1);\n\nexport const webhookRequestValueSourceSchema = z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"header\"), name: webhookRequestValueNameSchema }),\n z.object({ type: z.literal(\"query\"), name: webhookRequestValueNameSchema }),\n z.object({\n type: z.literal(\"jsonBodyPath\"),\n path: z.array(z.string().trim().min(1)).min(1),\n }),\n]);\n\nconst webhookVerificationPredicateSchema = z.discriminatedUnion(\"type\", [\n z.object({\n type: z.literal(\"present\"),\n source: webhookRequestValueSourceSchema,\n }),\n z.object({\n type: z.literal(\"equals\"),\n source: webhookRequestValueSourceSchema,\n value: z.string().min(1),\n }),\n]);\n\nexport const webhookVerificationConfigSchema = z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"none\") }),\n z.object({\n type: z.literal(\"challenge\"),\n method: z.enum([\"GET\", \"POST\"]),\n when: webhookVerificationPredicateSchema,\n response: z.object({\n type: z.literal(\"echoText\"),\n source: webhookRequestValueSourceSchema,\n }),\n }),\n]);\n\nexport type WebhookRequestValueSource = z.infer<typeof webhookRequestValueSourceSchema>;\nexport type WebhookVerificationConfig = z.infer<typeof webhookVerificationConfigSchema>;\n\nexport type WebhookJsonBodyResult =\n | { ok: true; body: Record<string, unknown> }\n | { ok: false; reason: \"invalid_json\" | \"invalid_value\" };\n\nexport type WebhookRequestValueResult =\n | { ok: true; value: string }\n | { ok: false; reason: \"missing\" | \"invalid_json\" | \"invalid_value\" };\n\nexport type WebhookRequestValues = {\n jsonBody(): WebhookJsonBodyResult;\n read(source: WebhookRequestValueSource): WebhookRequestValueResult;\n};\n\nexport type WebhookVerificationResult =\n | { type: \"not_verification\" }\n | { type: \"response\"; body: string }\n | {\n type: \"invalid_response\";\n reason: \"missing\" | \"invalid_json\" | \"invalid_value\";\n };\n\nexport function createWebhookRequestValues(input: {\n headers: Headers;\n query: URLSearchParams;\n rawBody: string | undefined;\n}): WebhookRequestValues {\n let parsedJsonBody: WebhookJsonBodyResult | undefined;\n\n function jsonBody(): WebhookJsonBodyResult {\n if (parsedJsonBody) {\n return parsedJsonBody;\n }\n\n let value: unknown;\n try {\n value = JSON.parse(input.rawBody ?? \"\");\n } catch {\n parsedJsonBody = { ok: false, reason: \"invalid_json\" };\n return parsedJsonBody;\n }\n\n parsedJsonBody = isJsonObject(value)\n ? { ok: true, body: value }\n : { ok: false, reason: \"invalid_value\" };\n return parsedJsonBody;\n }\n\n function read(source: WebhookRequestValueSource): WebhookRequestValueResult {\n if (source.type === \"header\") {\n return requestValue(input.headers.get(source.name));\n }\n if (source.type === \"query\") {\n return requestValue(input.query.get(source.name));\n }\n\n const body = jsonBody();\n if (!body.ok) {\n return body;\n }\n\n let value: unknown = body.body;\n for (const segment of source.path) {\n if (!isJsonObject(value) || !(segment in value)) {\n return { ok: false, reason: \"missing\" };\n }\n value = value[segment];\n }\n return requestValue(value);\n }\n\n return { jsonBody, read };\n}\n\nexport function evaluateWebhookVerification(input: {\n config: WebhookVerificationConfig;\n method: \"GET\" | \"POST\";\n requestValues: WebhookRequestValues;\n}): WebhookVerificationResult {\n if (input.config.type === \"none\" || input.config.method !== input.method) {\n return { type: \"not_verification\" };\n }\n\n const predicateValue = input.requestValues.read(input.config.when.source);\n if (!predicateValue.ok) {\n return { type: \"not_verification\" };\n }\n\n const matches =\n input.config.when.type === \"present\" || predicateValue.value === input.config.when.value;\n if (!matches) {\n return { type: \"not_verification\" };\n }\n\n const responseValue = input.requestValues.read(input.config.response.source);\n if (!responseValue.ok) {\n return { type: \"invalid_response\", reason: responseValue.reason };\n }\n\n return { type: \"response\", body: responseValue.value };\n}\n\nfunction requestValue(value: unknown): WebhookRequestValueResult {\n if (typeof value === \"string\") {\n return value.length > 0 ? { ok: true, value } : { ok: false, reason: \"missing\" };\n }\n if (typeof value === \"number\") {\n return { ok: true, value: `${value}` };\n }\n return value == null ? { ok: false, reason: \"missing\" } : { ok: false, reason: \"invalid_value\" };\n}\n\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"mappings":";;AAEA,MAAM,gCAAgC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;AAE7D,MAAa,kCAAkC,EAAE,mBAAmB,QAAQ;CAC1E,EAAE,OAAO;EAAE,MAAM,EAAE,QAAQ,QAAQ;EAAG,MAAM;CAA8B,CAAC;CAC3E,EAAE,OAAO;EAAE,MAAM,EAAE,QAAQ,OAAO;EAAG,MAAM;CAA8B,CAAC;CAC1E,EAAE,OAAO;EACP,MAAM,EAAE,QAAQ,cAAc;EAC9B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;CAC/C,CAAC;AACH,CAAC;AAED,MAAM,qCAAqC,EAAE,mBAAmB,QAAQ,CACtE,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,SAAS;CACzB,QAAQ;AACV,CAAC,GACD,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,QAAQ;CACxB,QAAQ;CACR,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;AACzB,CAAC,CACH,CAAC;AAED,MAAa,kCAAkC,EAAE,mBAAmB,QAAQ,CAC1E,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,EAAE,CAAC,GACpC,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,WAAW;CAC3B,QAAQ,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC;CAC9B,MAAM;CACN,UAAU,EAAE,OAAO;EACjB,MAAM,EAAE,QAAQ,UAAU;EAC1B,QAAQ;CACV,CAAC;AACH,CAAC,CACH,CAAC;AA0BD,SAAgB,2BAA2B,OAIlB;CACvB,IAAI;CAEJ,SAAS,WAAkC;EACzC,IAAI,gBACF,OAAO;EAGT,IAAI;EACJ,IAAI;GACF,QAAQ,KAAK,MAAM,MAAM,WAAW,EAAE;EACxC,QAAQ;GACN,iBAAiB;IAAE,IAAI;IAAO,QAAQ;GAAe;GACrD,OAAO;EACT;EAEA,iBAAiB,aAAa,KAAK,IAC/B;GAAE,IAAI;GAAM,MAAM;EAAM,IACxB;GAAE,IAAI;GAAO,QAAQ;EAAgB;EACzC,OAAO;CACT;CAEA,SAAS,KAAK,QAA8D;EAC1E,IAAI,OAAO,SAAS,UAClB,OAAO,aAAa,MAAM,QAAQ,IAAI,OAAO,IAAI,CAAC;EAEpD,IAAI,OAAO,SAAS,SAClB,OAAO,aAAa,MAAM,MAAM,IAAI,OAAO,IAAI,CAAC;EAGlD,MAAM,OAAO,SAAS;EACtB,IAAI,CAAC,KAAK,IACR,OAAO;EAGT,IAAI,QAAiB,KAAK;EAC1B,KAAK,MAAM,WAAW,OAAO,MAAM;GACjC,IAAI,CAAC,aAAa,KAAK,KAAK,EAAE,WAAW,QACvC,OAAO;IAAE,IAAI;IAAO,QAAQ;GAAU;GAExC,QAAQ,MAAM;EAChB;EACA,OAAO,aAAa,KAAK;CAC3B;CAEA,OAAO;EAAE;EAAU;CAAK;AAC1B;AAEA,SAAgB,4BAA4B,OAId;CAC5B,IAAI,MAAM,OAAO,SAAS,UAAU,MAAM,OAAO,WAAW,MAAM,QAChE,OAAO,EAAE,MAAM,mBAAmB;CAGpC,MAAM,iBAAiB,MAAM,cAAc,KAAK,MAAM,OAAO,KAAK,MAAM;CACxE,IAAI,CAAC,eAAe,IAClB,OAAO,EAAE,MAAM,mBAAmB;CAKpC,IAAI,EADF,MAAM,OAAO,KAAK,SAAS,aAAa,eAAe,UAAU,MAAM,OAAO,KAAK,QAEnF,OAAO,EAAE,MAAM,mBAAmB;CAGpC,MAAM,gBAAgB,MAAM,cAAc,KAAK,MAAM,OAAO,SAAS,MAAM;CAC3E,IAAI,CAAC,cAAc,IACjB,OAAO;EAAE,MAAM;EAAoB,QAAQ,cAAc;CAAO;CAGlE,OAAO;EAAE,MAAM;EAAY,MAAM,cAAc;CAAM;AACvD;AAEA,SAAS,aAAa,OAA2C;CAC/D,IAAI,OAAO,UAAU,UACnB,OAAO,MAAM,SAAS,IAAI;EAAE,IAAI;EAAM;CAAM,IAAI;EAAE,IAAI;EAAO,QAAQ;CAAU;CAEjF,IAAI,OAAO,UAAU,UACnB,OAAO;EAAE,IAAI;EAAM,OAAO,GAAG;CAAQ;CAEvC,OAAO,SAAS,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAU,IAAI;EAAE,IAAI;EAAO,QAAQ;CAAgB;AACjG;AAEA,SAAS,aAAa,OAAkD;CACtE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E"}