1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import { keyLabel, keyPolicyStatement, type KeyLike } from "./KeyBinding.ts";
5
6/**
7 * Shared scaffolding for the AWS KMS 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 KMS cryptographic operation scoped to a
17 * {@link KeyLike} target (a `Key` resource or the `alias/...` name of a
18 * pre-existing key): the deploy-time half grants `actions` on the bound key
19 * (exact key ARN, or `Resource: "*"` + `kms:RequestAlias` for an alias), and
20 * the runtime half injects the key identifier into every request.
21 */
22export const makeKmsKeyHttpBinding = <
23 I extends { KeyId?: string },
24 A,
25 E,
26 R,
27>(options: {
28 /** Fully-qualified binding tag, e.g. `AWS.KMS.Sign`. */
29 tag: string;
30 /** The distilled operation. */
31 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
32 /** IAM actions granted on the bound key. */
33 actions: readonly `kms:${string}`[];
34}) =>
35 Effect.gen(function* () {
36 const op = yield* options.operation;
37
38 return Effect.fn(function* (key: KeyLike) {
39 const KeyId =
40 typeof key === "string" ? Effect.succeed(key) : yield* key.keyId;
41 if (!globalThis.__ALCHEMY_RUNTIME__) {
42 const host = yield* Binding.Host;
43 if (isBindingHost(host)) {
44 yield* host.bind`Allow(${host}, ${options.tag}(${key}))`({
45 policyStatements: [keyPolicyStatement(options.actions, key)],
46 });
47 }
48 }
49 return Effect.fn(`${options.tag}(${keyLabel(key)})`)(function* (
50 request?: Omit<I, "KeyId">,
51 ) {
52 const keyId = yield* KeyId;
53 return yield* op({ ...request, KeyId: keyId } as I);
54 });
55 });
56 });
57
58/**
59 * Build the impl Effect for an account-level KMS operation that is not
60 * scoped to a key (e.g. `kms:GenerateRandom`). The deploy-time half grants
61 * `actions` on `*`.
62 */
63export const makeKmsAccountHttpBinding = <I, A, E, R>(options: {
64 /** Fully-qualified binding tag, e.g. `AWS.KMS.GenerateRandom`. */
65 tag: string;
66 /** The distilled operation, invoked with the caller's request as-is. */
67 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
68 /** IAM actions granted on `*`. */
69 actions: readonly `kms:${string}`[];
70}) =>
71 Effect.gen(function* () {
72 const op = yield* options.operation;
73
74 return Effect.fn(function* () {
75 if (!globalThis.__ALCHEMY_RUNTIME__) {
76 const host = yield* Binding.Host;
77 if (isBindingHost(host)) {
78 yield* host.bind`Allow(${host}, ${options.tag}())`({
79 policyStatements: [
80 {
81 Effect: "Allow",
82 Action: [...options.actions],
83 Resource: ["*"],
84 },
85 ],
86 });
87 }
88 }
89 return Effect.fn(options.tag)(function* (request?: I) {
90 return yield* op((request ?? {}) as I);
91 });
92 });
93 });