@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 @@
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../../src/server.ts"],"mappings":";;;;;iBAQgB,kBACd,QAAQ,mBACR,cAAc,4DAA8B,gEAAA,0EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kGAA9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAQ+9O"}
@@ -0,0 +1,12 @@
1
+ import { apiFragmentDefinition } from "./definition.js";
2
+ import { apiRoutesFactory } from "./routes.js";
3
+ import { instantiate } from "@fragno-dev/core";
4
+ //#region src/server.ts
5
+ const apiRoutes = [apiRoutesFactory];
6
+ function createApiFragment(config, fragnoConfig) {
7
+ return instantiate(apiFragmentDefinition).withConfig(config).withRoutes(apiRoutes).withOptions(fragnoConfig).build();
8
+ }
9
+ //#endregion
10
+ export { createApiFragment };
11
+
12
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","names":[],"sources":["../../src/server.ts"],"sourcesContent":["import { instantiate } from \"@fragno-dev/core\";\nimport type { FragnoPublicConfigWithDatabase } from \"@fragno-dev/db\";\n\nimport { apiFragmentDefinition, type ApiFragmentConfig } from \"./definition\";\nimport { apiRoutesFactory } from \"./routes\";\n\nconst apiRoutes = [apiRoutesFactory] as const;\n\nexport function createApiFragment(\n config: ApiFragmentConfig,\n fragnoConfig: FragnoPublicConfigWithDatabase,\n) {\n return instantiate(apiFragmentDefinition)\n .withConfig(config)\n .withRoutes(apiRoutes)\n .withOptions(fragnoConfig)\n .build();\n}\n"],"mappings":";;;;AAMA,MAAM,YAAY,CAAC,gBAAgB;AAEnC,SAAgB,kBACd,QACA,cACA;CACA,OAAO,YAAY,qBAAqB,CAAC,CACtC,WAAW,MAAM,CAAC,CAClB,WAAW,SAAS,CAAC,CACrB,YAAY,YAAY,CAAC,CACzB,MAAM;AACX"}
@@ -0,0 +1,10 @@
1
+ import "./api-types.js";
2
+ import { z } from "zod";
3
+ //#region src/services.d.ts
4
+ interface AuthPersistenceChanges {
5
+ authPayload?: string;
6
+ authExpiresAt?: Date | null;
7
+ }
8
+ //#endregion
9
+ export { AuthPersistenceChanges };
10
+ //# sourceMappingURL=services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.d.ts","names":[],"sources":["../../src/services.ts"],"mappings":";;;UA+CiB;EACf;EACA,gBAAgB"}
@@ -0,0 +1,304 @@
1
+ import { bytesToBase64, randomBase64Url, utf8Bytes } from "./crypto.js";
2
+ import { authConfigSchema } from "./api-types.js";
3
+ import { createAuthorizationUrl, createCodeChallenge, exchangeAuthorizationCode, oauthTokensSchema, refreshOAuthToken, requestClientCredentialsToken, tokenExpiry } from "./oauth.js";
4
+ import { z } from "zod";
5
+ //#region src/services.ts
6
+ const storedAuthPayloadSchema = z.discriminatedUnion("type", [
7
+ z.object({
8
+ type: z.literal("bearer"),
9
+ token: z.string().min(1)
10
+ }),
11
+ z.object({
12
+ type: z.literal("basic"),
13
+ username: z.string().min(1),
14
+ password: z.string().min(1)
15
+ }),
16
+ authConfigSchema.options[3].extend({ tokens: oauthTokensSchema.optional() }),
17
+ authConfigSchema.options[4].extend({
18
+ tokens: oauthTokensSchema.optional(),
19
+ redirectUri: z.url().optional()
20
+ })
21
+ ]);
22
+ const TOKEN_EXPIRY_SKEW_MS = 3e4;
23
+ function assertAllowedBaseUrl(baseUrl, config) {
24
+ const url = new URL(baseUrl);
25
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error("API base URL must be an HTTP(S) URL");
26
+ if (config.allowedBaseUrls && !config.allowedBaseUrls(url)) throw new Error("API base URL is not allowed");
27
+ return url;
28
+ }
29
+ function secretByKind(secrets, kind) {
30
+ return secrets.find((secret) => secret.kind === kind);
31
+ }
32
+ function secretExpiresInFuture(secret) {
33
+ if (!secret?.expiresAt) return false;
34
+ return new Date(secret.expiresAt).getTime() > Date.now() + TOKEN_EXPIRY_SKEW_MS;
35
+ }
36
+ function secretIsUnexpired(secret) {
37
+ return !secret?.expiresAt || secretExpiresInFuture(secret);
38
+ }
39
+ /** Projects stored auth without secrets; whether credentials work can only be learned by using them. */
40
+ function projectApiAuthStatus(args) {
41
+ const mode = args.authMode;
42
+ if (mode === "none") return { mode };
43
+ if (mode !== "oauth") return {
44
+ mode,
45
+ credentials: args.authSecret ? "present" : "missing"
46
+ };
47
+ if (!args.authSecret) return {
48
+ mode,
49
+ state: "client-missing"
50
+ };
51
+ const auth = storedAuthPayloadSchema.parse(JSON.parse(args.authSecret.payload));
52
+ const tokens = auth.type === "oauth" ? auth.tokens : void 0;
53
+ if (tokens?.accessToken && (tokens.refreshToken || secretIsUnexpired(args.authSecret))) return {
54
+ mode,
55
+ state: "authorized"
56
+ };
57
+ if (args.hasPendingOAuth) return {
58
+ mode,
59
+ state: "consent-pending"
60
+ };
61
+ return {
62
+ mode,
63
+ state: tokens?.accessToken ? "expired" : "consent-required"
64
+ };
65
+ }
66
+ /** Whether stored auth can authorize requests without new consent or credentials. */
67
+ function isApiAuthReady(authMode, authSecret) {
68
+ const status = projectApiAuthStatus({
69
+ authMode,
70
+ authSecret,
71
+ hasPendingOAuth: false
72
+ });
73
+ switch (status.mode) {
74
+ case "none": return true;
75
+ case "oauth": return status.state === "authorized";
76
+ case "bearer":
77
+ case "basic":
78
+ case "client_credentials": return status.credentials === "present";
79
+ default: throw new Error("Unknown API auth mode.", { cause: status });
80
+ }
81
+ }
82
+ /**
83
+ * When stored auth stops authorizing requests because its access token expires without renewal,
84
+ * whether or not that moment has passed. Null when a refresh token renews it or nothing expires.
85
+ */
86
+ function apiAuthReadyUntil(authMode, authSecret) {
87
+ if (authMode !== "oauth" || !authSecret.expiresAt) return null;
88
+ const auth = storedAuthPayloadSchema.parse(JSON.parse(authSecret.payload));
89
+ if (auth.type !== "oauth" || !auth.tokens?.accessToken || auth.tokens.refreshToken) return null;
90
+ return /* @__PURE__ */ new Date(new Date(authSecret.expiresAt).getTime() - TOKEN_EXPIRY_SKEW_MS);
91
+ }
92
+ /**
93
+ * Projects the newest unexpired, unconsumed state; any such state completes the same connection.
94
+ * States started before links were retained have no URL and cannot be resumed.
95
+ */
96
+ function pendingOAuthLink(state) {
97
+ return state?.authorizationUrl ? {
98
+ authorizationUrl: state.authorizationUrl,
99
+ expiresAt: state.expiresAt
100
+ } : null;
101
+ }
102
+ async function createOAuthStartSnapshot(args) {
103
+ const codeVerifier = randomBase64Url(32);
104
+ const codeChallenge = await createCodeChallenge(codeVerifier);
105
+ const scopes = args.scopes ?? args.auth.scopes;
106
+ const authorizationUrl = createAuthorizationUrl({
107
+ authorizationEndpoint: args.auth.authorizationEndpoint,
108
+ clientId: args.auth.clientId,
109
+ redirectUri: args.redirectUri,
110
+ state: args.stateId,
111
+ scopes,
112
+ codeChallenge,
113
+ extraParams: {
114
+ ...args.auth.extraAuthorizationParams,
115
+ ...args.extraAuthorizationParams
116
+ }
117
+ });
118
+ return {
119
+ authorizationUrl,
120
+ oauthState: {
121
+ id: args.stateId,
122
+ connectionId: args.connection.id.toString(),
123
+ authorizationUrl: authorizationUrl.toString(),
124
+ codeVerifier,
125
+ redirectUri: args.redirectUri,
126
+ scope: scopes?.join(" ") ?? null,
127
+ expiresAt: new Date(Date.now() + 600 * 1e3)
128
+ }
129
+ };
130
+ }
131
+ async function createOAuthCallbackSnapshot(args) {
132
+ const tokens = await exchangeAuthorizationCode({
133
+ auth: args.auth,
134
+ code: args.code,
135
+ redirectUri: args.state.redirectUri,
136
+ codeVerifier: args.state.codeVerifier,
137
+ fetchImplementation: args.config.fetch
138
+ });
139
+ return {
140
+ authPayload: JSON.stringify({
141
+ ...args.auth,
142
+ redirectUri: args.state.redirectUri,
143
+ tokens
144
+ }),
145
+ authExpiresAt: tokenExpiry(tokens)
146
+ };
147
+ }
148
+ async function resolveOAuthAuth(args) {
149
+ if (!args.authPayload.tokens?.accessToken) return {};
150
+ if (secretIsUnexpired(args.authSecret)) return { authorization: `Bearer ${args.authPayload.tokens.accessToken}` };
151
+ if (!args.authPayload.tokens.refreshToken) throw new Error("OAuth access token expired and no refresh token is available");
152
+ const tokens = await refreshOAuthToken({
153
+ auth: args.authPayload,
154
+ refreshToken: args.authPayload.tokens.refreshToken,
155
+ fetchImplementation: args.config.fetch
156
+ });
157
+ if (!tokens.accessToken) throw new Error("OAuth refresh did not return an access token");
158
+ return {
159
+ authorization: `Bearer ${tokens.accessToken}`,
160
+ authChanges: {
161
+ authPayload: JSON.stringify({
162
+ ...args.authPayload,
163
+ tokens
164
+ }),
165
+ authExpiresAt: tokenExpiry(tokens)
166
+ }
167
+ };
168
+ }
169
+ async function resolveClientCredentialsAuth(args) {
170
+ if (args.authPayload.tokens?.accessToken && secretExpiresInFuture(args.authSecret)) return { authorization: `Bearer ${args.authPayload.tokens.accessToken}` };
171
+ const tokens = await requestClientCredentialsToken({
172
+ auth: args.authPayload,
173
+ fetchImplementation: args.config.fetch
174
+ });
175
+ if (!tokens.accessToken) throw new Error("Client credentials flow did not return an access token");
176
+ return {
177
+ authorization: `Bearer ${tokens.accessToken}`,
178
+ authChanges: {
179
+ authPayload: JSON.stringify({
180
+ ...args.authPayload,
181
+ tokens
182
+ }),
183
+ authExpiresAt: tokenExpiry(tokens)
184
+ }
185
+ };
186
+ }
187
+ async function resolveApiOperationAuth(args) {
188
+ const authSecret = secretByKind(args.secrets, "auth");
189
+ const authPayload = authSecret ? storedAuthPayloadSchema.parse(JSON.parse(authSecret.payload)) : void 0;
190
+ if (!authPayload) return {};
191
+ if (authPayload.type === "bearer") return { authorization: `Bearer ${authPayload.token}` };
192
+ if (authPayload.type === "basic") return { authorization: `Basic ${bytesToBase64(utf8Bytes(`${authPayload.username}:${authPayload.password}`))}` };
193
+ if (authPayload.type === "oauth") return resolveOAuthAuth({
194
+ config: args.config,
195
+ authSecret,
196
+ authPayload
197
+ });
198
+ return resolveClientCredentialsAuth({
199
+ config: args.config,
200
+ authSecret,
201
+ authPayload
202
+ });
203
+ }
204
+ function resolveRequestUrl(baseUrl, path, query) {
205
+ if (/^[a-z][a-z0-9+.-]*:/i.test(path) || path.startsWith("//")) throw new Error("API request path must be relative");
206
+ const url = new URL(path.startsWith("/") ? path.slice(1) : path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
207
+ for (const [key, value] of Object.entries(query ?? {})) url.searchParams.set(key, value);
208
+ return url;
209
+ }
210
+ function boundedErrorMessage(message) {
211
+ const normalized = message.trim();
212
+ return normalized.length > 500 ? `${normalized.slice(0, 500)}…` : normalized;
213
+ }
214
+ async function performApiRequest(args) {
215
+ const controller = args.request.timeoutMs ? new AbortController() : void 0;
216
+ const timeout = controller ? setTimeout(() => {
217
+ controller.abort();
218
+ }, args.request.timeoutMs) : void 0;
219
+ try {
220
+ const url = resolveRequestUrl(args.baseUrl, args.request.path, args.request.query);
221
+ const headers = new Headers(args.request.headers ?? {});
222
+ headers.delete("authorization");
223
+ headers.delete("Authorization");
224
+ if (args.authorization) headers.set("authorization", args.authorization);
225
+ let body;
226
+ if (args.request.body.type === "json") {
227
+ headers.set("content-type", headers.get("content-type") ?? "application/json");
228
+ body = JSON.stringify(args.request.body.value);
229
+ } else if (args.request.body.type === "text") body = args.request.body.value;
230
+ const response = await (args.config.fetch ?? fetch)(url, {
231
+ method: args.request.method,
232
+ headers,
233
+ body,
234
+ signal: controller?.signal
235
+ });
236
+ const responseHeaders = Object.fromEntries(response.headers.entries());
237
+ const responseText = await response.text();
238
+ const contentType = response.headers.get("content-type") ?? "";
239
+ let responseBody;
240
+ let responseDecodingFailed = false;
241
+ if (!responseText) responseBody = {
242
+ type: "empty",
243
+ value: null
244
+ };
245
+ else if (contentType.includes("application/json")) try {
246
+ responseBody = {
247
+ type: "json",
248
+ value: JSON.parse(responseText)
249
+ };
250
+ } catch {
251
+ responseBody = {
252
+ type: "text",
253
+ value: responseText
254
+ };
255
+ responseDecodingFailed = true;
256
+ }
257
+ else responseBody = {
258
+ type: "text",
259
+ value: responseText
260
+ };
261
+ const apiResponse = {
262
+ status: response.status,
263
+ statusText: response.statusText,
264
+ headers: responseHeaders,
265
+ body: responseBody
266
+ };
267
+ if (responseDecodingFailed) return {
268
+ ok: false,
269
+ response: apiResponse,
270
+ error: {
271
+ code: "RESPONSE_DECODING_ERROR",
272
+ message: "Upstream response declared JSON but could not be decoded"
273
+ }
274
+ };
275
+ if (response.ok) return {
276
+ ok: true,
277
+ response: apiResponse,
278
+ error: null
279
+ };
280
+ return {
281
+ ok: false,
282
+ response: apiResponse,
283
+ error: {
284
+ code: "HTTP_ERROR",
285
+ message: responseBody.type === "json" && responseBody.value && typeof responseBody.value === "object" && "message" in responseBody.value ? boundedErrorMessage(String(responseBody.value.message)) : boundedErrorMessage(responseText) || response.statusText || `HTTP ${response.status}`
286
+ }
287
+ };
288
+ } catch (error) {
289
+ return {
290
+ ok: false,
291
+ response: null,
292
+ error: {
293
+ code: "REQUEST_ERROR",
294
+ message: error instanceof Error && error.name === "AbortError" ? `API request timed out after ${args.request.timeoutMs}ms` : error instanceof Error ? error.message : String(error)
295
+ }
296
+ };
297
+ } finally {
298
+ if (timeout) clearTimeout(timeout);
299
+ }
300
+ }
301
+ //#endregion
302
+ export { apiAuthReadyUntil, assertAllowedBaseUrl, createOAuthCallbackSnapshot, createOAuthStartSnapshot, isApiAuthReady, pendingOAuthLink, performApiRequest, projectApiAuthStatus, resolveApiOperationAuth, storedAuthPayloadSchema };
303
+
304
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.js","names":[],"sources":["../../src/services.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport {\n authConfigSchema,\n type ApiAuthStatus,\n type ApiFragmentConfig,\n type ApiRequestInput,\n type ApiRequestOutput,\n type AuthConfig,\n} from \"./api-types\";\nimport { bytesToBase64, utf8Bytes } from \"./crypto\";\nimport {\n createAuthorizationUrl,\n createCodeChallenge,\n exchangeAuthorizationCode,\n randomBase64Url,\n refreshOAuthToken,\n requestClientCredentialsToken,\n tokenExpiry,\n oauthTokensSchema,\n} from \"./oauth\";\n\nexport type SecretRecord = {\n id: { toString(): string } | string;\n kind: string;\n payload: string;\n expiresAt?: Date | string | null;\n};\n\nexport const storedAuthPayloadSchema = z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"bearer\"), token: z.string().min(1) }),\n z.object({\n type: z.literal(\"basic\"),\n username: z.string().min(1),\n password: z.string().min(1),\n }),\n authConfigSchema.options[3].extend({ tokens: oauthTokensSchema.optional() }),\n authConfigSchema.options[4].extend({\n tokens: oauthTokensSchema.optional(),\n redirectUri: z.url().optional(),\n }),\n]);\n\ntype StoredAuthPayload = z.infer<typeof storedAuthPayloadSchema>;\n\nconst TOKEN_EXPIRY_SKEW_MS = 30_000;\n\nexport interface AuthPersistenceChanges {\n authPayload?: string;\n authExpiresAt?: Date | null;\n}\n\nexport function assertAllowedBaseUrl(baseUrl: string, config: ApiFragmentConfig) {\n const url = new URL(baseUrl);\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new Error(\"API base URL must be an HTTP(S) URL\");\n }\n if (config.allowedBaseUrls && !config.allowedBaseUrls(url)) {\n throw new Error(\"API base URL is not allowed\");\n }\n return url;\n}\n\nfunction secretByKind<TSecret extends { kind: string }>(secrets: TSecret[], kind: string) {\n return secrets.find((secret) => secret.kind === kind);\n}\n\nfunction secretExpiresInFuture(secret: Pick<SecretRecord, \"expiresAt\"> | undefined) {\n if (!secret?.expiresAt) {\n return false;\n }\n return new Date(secret.expiresAt).getTime() > Date.now() + TOKEN_EXPIRY_SKEW_MS;\n}\n\nfunction secretIsUnexpired(secret: Pick<SecretRecord, \"expiresAt\"> | undefined) {\n return !secret?.expiresAt || secretExpiresInFuture(secret);\n}\n\n/** Projects stored auth without secrets; whether credentials work can only be learned by using them. */\nexport function projectApiAuthStatus(args: {\n authMode: string;\n authSecret: Pick<SecretRecord, \"payload\" | \"expiresAt\"> | undefined;\n hasPendingOAuth: boolean;\n}): ApiAuthStatus {\n // Routes write authMode only from authConfigSchema discriminators.\n const mode = args.authMode as AuthConfig[\"type\"];\n if (mode === \"none\") {\n return { mode };\n }\n if (mode !== \"oauth\") {\n return { mode, credentials: args.authSecret ? \"present\" : \"missing\" };\n }\n if (!args.authSecret) {\n return { mode, state: \"client-missing\" };\n }\n const auth = storedAuthPayloadSchema.parse(JSON.parse(args.authSecret.payload));\n const tokens = auth.type === \"oauth\" ? auth.tokens : undefined;\n // Matches request-time resolution: a refresh token renews an expired access token.\n if (tokens?.accessToken && (tokens.refreshToken || secretIsUnexpired(args.authSecret))) {\n return { mode, state: \"authorized\" };\n }\n // Expired tokens without a refresh token are unusable, so a pending link is the actionable state.\n if (args.hasPendingOAuth) {\n return { mode, state: \"consent-pending\" };\n }\n return { mode, state: tokens?.accessToken ? \"expired\" : \"consent-required\" };\n}\n\n/** Whether stored auth can authorize requests without new consent or credentials. */\nexport function isApiAuthReady(\n authMode: string,\n authSecret: Pick<SecretRecord, \"payload\" | \"expiresAt\"> | undefined,\n): boolean {\n // Pending consent only distinguishes states that are not ready.\n const status = projectApiAuthStatus({ authMode, authSecret, hasPendingOAuth: false });\n switch (status.mode) {\n case \"none\":\n return true;\n case \"oauth\":\n return status.state === \"authorized\";\n case \"bearer\":\n case \"basic\":\n case \"client_credentials\":\n return status.credentials === \"present\";\n default:\n throw new Error(\"Unknown API auth mode.\", { cause: status satisfies never });\n }\n}\n\n/**\n * When stored auth stops authorizing requests because its access token expires without renewal,\n * whether or not that moment has passed. Null when a refresh token renews it or nothing expires.\n */\nexport function apiAuthReadyUntil(\n authMode: string,\n authSecret: Pick<SecretRecord, \"payload\" | \"expiresAt\">,\n): Date | null {\n if (authMode !== \"oauth\" || !authSecret.expiresAt) {\n return null;\n }\n const auth = storedAuthPayloadSchema.parse(JSON.parse(authSecret.payload));\n if (auth.type !== \"oauth\" || !auth.tokens?.accessToken || auth.tokens.refreshToken) {\n return null;\n }\n return new Date(new Date(authSecret.expiresAt).getTime() - TOKEN_EXPIRY_SKEW_MS);\n}\n\n/**\n * Projects the newest unexpired, unconsumed state; any such state completes the same connection.\n * States started before links were retained have no URL and cannot be resumed.\n */\nexport function pendingOAuthLink(\n state: { authorizationUrl: string | null; expiresAt: Date } | null,\n): { authorizationUrl: string; expiresAt: Date } | null {\n return state?.authorizationUrl\n ? { authorizationUrl: state.authorizationUrl, expiresAt: state.expiresAt }\n : null;\n}\n\nexport async function createOAuthStartSnapshot(args: {\n connection: { id: { toString(): string } | string };\n auth: Extract<AuthConfig, { type: \"oauth\" }>;\n stateId: string;\n redirectUri: string;\n scopes?: string[];\n extraAuthorizationParams?: Record<string, string>;\n}) {\n const codeVerifier = randomBase64Url(32);\n const codeChallenge = await createCodeChallenge(codeVerifier);\n const scopes = args.scopes ?? args.auth.scopes;\n const authorizationUrl = createAuthorizationUrl({\n authorizationEndpoint: args.auth.authorizationEndpoint,\n clientId: args.auth.clientId,\n redirectUri: args.redirectUri,\n state: args.stateId,\n scopes,\n codeChallenge,\n extraParams: {\n ...args.auth.extraAuthorizationParams,\n ...args.extraAuthorizationParams,\n },\n });\n return {\n authorizationUrl,\n oauthState: {\n id: args.stateId,\n connectionId: args.connection.id.toString(),\n // The link carries only the PKCE challenge, never the verifier.\n authorizationUrl: authorizationUrl.toString(),\n codeVerifier,\n redirectUri: args.redirectUri,\n scope: scopes?.join(\" \") ?? null,\n expiresAt: new Date(Date.now() + 10 * 60 * 1000),\n },\n };\n}\n\nexport async function createOAuthCallbackSnapshot(args: {\n config: ApiFragmentConfig;\n stateId: string;\n code: string;\n state: {\n codeVerifier: string;\n redirectUri: string;\n scope?: string | null;\n expiresAt: Date | string;\n consumedAt?: Date | string | null;\n };\n auth: Extract<AuthConfig, { type: \"oauth\" }>;\n}) {\n const tokens = await exchangeAuthorizationCode({\n auth: args.auth,\n code: args.code,\n redirectUri: args.state.redirectUri,\n codeVerifier: args.state.codeVerifier,\n fetchImplementation: args.config.fetch,\n });\n return {\n authPayload: JSON.stringify({\n ...args.auth,\n redirectUri: args.state.redirectUri,\n tokens,\n }),\n authExpiresAt: tokenExpiry(tokens),\n } satisfies AuthPersistenceChanges;\n}\n\nasync function resolveOAuthAuth(args: {\n config: ApiFragmentConfig;\n authSecret: SecretRecord;\n authPayload: Extract<StoredAuthPayload, { type: \"oauth\" }>;\n}) {\n if (!args.authPayload.tokens?.accessToken) {\n return {};\n }\n if (secretIsUnexpired(args.authSecret)) {\n return { authorization: `Bearer ${args.authPayload.tokens.accessToken}` };\n }\n if (!args.authPayload.tokens.refreshToken) {\n throw new Error(\"OAuth access token expired and no refresh token is available\");\n }\n const tokens = await refreshOAuthToken({\n auth: args.authPayload,\n refreshToken: args.authPayload.tokens.refreshToken,\n fetchImplementation: args.config.fetch,\n });\n if (!tokens.accessToken) {\n throw new Error(\"OAuth refresh did not return an access token\");\n }\n return {\n authorization: `Bearer ${tokens.accessToken}`,\n authChanges: {\n authPayload: JSON.stringify({ ...args.authPayload, tokens }),\n authExpiresAt: tokenExpiry(tokens),\n },\n };\n}\n\nasync function resolveClientCredentialsAuth(args: {\n config: ApiFragmentConfig;\n authSecret: SecretRecord;\n authPayload: Extract<StoredAuthPayload, { type: \"client_credentials\" }>;\n}) {\n if (args.authPayload.tokens?.accessToken && secretExpiresInFuture(args.authSecret)) {\n return { authorization: `Bearer ${args.authPayload.tokens.accessToken}` };\n }\n const tokens = await requestClientCredentialsToken({\n auth: args.authPayload,\n fetchImplementation: args.config.fetch,\n });\n if (!tokens.accessToken) {\n throw new Error(\"Client credentials flow did not return an access token\");\n }\n return {\n authorization: `Bearer ${tokens.accessToken}`,\n authChanges: {\n authPayload: JSON.stringify({ ...args.authPayload, tokens }),\n authExpiresAt: tokenExpiry(tokens),\n },\n };\n}\n\nexport async function resolveApiOperationAuth(args: {\n config: ApiFragmentConfig;\n secrets: SecretRecord[];\n}): Promise<{ authorization?: string; authChanges?: AuthPersistenceChanges }> {\n const authSecret = secretByKind(args.secrets, \"auth\");\n const authPayload = authSecret\n ? storedAuthPayloadSchema.parse(JSON.parse(authSecret.payload))\n : undefined;\n if (!authPayload) {\n return {};\n }\n if (authPayload.type === \"bearer\") {\n return { authorization: `Bearer ${authPayload.token}` };\n }\n if (authPayload.type === \"basic\") {\n const credentials = bytesToBase64(utf8Bytes(`${authPayload.username}:${authPayload.password}`));\n return { authorization: `Basic ${credentials}` };\n }\n if (authPayload.type === \"oauth\") {\n return resolveOAuthAuth({ config: args.config, authSecret: authSecret!, authPayload });\n }\n return resolveClientCredentialsAuth({\n config: args.config,\n authSecret: authSecret!,\n authPayload,\n });\n}\n\nfunction resolveRequestUrl(baseUrl: string, path: string, query?: Record<string, string>) {\n if (/^[a-z][a-z0-9+.-]*:/i.test(path) || path.startsWith(\"//\")) {\n throw new Error(\"API request path must be relative\");\n }\n const url = new URL(\n path.startsWith(\"/\") ? path.slice(1) : path,\n baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`,\n );\n for (const [key, value] of Object.entries(query ?? {})) {\n url.searchParams.set(key, value);\n }\n return url;\n}\n\nfunction boundedErrorMessage(message: string) {\n const normalized = message.trim();\n return normalized.length > 500 ? `${normalized.slice(0, 500)}…` : normalized;\n}\n\nexport async function performApiRequest(args: {\n config: ApiFragmentConfig;\n baseUrl: string;\n authorization?: string;\n request: ApiRequestInput;\n}): Promise<ApiRequestOutput> {\n const controller = args.request.timeoutMs ? new AbortController() : undefined;\n const timeout = controller\n ? setTimeout(() => {\n controller.abort();\n }, args.request.timeoutMs)\n : undefined;\n\n try {\n const url = resolveRequestUrl(args.baseUrl, args.request.path, args.request.query);\n const headers = new Headers(args.request.headers ?? {});\n headers.delete(\"authorization\");\n headers.delete(\"Authorization\");\n if (args.authorization) {\n headers.set(\"authorization\", args.authorization);\n }\n let body: BodyInit | undefined;\n if (args.request.body.type === \"json\") {\n headers.set(\"content-type\", headers.get(\"content-type\") ?? \"application/json\");\n body = JSON.stringify(args.request.body.value);\n } else if (args.request.body.type === \"text\") {\n body = args.request.body.value;\n }\n\n const response = await (args.config.fetch ?? fetch)(url, {\n method: args.request.method,\n headers,\n body,\n signal: controller?.signal,\n });\n const responseHeaders = Object.fromEntries(response.headers.entries());\n const responseText = await response.text();\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n let responseBody:\n | { type: \"json\"; value: unknown }\n | { type: \"text\"; value: string }\n | { type: \"empty\"; value: null };\n let responseDecodingFailed = false;\n if (!responseText) {\n responseBody = { type: \"empty\", value: null };\n } else if (contentType.includes(\"application/json\")) {\n try {\n responseBody = { type: \"json\", value: JSON.parse(responseText) as unknown };\n } catch {\n responseBody = { type: \"text\", value: responseText };\n responseDecodingFailed = true;\n }\n } else {\n responseBody = { type: \"text\", value: responseText };\n }\n const apiResponse = {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n body: responseBody,\n };\n if (responseDecodingFailed) {\n return {\n ok: false,\n response: apiResponse,\n error: {\n code: \"RESPONSE_DECODING_ERROR\",\n message: \"Upstream response declared JSON but could not be decoded\",\n },\n };\n }\n if (response.ok) {\n return { ok: true, response: apiResponse, error: null };\n }\n\n const responseMessage =\n responseBody.type === \"json\" &&\n responseBody.value &&\n typeof responseBody.value === \"object\" &&\n \"message\" in responseBody.value\n ? boundedErrorMessage(String(responseBody.value.message))\n : boundedErrorMessage(responseText) || response.statusText || `HTTP ${response.status}`;\n return {\n ok: false,\n response: apiResponse,\n error: { code: \"HTTP_ERROR\", message: responseMessage },\n };\n } catch (error) {\n const message =\n error instanceof Error && error.name === \"AbortError\"\n ? `API request timed out after ${args.request.timeoutMs}ms`\n : error instanceof Error\n ? error.message\n : String(error);\n return {\n ok: false,\n response: null,\n error: { code: \"REQUEST_ERROR\", message },\n };\n } finally {\n if (timeout) {\n clearTimeout(timeout);\n }\n }\n}\n"],"mappings":";;;;;AA6BA,MAAa,0BAA0B,EAAE,mBAAmB,QAAQ;CAClE,EAAE,OAAO;EAAE,MAAM,EAAE,QAAQ,QAAQ;EAAG,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAAE,CAAC;CAChE,EAAE,OAAO;EACP,MAAM,EAAE,QAAQ,OAAO;EACvB,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EAC1B,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAC5B,CAAC;CACD,iBAAiB,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,kBAAkB,SAAS,EAAE,CAAC;CAC3E,iBAAiB,QAAQ,EAAE,CAAC,OAAO;EACjC,QAAQ,kBAAkB,SAAS;EACnC,aAAa,EAAE,IAAI,CAAC,CAAC,SAAS;CAChC,CAAC;AACH,CAAC;AAID,MAAM,uBAAuB;AAO7B,SAAgB,qBAAqB,SAAiB,QAA2B;CAC/E,MAAM,MAAM,IAAI,IAAI,OAAO;CAC3B,IAAI,IAAI,aAAa,YAAY,IAAI,aAAa,SAChD,MAAM,IAAI,MAAM,qCAAqC;CAEvD,IAAI,OAAO,mBAAmB,CAAC,OAAO,gBAAgB,GAAG,GACvD,MAAM,IAAI,MAAM,6BAA6B;CAE/C,OAAO;AACT;AAEA,SAAS,aAA+C,SAAoB,MAAc;CACxF,OAAO,QAAQ,MAAM,WAAW,OAAO,SAAS,IAAI;AACtD;AAEA,SAAS,sBAAsB,QAAqD;CAClF,IAAI,CAAC,QAAQ,WACX,OAAO;CAET,OAAO,IAAI,KAAK,OAAO,SAAS,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI,IAAI;AAC7D;AAEA,SAAS,kBAAkB,QAAqD;CAC9E,OAAO,CAAC,QAAQ,aAAa,sBAAsB,MAAM;AAC3D;;AAGA,SAAgB,qBAAqB,MAInB;CAEhB,MAAM,OAAO,KAAK;CAClB,IAAI,SAAS,QACX,OAAO,EAAE,KAAK;CAEhB,IAAI,SAAS,SACX,OAAO;EAAE;EAAM,aAAa,KAAK,aAAa,YAAY;CAAU;CAEtE,IAAI,CAAC,KAAK,YACR,OAAO;EAAE;EAAM,OAAO;CAAiB;CAEzC,MAAM,OAAO,wBAAwB,MAAM,KAAK,MAAM,KAAK,WAAW,OAAO,CAAC;CAC9E,MAAM,SAAS,KAAK,SAAS,UAAU,KAAK,SAAS,KAAA;CAErD,IAAI,QAAQ,gBAAgB,OAAO,gBAAgB,kBAAkB,KAAK,UAAU,IAClF,OAAO;EAAE;EAAM,OAAO;CAAa;CAGrC,IAAI,KAAK,iBACP,OAAO;EAAE;EAAM,OAAO;CAAkB;CAE1C,OAAO;EAAE;EAAM,OAAO,QAAQ,cAAc,YAAY;CAAmB;AAC7E;;AAGA,SAAgB,eACd,UACA,YACS;CAET,MAAM,SAAS,qBAAqB;EAAE;EAAU;EAAY,iBAAiB;CAAM,CAAC;CACpF,QAAQ,OAAO,MAAf;EACE,KAAK,QACH,OAAO;EACT,KAAK,SACH,OAAO,OAAO,UAAU;EAC1B,KAAK;EACL,KAAK;EACL,KAAK,sBACH,OAAO,OAAO,gBAAgB;EAChC,SACE,MAAM,IAAI,MAAM,0BAA0B,EAAE,OAAO,OAAuB,CAAC;CAC/E;AACF;;;;;AAMA,SAAgB,kBACd,UACA,YACa;CACb,IAAI,aAAa,WAAW,CAAC,WAAW,WACtC,OAAO;CAET,MAAM,OAAO,wBAAwB,MAAM,KAAK,MAAM,WAAW,OAAO,CAAC;CACzE,IAAI,KAAK,SAAS,WAAW,CAAC,KAAK,QAAQ,eAAe,KAAK,OAAO,cACpE,OAAO;CAET,uBAAO,IAAI,KAAK,IAAI,KAAK,WAAW,SAAS,CAAC,CAAC,QAAQ,IAAI,oBAAoB;AACjF;;;;;AAMA,SAAgB,iBACd,OACsD;CACtD,OAAO,OAAO,mBACV;EAAE,kBAAkB,MAAM;EAAkB,WAAW,MAAM;CAAU,IACvE;AACN;AAEA,eAAsB,yBAAyB,MAO5C;CACD,MAAM,eAAe,gBAAgB,EAAE;CACvC,MAAM,gBAAgB,MAAM,oBAAoB,YAAY;CAC5D,MAAM,SAAS,KAAK,UAAU,KAAK,KAAK;CACxC,MAAM,mBAAmB,uBAAuB;EAC9C,uBAAuB,KAAK,KAAK;EACjC,UAAU,KAAK,KAAK;EACpB,aAAa,KAAK;EAClB,OAAO,KAAK;EACZ;EACA;EACA,aAAa;GACX,GAAG,KAAK,KAAK;GACb,GAAG,KAAK;EACV;CACF,CAAC;CACD,OAAO;EACL;EACA,YAAY;GACV,IAAI,KAAK;GACT,cAAc,KAAK,WAAW,GAAG,SAAS;GAE1C,kBAAkB,iBAAiB,SAAS;GAC5C;GACA,aAAa,KAAK;GAClB,OAAO,QAAQ,KAAK,GAAG,KAAK;GAC5B,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,MAAU,GAAI;EACjD;CACF;AACF;AAEA,eAAsB,4BAA4B,MAY/C;CACD,MAAM,SAAS,MAAM,0BAA0B;EAC7C,MAAM,KAAK;EACX,MAAM,KAAK;EACX,aAAa,KAAK,MAAM;EACxB,cAAc,KAAK,MAAM;EACzB,qBAAqB,KAAK,OAAO;CACnC,CAAC;CACD,OAAO;EACL,aAAa,KAAK,UAAU;GAC1B,GAAG,KAAK;GACR,aAAa,KAAK,MAAM;GACxB;EACF,CAAC;EACD,eAAe,YAAY,MAAM;CACnC;AACF;AAEA,eAAe,iBAAiB,MAI7B;CACD,IAAI,CAAC,KAAK,YAAY,QAAQ,aAC5B,OAAO,CAAC;CAEV,IAAI,kBAAkB,KAAK,UAAU,GACnC,OAAO,EAAE,eAAe,UAAU,KAAK,YAAY,OAAO,cAAc;CAE1E,IAAI,CAAC,KAAK,YAAY,OAAO,cAC3B,MAAM,IAAI,MAAM,8DAA8D;CAEhF,MAAM,SAAS,MAAM,kBAAkB;EACrC,MAAM,KAAK;EACX,cAAc,KAAK,YAAY,OAAO;EACtC,qBAAqB,KAAK,OAAO;CACnC,CAAC;CACD,IAAI,CAAC,OAAO,aACV,MAAM,IAAI,MAAM,8CAA8C;CAEhE,OAAO;EACL,eAAe,UAAU,OAAO;EAChC,aAAa;GACX,aAAa,KAAK,UAAU;IAAE,GAAG,KAAK;IAAa;GAAO,CAAC;GAC3D,eAAe,YAAY,MAAM;EACnC;CACF;AACF;AAEA,eAAe,6BAA6B,MAIzC;CACD,IAAI,KAAK,YAAY,QAAQ,eAAe,sBAAsB,KAAK,UAAU,GAC/E,OAAO,EAAE,eAAe,UAAU,KAAK,YAAY,OAAO,cAAc;CAE1E,MAAM,SAAS,MAAM,8BAA8B;EACjD,MAAM,KAAK;EACX,qBAAqB,KAAK,OAAO;CACnC,CAAC;CACD,IAAI,CAAC,OAAO,aACV,MAAM,IAAI,MAAM,wDAAwD;CAE1E,OAAO;EACL,eAAe,UAAU,OAAO;EAChC,aAAa;GACX,aAAa,KAAK,UAAU;IAAE,GAAG,KAAK;IAAa;GAAO,CAAC;GAC3D,eAAe,YAAY,MAAM;EACnC;CACF;AACF;AAEA,eAAsB,wBAAwB,MAGgC;CAC5E,MAAM,aAAa,aAAa,KAAK,SAAS,MAAM;CACpD,MAAM,cAAc,aAChB,wBAAwB,MAAM,KAAK,MAAM,WAAW,OAAO,CAAC,IAC5D,KAAA;CACJ,IAAI,CAAC,aACH,OAAO,CAAC;CAEV,IAAI,YAAY,SAAS,UACvB,OAAO,EAAE,eAAe,UAAU,YAAY,QAAQ;CAExD,IAAI,YAAY,SAAS,SAEvB,OAAO,EAAE,eAAe,SADJ,cAAc,UAAU,GAAG,YAAY,SAAS,GAAG,YAAY,UAAU,CAClD,IAAI;CAEjD,IAAI,YAAY,SAAS,SACvB,OAAO,iBAAiB;EAAE,QAAQ,KAAK;EAAoB;EAAa;CAAY,CAAC;CAEvF,OAAO,6BAA6B;EAClC,QAAQ,KAAK;EACD;EACZ;CACF,CAAC;AACH;AAEA,SAAS,kBAAkB,SAAiB,MAAc,OAAgC;CACxF,IAAI,uBAAuB,KAAK,IAAI,KAAK,KAAK,WAAW,IAAI,GAC3D,MAAM,IAAI,MAAM,mCAAmC;CAErD,MAAM,MAAM,IAAI,IACd,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI,MACvC,QAAQ,SAAS,GAAG,IAAI,UAAU,GAAG,QAAQ,EAC/C;CACA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,CAAC,CAAC,GACnD,IAAI,aAAa,IAAI,KAAK,KAAK;CAEjC,OAAO;AACT;AAEA,SAAS,oBAAoB,SAAiB;CAC5C,MAAM,aAAa,QAAQ,KAAK;CAChC,OAAO,WAAW,SAAS,MAAM,GAAG,WAAW,MAAM,GAAG,GAAG,EAAE,KAAK;AACpE;AAEA,eAAsB,kBAAkB,MAKV;CAC5B,MAAM,aAAa,KAAK,QAAQ,YAAY,IAAI,gBAAgB,IAAI,KAAA;CACpE,MAAM,UAAU,aACZ,iBAAiB;EACf,WAAW,MAAM;CACnB,GAAG,KAAK,QAAQ,SAAS,IACzB,KAAA;CAEJ,IAAI;EACF,MAAM,MAAM,kBAAkB,KAAK,SAAS,KAAK,QAAQ,MAAM,KAAK,QAAQ,KAAK;EACjF,MAAM,UAAU,IAAI,QAAQ,KAAK,QAAQ,WAAW,CAAC,CAAC;EACtD,QAAQ,OAAO,eAAe;EAC9B,QAAQ,OAAO,eAAe;EAC9B,IAAI,KAAK,eACP,QAAQ,IAAI,iBAAiB,KAAK,aAAa;EAEjD,IAAI;EACJ,IAAI,KAAK,QAAQ,KAAK,SAAS,QAAQ;GACrC,QAAQ,IAAI,gBAAgB,QAAQ,IAAI,cAAc,KAAK,kBAAkB;GAC7E,OAAO,KAAK,UAAU,KAAK,QAAQ,KAAK,KAAK;EAC/C,OAAO,IAAI,KAAK,QAAQ,KAAK,SAAS,QACpC,OAAO,KAAK,QAAQ,KAAK;EAG3B,MAAM,WAAW,OAAO,KAAK,OAAO,SAAS,MAAA,CAAO,KAAK;GACvD,QAAQ,KAAK,QAAQ;GACrB;GACA;GACA,QAAQ,YAAY;EACtB,CAAC;EACD,MAAM,kBAAkB,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC;EACrE,MAAM,eAAe,MAAM,SAAS,KAAK;EACzC,MAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;EAC5D,IAAI;EAIJ,IAAI,yBAAyB;EAC7B,IAAI,CAAC,cACH,eAAe;GAAE,MAAM;GAAS,OAAO;EAAK;OACvC,IAAI,YAAY,SAAS,kBAAkB,GAChD,IAAI;GACF,eAAe;IAAE,MAAM;IAAQ,OAAO,KAAK,MAAM,YAAY;GAAa;EAC5E,QAAQ;GACN,eAAe;IAAE,MAAM;IAAQ,OAAO;GAAa;GACnD,yBAAyB;EAC3B;OAEA,eAAe;GAAE,MAAM;GAAQ,OAAO;EAAa;EAErD,MAAM,cAAc;GAClB,QAAQ,SAAS;GACjB,YAAY,SAAS;GACrB,SAAS;GACT,MAAM;EACR;EACA,IAAI,wBACF,OAAO;GACL,IAAI;GACJ,UAAU;GACV,OAAO;IACL,MAAM;IACN,SAAS;GACX;EACF;EAEF,IAAI,SAAS,IACX,OAAO;GAAE,IAAI;GAAM,UAAU;GAAa,OAAO;EAAK;EAUxD,OAAO;GACL,IAAI;GACJ,UAAU;GACV,OAAO;IAAE,MAAM;IAAc,SAT7B,aAAa,SAAS,UACtB,aAAa,SACb,OAAO,aAAa,UAAU,YAC9B,aAAa,aAAa,QACtB,oBAAoB,OAAO,aAAa,MAAM,OAAO,CAAC,IACtD,oBAAoB,YAAY,KAAK,SAAS,cAAc,QAAQ,SAAS;GAI3B;EACxD;CACF,SAAS,OAAO;EAOd,OAAO;GACL,IAAI;GACJ,UAAU;GACV,OAAO;IAAE,MAAM;IAAiB,SARhC,iBAAiB,SAAS,MAAM,SAAS,eACrC,+BAA+B,KAAK,QAAQ,UAAU,MACtD,iBAAiB,QACf,MAAM,UACN,OAAO,KAAK;GAIsB;EAC1C;CACF,UAAU;EACR,IAAI,SACF,aAAa,OAAO;CAExB;AACF"}
@@ -0,0 +1,133 @@
1
+ import { timingSafeEqualBytes, timingSafeEqualText } from "../crypto.js";
2
+ import { z } from "zod";
3
+ //#region src/webhooks/auth.d.ts
4
+ declare const webhookAuthConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
5
+ type: z.ZodLiteral<"none">;
6
+ }, z.core.$strip>, z.ZodObject<{
7
+ type: z.ZodLiteral<"bearer">;
8
+ tokenRef: z.ZodString;
9
+ }, z.core.$strip>, z.ZodObject<{
10
+ type: z.ZodLiteral<"apiKey">;
11
+ location: z.ZodEnum<{
12
+ header: "header";
13
+ query: "query";
14
+ }>;
15
+ name: z.ZodString;
16
+ secretRef: z.ZodString;
17
+ }, z.core.$strip>, z.ZodObject<{
18
+ type: z.ZodLiteral<"basic">;
19
+ usernameRef: z.ZodString;
20
+ passwordRef: z.ZodString;
21
+ }, z.core.$strip>, z.ZodObject<{
22
+ type: z.ZodLiteral<"hmac">;
23
+ secretRef: z.ZodString;
24
+ algorithm: z.ZodEnum<{
25
+ sha1: "sha1";
26
+ sha256: "sha256";
27
+ sha512: "sha512";
28
+ }>;
29
+ signature: z.ZodObject<{
30
+ location: z.ZodEnum<{
31
+ header: "header";
32
+ query: "query";
33
+ }>;
34
+ name: z.ZodString;
35
+ encoding: z.ZodEnum<{
36
+ hex: "hex";
37
+ base64: "base64";
38
+ base64url: "base64url";
39
+ }>;
40
+ prefix: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strip>;
42
+ signedPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
43
+ type: z.ZodLiteral<"rawBody">;
44
+ }, z.core.$strip>, z.ZodObject<{
45
+ type: z.ZodLiteral<"timestampedBody">;
46
+ prefix: z.ZodString;
47
+ timestampHeader: z.ZodString;
48
+ delimiter: z.ZodString;
49
+ toleranceSeconds: z.ZodNumber;
50
+ }, z.core.$strip>], "type">;
51
+ }, z.core.$strip>], "type">;
52
+ type WebhookAuthConfig = z.infer<typeof webhookAuthConfigSchema>;
53
+ type WebhookAuthType = WebhookAuthConfig["type"];
54
+ type WebhookAuthSecretRef = string;
55
+ type WebhookSecretResolver = {
56
+ get(ref: WebhookAuthSecretRef): string | undefined | Promise<string | undefined>;
57
+ };
58
+ type WebhookAuthFailureReason = "missing_secret" | "missing_credential" | "invalid_credential" | "timestamp_out_of_range" | "bad_config";
59
+ type WebhookAuthResult = {
60
+ ok: true;
61
+ } | {
62
+ ok: false;
63
+ reason: WebhookAuthFailureReason;
64
+ };
65
+ type SensitiveRequestValue = {
66
+ location: "header" | "query";
67
+ name: string;
68
+ };
69
+ type WebhookAuthVerificationContext = {
70
+ request: Request;
71
+ now: Date;
72
+ secrets: WebhookSecretResolver;
73
+ rawBodyBytes(): Promise<Uint8Array>;
74
+ };
75
+ interface WebhookAuthVerifier<TConfig extends WebhookAuthConfig> {
76
+ type: TConfig["type"];
77
+ requiredSecretRefs(config: TConfig): readonly WebhookAuthSecretRef[];
78
+ sensitiveRequestValues(config: TConfig): readonly SensitiveRequestValue[];
79
+ verify(config: TConfig, context: WebhookAuthVerificationContext): Promise<WebhookAuthResult>;
80
+ }
81
+ type VerifyWebhookAuthInput = {
82
+ config: WebhookAuthConfig;
83
+ /** Pass the original web-standard Request or a clone. The body is read from request.clone(). */
84
+ request: Request;
85
+ secrets: WebhookSecretResolver;
86
+ now?: Date;
87
+ };
88
+ declare const webhookAuthVerifiers: {
89
+ none: WebhookAuthVerifier<{
90
+ type: "none";
91
+ }>;
92
+ bearer: WebhookAuthVerifier<{
93
+ type: "bearer";
94
+ tokenRef: string;
95
+ }>;
96
+ apiKey: WebhookAuthVerifier<{
97
+ type: "apiKey";
98
+ location: "header" | "query";
99
+ name: string;
100
+ secretRef: string;
101
+ }>;
102
+ basic: WebhookAuthVerifier<{
103
+ type: "basic";
104
+ usernameRef: string;
105
+ passwordRef: string;
106
+ }>;
107
+ hmac: WebhookAuthVerifier<{
108
+ type: "hmac";
109
+ secretRef: string;
110
+ algorithm: "sha1" | "sha256" | "sha512";
111
+ signature: {
112
+ location: "header" | "query";
113
+ name: string;
114
+ encoding: "hex" | "base64" | "base64url";
115
+ prefix?: string | undefined;
116
+ };
117
+ signedPayload: {
118
+ type: "rawBody";
119
+ } | {
120
+ type: "timestampedBody";
121
+ prefix: string;
122
+ timestampHeader: string;
123
+ delimiter: string;
124
+ toleranceSeconds: number;
125
+ };
126
+ }>;
127
+ };
128
+ declare function verifyWebhookAuth({ config, request, secrets, now }: VerifyWebhookAuthInput): Promise<WebhookAuthResult>;
129
+ declare function getWebhookAuthSecretRefs(config: WebhookAuthConfig): readonly WebhookAuthSecretRef[];
130
+ declare function getSensitiveWebhookAuthValues(config: WebhookAuthConfig): readonly SensitiveRequestValue[];
131
+ //#endregion
132
+ export { SensitiveRequestValue, VerifyWebhookAuthInput, WebhookAuthConfig, WebhookAuthFailureReason, WebhookAuthResult, WebhookAuthSecretRef, WebhookAuthType, WebhookAuthVerificationContext, WebhookAuthVerifier, WebhookSecretResolver, getSensitiveWebhookAuthValues, getWebhookAuthSecretRefs, timingSafeEqualBytes, timingSafeEqualText, verifyWebhookAuth, webhookAuthConfigSchema, webhookAuthVerifiers };
133
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","names":[],"sources":["../../../src/webhooks/auth.ts"],"mappings":";;;cAca,yBAAuB,EAAA,uBAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAqCxB,oBAAoB,EAAE,aAAa;KACnC,kBAAkB;KAClB;KAEA;EACV,IAAI,KAAK,4CAA4C;;KAG3C;KAOA;EAAsB;;EAAe;EAAW,QAAQ;;KAExD;EACV;EACA;;KAGU;EACV,SAAS;EACT,KAAK;EACL,SAAS;EACT,gBAAgB,QAAQ;;UAGT,oBAAoB,gBAAgB;EACnD,MAAM;EACN,mBAAmB,QAAQ,mBAAmB;EAC9C,uBAAuB,QAAQ,mBAAmB;EAClD,OAAO,QAAQ,SAAS,SAAS,iCAAiC,QAAQ;;KAOhE;EACV,QAAQ;;EAER,SAAS;EACT,SAAS;EACT,MAAM;;cAoLK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQS,oBACpB,QACA,SACA,SACA,OACC,yBAAyB,QAAQ;iBAepB,yBACd,QAAQ,6BACE;iBAII,8BACd,QAAQ,6BACE"}