@zapier/policy-context 0.3.0 → 0.5.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/index.cjs CHANGED
@@ -23,9 +23,11 @@ __export(index_exports, {
23
23
  ActionRunContextSchema: () => ActionRunContextSchema,
24
24
  BuildActionRunContextOptionsSchema: () => BuildActionRunContextOptionsSchema,
25
25
  BuildHttpRequestContextOptionsSchema: () => BuildHttpRequestContextOptionsSchema,
26
+ DEFAULT_PORTS: () => DEFAULT_PORTS,
26
27
  HttpRequestContextSchema: () => HttpRequestContextSchema,
27
28
  IGNORED_HEADERS: () => IGNORED_HEADERS,
28
29
  JsonValueSchema: () => JsonValueSchema,
30
+ ParsedUrlSchema: () => ParsedUrlSchema,
29
31
  RequestContextSchema: () => RequestContextSchema,
30
32
  buildActionResource: () => buildActionResource,
31
33
  buildActionRunContext: () => buildActionRunContext,
@@ -34,6 +36,8 @@ __export(index_exports, {
34
36
  buildExactActionConditions: () => buildExactActionConditions,
35
37
  buildExactHttpConditions: () => buildExactHttpConditions,
36
38
  buildHttpRequestContext: () => buildHttpRequestContext,
39
+ buildUrlString: () => buildUrlString,
40
+ parseUrl: () => parseUrl,
37
41
  toExactPolicy: () => toExactPolicy
38
42
  });
39
43
  module.exports = __toCommonJS(index_exports);
@@ -41,17 +45,56 @@ module.exports = __toCommonJS(index_exports);
41
45
  // src/types.ts
42
46
  var import_zod = require("zod");
43
47
  var JsonValueSchema = import_zod.z.custom();
48
+ var ParsedUrlSchema = import_zod.z.object({
49
+ scheme: import_zod.z.string(),
50
+ host: import_zod.z.string(),
51
+ port: import_zod.z.number(),
52
+ path: import_zod.z.string(),
53
+ query: import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))
54
+ });
55
+ var DEFAULT_PORTS = {
56
+ http: 80,
57
+ https: 443
58
+ };
59
+ function parseUrl(raw) {
60
+ const url = new URL(raw);
61
+ const scheme = url.protocol.slice(0, -1).toLowerCase();
62
+ const host = url.hostname.toLowerCase();
63
+ const port = url.port ? parseInt(url.port, 10) : DEFAULT_PORTS[scheme] ?? 0;
64
+ const path = url.pathname || "/";
65
+ const query = {};
66
+ for (const [key, value] of url.searchParams) {
67
+ if (!query[key]) {
68
+ query[key] = [];
69
+ }
70
+ query[key].push(value);
71
+ }
72
+ return { scheme, host, port, path, query };
73
+ }
74
+ function buildUrlString(parsed) {
75
+ const { scheme, host, port, path, query } = parsed;
76
+ const defaultPort = DEFAULT_PORTS[scheme];
77
+ const portString = port !== defaultPort ? `:${String(port)}` : "";
78
+ const params = new URLSearchParams();
79
+ for (const [key, values] of Object.entries(query)) {
80
+ for (const value of values) {
81
+ params.append(key, value);
82
+ }
83
+ }
84
+ const queryString = params.toString();
85
+ return `${scheme}://${host}${portString}${path}${queryString ? "?" + queryString : ""}`;
86
+ }
44
87
  var HttpRequestContextSchema = import_zod.z.object({
45
88
  request_type: import_zod.z.literal("http_request"),
46
89
  method: import_zod.z.string(),
47
- /** Full URL including query string, e.g. "https://api.example.com/v1/foo?bar=baz" */
48
- url: import_zod.z.string(),
90
+ /** Parsed URL components for policy condition matching */
91
+ url: ParsedUrlSchema,
49
92
  /** Filtered headers (IGNORED_HEADERS excluded, keys lowercased) */
50
- headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
93
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()),
51
94
  /** Raw body string */
52
- body: import_zod.z.string().optional(),
95
+ body: import_zod.z.string().nullable(),
53
96
  /** Parsed JSON body */
54
- body_json: JsonValueSchema.optional(),
97
+ body_json: JsonValueSchema.nullable(),
55
98
  /** Zapier connection ID used for this request */
56
99
  connection_id: import_zod.z.string().optional(),
57
100
  /** Optional human-readable reason for the request */
@@ -120,7 +163,8 @@ var BuildHttpRequestContextOptionsSchema = import_zod2.z.object({
120
163
  connection_id: import_zod2.z.string().optional()
121
164
  });
122
165
  function buildHttpRequestContext(options) {
123
- const url = options.url.includes("://") ? options.url : `https://${options.url}`;
166
+ const rawUrl = options.url.includes("://") ? options.url : `https://${options.url}`;
167
+ const url = parseUrl(rawUrl);
124
168
  const headers = {};
125
169
  if (options.headers) {
126
170
  for (const [key, value] of Object.entries(options.headers)) {
@@ -130,13 +174,26 @@ function buildHttpRequestContext(options) {
130
174
  }
131
175
  }
132
176
  }
177
+ let body_json = null;
178
+ if (options.body_json !== void 0) {
179
+ body_json = options.body_json;
180
+ } else if (options.body) {
181
+ const contentType = headers["content-type"];
182
+ const isJson = typeof contentType === "string" && (contentType.includes("application/json") || contentType.includes("application/") && contentType.includes("+json"));
183
+ if (isJson) {
184
+ try {
185
+ body_json = JSON.parse(options.body);
186
+ } catch {
187
+ }
188
+ }
189
+ }
133
190
  return HttpRequestContextSchema.parse({
134
191
  request_type: "http_request",
135
192
  method: options.method,
136
193
  url,
137
194
  headers,
138
- body: typeof options.body === "string" ? options.body : void 0,
139
- body_json: typeof options.body_json === "object" && options.body_json !== null ? options.body_json : void 0,
195
+ body: options.body ?? null,
196
+ body_json,
140
197
  connection_id: options.connection_id
141
198
  });
142
199
  }
@@ -163,70 +220,46 @@ function buildExactHttpConditions(context) {
163
220
  const conditions = [
164
221
  { path: ["method"], operator: "equals", value: context.method }
165
222
  ];
166
- let parsedUrl;
167
- try {
168
- parsedUrl = new URL(context.url);
169
- } catch {
170
- console.debug(
171
- "buildExactHttpConditions: unparseable URL, using raw value",
172
- { url: context.url }
173
- );
174
- conditions.push({
175
- path: ["url"],
176
- operator: "matches_url",
177
- value: context.url
178
- });
179
- return conditions;
180
- }
181
- const urlWithoutQuery = `${parsedUrl.protocol}//${parsedUrl.host}${parsedUrl.pathname}`;
223
+ const { url } = context;
224
+ const defaultPort = url.scheme === "https" ? 443 : url.scheme === "http" ? 80 : 0;
225
+ const portString = url.port !== defaultPort ? `:${String(url.port)}` : "";
226
+ const urlWithoutQuery = `${url.scheme}://${url.host}${portString}${url.path}`;
182
227
  conditions.push({
183
228
  path: ["url"],
184
229
  operator: "matches_url",
185
230
  value: urlWithoutQuery
186
231
  });
187
- const seenKeys = /* @__PURE__ */ new Set();
188
- for (const key of parsedUrl.searchParams.keys()) {
189
- if (seenKeys.has(key)) continue;
190
- seenKeys.add(key);
191
- const values = parsedUrl.searchParams.getAll(key);
232
+ for (const [key, values] of Object.entries(url.query)) {
192
233
  conditions.push({
193
234
  path: ["url", "query", key],
194
235
  operator: "equals",
195
236
  value: values
196
237
  });
197
238
  }
198
- if (context.body_json !== void 0 && context.body_json !== null) {
239
+ if (context.body_json !== null) {
199
240
  conditions.push({
200
241
  path: ["body_json"],
201
242
  operator: "equals",
202
243
  value: context.body_json
203
244
  });
204
245
  }
205
- if (context.headers) {
206
- for (const [key, value] of Object.entries(context.headers)) {
207
- if (!IGNORED_HEADERS.has(key.toLowerCase()) && typeof value === "string") {
208
- conditions.push({
209
- path: ["headers", key.toLowerCase()],
210
- operator: "equals",
211
- value
212
- });
213
- }
246
+ for (const [key, value] of Object.entries(context.headers)) {
247
+ if (!IGNORED_HEADERS.has(key.toLowerCase()) && typeof value === "string") {
248
+ conditions.push({
249
+ path: ["headers", key.toLowerCase()],
250
+ operator: "equals",
251
+ value
252
+ });
214
253
  }
215
254
  }
216
255
  return conditions;
217
256
  }
218
257
  function buildDomainHttpConditions(context) {
219
- let host;
220
- try {
221
- host = new URL(context.url).host;
222
- } catch {
223
- return null;
224
- }
225
258
  return [
226
259
  {
227
260
  path: ["url", "host"],
228
261
  operator: "matches_host",
229
- value: host
262
+ value: context.url.host
230
263
  }
231
264
  ];
232
265
  }
@@ -263,21 +296,11 @@ function httpContextToPolicy(context) {
263
296
  statements.push(buildAuthStatement(context.connection_id));
264
297
  }
265
298
  const conditions = buildExactHttpConditions(context);
266
- try {
267
- const queryKeys = [...new URL(context.url).searchParams.keys()];
268
- const uniqueKeys = [...new Set(queryKeys)];
269
- conditions.push({
270
- path: ["url", "query"],
271
- operator: "keys_in",
272
- value: uniqueKeys
273
- });
274
- } catch {
275
- conditions.push({
276
- path: ["url", "query"],
277
- operator: "keys_in",
278
- value: []
279
- });
280
- }
299
+ conditions.push({
300
+ path: ["url", "query"],
301
+ operator: "keys_in",
302
+ value: Object.keys(context.url.query)
303
+ });
281
304
  statements.push({
282
305
  effect: "allow",
283
306
  permissions: ["can_execute"],
@@ -310,9 +333,11 @@ function toExactPolicy(context) {
310
333
  ActionRunContextSchema,
311
334
  BuildActionRunContextOptionsSchema,
312
335
  BuildHttpRequestContextOptionsSchema,
336
+ DEFAULT_PORTS,
313
337
  HttpRequestContextSchema,
314
338
  IGNORED_HEADERS,
315
339
  JsonValueSchema,
340
+ ParsedUrlSchema,
316
341
  RequestContextSchema,
317
342
  buildActionResource,
318
343
  buildActionRunContext,
@@ -321,6 +346,8 @@ function toExactPolicy(context) {
321
346
  buildExactActionConditions,
322
347
  buildExactHttpConditions,
323
348
  buildHttpRequestContext,
349
+ buildUrlString,
350
+ parseUrl,
324
351
  toExactPolicy
325
352
  });
326
353
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/constants.ts","../src/builders.ts","../src/conditions.ts","../src/policies.ts"],"sourcesContent":["// ABOUTME: Public API for @zapier/policy-context.\n// ABOUTME: Shared context types, builders, and condition generators for sdkapi + approvalsapi.\n\nexport {\n HttpRequestContextSchema,\n ActionRunContextSchema,\n RequestContextSchema,\n JsonValueSchema,\n type HttpRequestContext,\n type ActionRunContext,\n type RequestContext,\n} from \"./types.ts\";\n\nexport { IGNORED_HEADERS } from \"./constants.ts\";\n\nexport {\n buildHttpRequestContext,\n buildActionRunContext,\n BuildHttpRequestContextOptionsSchema,\n BuildActionRunContextOptionsSchema,\n type BuildHttpRequestContextOptions,\n type BuildActionRunContextOptions,\n} from \"./builders.ts\";\n\nexport {\n buildExactHttpConditions,\n buildDomainHttpConditions,\n buildExactActionConditions,\n buildActionResource,\n buildBroadActionResource,\n} from \"./conditions.ts\";\n\nexport { toExactPolicy } from \"./policies.ts\";\n","// ABOUTME: Canonical request context types shared between sdkapi and approvalsapi.\n// ABOUTME: These define the contract for how requests are described in approval flows and policy evaluation.\n\nimport { z } from \"zod\";\nimport type { JsonValue } from \"@zapier/policy-schema\";\n\n/**\n * Zod schema for arbitrary JSON values. Used across context schemas for\n * unstructured fields like `body_json` and `inputs`.\n */\nexport const JsonValueSchema = z.custom<JsonValue>();\n\n/**\n * HTTP request context for approval/policy flows.\n * Sent by sdkapi when a relay request is denied, stored by approvalsapi,\n * and used to build policy conditions.\n */\nexport const HttpRequestContextSchema = z.object({\n request_type: z.literal(\"http_request\"),\n method: z.string(),\n /** Full URL including query string, e.g. \"https://api.example.com/v1/foo?bar=baz\" */\n url: z.string(),\n /** Filtered headers (IGNORED_HEADERS excluded, keys lowercased) */\n headers: z.record(z.string(), z.string()).optional(),\n /** Raw body string */\n body: z.string().optional(),\n /** Parsed JSON body */\n body_json: JsonValueSchema.optional(),\n /** Zapier connection ID used for this request */\n connection_id: z.string().optional(),\n /** Optional human-readable reason for the request */\n reason: z.string().optional(),\n});\nexport type HttpRequestContext = z.infer<typeof HttpRequestContextSchema>;\n\n/**\n * Action run context for approval/policy flows.\n * Sent by sdkapi when an action run is denied, stored by approvalsapi,\n * and used to build policy conditions.\n */\nexport const ActionRunContextSchema = z.object({\n request_type: z.literal(\"action_run\"),\n selected_api: z.string(),\n action_type: z.string(),\n action_key: z.string(),\n connection_id: z.string().optional(),\n inputs: JsonValueSchema.optional(),\n reason: z.string().optional(),\n});\nexport type ActionRunContext = z.infer<typeof ActionRunContextSchema>;\n\n/**\n * Discriminated union of all request context types.\n */\nexport const RequestContextSchema = z.discriminatedUnion(\"request_type\", [\n HttpRequestContextSchema,\n ActionRunContextSchema,\n]);\nexport type RequestContext = z.infer<typeof RequestContextSchema>;\n","// ABOUTME: Canonical set of headers to exclude when building request contexts.\n// ABOUTME: Shared between sdkapi (context creation) and approvalsapi (condition building).\n\n/**\n * Headers to exclude when building request contexts from raw HTTP requests.\n *\n * These are either sensitive (auth credentials), HTTP infrastructure headers\n * that vary per-request, or proxy-internal headers that are not meaningful\n * for policy decisions.\n */\nexport const IGNORED_HEADERS = new Set([\n // Auth / credentials — never include in contexts or policy conditions\n \"authorization\",\n \"x-relay-authorization\",\n \"x-relay-authentication-id\",\n \"x-api-key\",\n \"cookie\",\n \"set-cookie\",\n \"proxy-authorization\",\n\n // HTTP infrastructure — varies per-request, not policy-relevant\n \"host\",\n \"connection\",\n \"content-length\",\n \"transfer-encoding\",\n \"accept-encoding\",\n \"user-agent\",\n \"accept\",\n \"accept-language\",\n \"sec-fetch-mode\",\n\n // Proxy / forwarding — infrastructure, not meaningful for policy\n \"x-forwarded-for\",\n \"x-forwarded-host\",\n \"x-forwarded-proto\",\n \"x-real-ip\",\n\n // Proxy internal — sdkapi-specific headers\n \"x-is-interactive-session\",\n \"x-relay-callback-url\",\n]);\n","// ABOUTME: Canonical builders for creating request contexts from raw request parts.\n// ABOUTME: Used by sdkapi to build both approval contexts and policy eval contexts.\n\nimport { z } from \"zod\";\nimport {\n HttpRequestContextSchema,\n ActionRunContextSchema,\n JsonValueSchema,\n type HttpRequestContext,\n type ActionRunContext,\n} from \"./types.ts\";\nimport { IGNORED_HEADERS } from \"./constants.ts\";\n\nexport const BuildHttpRequestContextOptionsSchema = z.object({\n method: z.string(),\n /** Raw URL string. Will be normalized with https:// if no protocol present. */\n url: z.string(),\n /** Raw headers from the request. Will be filtered through IGNORED_HEADERS. */\n headers: z\n .record(\n z.string(),\n z.union([z.string(), z.array(z.string()), z.undefined()]),\n )\n .optional(),\n /** Raw body string */\n body: z.string().nullable().optional(),\n /** Pre-parsed JSON body (e.g. from Fastify's automatic JSON parsing) */\n body_json: JsonValueSchema.optional(),\n /** Connection ID associated with this request */\n connection_id: z.string().optional(),\n});\nexport type BuildHttpRequestContextOptions = z.infer<\n typeof BuildHttpRequestContextOptionsSchema\n>;\n\n/**\n * Build a canonical HttpRequestContext from raw request parts.\n *\n * This is the single source of truth for how HTTP request contexts are created.\n * Both sdkapi (for approval creation) and approvalsapi (for context storage)\n * should use this to ensure the context shape is consistent.\n *\n * - Normalizes URL (adds https:// if missing)\n * - Filters headers through IGNORED_HEADERS\n * - Only includes string-valued headers (drops string[] values like set-cookie)\n */\nexport function buildHttpRequestContext(\n options: BuildHttpRequestContextOptions,\n): HttpRequestContext {\n // Normalize URL — SDK CLI strips the protocol\n const url = options.url.includes(\"://\")\n ? options.url\n : `https://${options.url}`;\n\n // Filter headers: exclude ignored, lowercase keys, string values only\n const headers: Record<string, string> = {};\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n const lowerKey = key.toLowerCase();\n if (!IGNORED_HEADERS.has(lowerKey) && typeof value === \"string\") {\n headers[lowerKey] = value;\n }\n }\n }\n\n return HttpRequestContextSchema.parse({\n request_type: \"http_request\",\n method: options.method,\n url,\n headers,\n body: typeof options.body === \"string\" ? options.body : undefined,\n body_json:\n typeof options.body_json === \"object\" && options.body_json !== null\n ? options.body_json\n : undefined,\n connection_id: options.connection_id,\n });\n}\n\nexport const BuildActionRunContextOptionsSchema = z.object({\n selected_api: z.string(),\n action_type: z.string(),\n action_key: z.string(),\n connection_id: z.string().optional(),\n inputs: JsonValueSchema.optional(),\n});\nexport type BuildActionRunContextOptions = z.infer<\n typeof BuildActionRunContextOptionsSchema\n>;\n\n/**\n * Build a canonical ActionRunContext from raw action parts.\n */\nexport function buildActionRunContext(\n options: BuildActionRunContextOptions,\n): ActionRunContext {\n return ActionRunContextSchema.parse({\n request_type: \"action_run\",\n selected_api: options.selected_api,\n action_type: options.action_type,\n action_key: options.action_key,\n connection_id: options.connection_id,\n inputs: options.inputs,\n });\n}\n","// ABOUTME: Builds policy conditions from stored request contexts.\n// ABOUTME: Used by approvalsapi's \"allow exact request\" and \"allow domain\" instant policy flows.\n\nimport type { Condition } from \"@zapier/policy-schema\";\nimport type { HttpRequestContext, ActionRunContext } from \"./types.ts\";\nimport { IGNORED_HEADERS } from \"./constants.ts\";\n\n/**\n * Build conditions that match an exact HTTP request.\n *\n * Handles the matches_url / query param split correctly:\n * - `matches_url` only supports scheme+host+port+path (no query string per spec)\n * - Query parameters are added as separate decomposed conditions on `url.query.<key>`\n *\n * @returns Condition[] for a relay/http permission statement\n */\nexport function buildExactHttpConditions(\n context: HttpRequestContext,\n): Condition[] {\n const conditions: Condition[] = [\n { path: [\"method\"], operator: \"equals\", value: context.method },\n ];\n\n // Parse the URL to separate path from query params\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(context.url);\n } catch {\n // URL is unparseable — use it as-is (will fail matches_url, but that's correct)\n console.debug(\n \"buildExactHttpConditions: unparseable URL, using raw value\",\n { url: context.url },\n );\n conditions.push({\n path: [\"url\"],\n operator: \"matches_url\",\n value: context.url,\n });\n return conditions;\n }\n\n // matches_url condition uses URL without query string (per @zapier/policy spec)\n const urlWithoutQuery = `${parsedUrl.protocol}//${parsedUrl.host}${parsedUrl.pathname}`;\n conditions.push({\n path: [\"url\"],\n operator: \"matches_url\",\n value: urlWithoutQuery,\n });\n\n // Add decomposed conditions for each query parameter.\n // Use getAll to properly handle multi-value params (e.g. ?tag=a&tag=b → [\"a\", \"b\"]).\n const seenKeys = new Set<string>();\n for (const key of parsedUrl.searchParams.keys()) {\n if (seenKeys.has(key)) continue;\n seenKeys.add(key);\n const values = parsedUrl.searchParams.getAll(key);\n conditions.push({\n path: [\"url\", \"query\", key],\n operator: \"equals\",\n value: values,\n });\n }\n\n // body_json condition\n if (context.body_json !== undefined && context.body_json !== null) {\n conditions.push({\n path: [\"body_json\"],\n operator: \"equals\",\n value: context.body_json,\n });\n }\n\n // Header conditions (filtered through IGNORED_HEADERS again for safety)\n if (context.headers) {\n for (const [key, value] of Object.entries(context.headers)) {\n if (\n !IGNORED_HEADERS.has(key.toLowerCase()) &&\n typeof value === \"string\"\n ) {\n conditions.push({\n path: [\"headers\", key.toLowerCase()],\n operator: \"equals\",\n value,\n });\n }\n }\n }\n\n return conditions;\n}\n\n/**\n * Build conditions that match any request to a given domain.\n * Extracts the host from the context URL.\n *\n * @returns Condition[] for a relay/http permission statement, or null if URL is unparseable\n */\nexport function buildDomainHttpConditions(\n context: HttpRequestContext,\n): Condition[] | null {\n let host: string;\n try {\n host = new URL(context.url).host;\n } catch {\n return null;\n }\n\n return [\n {\n path: [\"url\", \"host\"],\n operator: \"matches_host\",\n value: host,\n },\n ];\n}\n\n/**\n * Build conditions that match an exact action run (with inputs).\n *\n * @returns Condition[] for an action permission statement\n */\nexport function buildExactActionConditions(\n context: ActionRunContext,\n): Condition[] {\n const conditions: Condition[] = [];\n\n if (\n context.inputs !== undefined &&\n context.inputs !== null &&\n typeof context.inputs === \"object\"\n ) {\n conditions.push({\n path: [\"params\"],\n operator: \"equals\",\n value: context.inputs,\n });\n }\n\n return conditions;\n}\n\n/**\n * Build the resource string for an action.\n * Format: action/{selected_api}/{action_type}/{action_key}\n */\nexport function buildActionResource(context: ActionRunContext): string {\n return `action/${context.selected_api}/${context.action_type}/${context.action_key}`;\n}\n\n/**\n * Build a broad action resource that matches all actions for an app.\n * Format: action/{selected_api}/{wildcard}/{wildcard}\n */\nexport function buildBroadActionResource(context: ActionRunContext): string {\n return `action/${context.selected_api}/*/*`;\n}\n","// ABOUTME: Assembles complete Policy objects from request contexts.\n// ABOUTME: Single source of truth for \"exact request\" instant policies used by web UI and API.\n\nimport type { Policy, Statement } from \"@zapier/policy-schema\";\nimport type {\n RequestContext,\n HttpRequestContext,\n ActionRunContext,\n} from \"./types.ts\";\nimport {\n buildExactHttpConditions,\n buildExactActionConditions,\n buildActionResource,\n} from \"./conditions.ts\";\n\n/**\n * Build a `can_use` authentication statement, if the context has a connection_id.\n */\nfunction buildAuthStatement(authenticationId: string): Statement {\n return {\n effect: \"allow\",\n permissions: [\"can_use\"],\n resources: [`authentication/${authenticationId}`],\n conditions: [],\n };\n}\n\n/**\n * Build an exact-match policy from an HTTP request context.\n *\n * Produces a policy that allows only the specific request described by the context:\n * - `can_use` statement for the authentication (if present)\n * - `can_execute` statement for `relay/http` with conditions from `buildExactHttpConditions`\n * plus a `keys_in` guard restricting allowed query parameter keys\n */\nfunction httpContextToPolicy(context: HttpRequestContext): Policy {\n const statements: Statement[] = [];\n\n if (context.connection_id) {\n statements.push(buildAuthStatement(context.connection_id));\n }\n\n const conditions = buildExactHttpConditions(context);\n\n // Add a keys_in guard so only the query param keys present in the original\n // request are allowed — blocks injection of extra query params.\n try {\n const queryKeys = [...new URL(context.url).searchParams.keys()];\n const uniqueKeys = [...new Set(queryKeys)];\n conditions.push({\n path: [\"url\", \"query\"],\n operator: \"keys_in\",\n value: uniqueKeys,\n });\n } catch {\n // If URL parsing fails, block all query params\n conditions.push({\n path: [\"url\", \"query\"],\n operator: \"keys_in\",\n value: [],\n });\n }\n\n statements.push({\n effect: \"allow\",\n permissions: [\"can_execute\"],\n resources: [\"relay/http\"],\n conditions,\n });\n\n return { version: 2, statements };\n}\n\n/**\n * Build an exact-match policy from an action run context.\n *\n * Produces a policy that allows only the specific action described by the context:\n * - `can_use` statement for the authentication (if present)\n * - `can_execute` statement for the action resource with input conditions\n */\nfunction actionContextToPolicy(context: ActionRunContext): Policy {\n const statements: Statement[] = [];\n\n if (context.connection_id) {\n statements.push(buildAuthStatement(context.connection_id));\n }\n\n statements.push({\n effect: \"allow\",\n permissions: [\"can_execute\"],\n resources: [buildActionResource(context)],\n conditions: buildExactActionConditions(context),\n });\n\n return { version: 2, statements };\n}\n\n/**\n * Build an exact-match policy from any supported request context.\n *\n * This is the single source of truth for \"allow this exact request\" instant policies.\n * Used by the web UI and anywhere else that needs to produce an exact policy from a context.\n */\nexport function toExactPolicy(context: RequestContext): Policy {\n if (context.request_type === \"http_request\") {\n return httpContextToPolicy(context);\n }\n return actionContextToPolicy(context);\n}\n"],"mappings":"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","names":["import_zod"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/constants.ts","../src/builders.ts","../src/conditions.ts","../src/policies.ts"],"sourcesContent":["// ABOUTME: Public API for @zapier/policy-context.\n// ABOUTME: Shared context types, builders, and condition generators for sdkapi + approvalsapi.\n\nexport {\n HttpRequestContextSchema,\n ActionRunContextSchema,\n RequestContextSchema,\n JsonValueSchema,\n ParsedUrlSchema,\n DEFAULT_PORTS,\n parseUrl,\n buildUrlString,\n type HttpRequestContext,\n type ActionRunContext,\n type RequestContext,\n type ParsedUrl,\n} from \"./types.ts\";\n\nexport { IGNORED_HEADERS } from \"./constants.ts\";\n\nexport {\n buildHttpRequestContext,\n buildActionRunContext,\n BuildHttpRequestContextOptionsSchema,\n BuildActionRunContextOptionsSchema,\n type BuildHttpRequestContextOptions,\n type BuildActionRunContextOptions,\n} from \"./builders.ts\";\n\nexport {\n buildExactHttpConditions,\n buildDomainHttpConditions,\n buildExactActionConditions,\n buildActionResource,\n buildBroadActionResource,\n} from \"./conditions.ts\";\n\nexport { toExactPolicy } from \"./policies.ts\";\n","// ABOUTME: Canonical request context types shared between sdkapi and approvalsapi.\n// ABOUTME: These define the contract for how requests are described in approval flows and policy evaluation.\n\nimport { z } from \"zod\";\nimport type { JsonValue } from \"@zapier/policy-schema\";\n\n/**\n * Zod schema for arbitrary JSON values. Used across context schemas for\n * unstructured fields like `body_json` and `inputs`.\n */\nexport const JsonValueSchema = z.custom<JsonValue>();\n\n/**\n * Parsed URL components for policy evaluation.\n * Matches the ParsedUrl shape from @zapier/policy-schema so that\n * evaluatePolicy condition paths like [\"url\", \"host\"] resolve correctly.\n */\nexport const ParsedUrlSchema = z.object({\n scheme: z.string(),\n host: z.string(),\n port: z.number(),\n path: z.string(),\n query: z.record(z.string(), z.array(z.string())),\n});\nexport type ParsedUrl = z.infer<typeof ParsedUrlSchema>;\n\n/** Default ports by scheme, used when URL has no explicit port. */\nexport const DEFAULT_PORTS: Record<string, number> = {\n http: 80,\n https: 443,\n};\n\n/**\n * Parse a raw URL string into a ParsedUrl.\n * Same logic as parseUrlForPolicyContext in @zapier/policy.\n */\nexport function parseUrl(raw: string): ParsedUrl {\n const url = new URL(raw);\n const scheme = url.protocol.slice(0, -1).toLowerCase();\n const host = url.hostname.toLowerCase();\n const port = url.port\n ? parseInt(url.port, 10)\n : (DEFAULT_PORTS[scheme] ?? 0);\n const path = url.pathname || \"/\";\n const query: Record<string, string[]> = {};\n for (const [key, value] of url.searchParams) {\n if (!query[key]) {\n query[key] = [];\n }\n query[key].push(value);\n }\n return { scheme, host, port, path, query };\n}\n\n/**\n * Reconstruct a URL string from a ParsedUrl.\n * Same logic as buildUrl in @zapier/policy.\n */\nexport function buildUrlString(parsed: ParsedUrl): string {\n const { scheme, host, port, path, query } = parsed;\n const defaultPort = DEFAULT_PORTS[scheme];\n const portString = port !== defaultPort ? `:${String(port)}` : \"\";\n const params = new URLSearchParams();\n for (const [key, values] of Object.entries(query)) {\n for (const value of values) {\n params.append(key, value);\n }\n }\n const queryString = params.toString();\n return `${scheme}://${host}${portString}${path}${queryString ? \"?\" + queryString : \"\"}`;\n}\n\n/**\n * HTTP request context for approval/policy flows.\n * Sent by sdkapi when a relay request is denied, stored by approvalsapi,\n * and used for both policy evaluation and building policy conditions.\n *\n * The url field is a ParsedUrl object (matching @zapier/policy-schema's ParsedUrl)\n * so that evaluatePolicy conditions like [\"url\", \"host\"] resolve correctly.\n */\nexport const HttpRequestContextSchema = z.object({\n request_type: z.literal(\"http_request\"),\n method: z.string(),\n /** Parsed URL components for policy condition matching */\n url: ParsedUrlSchema,\n /** Filtered headers (IGNORED_HEADERS excluded, keys lowercased) */\n headers: z.record(z.string(), z.string()),\n /** Raw body string */\n body: z.string().nullable(),\n /** Parsed JSON body */\n body_json: JsonValueSchema.nullable(),\n /** Zapier connection ID used for this request */\n connection_id: z.string().optional(),\n /** Optional human-readable reason for the request */\n reason: z.string().optional(),\n});\nexport type HttpRequestContext = z.infer<typeof HttpRequestContextSchema>;\n\n/**\n * Action run context for approval/policy flows.\n * Sent by sdkapi when an action run is denied, stored by approvalsapi,\n * and used to build policy conditions.\n */\nexport const ActionRunContextSchema = z.object({\n request_type: z.literal(\"action_run\"),\n selected_api: z.string(),\n action_type: z.string(),\n action_key: z.string(),\n connection_id: z.string().optional(),\n inputs: JsonValueSchema.optional(),\n reason: z.string().optional(),\n});\nexport type ActionRunContext = z.infer<typeof ActionRunContextSchema>;\n\n/**\n * Discriminated union of all request context types.\n */\nexport const RequestContextSchema = z.discriminatedUnion(\"request_type\", [\n HttpRequestContextSchema,\n ActionRunContextSchema,\n]);\nexport type RequestContext = z.infer<typeof RequestContextSchema>;\n","// ABOUTME: Canonical set of headers to exclude when building request contexts.\n// ABOUTME: Shared between sdkapi (context creation) and approvalsapi (condition building).\n\n/**\n * Headers to exclude when building request contexts from raw HTTP requests.\n *\n * These are either sensitive (auth credentials), HTTP infrastructure headers\n * that vary per-request, or proxy-internal headers that are not meaningful\n * for policy decisions.\n */\nexport const IGNORED_HEADERS = new Set([\n // Auth / credentials — never include in contexts or policy conditions\n \"authorization\",\n \"x-relay-authorization\",\n \"x-relay-authentication-id\",\n \"x-api-key\",\n \"cookie\",\n \"set-cookie\",\n \"proxy-authorization\",\n\n // HTTP infrastructure — varies per-request, not policy-relevant\n \"host\",\n \"connection\",\n \"content-length\",\n \"transfer-encoding\",\n \"accept-encoding\",\n \"user-agent\",\n \"accept\",\n \"accept-language\",\n \"sec-fetch-mode\",\n\n // Proxy / forwarding — infrastructure, not meaningful for policy\n \"x-forwarded-for\",\n \"x-forwarded-host\",\n \"x-forwarded-proto\",\n \"x-real-ip\",\n\n // Proxy internal — sdkapi-specific headers\n \"x-is-interactive-session\",\n \"x-relay-callback-url\",\n]);\n","// ABOUTME: Canonical builders for creating request contexts from raw request parts.\n// ABOUTME: Used by sdkapi to build both approval contexts and policy eval contexts.\n\nimport { z } from \"zod\";\nimport {\n HttpRequestContextSchema,\n ActionRunContextSchema,\n JsonValueSchema,\n parseUrl,\n type HttpRequestContext,\n type ActionRunContext,\n} from \"./types.ts\";\nimport { IGNORED_HEADERS } from \"./constants.ts\";\n\nexport const BuildHttpRequestContextOptionsSchema = z.object({\n method: z.string(),\n /** Raw URL string. Will be normalized with https:// if no protocol present. */\n url: z.string(),\n /** Raw headers from the request. Will be filtered through IGNORED_HEADERS. */\n headers: z\n .record(\n z.string(),\n z.union([z.string(), z.array(z.string()), z.undefined()]),\n )\n .optional(),\n /** Raw body string */\n body: z.string().nullable().optional(),\n /** Pre-parsed JSON body (e.g. from Fastify's automatic JSON parsing) */\n body_json: JsonValueSchema.optional(),\n /** Connection ID associated with this request */\n connection_id: z.string().optional(),\n});\nexport type BuildHttpRequestContextOptions = z.infer<\n typeof BuildHttpRequestContextOptionsSchema\n>;\n\n/**\n * Build a canonical HttpRequestContext from raw request parts.\n *\n * This is the single source of truth for how HTTP request contexts are created.\n * Both sdkapi (for policy evaluation) and approvalsapi (for context storage)\n * should use this to ensure the context shape is consistent.\n *\n * Matches the behavior of createPolicyHttpRequest from @zapier/policy, with\n * the addition of header filtering through IGNORED_HEADERS.\n *\n * - Normalizes URL (adds https:// if missing) and parses into { scheme, host, port, path, query }\n * - Filters headers through IGNORED_HEADERS, lowercases keys, string values only\n * - Auto-parses body_json from body when content-type is JSON and body_json not provided\n * - body defaults to null (not undefined)\n */\nexport function buildHttpRequestContext(\n options: BuildHttpRequestContextOptions,\n): HttpRequestContext {\n // Normalize URL — SDK CLI strips the protocol\n const rawUrl = options.url.includes(\"://\")\n ? options.url\n : `https://${options.url}`;\n const url = parseUrl(rawUrl);\n\n // Filter headers: exclude ignored, lowercase keys, string values only\n const headers: Record<string, string> = {};\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n const lowerKey = key.toLowerCase();\n if (!IGNORED_HEADERS.has(lowerKey) && typeof value === \"string\") {\n headers[lowerKey] = value;\n }\n }\n }\n\n // Auto-parse body_json from body when content-type is JSON\n // (matches createPolicyHttpRequest behavior from @zapier/policy)\n let body_json: unknown = null;\n if (options.body_json !== undefined) {\n body_json = options.body_json;\n } else if (options.body) {\n const contentType = headers[\"content-type\"];\n const isJson =\n typeof contentType === \"string\" &&\n (contentType.includes(\"application/json\") ||\n (contentType.includes(\"application/\") &&\n contentType.includes(\"+json\")));\n if (isJson) {\n try {\n body_json = JSON.parse(options.body);\n } catch {\n // Invalid JSON body — leave body_json as null\n }\n }\n }\n\n return HttpRequestContextSchema.parse({\n request_type: \"http_request\",\n method: options.method,\n url,\n headers,\n body: options.body ?? null,\n body_json,\n connection_id: options.connection_id,\n });\n}\n\nexport const BuildActionRunContextOptionsSchema = z.object({\n selected_api: z.string(),\n action_type: z.string(),\n action_key: z.string(),\n connection_id: z.string().optional(),\n inputs: JsonValueSchema.optional(),\n});\nexport type BuildActionRunContextOptions = z.infer<\n typeof BuildActionRunContextOptionsSchema\n>;\n\n/**\n * Build a canonical ActionRunContext from raw action parts.\n */\nexport function buildActionRunContext(\n options: BuildActionRunContextOptions,\n): ActionRunContext {\n return ActionRunContextSchema.parse({\n request_type: \"action_run\",\n selected_api: options.selected_api,\n action_type: options.action_type,\n action_key: options.action_key,\n connection_id: options.connection_id,\n inputs: options.inputs,\n });\n}\n","// ABOUTME: Builds policy conditions from stored request contexts.\n// ABOUTME: Used by approvalsapi's \"allow exact request\" and \"allow domain\" instant policy flows.\n\nimport type { Condition } from \"@zapier/policy-schema\";\nimport type { HttpRequestContext, ActionRunContext } from \"./types.ts\";\nimport { IGNORED_HEADERS } from \"./constants.ts\";\n\n/**\n * Build conditions that match an exact HTTP request.\n *\n * Handles the matches_url / query param split correctly:\n * - `matches_url` only supports scheme+host+port+path (no query string per spec)\n * - Query parameters are added as separate decomposed conditions on `url.query.<key>`\n *\n * @returns Condition[] for a relay/http permission statement\n */\nexport function buildExactHttpConditions(\n context: HttpRequestContext,\n): Condition[] {\n const conditions: Condition[] = [\n { path: [\"method\"], operator: \"equals\", value: context.method },\n ];\n\n const { url } = context;\n\n // matches_url condition uses URL without query string (per @zapier/policy spec)\n const defaultPort = url.scheme === \"https\" ? 443 : url.scheme === \"http\" ? 80 : 0;\n const portString = url.port !== defaultPort ? `:${String(url.port)}` : \"\";\n const urlWithoutQuery = `${url.scheme}://${url.host}${portString}${url.path}`;\n conditions.push({\n path: [\"url\"],\n operator: \"matches_url\",\n value: urlWithoutQuery,\n });\n\n // Add decomposed conditions for each query parameter.\n for (const [key, values] of Object.entries(url.query)) {\n conditions.push({\n path: [\"url\", \"query\", key],\n operator: \"equals\",\n value: values,\n });\n }\n\n // body_json condition\n if (context.body_json !== null) {\n conditions.push({\n path: [\"body_json\"],\n operator: \"equals\",\n value: context.body_json,\n });\n }\n\n // Header conditions (filtered through IGNORED_HEADERS again for safety)\n for (const [key, value] of Object.entries(context.headers)) {\n if (\n !IGNORED_HEADERS.has(key.toLowerCase()) &&\n typeof value === \"string\"\n ) {\n conditions.push({\n path: [\"headers\", key.toLowerCase()],\n operator: \"equals\",\n value,\n });\n }\n }\n\n return conditions;\n}\n\n/**\n * Build conditions that match any request to a given domain.\n * Uses the host from the parsed URL.\n *\n * @returns Condition[] for a relay/http permission statement\n */\nexport function buildDomainHttpConditions(\n context: HttpRequestContext,\n): Condition[] {\n return [\n {\n path: [\"url\", \"host\"],\n operator: \"matches_host\",\n value: context.url.host,\n },\n ];\n}\n\n/**\n * Build conditions that match an exact action run (with inputs).\n *\n * @returns Condition[] for an action permission statement\n */\nexport function buildExactActionConditions(\n context: ActionRunContext,\n): Condition[] {\n const conditions: Condition[] = [];\n\n if (\n context.inputs !== undefined &&\n context.inputs !== null &&\n typeof context.inputs === \"object\"\n ) {\n conditions.push({\n path: [\"params\"],\n operator: \"equals\",\n value: context.inputs,\n });\n }\n\n return conditions;\n}\n\n/**\n * Build the resource string for an action.\n * Format: action/{selected_api}/{action_type}/{action_key}\n */\nexport function buildActionResource(context: ActionRunContext): string {\n return `action/${context.selected_api}/${context.action_type}/${context.action_key}`;\n}\n\n/**\n * Build a broad action resource that matches all actions for an app.\n * Format: action/{selected_api}/{wildcard}/{wildcard}\n */\nexport function buildBroadActionResource(context: ActionRunContext): string {\n return `action/${context.selected_api}/*/*`;\n}\n","// ABOUTME: Assembles complete Policy objects from request contexts.\n// ABOUTME: Single source of truth for \"exact request\" instant policies used by web UI and API.\n\nimport type { Policy, Statement } from \"@zapier/policy-schema\";\nimport type {\n RequestContext,\n HttpRequestContext,\n ActionRunContext,\n} from \"./types.ts\";\nimport {\n buildExactHttpConditions,\n buildExactActionConditions,\n buildActionResource,\n} from \"./conditions.ts\";\n\n/**\n * Build a `can_use` authentication statement, if the context has a connection_id.\n */\nfunction buildAuthStatement(authenticationId: string): Statement {\n return {\n effect: \"allow\",\n permissions: [\"can_use\"],\n resources: [`authentication/${authenticationId}`],\n conditions: [],\n };\n}\n\n/**\n * Build an exact-match policy from an HTTP request context.\n *\n * Produces a policy that allows only the specific request described by the context:\n * - `can_use` statement for the authentication (if present)\n * - `can_execute` statement for `relay/http` with conditions from `buildExactHttpConditions`\n * plus a `keys_in` guard restricting allowed query parameter keys\n */\nfunction httpContextToPolicy(context: HttpRequestContext): Policy {\n const statements: Statement[] = [];\n\n if (context.connection_id) {\n statements.push(buildAuthStatement(context.connection_id));\n }\n\n const conditions = buildExactHttpConditions(context);\n\n // Add a keys_in guard so only the query param keys present in the original\n // request are allowed — blocks injection of extra query params.\n conditions.push({\n path: [\"url\", \"query\"],\n operator: \"keys_in\",\n value: Object.keys(context.url.query),\n });\n\n statements.push({\n effect: \"allow\",\n permissions: [\"can_execute\"],\n resources: [\"relay/http\"],\n conditions,\n });\n\n return { version: 2, statements };\n}\n\n/**\n * Build an exact-match policy from an action run context.\n *\n * Produces a policy that allows only the specific action described by the context:\n * - `can_use` statement for the authentication (if present)\n * - `can_execute` statement for the action resource with input conditions\n */\nfunction actionContextToPolicy(context: ActionRunContext): Policy {\n const statements: Statement[] = [];\n\n if (context.connection_id) {\n statements.push(buildAuthStatement(context.connection_id));\n }\n\n statements.push({\n effect: \"allow\",\n permissions: [\"can_execute\"],\n resources: [buildActionResource(context)],\n conditions: buildExactActionConditions(context),\n });\n\n return { version: 2, statements };\n}\n\n/**\n * Build an exact-match policy from any supported request context.\n *\n * This is the single source of truth for \"allow this exact request\" instant policies.\n * Used by the web UI and anywhere else that needs to produce an exact policy from a context.\n */\nexport function toExactPolicy(context: RequestContext): Policy {\n if (context.request_type === \"http_request\") {\n return httpContextToPolicy(context);\n }\n return actionContextToPolicy(context);\n}\n"],"mappings":"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","names":["import_zod"]}
package/dist/index.d.cts CHANGED
@@ -7,18 +7,52 @@ import { JsonValue, Condition, Policy } from '@zapier/policy-schema';
7
7
  * unstructured fields like `body_json` and `inputs`.
8
8
  */
9
9
  declare const JsonValueSchema: z.ZodCustom<JsonValue, JsonValue>;
10
+ /**
11
+ * Parsed URL components for policy evaluation.
12
+ * Matches the ParsedUrl shape from @zapier/policy-schema so that
13
+ * evaluatePolicy condition paths like ["url", "host"] resolve correctly.
14
+ */
15
+ declare const ParsedUrlSchema: z.ZodObject<{
16
+ scheme: z.ZodString;
17
+ host: z.ZodString;
18
+ port: z.ZodNumber;
19
+ path: z.ZodString;
20
+ query: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
21
+ }, z.core.$strip>;
22
+ type ParsedUrl = z.infer<typeof ParsedUrlSchema>;
23
+ /** Default ports by scheme, used when URL has no explicit port. */
24
+ declare const DEFAULT_PORTS: Record<string, number>;
25
+ /**
26
+ * Parse a raw URL string into a ParsedUrl.
27
+ * Same logic as parseUrlForPolicyContext in @zapier/policy.
28
+ */
29
+ declare function parseUrl(raw: string): ParsedUrl;
30
+ /**
31
+ * Reconstruct a URL string from a ParsedUrl.
32
+ * Same logic as buildUrl in @zapier/policy.
33
+ */
34
+ declare function buildUrlString(parsed: ParsedUrl): string;
10
35
  /**
11
36
  * HTTP request context for approval/policy flows.
12
37
  * Sent by sdkapi when a relay request is denied, stored by approvalsapi,
13
- * and used to build policy conditions.
38
+ * and used for both policy evaluation and building policy conditions.
39
+ *
40
+ * The url field is a ParsedUrl object (matching @zapier/policy-schema's ParsedUrl)
41
+ * so that evaluatePolicy conditions like ["url", "host"] resolve correctly.
14
42
  */
15
43
  declare const HttpRequestContextSchema: z.ZodObject<{
16
44
  request_type: z.ZodLiteral<"http_request">;
17
45
  method: z.ZodString;
18
- url: z.ZodString;
19
- headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
20
- body: z.ZodOptional<z.ZodString>;
21
- body_json: z.ZodOptional<z.ZodCustom<JsonValue, JsonValue>>;
46
+ url: z.ZodObject<{
47
+ scheme: z.ZodString;
48
+ host: z.ZodString;
49
+ port: z.ZodNumber;
50
+ path: z.ZodString;
51
+ query: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
52
+ }, z.core.$strip>;
53
+ headers: z.ZodRecord<z.ZodString, z.ZodString>;
54
+ body: z.ZodNullable<z.ZodString>;
55
+ body_json: z.ZodNullable<z.ZodCustom<JsonValue, JsonValue>>;
22
56
  connection_id: z.ZodOptional<z.ZodString>;
23
57
  reason: z.ZodOptional<z.ZodString>;
24
58
  }, z.core.$strip>;
@@ -44,10 +78,16 @@ type ActionRunContext = z.infer<typeof ActionRunContextSchema>;
44
78
  declare const RequestContextSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
45
79
  request_type: z.ZodLiteral<"http_request">;
46
80
  method: z.ZodString;
47
- url: z.ZodString;
48
- headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
49
- body: z.ZodOptional<z.ZodString>;
50
- body_json: z.ZodOptional<z.ZodCustom<JsonValue, JsonValue>>;
81
+ url: z.ZodObject<{
82
+ scheme: z.ZodString;
83
+ host: z.ZodString;
84
+ port: z.ZodNumber;
85
+ path: z.ZodString;
86
+ query: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
87
+ }, z.core.$strip>;
88
+ headers: z.ZodRecord<z.ZodString, z.ZodString>;
89
+ body: z.ZodNullable<z.ZodString>;
90
+ body_json: z.ZodNullable<z.ZodCustom<JsonValue, JsonValue>>;
51
91
  connection_id: z.ZodOptional<z.ZodString>;
52
92
  reason: z.ZodOptional<z.ZodString>;
53
93
  }, z.core.$strip>, z.ZodObject<{
@@ -83,12 +123,16 @@ type BuildHttpRequestContextOptions = z.infer<typeof BuildHttpRequestContextOpti
83
123
  * Build a canonical HttpRequestContext from raw request parts.
84
124
  *
85
125
  * This is the single source of truth for how HTTP request contexts are created.
86
- * Both sdkapi (for approval creation) and approvalsapi (for context storage)
126
+ * Both sdkapi (for policy evaluation) and approvalsapi (for context storage)
87
127
  * should use this to ensure the context shape is consistent.
88
128
  *
89
- * - Normalizes URL (adds https:// if missing)
90
- * - Filters headers through IGNORED_HEADERS
91
- * - Only includes string-valued headers (drops string[] values like set-cookie)
129
+ * Matches the behavior of createPolicyHttpRequest from @zapier/policy, with
130
+ * the addition of header filtering through IGNORED_HEADERS.
131
+ *
132
+ * - Normalizes URL (adds https:// if missing) and parses into { scheme, host, port, path, query }
133
+ * - Filters headers through IGNORED_HEADERS, lowercases keys, string values only
134
+ * - Auto-parses body_json from body when content-type is JSON and body_json not provided
135
+ * - body defaults to null (not undefined)
92
136
  */
93
137
  declare function buildHttpRequestContext(options: BuildHttpRequestContextOptions): HttpRequestContext;
94
138
  declare const BuildActionRunContextOptionsSchema: z.ZodObject<{
@@ -116,11 +160,11 @@ declare function buildActionRunContext(options: BuildActionRunContextOptions): A
116
160
  declare function buildExactHttpConditions(context: HttpRequestContext): Condition[];
117
161
  /**
118
162
  * Build conditions that match any request to a given domain.
119
- * Extracts the host from the context URL.
163
+ * Uses the host from the parsed URL.
120
164
  *
121
- * @returns Condition[] for a relay/http permission statement, or null if URL is unparseable
165
+ * @returns Condition[] for a relay/http permission statement
122
166
  */
123
- declare function buildDomainHttpConditions(context: HttpRequestContext): Condition[] | null;
167
+ declare function buildDomainHttpConditions(context: HttpRequestContext): Condition[];
124
168
  /**
125
169
  * Build conditions that match an exact action run (with inputs).
126
170
  *
@@ -146,4 +190,4 @@ declare function buildBroadActionResource(context: ActionRunContext): string;
146
190
  */
147
191
  declare function toExactPolicy(context: RequestContext): Policy;
148
192
 
149
- export { type ActionRunContext, ActionRunContextSchema, type BuildActionRunContextOptions, BuildActionRunContextOptionsSchema, type BuildHttpRequestContextOptions, BuildHttpRequestContextOptionsSchema, type HttpRequestContext, HttpRequestContextSchema, IGNORED_HEADERS, JsonValueSchema, type RequestContext, RequestContextSchema, buildActionResource, buildActionRunContext, buildBroadActionResource, buildDomainHttpConditions, buildExactActionConditions, buildExactHttpConditions, buildHttpRequestContext, toExactPolicy };
193
+ export { type ActionRunContext, ActionRunContextSchema, type BuildActionRunContextOptions, BuildActionRunContextOptionsSchema, type BuildHttpRequestContextOptions, BuildHttpRequestContextOptionsSchema, DEFAULT_PORTS, type HttpRequestContext, HttpRequestContextSchema, IGNORED_HEADERS, JsonValueSchema, type ParsedUrl, ParsedUrlSchema, type RequestContext, RequestContextSchema, buildActionResource, buildActionRunContext, buildBroadActionResource, buildDomainHttpConditions, buildExactActionConditions, buildExactHttpConditions, buildHttpRequestContext, buildUrlString, parseUrl, toExactPolicy };
package/dist/index.d.ts CHANGED
@@ -7,18 +7,52 @@ import { JsonValue, Condition, Policy } from '@zapier/policy-schema';
7
7
  * unstructured fields like `body_json` and `inputs`.
8
8
  */
9
9
  declare const JsonValueSchema: z.ZodCustom<JsonValue, JsonValue>;
10
+ /**
11
+ * Parsed URL components for policy evaluation.
12
+ * Matches the ParsedUrl shape from @zapier/policy-schema so that
13
+ * evaluatePolicy condition paths like ["url", "host"] resolve correctly.
14
+ */
15
+ declare const ParsedUrlSchema: z.ZodObject<{
16
+ scheme: z.ZodString;
17
+ host: z.ZodString;
18
+ port: z.ZodNumber;
19
+ path: z.ZodString;
20
+ query: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
21
+ }, z.core.$strip>;
22
+ type ParsedUrl = z.infer<typeof ParsedUrlSchema>;
23
+ /** Default ports by scheme, used when URL has no explicit port. */
24
+ declare const DEFAULT_PORTS: Record<string, number>;
25
+ /**
26
+ * Parse a raw URL string into a ParsedUrl.
27
+ * Same logic as parseUrlForPolicyContext in @zapier/policy.
28
+ */
29
+ declare function parseUrl(raw: string): ParsedUrl;
30
+ /**
31
+ * Reconstruct a URL string from a ParsedUrl.
32
+ * Same logic as buildUrl in @zapier/policy.
33
+ */
34
+ declare function buildUrlString(parsed: ParsedUrl): string;
10
35
  /**
11
36
  * HTTP request context for approval/policy flows.
12
37
  * Sent by sdkapi when a relay request is denied, stored by approvalsapi,
13
- * and used to build policy conditions.
38
+ * and used for both policy evaluation and building policy conditions.
39
+ *
40
+ * The url field is a ParsedUrl object (matching @zapier/policy-schema's ParsedUrl)
41
+ * so that evaluatePolicy conditions like ["url", "host"] resolve correctly.
14
42
  */
15
43
  declare const HttpRequestContextSchema: z.ZodObject<{
16
44
  request_type: z.ZodLiteral<"http_request">;
17
45
  method: z.ZodString;
18
- url: z.ZodString;
19
- headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
20
- body: z.ZodOptional<z.ZodString>;
21
- body_json: z.ZodOptional<z.ZodCustom<JsonValue, JsonValue>>;
46
+ url: z.ZodObject<{
47
+ scheme: z.ZodString;
48
+ host: z.ZodString;
49
+ port: z.ZodNumber;
50
+ path: z.ZodString;
51
+ query: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
52
+ }, z.core.$strip>;
53
+ headers: z.ZodRecord<z.ZodString, z.ZodString>;
54
+ body: z.ZodNullable<z.ZodString>;
55
+ body_json: z.ZodNullable<z.ZodCustom<JsonValue, JsonValue>>;
22
56
  connection_id: z.ZodOptional<z.ZodString>;
23
57
  reason: z.ZodOptional<z.ZodString>;
24
58
  }, z.core.$strip>;
@@ -44,10 +78,16 @@ type ActionRunContext = z.infer<typeof ActionRunContextSchema>;
44
78
  declare const RequestContextSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
45
79
  request_type: z.ZodLiteral<"http_request">;
46
80
  method: z.ZodString;
47
- url: z.ZodString;
48
- headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
49
- body: z.ZodOptional<z.ZodString>;
50
- body_json: z.ZodOptional<z.ZodCustom<JsonValue, JsonValue>>;
81
+ url: z.ZodObject<{
82
+ scheme: z.ZodString;
83
+ host: z.ZodString;
84
+ port: z.ZodNumber;
85
+ path: z.ZodString;
86
+ query: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
87
+ }, z.core.$strip>;
88
+ headers: z.ZodRecord<z.ZodString, z.ZodString>;
89
+ body: z.ZodNullable<z.ZodString>;
90
+ body_json: z.ZodNullable<z.ZodCustom<JsonValue, JsonValue>>;
51
91
  connection_id: z.ZodOptional<z.ZodString>;
52
92
  reason: z.ZodOptional<z.ZodString>;
53
93
  }, z.core.$strip>, z.ZodObject<{
@@ -83,12 +123,16 @@ type BuildHttpRequestContextOptions = z.infer<typeof BuildHttpRequestContextOpti
83
123
  * Build a canonical HttpRequestContext from raw request parts.
84
124
  *
85
125
  * This is the single source of truth for how HTTP request contexts are created.
86
- * Both sdkapi (for approval creation) and approvalsapi (for context storage)
126
+ * Both sdkapi (for policy evaluation) and approvalsapi (for context storage)
87
127
  * should use this to ensure the context shape is consistent.
88
128
  *
89
- * - Normalizes URL (adds https:// if missing)
90
- * - Filters headers through IGNORED_HEADERS
91
- * - Only includes string-valued headers (drops string[] values like set-cookie)
129
+ * Matches the behavior of createPolicyHttpRequest from @zapier/policy, with
130
+ * the addition of header filtering through IGNORED_HEADERS.
131
+ *
132
+ * - Normalizes URL (adds https:// if missing) and parses into { scheme, host, port, path, query }
133
+ * - Filters headers through IGNORED_HEADERS, lowercases keys, string values only
134
+ * - Auto-parses body_json from body when content-type is JSON and body_json not provided
135
+ * - body defaults to null (not undefined)
92
136
  */
93
137
  declare function buildHttpRequestContext(options: BuildHttpRequestContextOptions): HttpRequestContext;
94
138
  declare const BuildActionRunContextOptionsSchema: z.ZodObject<{
@@ -116,11 +160,11 @@ declare function buildActionRunContext(options: BuildActionRunContextOptions): A
116
160
  declare function buildExactHttpConditions(context: HttpRequestContext): Condition[];
117
161
  /**
118
162
  * Build conditions that match any request to a given domain.
119
- * Extracts the host from the context URL.
163
+ * Uses the host from the parsed URL.
120
164
  *
121
- * @returns Condition[] for a relay/http permission statement, or null if URL is unparseable
165
+ * @returns Condition[] for a relay/http permission statement
122
166
  */
123
- declare function buildDomainHttpConditions(context: HttpRequestContext): Condition[] | null;
167
+ declare function buildDomainHttpConditions(context: HttpRequestContext): Condition[];
124
168
  /**
125
169
  * Build conditions that match an exact action run (with inputs).
126
170
  *
@@ -146,4 +190,4 @@ declare function buildBroadActionResource(context: ActionRunContext): string;
146
190
  */
147
191
  declare function toExactPolicy(context: RequestContext): Policy;
148
192
 
149
- export { type ActionRunContext, ActionRunContextSchema, type BuildActionRunContextOptions, BuildActionRunContextOptionsSchema, type BuildHttpRequestContextOptions, BuildHttpRequestContextOptionsSchema, type HttpRequestContext, HttpRequestContextSchema, IGNORED_HEADERS, JsonValueSchema, type RequestContext, RequestContextSchema, buildActionResource, buildActionRunContext, buildBroadActionResource, buildDomainHttpConditions, buildExactActionConditions, buildExactHttpConditions, buildHttpRequestContext, toExactPolicy };
193
+ export { type ActionRunContext, ActionRunContextSchema, type BuildActionRunContextOptions, BuildActionRunContextOptionsSchema, type BuildHttpRequestContextOptions, BuildHttpRequestContextOptionsSchema, DEFAULT_PORTS, type HttpRequestContext, HttpRequestContextSchema, IGNORED_HEADERS, JsonValueSchema, type ParsedUrl, ParsedUrlSchema, type RequestContext, RequestContextSchema, buildActionResource, buildActionRunContext, buildBroadActionResource, buildDomainHttpConditions, buildExactActionConditions, buildExactHttpConditions, buildHttpRequestContext, buildUrlString, parseUrl, toExactPolicy };
package/dist/index.js CHANGED
@@ -1,17 +1,56 @@
1
1
  // src/types.ts
2
2
  import { z } from "zod";
3
3
  var JsonValueSchema = z.custom();
4
+ var ParsedUrlSchema = z.object({
5
+ scheme: z.string(),
6
+ host: z.string(),
7
+ port: z.number(),
8
+ path: z.string(),
9
+ query: z.record(z.string(), z.array(z.string()))
10
+ });
11
+ var DEFAULT_PORTS = {
12
+ http: 80,
13
+ https: 443
14
+ };
15
+ function parseUrl(raw) {
16
+ const url = new URL(raw);
17
+ const scheme = url.protocol.slice(0, -1).toLowerCase();
18
+ const host = url.hostname.toLowerCase();
19
+ const port = url.port ? parseInt(url.port, 10) : DEFAULT_PORTS[scheme] ?? 0;
20
+ const path = url.pathname || "/";
21
+ const query = {};
22
+ for (const [key, value] of url.searchParams) {
23
+ if (!query[key]) {
24
+ query[key] = [];
25
+ }
26
+ query[key].push(value);
27
+ }
28
+ return { scheme, host, port, path, query };
29
+ }
30
+ function buildUrlString(parsed) {
31
+ const { scheme, host, port, path, query } = parsed;
32
+ const defaultPort = DEFAULT_PORTS[scheme];
33
+ const portString = port !== defaultPort ? `:${String(port)}` : "";
34
+ const params = new URLSearchParams();
35
+ for (const [key, values] of Object.entries(query)) {
36
+ for (const value of values) {
37
+ params.append(key, value);
38
+ }
39
+ }
40
+ const queryString = params.toString();
41
+ return `${scheme}://${host}${portString}${path}${queryString ? "?" + queryString : ""}`;
42
+ }
4
43
  var HttpRequestContextSchema = z.object({
5
44
  request_type: z.literal("http_request"),
6
45
  method: z.string(),
7
- /** Full URL including query string, e.g. "https://api.example.com/v1/foo?bar=baz" */
8
- url: z.string(),
46
+ /** Parsed URL components for policy condition matching */
47
+ url: ParsedUrlSchema,
9
48
  /** Filtered headers (IGNORED_HEADERS excluded, keys lowercased) */
10
- headers: z.record(z.string(), z.string()).optional(),
49
+ headers: z.record(z.string(), z.string()),
11
50
  /** Raw body string */
12
- body: z.string().optional(),
51
+ body: z.string().nullable(),
13
52
  /** Parsed JSON body */
14
- body_json: JsonValueSchema.optional(),
53
+ body_json: JsonValueSchema.nullable(),
15
54
  /** Zapier connection ID used for this request */
16
55
  connection_id: z.string().optional(),
17
56
  /** Optional human-readable reason for the request */
@@ -80,7 +119,8 @@ var BuildHttpRequestContextOptionsSchema = z2.object({
80
119
  connection_id: z2.string().optional()
81
120
  });
82
121
  function buildHttpRequestContext(options) {
83
- const url = options.url.includes("://") ? options.url : `https://${options.url}`;
122
+ const rawUrl = options.url.includes("://") ? options.url : `https://${options.url}`;
123
+ const url = parseUrl(rawUrl);
84
124
  const headers = {};
85
125
  if (options.headers) {
86
126
  for (const [key, value] of Object.entries(options.headers)) {
@@ -90,13 +130,26 @@ function buildHttpRequestContext(options) {
90
130
  }
91
131
  }
92
132
  }
133
+ let body_json = null;
134
+ if (options.body_json !== void 0) {
135
+ body_json = options.body_json;
136
+ } else if (options.body) {
137
+ const contentType = headers["content-type"];
138
+ const isJson = typeof contentType === "string" && (contentType.includes("application/json") || contentType.includes("application/") && contentType.includes("+json"));
139
+ if (isJson) {
140
+ try {
141
+ body_json = JSON.parse(options.body);
142
+ } catch {
143
+ }
144
+ }
145
+ }
93
146
  return HttpRequestContextSchema.parse({
94
147
  request_type: "http_request",
95
148
  method: options.method,
96
149
  url,
97
150
  headers,
98
- body: typeof options.body === "string" ? options.body : void 0,
99
- body_json: typeof options.body_json === "object" && options.body_json !== null ? options.body_json : void 0,
151
+ body: options.body ?? null,
152
+ body_json,
100
153
  connection_id: options.connection_id
101
154
  });
102
155
  }
@@ -123,70 +176,46 @@ function buildExactHttpConditions(context) {
123
176
  const conditions = [
124
177
  { path: ["method"], operator: "equals", value: context.method }
125
178
  ];
126
- let parsedUrl;
127
- try {
128
- parsedUrl = new URL(context.url);
129
- } catch {
130
- console.debug(
131
- "buildExactHttpConditions: unparseable URL, using raw value",
132
- { url: context.url }
133
- );
134
- conditions.push({
135
- path: ["url"],
136
- operator: "matches_url",
137
- value: context.url
138
- });
139
- return conditions;
140
- }
141
- const urlWithoutQuery = `${parsedUrl.protocol}//${parsedUrl.host}${parsedUrl.pathname}`;
179
+ const { url } = context;
180
+ const defaultPort = url.scheme === "https" ? 443 : url.scheme === "http" ? 80 : 0;
181
+ const portString = url.port !== defaultPort ? `:${String(url.port)}` : "";
182
+ const urlWithoutQuery = `${url.scheme}://${url.host}${portString}${url.path}`;
142
183
  conditions.push({
143
184
  path: ["url"],
144
185
  operator: "matches_url",
145
186
  value: urlWithoutQuery
146
187
  });
147
- const seenKeys = /* @__PURE__ */ new Set();
148
- for (const key of parsedUrl.searchParams.keys()) {
149
- if (seenKeys.has(key)) continue;
150
- seenKeys.add(key);
151
- const values = parsedUrl.searchParams.getAll(key);
188
+ for (const [key, values] of Object.entries(url.query)) {
152
189
  conditions.push({
153
190
  path: ["url", "query", key],
154
191
  operator: "equals",
155
192
  value: values
156
193
  });
157
194
  }
158
- if (context.body_json !== void 0 && context.body_json !== null) {
195
+ if (context.body_json !== null) {
159
196
  conditions.push({
160
197
  path: ["body_json"],
161
198
  operator: "equals",
162
199
  value: context.body_json
163
200
  });
164
201
  }
165
- if (context.headers) {
166
- for (const [key, value] of Object.entries(context.headers)) {
167
- if (!IGNORED_HEADERS.has(key.toLowerCase()) && typeof value === "string") {
168
- conditions.push({
169
- path: ["headers", key.toLowerCase()],
170
- operator: "equals",
171
- value
172
- });
173
- }
202
+ for (const [key, value] of Object.entries(context.headers)) {
203
+ if (!IGNORED_HEADERS.has(key.toLowerCase()) && typeof value === "string") {
204
+ conditions.push({
205
+ path: ["headers", key.toLowerCase()],
206
+ operator: "equals",
207
+ value
208
+ });
174
209
  }
175
210
  }
176
211
  return conditions;
177
212
  }
178
213
  function buildDomainHttpConditions(context) {
179
- let host;
180
- try {
181
- host = new URL(context.url).host;
182
- } catch {
183
- return null;
184
- }
185
214
  return [
186
215
  {
187
216
  path: ["url", "host"],
188
217
  operator: "matches_host",
189
- value: host
218
+ value: context.url.host
190
219
  }
191
220
  ];
192
221
  }
@@ -223,21 +252,11 @@ function httpContextToPolicy(context) {
223
252
  statements.push(buildAuthStatement(context.connection_id));
224
253
  }
225
254
  const conditions = buildExactHttpConditions(context);
226
- try {
227
- const queryKeys = [...new URL(context.url).searchParams.keys()];
228
- const uniqueKeys = [...new Set(queryKeys)];
229
- conditions.push({
230
- path: ["url", "query"],
231
- operator: "keys_in",
232
- value: uniqueKeys
233
- });
234
- } catch {
235
- conditions.push({
236
- path: ["url", "query"],
237
- operator: "keys_in",
238
- value: []
239
- });
240
- }
255
+ conditions.push({
256
+ path: ["url", "query"],
257
+ operator: "keys_in",
258
+ value: Object.keys(context.url.query)
259
+ });
241
260
  statements.push({
242
261
  effect: "allow",
243
262
  permissions: ["can_execute"],
@@ -269,9 +288,11 @@ export {
269
288
  ActionRunContextSchema,
270
289
  BuildActionRunContextOptionsSchema,
271
290
  BuildHttpRequestContextOptionsSchema,
291
+ DEFAULT_PORTS,
272
292
  HttpRequestContextSchema,
273
293
  IGNORED_HEADERS,
274
294
  JsonValueSchema,
295
+ ParsedUrlSchema,
275
296
  RequestContextSchema,
276
297
  buildActionResource,
277
298
  buildActionRunContext,
@@ -280,6 +301,8 @@ export {
280
301
  buildExactActionConditions,
281
302
  buildExactHttpConditions,
282
303
  buildHttpRequestContext,
304
+ buildUrlString,
305
+ parseUrl,
283
306
  toExactPolicy
284
307
  };
285
308
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/constants.ts","../src/builders.ts","../src/conditions.ts","../src/policies.ts"],"sourcesContent":["// ABOUTME: Canonical request context types shared between sdkapi and approvalsapi.\n// ABOUTME: These define the contract for how requests are described in approval flows and policy evaluation.\n\nimport { z } from \"zod\";\nimport type { JsonValue } from \"@zapier/policy-schema\";\n\n/**\n * Zod schema for arbitrary JSON values. Used across context schemas for\n * unstructured fields like `body_json` and `inputs`.\n */\nexport const JsonValueSchema = z.custom<JsonValue>();\n\n/**\n * HTTP request context for approval/policy flows.\n * Sent by sdkapi when a relay request is denied, stored by approvalsapi,\n * and used to build policy conditions.\n */\nexport const HttpRequestContextSchema = z.object({\n request_type: z.literal(\"http_request\"),\n method: z.string(),\n /** Full URL including query string, e.g. \"https://api.example.com/v1/foo?bar=baz\" */\n url: z.string(),\n /** Filtered headers (IGNORED_HEADERS excluded, keys lowercased) */\n headers: z.record(z.string(), z.string()).optional(),\n /** Raw body string */\n body: z.string().optional(),\n /** Parsed JSON body */\n body_json: JsonValueSchema.optional(),\n /** Zapier connection ID used for this request */\n connection_id: z.string().optional(),\n /** Optional human-readable reason for the request */\n reason: z.string().optional(),\n});\nexport type HttpRequestContext = z.infer<typeof HttpRequestContextSchema>;\n\n/**\n * Action run context for approval/policy flows.\n * Sent by sdkapi when an action run is denied, stored by approvalsapi,\n * and used to build policy conditions.\n */\nexport const ActionRunContextSchema = z.object({\n request_type: z.literal(\"action_run\"),\n selected_api: z.string(),\n action_type: z.string(),\n action_key: z.string(),\n connection_id: z.string().optional(),\n inputs: JsonValueSchema.optional(),\n reason: z.string().optional(),\n});\nexport type ActionRunContext = z.infer<typeof ActionRunContextSchema>;\n\n/**\n * Discriminated union of all request context types.\n */\nexport const RequestContextSchema = z.discriminatedUnion(\"request_type\", [\n HttpRequestContextSchema,\n ActionRunContextSchema,\n]);\nexport type RequestContext = z.infer<typeof RequestContextSchema>;\n","// ABOUTME: Canonical set of headers to exclude when building request contexts.\n// ABOUTME: Shared between sdkapi (context creation) and approvalsapi (condition building).\n\n/**\n * Headers to exclude when building request contexts from raw HTTP requests.\n *\n * These are either sensitive (auth credentials), HTTP infrastructure headers\n * that vary per-request, or proxy-internal headers that are not meaningful\n * for policy decisions.\n */\nexport const IGNORED_HEADERS = new Set([\n // Auth / credentials — never include in contexts or policy conditions\n \"authorization\",\n \"x-relay-authorization\",\n \"x-relay-authentication-id\",\n \"x-api-key\",\n \"cookie\",\n \"set-cookie\",\n \"proxy-authorization\",\n\n // HTTP infrastructure — varies per-request, not policy-relevant\n \"host\",\n \"connection\",\n \"content-length\",\n \"transfer-encoding\",\n \"accept-encoding\",\n \"user-agent\",\n \"accept\",\n \"accept-language\",\n \"sec-fetch-mode\",\n\n // Proxy / forwarding — infrastructure, not meaningful for policy\n \"x-forwarded-for\",\n \"x-forwarded-host\",\n \"x-forwarded-proto\",\n \"x-real-ip\",\n\n // Proxy internal — sdkapi-specific headers\n \"x-is-interactive-session\",\n \"x-relay-callback-url\",\n]);\n","// ABOUTME: Canonical builders for creating request contexts from raw request parts.\n// ABOUTME: Used by sdkapi to build both approval contexts and policy eval contexts.\n\nimport { z } from \"zod\";\nimport {\n HttpRequestContextSchema,\n ActionRunContextSchema,\n JsonValueSchema,\n type HttpRequestContext,\n type ActionRunContext,\n} from \"./types.ts\";\nimport { IGNORED_HEADERS } from \"./constants.ts\";\n\nexport const BuildHttpRequestContextOptionsSchema = z.object({\n method: z.string(),\n /** Raw URL string. Will be normalized with https:// if no protocol present. */\n url: z.string(),\n /** Raw headers from the request. Will be filtered through IGNORED_HEADERS. */\n headers: z\n .record(\n z.string(),\n z.union([z.string(), z.array(z.string()), z.undefined()]),\n )\n .optional(),\n /** Raw body string */\n body: z.string().nullable().optional(),\n /** Pre-parsed JSON body (e.g. from Fastify's automatic JSON parsing) */\n body_json: JsonValueSchema.optional(),\n /** Connection ID associated with this request */\n connection_id: z.string().optional(),\n});\nexport type BuildHttpRequestContextOptions = z.infer<\n typeof BuildHttpRequestContextOptionsSchema\n>;\n\n/**\n * Build a canonical HttpRequestContext from raw request parts.\n *\n * This is the single source of truth for how HTTP request contexts are created.\n * Both sdkapi (for approval creation) and approvalsapi (for context storage)\n * should use this to ensure the context shape is consistent.\n *\n * - Normalizes URL (adds https:// if missing)\n * - Filters headers through IGNORED_HEADERS\n * - Only includes string-valued headers (drops string[] values like set-cookie)\n */\nexport function buildHttpRequestContext(\n options: BuildHttpRequestContextOptions,\n): HttpRequestContext {\n // Normalize URL — SDK CLI strips the protocol\n const url = options.url.includes(\"://\")\n ? options.url\n : `https://${options.url}`;\n\n // Filter headers: exclude ignored, lowercase keys, string values only\n const headers: Record<string, string> = {};\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n const lowerKey = key.toLowerCase();\n if (!IGNORED_HEADERS.has(lowerKey) && typeof value === \"string\") {\n headers[lowerKey] = value;\n }\n }\n }\n\n return HttpRequestContextSchema.parse({\n request_type: \"http_request\",\n method: options.method,\n url,\n headers,\n body: typeof options.body === \"string\" ? options.body : undefined,\n body_json:\n typeof options.body_json === \"object\" && options.body_json !== null\n ? options.body_json\n : undefined,\n connection_id: options.connection_id,\n });\n}\n\nexport const BuildActionRunContextOptionsSchema = z.object({\n selected_api: z.string(),\n action_type: z.string(),\n action_key: z.string(),\n connection_id: z.string().optional(),\n inputs: JsonValueSchema.optional(),\n});\nexport type BuildActionRunContextOptions = z.infer<\n typeof BuildActionRunContextOptionsSchema\n>;\n\n/**\n * Build a canonical ActionRunContext from raw action parts.\n */\nexport function buildActionRunContext(\n options: BuildActionRunContextOptions,\n): ActionRunContext {\n return ActionRunContextSchema.parse({\n request_type: \"action_run\",\n selected_api: options.selected_api,\n action_type: options.action_type,\n action_key: options.action_key,\n connection_id: options.connection_id,\n inputs: options.inputs,\n });\n}\n","// ABOUTME: Builds policy conditions from stored request contexts.\n// ABOUTME: Used by approvalsapi's \"allow exact request\" and \"allow domain\" instant policy flows.\n\nimport type { Condition } from \"@zapier/policy-schema\";\nimport type { HttpRequestContext, ActionRunContext } from \"./types.ts\";\nimport { IGNORED_HEADERS } from \"./constants.ts\";\n\n/**\n * Build conditions that match an exact HTTP request.\n *\n * Handles the matches_url / query param split correctly:\n * - `matches_url` only supports scheme+host+port+path (no query string per spec)\n * - Query parameters are added as separate decomposed conditions on `url.query.<key>`\n *\n * @returns Condition[] for a relay/http permission statement\n */\nexport function buildExactHttpConditions(\n context: HttpRequestContext,\n): Condition[] {\n const conditions: Condition[] = [\n { path: [\"method\"], operator: \"equals\", value: context.method },\n ];\n\n // Parse the URL to separate path from query params\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(context.url);\n } catch {\n // URL is unparseable — use it as-is (will fail matches_url, but that's correct)\n console.debug(\n \"buildExactHttpConditions: unparseable URL, using raw value\",\n { url: context.url },\n );\n conditions.push({\n path: [\"url\"],\n operator: \"matches_url\",\n value: context.url,\n });\n return conditions;\n }\n\n // matches_url condition uses URL without query string (per @zapier/policy spec)\n const urlWithoutQuery = `${parsedUrl.protocol}//${parsedUrl.host}${parsedUrl.pathname}`;\n conditions.push({\n path: [\"url\"],\n operator: \"matches_url\",\n value: urlWithoutQuery,\n });\n\n // Add decomposed conditions for each query parameter.\n // Use getAll to properly handle multi-value params (e.g. ?tag=a&tag=b → [\"a\", \"b\"]).\n const seenKeys = new Set<string>();\n for (const key of parsedUrl.searchParams.keys()) {\n if (seenKeys.has(key)) continue;\n seenKeys.add(key);\n const values = parsedUrl.searchParams.getAll(key);\n conditions.push({\n path: [\"url\", \"query\", key],\n operator: \"equals\",\n value: values,\n });\n }\n\n // body_json condition\n if (context.body_json !== undefined && context.body_json !== null) {\n conditions.push({\n path: [\"body_json\"],\n operator: \"equals\",\n value: context.body_json,\n });\n }\n\n // Header conditions (filtered through IGNORED_HEADERS again for safety)\n if (context.headers) {\n for (const [key, value] of Object.entries(context.headers)) {\n if (\n !IGNORED_HEADERS.has(key.toLowerCase()) &&\n typeof value === \"string\"\n ) {\n conditions.push({\n path: [\"headers\", key.toLowerCase()],\n operator: \"equals\",\n value,\n });\n }\n }\n }\n\n return conditions;\n}\n\n/**\n * Build conditions that match any request to a given domain.\n * Extracts the host from the context URL.\n *\n * @returns Condition[] for a relay/http permission statement, or null if URL is unparseable\n */\nexport function buildDomainHttpConditions(\n context: HttpRequestContext,\n): Condition[] | null {\n let host: string;\n try {\n host = new URL(context.url).host;\n } catch {\n return null;\n }\n\n return [\n {\n path: [\"url\", \"host\"],\n operator: \"matches_host\",\n value: host,\n },\n ];\n}\n\n/**\n * Build conditions that match an exact action run (with inputs).\n *\n * @returns Condition[] for an action permission statement\n */\nexport function buildExactActionConditions(\n context: ActionRunContext,\n): Condition[] {\n const conditions: Condition[] = [];\n\n if (\n context.inputs !== undefined &&\n context.inputs !== null &&\n typeof context.inputs === \"object\"\n ) {\n conditions.push({\n path: [\"params\"],\n operator: \"equals\",\n value: context.inputs,\n });\n }\n\n return conditions;\n}\n\n/**\n * Build the resource string for an action.\n * Format: action/{selected_api}/{action_type}/{action_key}\n */\nexport function buildActionResource(context: ActionRunContext): string {\n return `action/${context.selected_api}/${context.action_type}/${context.action_key}`;\n}\n\n/**\n * Build a broad action resource that matches all actions for an app.\n * Format: action/{selected_api}/{wildcard}/{wildcard}\n */\nexport function buildBroadActionResource(context: ActionRunContext): string {\n return `action/${context.selected_api}/*/*`;\n}\n","// ABOUTME: Assembles complete Policy objects from request contexts.\n// ABOUTME: Single source of truth for \"exact request\" instant policies used by web UI and API.\n\nimport type { Policy, Statement } from \"@zapier/policy-schema\";\nimport type {\n RequestContext,\n HttpRequestContext,\n ActionRunContext,\n} from \"./types.ts\";\nimport {\n buildExactHttpConditions,\n buildExactActionConditions,\n buildActionResource,\n} from \"./conditions.ts\";\n\n/**\n * Build a `can_use` authentication statement, if the context has a connection_id.\n */\nfunction buildAuthStatement(authenticationId: string): Statement {\n return {\n effect: \"allow\",\n permissions: [\"can_use\"],\n resources: [`authentication/${authenticationId}`],\n conditions: [],\n };\n}\n\n/**\n * Build an exact-match policy from an HTTP request context.\n *\n * Produces a policy that allows only the specific request described by the context:\n * - `can_use` statement for the authentication (if present)\n * - `can_execute` statement for `relay/http` with conditions from `buildExactHttpConditions`\n * plus a `keys_in` guard restricting allowed query parameter keys\n */\nfunction httpContextToPolicy(context: HttpRequestContext): Policy {\n const statements: Statement[] = [];\n\n if (context.connection_id) {\n statements.push(buildAuthStatement(context.connection_id));\n }\n\n const conditions = buildExactHttpConditions(context);\n\n // Add a keys_in guard so only the query param keys present in the original\n // request are allowed — blocks injection of extra query params.\n try {\n const queryKeys = [...new URL(context.url).searchParams.keys()];\n const uniqueKeys = [...new Set(queryKeys)];\n conditions.push({\n path: [\"url\", \"query\"],\n operator: \"keys_in\",\n value: uniqueKeys,\n });\n } catch {\n // If URL parsing fails, block all query params\n conditions.push({\n path: [\"url\", \"query\"],\n operator: \"keys_in\",\n value: [],\n });\n }\n\n statements.push({\n effect: \"allow\",\n permissions: [\"can_execute\"],\n resources: [\"relay/http\"],\n conditions,\n });\n\n return { version: 2, statements };\n}\n\n/**\n * Build an exact-match policy from an action run context.\n *\n * Produces a policy that allows only the specific action described by the context:\n * - `can_use` statement for the authentication (if present)\n * - `can_execute` statement for the action resource with input conditions\n */\nfunction actionContextToPolicy(context: ActionRunContext): Policy {\n const statements: Statement[] = [];\n\n if (context.connection_id) {\n statements.push(buildAuthStatement(context.connection_id));\n }\n\n statements.push({\n effect: \"allow\",\n permissions: [\"can_execute\"],\n resources: [buildActionResource(context)],\n conditions: buildExactActionConditions(context),\n });\n\n return { version: 2, statements };\n}\n\n/**\n * Build an exact-match policy from any supported request context.\n *\n * This is the single source of truth for \"allow this exact request\" instant policies.\n * Used by the web UI and anywhere else that needs to produce an exact policy from a context.\n */\nexport function toExactPolicy(context: RequestContext): Policy {\n if (context.request_type === \"http_request\") {\n return httpContextToPolicy(context);\n }\n return actionContextToPolicy(context);\n}\n"],"mappings":"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","names":["z","z"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/constants.ts","../src/builders.ts","../src/conditions.ts","../src/policies.ts"],"sourcesContent":["// ABOUTME: Canonical request context types shared between sdkapi and approvalsapi.\n// ABOUTME: These define the contract for how requests are described in approval flows and policy evaluation.\n\nimport { z } from \"zod\";\nimport type { JsonValue } from \"@zapier/policy-schema\";\n\n/**\n * Zod schema for arbitrary JSON values. Used across context schemas for\n * unstructured fields like `body_json` and `inputs`.\n */\nexport const JsonValueSchema = z.custom<JsonValue>();\n\n/**\n * Parsed URL components for policy evaluation.\n * Matches the ParsedUrl shape from @zapier/policy-schema so that\n * evaluatePolicy condition paths like [\"url\", \"host\"] resolve correctly.\n */\nexport const ParsedUrlSchema = z.object({\n scheme: z.string(),\n host: z.string(),\n port: z.number(),\n path: z.string(),\n query: z.record(z.string(), z.array(z.string())),\n});\nexport type ParsedUrl = z.infer<typeof ParsedUrlSchema>;\n\n/** Default ports by scheme, used when URL has no explicit port. */\nexport const DEFAULT_PORTS: Record<string, number> = {\n http: 80,\n https: 443,\n};\n\n/**\n * Parse a raw URL string into a ParsedUrl.\n * Same logic as parseUrlForPolicyContext in @zapier/policy.\n */\nexport function parseUrl(raw: string): ParsedUrl {\n const url = new URL(raw);\n const scheme = url.protocol.slice(0, -1).toLowerCase();\n const host = url.hostname.toLowerCase();\n const port = url.port\n ? parseInt(url.port, 10)\n : (DEFAULT_PORTS[scheme] ?? 0);\n const path = url.pathname || \"/\";\n const query: Record<string, string[]> = {};\n for (const [key, value] of url.searchParams) {\n if (!query[key]) {\n query[key] = [];\n }\n query[key].push(value);\n }\n return { scheme, host, port, path, query };\n}\n\n/**\n * Reconstruct a URL string from a ParsedUrl.\n * Same logic as buildUrl in @zapier/policy.\n */\nexport function buildUrlString(parsed: ParsedUrl): string {\n const { scheme, host, port, path, query } = parsed;\n const defaultPort = DEFAULT_PORTS[scheme];\n const portString = port !== defaultPort ? `:${String(port)}` : \"\";\n const params = new URLSearchParams();\n for (const [key, values] of Object.entries(query)) {\n for (const value of values) {\n params.append(key, value);\n }\n }\n const queryString = params.toString();\n return `${scheme}://${host}${portString}${path}${queryString ? \"?\" + queryString : \"\"}`;\n}\n\n/**\n * HTTP request context for approval/policy flows.\n * Sent by sdkapi when a relay request is denied, stored by approvalsapi,\n * and used for both policy evaluation and building policy conditions.\n *\n * The url field is a ParsedUrl object (matching @zapier/policy-schema's ParsedUrl)\n * so that evaluatePolicy conditions like [\"url\", \"host\"] resolve correctly.\n */\nexport const HttpRequestContextSchema = z.object({\n request_type: z.literal(\"http_request\"),\n method: z.string(),\n /** Parsed URL components for policy condition matching */\n url: ParsedUrlSchema,\n /** Filtered headers (IGNORED_HEADERS excluded, keys lowercased) */\n headers: z.record(z.string(), z.string()),\n /** Raw body string */\n body: z.string().nullable(),\n /** Parsed JSON body */\n body_json: JsonValueSchema.nullable(),\n /** Zapier connection ID used for this request */\n connection_id: z.string().optional(),\n /** Optional human-readable reason for the request */\n reason: z.string().optional(),\n});\nexport type HttpRequestContext = z.infer<typeof HttpRequestContextSchema>;\n\n/**\n * Action run context for approval/policy flows.\n * Sent by sdkapi when an action run is denied, stored by approvalsapi,\n * and used to build policy conditions.\n */\nexport const ActionRunContextSchema = z.object({\n request_type: z.literal(\"action_run\"),\n selected_api: z.string(),\n action_type: z.string(),\n action_key: z.string(),\n connection_id: z.string().optional(),\n inputs: JsonValueSchema.optional(),\n reason: z.string().optional(),\n});\nexport type ActionRunContext = z.infer<typeof ActionRunContextSchema>;\n\n/**\n * Discriminated union of all request context types.\n */\nexport const RequestContextSchema = z.discriminatedUnion(\"request_type\", [\n HttpRequestContextSchema,\n ActionRunContextSchema,\n]);\nexport type RequestContext = z.infer<typeof RequestContextSchema>;\n","// ABOUTME: Canonical set of headers to exclude when building request contexts.\n// ABOUTME: Shared between sdkapi (context creation) and approvalsapi (condition building).\n\n/**\n * Headers to exclude when building request contexts from raw HTTP requests.\n *\n * These are either sensitive (auth credentials), HTTP infrastructure headers\n * that vary per-request, or proxy-internal headers that are not meaningful\n * for policy decisions.\n */\nexport const IGNORED_HEADERS = new Set([\n // Auth / credentials — never include in contexts or policy conditions\n \"authorization\",\n \"x-relay-authorization\",\n \"x-relay-authentication-id\",\n \"x-api-key\",\n \"cookie\",\n \"set-cookie\",\n \"proxy-authorization\",\n\n // HTTP infrastructure — varies per-request, not policy-relevant\n \"host\",\n \"connection\",\n \"content-length\",\n \"transfer-encoding\",\n \"accept-encoding\",\n \"user-agent\",\n \"accept\",\n \"accept-language\",\n \"sec-fetch-mode\",\n\n // Proxy / forwarding — infrastructure, not meaningful for policy\n \"x-forwarded-for\",\n \"x-forwarded-host\",\n \"x-forwarded-proto\",\n \"x-real-ip\",\n\n // Proxy internal — sdkapi-specific headers\n \"x-is-interactive-session\",\n \"x-relay-callback-url\",\n]);\n","// ABOUTME: Canonical builders for creating request contexts from raw request parts.\n// ABOUTME: Used by sdkapi to build both approval contexts and policy eval contexts.\n\nimport { z } from \"zod\";\nimport {\n HttpRequestContextSchema,\n ActionRunContextSchema,\n JsonValueSchema,\n parseUrl,\n type HttpRequestContext,\n type ActionRunContext,\n} from \"./types.ts\";\nimport { IGNORED_HEADERS } from \"./constants.ts\";\n\nexport const BuildHttpRequestContextOptionsSchema = z.object({\n method: z.string(),\n /** Raw URL string. Will be normalized with https:// if no protocol present. */\n url: z.string(),\n /** Raw headers from the request. Will be filtered through IGNORED_HEADERS. */\n headers: z\n .record(\n z.string(),\n z.union([z.string(), z.array(z.string()), z.undefined()]),\n )\n .optional(),\n /** Raw body string */\n body: z.string().nullable().optional(),\n /** Pre-parsed JSON body (e.g. from Fastify's automatic JSON parsing) */\n body_json: JsonValueSchema.optional(),\n /** Connection ID associated with this request */\n connection_id: z.string().optional(),\n});\nexport type BuildHttpRequestContextOptions = z.infer<\n typeof BuildHttpRequestContextOptionsSchema\n>;\n\n/**\n * Build a canonical HttpRequestContext from raw request parts.\n *\n * This is the single source of truth for how HTTP request contexts are created.\n * Both sdkapi (for policy evaluation) and approvalsapi (for context storage)\n * should use this to ensure the context shape is consistent.\n *\n * Matches the behavior of createPolicyHttpRequest from @zapier/policy, with\n * the addition of header filtering through IGNORED_HEADERS.\n *\n * - Normalizes URL (adds https:// if missing) and parses into { scheme, host, port, path, query }\n * - Filters headers through IGNORED_HEADERS, lowercases keys, string values only\n * - Auto-parses body_json from body when content-type is JSON and body_json not provided\n * - body defaults to null (not undefined)\n */\nexport function buildHttpRequestContext(\n options: BuildHttpRequestContextOptions,\n): HttpRequestContext {\n // Normalize URL — SDK CLI strips the protocol\n const rawUrl = options.url.includes(\"://\")\n ? options.url\n : `https://${options.url}`;\n const url = parseUrl(rawUrl);\n\n // Filter headers: exclude ignored, lowercase keys, string values only\n const headers: Record<string, string> = {};\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n const lowerKey = key.toLowerCase();\n if (!IGNORED_HEADERS.has(lowerKey) && typeof value === \"string\") {\n headers[lowerKey] = value;\n }\n }\n }\n\n // Auto-parse body_json from body when content-type is JSON\n // (matches createPolicyHttpRequest behavior from @zapier/policy)\n let body_json: unknown = null;\n if (options.body_json !== undefined) {\n body_json = options.body_json;\n } else if (options.body) {\n const contentType = headers[\"content-type\"];\n const isJson =\n typeof contentType === \"string\" &&\n (contentType.includes(\"application/json\") ||\n (contentType.includes(\"application/\") &&\n contentType.includes(\"+json\")));\n if (isJson) {\n try {\n body_json = JSON.parse(options.body);\n } catch {\n // Invalid JSON body — leave body_json as null\n }\n }\n }\n\n return HttpRequestContextSchema.parse({\n request_type: \"http_request\",\n method: options.method,\n url,\n headers,\n body: options.body ?? null,\n body_json,\n connection_id: options.connection_id,\n });\n}\n\nexport const BuildActionRunContextOptionsSchema = z.object({\n selected_api: z.string(),\n action_type: z.string(),\n action_key: z.string(),\n connection_id: z.string().optional(),\n inputs: JsonValueSchema.optional(),\n});\nexport type BuildActionRunContextOptions = z.infer<\n typeof BuildActionRunContextOptionsSchema\n>;\n\n/**\n * Build a canonical ActionRunContext from raw action parts.\n */\nexport function buildActionRunContext(\n options: BuildActionRunContextOptions,\n): ActionRunContext {\n return ActionRunContextSchema.parse({\n request_type: \"action_run\",\n selected_api: options.selected_api,\n action_type: options.action_type,\n action_key: options.action_key,\n connection_id: options.connection_id,\n inputs: options.inputs,\n });\n}\n","// ABOUTME: Builds policy conditions from stored request contexts.\n// ABOUTME: Used by approvalsapi's \"allow exact request\" and \"allow domain\" instant policy flows.\n\nimport type { Condition } from \"@zapier/policy-schema\";\nimport type { HttpRequestContext, ActionRunContext } from \"./types.ts\";\nimport { IGNORED_HEADERS } from \"./constants.ts\";\n\n/**\n * Build conditions that match an exact HTTP request.\n *\n * Handles the matches_url / query param split correctly:\n * - `matches_url` only supports scheme+host+port+path (no query string per spec)\n * - Query parameters are added as separate decomposed conditions on `url.query.<key>`\n *\n * @returns Condition[] for a relay/http permission statement\n */\nexport function buildExactHttpConditions(\n context: HttpRequestContext,\n): Condition[] {\n const conditions: Condition[] = [\n { path: [\"method\"], operator: \"equals\", value: context.method },\n ];\n\n const { url } = context;\n\n // matches_url condition uses URL without query string (per @zapier/policy spec)\n const defaultPort = url.scheme === \"https\" ? 443 : url.scheme === \"http\" ? 80 : 0;\n const portString = url.port !== defaultPort ? `:${String(url.port)}` : \"\";\n const urlWithoutQuery = `${url.scheme}://${url.host}${portString}${url.path}`;\n conditions.push({\n path: [\"url\"],\n operator: \"matches_url\",\n value: urlWithoutQuery,\n });\n\n // Add decomposed conditions for each query parameter.\n for (const [key, values] of Object.entries(url.query)) {\n conditions.push({\n path: [\"url\", \"query\", key],\n operator: \"equals\",\n value: values,\n });\n }\n\n // body_json condition\n if (context.body_json !== null) {\n conditions.push({\n path: [\"body_json\"],\n operator: \"equals\",\n value: context.body_json,\n });\n }\n\n // Header conditions (filtered through IGNORED_HEADERS again for safety)\n for (const [key, value] of Object.entries(context.headers)) {\n if (\n !IGNORED_HEADERS.has(key.toLowerCase()) &&\n typeof value === \"string\"\n ) {\n conditions.push({\n path: [\"headers\", key.toLowerCase()],\n operator: \"equals\",\n value,\n });\n }\n }\n\n return conditions;\n}\n\n/**\n * Build conditions that match any request to a given domain.\n * Uses the host from the parsed URL.\n *\n * @returns Condition[] for a relay/http permission statement\n */\nexport function buildDomainHttpConditions(\n context: HttpRequestContext,\n): Condition[] {\n return [\n {\n path: [\"url\", \"host\"],\n operator: \"matches_host\",\n value: context.url.host,\n },\n ];\n}\n\n/**\n * Build conditions that match an exact action run (with inputs).\n *\n * @returns Condition[] for an action permission statement\n */\nexport function buildExactActionConditions(\n context: ActionRunContext,\n): Condition[] {\n const conditions: Condition[] = [];\n\n if (\n context.inputs !== undefined &&\n context.inputs !== null &&\n typeof context.inputs === \"object\"\n ) {\n conditions.push({\n path: [\"params\"],\n operator: \"equals\",\n value: context.inputs,\n });\n }\n\n return conditions;\n}\n\n/**\n * Build the resource string for an action.\n * Format: action/{selected_api}/{action_type}/{action_key}\n */\nexport function buildActionResource(context: ActionRunContext): string {\n return `action/${context.selected_api}/${context.action_type}/${context.action_key}`;\n}\n\n/**\n * Build a broad action resource that matches all actions for an app.\n * Format: action/{selected_api}/{wildcard}/{wildcard}\n */\nexport function buildBroadActionResource(context: ActionRunContext): string {\n return `action/${context.selected_api}/*/*`;\n}\n","// ABOUTME: Assembles complete Policy objects from request contexts.\n// ABOUTME: Single source of truth for \"exact request\" instant policies used by web UI and API.\n\nimport type { Policy, Statement } from \"@zapier/policy-schema\";\nimport type {\n RequestContext,\n HttpRequestContext,\n ActionRunContext,\n} from \"./types.ts\";\nimport {\n buildExactHttpConditions,\n buildExactActionConditions,\n buildActionResource,\n} from \"./conditions.ts\";\n\n/**\n * Build a `can_use` authentication statement, if the context has a connection_id.\n */\nfunction buildAuthStatement(authenticationId: string): Statement {\n return {\n effect: \"allow\",\n permissions: [\"can_use\"],\n resources: [`authentication/${authenticationId}`],\n conditions: [],\n };\n}\n\n/**\n * Build an exact-match policy from an HTTP request context.\n *\n * Produces a policy that allows only the specific request described by the context:\n * - `can_use` statement for the authentication (if present)\n * - `can_execute` statement for `relay/http` with conditions from `buildExactHttpConditions`\n * plus a `keys_in` guard restricting allowed query parameter keys\n */\nfunction httpContextToPolicy(context: HttpRequestContext): Policy {\n const statements: Statement[] = [];\n\n if (context.connection_id) {\n statements.push(buildAuthStatement(context.connection_id));\n }\n\n const conditions = buildExactHttpConditions(context);\n\n // Add a keys_in guard so only the query param keys present in the original\n // request are allowed — blocks injection of extra query params.\n conditions.push({\n path: [\"url\", \"query\"],\n operator: \"keys_in\",\n value: Object.keys(context.url.query),\n });\n\n statements.push({\n effect: \"allow\",\n permissions: [\"can_execute\"],\n resources: [\"relay/http\"],\n conditions,\n });\n\n return { version: 2, statements };\n}\n\n/**\n * Build an exact-match policy from an action run context.\n *\n * Produces a policy that allows only the specific action described by the context:\n * - `can_use` statement for the authentication (if present)\n * - `can_execute` statement for the action resource with input conditions\n */\nfunction actionContextToPolicy(context: ActionRunContext): Policy {\n const statements: Statement[] = [];\n\n if (context.connection_id) {\n statements.push(buildAuthStatement(context.connection_id));\n }\n\n statements.push({\n effect: \"allow\",\n permissions: [\"can_execute\"],\n resources: [buildActionResource(context)],\n conditions: buildExactActionConditions(context),\n });\n\n return { version: 2, statements };\n}\n\n/**\n * Build an exact-match policy from any supported request context.\n *\n * This is the single source of truth for \"allow this exact request\" instant policies.\n * Used by the web UI and anywhere else that needs to produce an exact policy from a context.\n */\nexport function toExactPolicy(context: RequestContext): Policy {\n if (context.request_type === \"http_request\") {\n return httpContextToPolicy(context);\n }\n return actionContextToPolicy(context);\n}\n"],"mappings":"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","names":["z","z"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/policy-context",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared request context types, builders, and policy condition generators for sdkapi and approvalsapi",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,12 +9,30 @@
9
9
  },
10
10
  "type": "module",
11
11
  "exports": {
12
- ".": "./src/index.ts",
13
- "./*": "./src/*.ts"
12
+ ".": {
13
+ "require": {
14
+ "types": "./dist/index.d.cts",
15
+ "default": "./dist/index.cjs"
16
+ },
17
+ "import": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ }
21
+ }
14
22
  },
15
23
  "files": [
16
24
  "dist"
17
25
  ],
26
+ "publishConfig": {
27
+ "access": "restricted"
28
+ },
29
+ "dependencies": {
30
+ "@zapier/policy-schema": "0.8.0",
31
+ "zod": "^4.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "tsup": "^8.0.0"
35
+ },
18
36
  "scripts": {
19
37
  "build": "tsup",
20
38
  "lint": "eslint src/**/*.ts",
@@ -24,30 +42,7 @@
24
42
  "types": "tsc",
25
43
  "types:watch": "tsc --watch"
26
44
  },
27
- "publishConfig": {
28
- "access": "restricted",
29
- "provenance": true,
30
- "main": "dist/index.cjs",
31
- "module": "dist/index.js",
32
- "types": "dist/index.d.ts",
33
- "exports": {
34
- ".": {
35
- "require": {
36
- "types": "./dist/index.d.cts",
37
- "default": "./dist/index.cjs"
38
- },
39
- "import": {
40
- "types": "./dist/index.d.ts",
41
- "default": "./dist/index.js"
42
- }
43
- }
44
- }
45
- },
46
- "dependencies": {
47
- "@zapier/policy-schema": "0.8.0",
48
- "zod": "^4.0.0"
49
- },
50
- "devDependencies": {
51
- "tsup": "^8.0.0"
52
- }
53
- }
45
+ "main": "dist/index.cjs",
46
+ "module": "dist/index.js",
47
+ "types": "dist/index.d.ts"
48
+ }