1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { IPSet } from "./IPSet.ts";
5import { type WafScope, withWafScope } from "./internal.ts";
6import type { RuleGroup } from "./RuleGroup.ts";
7import type { WebACL } from "./WebACL.ts";
8
9/**
10 * Shared scaffolding for AWS WAFv2 HTTP bindings.
11 *
12 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
13 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
14 * below. Everything except the operation, the IAM action list, and the
15 * injected identifiers is boilerplate:
16 *
17 * - {@link makeWafv2WebAclHttpBinding} — operations scoped to one bound
18 * {@link WebACL}; `inject` picks which resolved identifiers (ARN, name,
19 * id, scope) the request needs. CLOUDFRONT-scoped web ACLs are pinned to
20 * `us-east-1` automatically.
21 * - {@link makeWafv2IPSetHttpBinding} — operations scoped to one bound
22 * {@link IPSet}; injects `Name`/`Scope`/`Id`.
23 * - {@link makeWafv2RuleGroupHttpBinding} — operations keyed by a rule
24 * group ARN (the permission-policy interface); injects `ResourceArn`.
25 * - {@link makeWafv2AccountHttpBinding} — account-level operations (API
26 * keys, capacity checks, managed rule group catalog); the caller's
27 * request passes through and the grant is on `*`. A `Scope: "CLOUDFRONT"`
28 * request is pinned to `us-east-1`.
29 */
30
31/** Identifiers of a bound {@link WebACL}, resolved at request time. */
32export interface ResolvedWebAcl {
33 readonly arn: string;
34 readonly name: string;
35 readonly id: string;
36 readonly scope: WafScope;
37}
38
39/**
40 * Build the impl Effect for an operation scoped to a single bound
41 * {@link WebACL}. `inject` maps the resolved web ACL identifiers onto the
42 * request fields the operation expects; the deploy-time half grants
43 * `actions` on the web ACL ARN.
44 */
45export const makeWafv2WebAclHttpBinding = <I, A, E, R>(options: {
46 /** Fully-qualified binding tag, e.g. `AWS.WAFv2.GetSampledRequests`. */
47 tag: string;
48 /** The distilled operation. */
49 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
50 /** IAM actions granted on the web ACL ARN. */
51 actions: readonly string[];
52 /** Request fields injected from the bound web ACL. */
53 inject: (webAcl: ResolvedWebAcl) => Partial<I>;
54}) =>
55 Effect.gen(function* () {
56 const op = yield* options.operation;
57
58 return Effect.fn(function* (webAcl: WebACL) {
59 const Arn = yield* webAcl.webAclArn;
60 const Name = yield* webAcl.webAclName;
61 const Id = yield* webAcl.webAclId;
62 const Scope = yield* webAcl.scope;
63 if (!globalThis.__ALCHEMY_RUNTIME__) {
64 const host = yield* Binding.Host;
65 if (isBindingHost(host)) {
66 yield* host.bind`Allow(${host}, ${options.tag}(${webAcl}))`({
67 policyStatements: [
68 {
69 Effect: "Allow",
70 Action: [...options.actions],
71 Resource: [webAcl.webAclArn],
72 },
73 ],
74 });
75 }
76 }
77 return Effect.fn(`${options.tag}(${webAcl.LogicalId})`)(function* (
78 request?: Partial<I>,
79 ) {
80 const resolved: ResolvedWebAcl = {
81 arn: yield* Arn,
82 name: yield* Name,
83 id: yield* Id,
84 scope: yield* Scope,
85 };
86 return yield* withWafScope(
87 resolved.scope,
88 op({ ...request, ...options.inject(resolved) } as I),
89 );
90 });
91 });
92 });
93
94/**
95 * Build the impl Effect for an operation scoped to a single bound
96 * {@link IPSet}: the runtime callable injects the IP set's `Name`, `Scope`,
97 * and `Id`; the deploy-time half grants `actions` on the IP set ARN.
98 * CLOUDFRONT-scoped IP sets are pinned to `us-east-1` automatically.
99 */
100export const makeWafv2IPSetHttpBinding = <
101 I extends { Name?: string; Scope?: string; Id?: string },
102 A,
103 E,
104 R,
105>(options: {
106 /** Fully-qualified binding tag, e.g. `AWS.WAFv2.GetIPSet`. */
107 tag: string;
108 /** The distilled operation; `Name`/`Scope`/`Id` are injected. */
109 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
110 /** IAM actions granted on the IP set ARN. */
111 actions: readonly string[];
112}) =>
113 Effect.gen(function* () {
114 const op = yield* options.operation;
115
116 return Effect.fn(function* (ipSet: IPSet) {
117 const Name = yield* ipSet.ipSetName;
118 const Id = yield* ipSet.ipSetId;
119 const Scope = yield* ipSet.scope;
120 if (!globalThis.__ALCHEMY_RUNTIME__) {
121 const host = yield* Binding.Host;
122 if (isBindingHost(host)) {
123 yield* host.bind`Allow(${host}, ${options.tag}(${ipSet}))`({
124 policyStatements: [
125 {
126 Effect: "Allow",
127 Action: [...options.actions],
128 Resource: [ipSet.ipSetArn],
129 },
130 ],
131 });
132 }
133 }
134 return Effect.fn(`${options.tag}(${ipSet.LogicalId})`)(function* (
135 request?: Omit<I, "Name" | "Scope" | "Id">,
136 ) {
137 const scope = yield* Scope;
138 return yield* withWafScope(
139 scope,
140 op({
141 ...request,
142 Name: yield* Name,
143 Scope: scope,
144 Id: yield* Id,
145 } as I),
146 );
147 });
148 });
149 });
150
151/**
152 * Build the impl Effect for an operation keyed by a bound {@link RuleGroup}
153 * ARN (the permission-policy interface): the runtime callable injects the
154 * rule group's ARN as `ResourceArn`; the deploy-time half grants `actions`
155 * on the rule group ARN. CLOUDFRONT-scoped rule groups are pinned to
156 * `us-east-1` automatically.
157 */
158export const makeWafv2RuleGroupHttpBinding = <
159 I extends { ResourceArn?: string },
160 A,
161 E,
162 R,
163>(options: {
164 /** Fully-qualified binding tag, e.g. `AWS.WAFv2.GetPermissionPolicy`. */
165 tag: string;
166 /** The distilled operation; `ResourceArn` is injected. */
167 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
168 /** IAM actions granted on the rule group ARN. */
169 actions: readonly string[];
170}) =>
171 Effect.gen(function* () {
172 const op = yield* options.operation;
173
174 return Effect.fn(function* (ruleGroup: RuleGroup) {
175 const Arn = yield* ruleGroup.ruleGroupArn;
176 const Scope = yield* ruleGroup.scope;
177 if (!globalThis.__ALCHEMY_RUNTIME__) {
178 const host = yield* Binding.Host;
179 if (isBindingHost(host)) {
180 yield* host.bind`Allow(${host}, ${options.tag}(${ruleGroup}))`({
181 policyStatements: [
182 {
183 Effect: "Allow",
184 Action: [...options.actions],
185 Resource: [ruleGroup.ruleGroupArn],
186 },
187 ],
188 });
189 }
190 }
191 return Effect.fn(`${options.tag}(${ruleGroup.LogicalId})`)(function* (
192 request?: Omit<I, "ResourceArn">,
193 ) {
194 return yield* withWafScope(
195 yield* Scope,
196 op({ ...request, ResourceArn: yield* Arn } as I),
197 );
198 });
199 });
200 });
201
202/**
203 * Build the impl Effect for an account-level WAFv2 operation (API keys,
204 * capacity checks, managed rule group catalog reads): the caller's request
205 * passes through unchanged and the deploy-time half grants `actions` on
206 * `*` (these operations authorize account-wide, not against a resource
207 * ARN). A request with `Scope: "CLOUDFRONT"` is pinned to `us-east-1`.
208 */
209export const makeWafv2AccountHttpBinding = <I, A, E, R>(options: {
210 /** Fully-qualified binding tag, e.g. `AWS.WAFv2.CheckCapacity`. */
211 tag: string;
212 /** The distilled operation, invoked with the caller's request as-is. */
213 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
214 /** IAM actions granted on `*`. */
215 actions: readonly string[];
216}) =>
217 Effect.gen(function* () {
218 const op = yield* options.operation;
219
220 return Effect.fn(function* () {
221 if (!globalThis.__ALCHEMY_RUNTIME__) {
222 const host = yield* Binding.Host;
223 if (isBindingHost(host)) {
224 yield* host.bind`Allow(${host}, ${options.tag}())`({
225 policyStatements: [
226 {
227 Effect: "Allow",
228 Action: [...options.actions],
229 Resource: ["*"],
230 },
231 ],
232 });
233 }
234 }
235 return Effect.fn(options.tag)(function* (request?: I) {
236 const input = (request ?? {}) as I;
237 const scope: WafScope =
238 (input as { Scope?: string }).Scope === "CLOUDFRONT"
239 ? "CLOUDFRONT"
240 : "REGIONAL";
241 return yield* withWafScope(scope, op(input));
242 });
243 });
244 });