1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { Detector } from "./Detector.ts";
5
6/**
7 * Shared scaffolding for Amazon GuardDuty HTTP bindings.
8 *
9 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
10 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two builders
11 * below. Everything except the operation and the IAM action list is
12 * boilerplate.
13 */
14
15/**
16 * Build the impl Effect for a GuardDuty operation scoped to a
17 * {@link Detector}: the deploy-time half grants `actions` (on the bound
18 * detector's ARN where the action supports resource-level permissions,
19 * otherwise on `*`), and the runtime half injects the detector's
20 * `DetectorId` into every request.
21 */
22export const makeGuardDutyDetectorHttpBinding = <
23 I extends { DetectorId: string },
24 A,
25 E,
26 R,
27>(options: {
28 /** Fully-qualified binding tag, e.g. `AWS.GuardDuty.ListFindings`. */
29 tag: string;
30 /** The distilled operation. */
31 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
32 /** IAM actions granted. */
33 actions: readonly string[];
34 /**
35 * Whether the IAM action supports resource-level permissions on the
36 * detector ARN. Most GuardDuty detector-scoped actions do NOT — IAM only
37 * evaluates them against `Resource: "*"` (verified empirically: an
38 * ARN-scoped Allow is an implicit deny for e.g. `ListFindings`,
39 * `ListMembers`, `CreateSampleFindings`). Only the coverage actions
40 * (`ListCoverage`, `GetCoverageStatistics`) opt in.
41 * @default false
42 */
43 resourceLevel?: boolean;
44}) =>
45 Effect.gen(function* () {
46 const op = yield* options.operation;
47
48 return Effect.fn(function* (detector: Detector) {
49 const DetectorId = yield* detector.detectorId;
50 if (!globalThis.__ALCHEMY_RUNTIME__) {
51 const host = yield* Binding.Host;
52 if (isBindingHost(host)) {
53 yield* host.bind`Allow(${host}, ${options.tag}(${detector}))`({
54 policyStatements: [
55 {
56 Effect: "Allow",
57 Action: [...options.actions],
58 Resource: options.resourceLevel
59 ? [detector.detectorArn]
60 : ["*"],
61 },
62 ],
63 });
64 }
65 }
66 return Effect.fn(`${options.tag}(${detector.LogicalId})`)(function* (
67 request?: Omit<I, "DetectorId">,
68 ) {
69 const detectorId = yield* DetectorId;
70 return yield* op({ ...request, DetectorId: detectorId } as I);
71 });
72 });
73 });
74
75/**
76 * Build the impl Effect for an account-level GuardDuty operation — the
77 * member-account invitation flow (`ListInvitations`, `DeclineInvitations`,
78 * `DeleteInvitations`, `GetInvitationsCount`), the organization-admin
79 * actions, and the on-demand malware scan operations. The deploy-time half
80 * grants `actions` on `*`: these operations either take no resource at all
81 * or target resources (EC2 instances, S3 objects, foreign detectors) whose
82 * ARNs are only known at runtime.
83 */
84export const makeGuardDutyAccountHttpBinding = <I, A, E, R>(options: {
85 /** Fully-qualified binding tag, e.g. `AWS.GuardDuty.ListInvitations`. */
86 tag: string;
87 /** The distilled operation, invoked with the caller's request as-is. */
88 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
89 /** IAM actions granted on `*`. */
90 actions: readonly string[];
91}) =>
92 Effect.gen(function* () {
93 const op = yield* options.operation;
94
95 return Effect.fn(function* () {
96 if (!globalThis.__ALCHEMY_RUNTIME__) {
97 const host = yield* Binding.Host;
98 if (isBindingHost(host)) {
99 yield* host.bind`Allow(${host}, ${options.tag}())`({
100 policyStatements: [
101 {
102 Effect: "Allow",
103 Action: [...options.actions],
104 Resource: ["*"],
105 },
106 ],
107 });
108 }
109 }
110 return Effect.fn(options.tag)(function* (request?: I) {
111 return yield* op((request ?? {}) as I);
112 });
113 });
114 });