| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Binding from "../../Binding.ts"; |
| 3 | import type { Input } from "../../Input.ts"; |
| 4 | import { AWSEnvironment } from "../Environment.ts"; |
| 5 | import { isBindingHost } from "../Lambda/Function.ts"; |
| 6 | |
| 7 | /** |
| 8 | * Shared HTTP-binding scaffolding for the ApiGateway capabilities |
| 9 | * (`CreateApiKeyHttp`, `GetUsageHttp`, `FlushStageCacheHttp`, …). |
| 10 | * |
| 11 | * NOT exported from `index.ts` — this is internal plumbing, like |
| 12 | * Cloudflare's `BucketHttp.ts`. |
| 13 | * |
| 14 | * API Gateway's control plane authorizes by HTTP verb (`apigateway:GET`, |
| 15 | * `apigateway:POST`, …) against path-shaped ARNs |
| 16 | * (`arn:aws:apigateway:{region}::/usageplans/{id}/keys`), so every |
| 17 | * capability reduces to { verb, ARN paths, operation }. Everything else — |
| 18 | * the runtime guard, host resolution, region lookup, and the |
| 19 | * `host.bind` policy registration — is identical and lives here. |
| 20 | */ |
| 21 | |
| 22 | /** API Gateway authorizes control-plane calls by HTTP verb. */ |
| 23 | export type ApiGatewayIamVerb = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; |
| 24 | |
| 25 | /** |
| 26 | * Deploy-time half of an ApiGateway HTTP binding: resolves the host |
| 27 | * Function, and registers an IAM policy statement for |
| 28 | * `apigateway:{verb}` over the given API Gateway ARN paths. A no-op at |
| 29 | * runtime (`__ALCHEMY_RUNTIME__`) and for non-Lambda hosts. |
| 30 | * |
| 31 | * @param cap fully-qualified capability name, e.g. `AWS.ApiGateway.GetUsage` |
| 32 | * @param target the bound resource (or a string literal for account-scoped |
| 33 | * capabilities) — used for the binding's identity label |
| 34 | * @param verb the API Gateway IAM verb the capability requires |
| 35 | * @param paths builds the ARN path list once the deploy region is known; |
| 36 | * entries may be `Output`-derived (e.g. `Output.interpolate`) |
| 37 | */ |
| 38 | export const registerApiGatewayBinding = Effect.fn(function* (opts: { |
| 39 | cap: string; |
| 40 | target: unknown; |
| 41 | verb: ApiGatewayIamVerb; |
| 42 | paths: (region: string) => ReadonlyArray<Input<string>>; |
| 43 | }) { |
| 44 | if (globalThis.__ALCHEMY_RUNTIME__) return; |
| 45 | const host = yield* Binding.Host; |
| 46 | if (!isBindingHost(host)) return; |
| 47 | const { region } = yield* AWSEnvironment.current as unknown as Effect.Effect<{ |
| 48 | region: string; |
| 49 | }>; |
| 50 | yield* host.bind`Allow(${host}, ${opts.cap}(${opts.target}))`({ |
| 51 | policyStatements: [ |
| 52 | { |
| 53 | Effect: "Allow", |
| 54 | Action: [`apigateway:${opts.verb}`], |
| 55 | Resource: [...opts.paths(region)], |
| 56 | }, |
| 57 | ], |
| 58 | }); |
| 59 | }); |