1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4
5/**
6 * Shared scaffolding for AWS Security Hub HTTP bindings.
7 *
8 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
9 * a `Layer.effect(Cap, makeSecurityHubHttpBinding({ … }))` over the builder
10 * below. Everything except the operation and the IAM action list is
11 * boilerplate.
12 *
13 * Security Hub is an account/region singleton service: every operation is
14 * implicitly scoped to the caller's Hub, so there is no identifier to inject —
15 * the caller's request is passed through as-is. The deploy-time half grants
16 * `actions` on `*`: Security Hub IAM actions are evaluated against the
17 * account's `hub/default` ARN (or take no resource at all), which is not
18 * known until runtime and gains nothing from ARN-scoping within one account.
19 */
20export const makeSecurityHubHttpBinding = <I, A, E, R>(options: {
21 /** Fully-qualified binding tag, e.g. `AWS.SecurityHub.GetFindings`. */
22 tag: string;
23 /** The distilled operation, invoked with the caller's request as-is. */
24 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
25 /** IAM actions granted on `*`. */
26 actions: readonly string[];
27}) =>
28 Effect.gen(function* () {
29 const op = yield* options.operation;
30
31 return Effect.fn(function* () {
32 if (!globalThis.__ALCHEMY_RUNTIME__) {
33 const host = yield* Binding.Host;
34 if (isBindingHost(host)) {
35 yield* host.bind`Allow(${host}, ${options.tag}())`({
36 policyStatements: [
37 {
38 Effect: "Allow",
39 Action: [...options.actions],
40 Resource: ["*"],
41 },
42 ],
43 });
44 }
45 }
46 return Effect.fn(options.tag)(function* (request?: I) {
47 return yield* op((request ?? {}) as I);
48 });
49 });
50 });