/**
* Shared scaffolding for the Amazon S3 Control HTTP bindings.
*
* NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
* a `Layer.effect(Cap, makeS3Control…HttpBinding({ … }))` over one of the
* builders below. Every S3 Control request carries the owning `AccountId`
* (an endpoint host label), so each builder's only real job is deciding
* where that account id comes from:
*
* - Access-point-scoped operations inject `AccountId` + `Name` from the
* bound {@link AccessPoint} and are granted on the access point ARN.
* - Multi-Region Access Point operations inject `AccountId` + `Mrap` from
* the bound {@link MultiRegionAccessPoint}, are granted on the MRAP ARN,
* and are pinned to `us-west-2` (the region the MRAP control plane and
* route APIs are served from).
* - Account-level operations (access point listing, S3 Batch Operations
* jobs) resolve the caller's account once via `sts:GetCallerIdentity`
* (cached per binding) and are granted on `*` — job ARNs are
* server-assigned at runtime and unknowable at deploy time.
*/
import { Region } from "@distilled.cloud/aws/Region";
import * as sts from "@distilled.cloud/aws/sts";
import * as Effect from "effect/Effect";
import * as Binding from "../../Binding.ts";
import type { PolicyStatement } from "../IAM/Policy.ts";
import { isBindingHost } from "../Lambda/Function.ts";
import type { AccessPoint } from "./AccessPoint.ts";
import type { MultiRegionAccessPoint } from "./MultiRegionAccessPoint.ts";
/**
* All Multi-Region Access Point control-plane requests are routed to the
* US West (Oregon) region regardless of the ambient region — mirrors the
* {@link MultiRegionAccessPoint} resource provider.
*/
const inMrapRegion = (
self: Effect.Effect,
): Effect.Effect> =>
self.pipe(Effect.provideService(Region, Effect.succeed("us-west-2")));
/**
* Build the impl Effect for an S3 Control operation scoped to an
* {@link AccessPoint}: the deploy-time half grants `actions` on the bound
* access point's ARN, and the runtime half injects the access point's
* `AccountId` and `Name` into every request.
*/
export const makeS3ControlAccessPointHttpBinding = <
I extends { AccountId: string; Name: string },
A,
E,
R,
>(options: {
/** Fully-qualified binding tag, e.g. `AWS.S3Control.GetAccessPoint`. */
tag: string;
/** The distilled operation; `AccountId`/`Name` are injected. */
operation: Effect.Effect<(input: I) => Effect.Effect, never, R>;
/** IAM actions granted on the access point ARN. */
actions: readonly string[];
}) =>
Effect.gen(function* () {
const op = yield* options.operation;
return Effect.fn(function* (accessPoint: AccessPoint) {
const AccountId = yield* accessPoint.accountId;
const Name = yield* accessPoint.accessPointName;
if (!globalThis.__ALCHEMY_RUNTIME__) {
const host = yield* Binding.Host;
if (isBindingHost(host)) {
yield* host.bind`Allow(${host}, ${options.tag}(${accessPoint}))`({
policyStatements: [
{
Effect: "Allow",
Action: [...options.actions],
Resource: [accessPoint.accessPointArn],
},
],
});
}
}
return Effect.fn(`${options.tag}(${accessPoint.LogicalId})`)(function* (
request?: Omit,
) {
return yield* op({
...request,
AccountId: yield* AccountId,
Name: yield* Name,
} as I);
});
});
});
/**
* Build the impl Effect for an S3 Control operation scoped to a
* {@link MultiRegionAccessPoint}: the deploy-time half grants `actions` on
* the bound MRAP's ARN, and the runtime half injects the MRAP's `AccountId`
* and alias-addressed ARN (`Mrap`) into every request, pinned to the
* `us-west-2` MRAP control-plane region.
*/
export const makeS3ControlMrapHttpBinding = <
I extends { AccountId: string; Mrap: string },
A,
E,
R,
>(options: {
/** Fully-qualified binding tag, e.g. `AWS.S3Control.GetMultiRegionAccessPointRoutes`. */
tag: string;
/** The distilled operation; `AccountId`/`Mrap` are injected. */
operation: Effect.Effect<(input: I) => Effect.Effect, never, R>;
/** IAM actions granted on the Multi-Region Access Point ARN. */
actions: readonly string[];
}) =>
Effect.gen(function* () {
const op = yield* inMrapRegion(options.operation);
return Effect.fn(function* (mrap: MultiRegionAccessPoint) {
const AccountId = yield* mrap.accountId;
const Mrap = yield* mrap.multiRegionAccessPointArn;
if (!globalThis.__ALCHEMY_RUNTIME__) {
const host = yield* Binding.Host;
if (isBindingHost(host)) {
yield* host.bind`Allow(${host}, ${options.tag}(${mrap}))`({
policyStatements: [
{
Effect: "Allow",
Action: [...options.actions],
Resource: [mrap.multiRegionAccessPointArn],
},
],
});
}
}
return Effect.fn(`${options.tag}(${mrap.LogicalId})`)(function* (
request?: Omit,
) {
return yield* op({
...request,
AccountId: yield* AccountId,
Mrap: yield* Mrap,
} as I);
});
});
});
/**
* Build the impl Effect for an account-level S3 Control operation (access
* point listing, S3 Batch Operations jobs). The deploy-time half grants
* `actions` on `*` — job ARNs are server-assigned at runtime and unknowable
* at deploy time. The runtime half resolves the caller's account id once via
* `sts:GetCallerIdentity` (needs no extra IAM permission) and injects it as
* `AccountId`.
*/
export const makeS3ControlAccountHttpBinding = <
I extends { AccountId: string },
A,
E,
R,
>(options: {
/** Fully-qualified binding tag, e.g. `AWS.S3Control.ListJobs`. */
tag: string;
/** The distilled operation; `AccountId` is injected. */
operation: Effect.Effect<(input: I) => Effect.Effect, never, R>;
/** IAM actions granted on `*`. */
actions: readonly string[];
/**
* Grant `iam:PassRole` so the function can hand S3 Batch Operations the
* execution role named in the job. Set on `CreateJob`. Matches the AWS
* Batch Operations permission model (an `iam:PassedToService` condition is
* NOT populated for `s3:CreateJob` — IAM denies a conditioned grant).
*/
passRole?: boolean;
}) =>
Effect.gen(function* () {
const op = yield* options.operation;
const getCallerIdentity = yield* sts.getCallerIdentity;
return Effect.fn(function* () {
if (!globalThis.__ALCHEMY_RUNTIME__) {
const host = yield* Binding.Host;
if (isBindingHost(host)) {
const policyStatements: PolicyStatement[] = [
{
Effect: "Allow",
Action: [...options.actions],
Resource: ["*"],
},
];
if (options.passRole) {
// No `iam:PassedToService` condition — IAM does not populate the
// key for `s3:CreateJob`, so a conditioned grant is always
// denied. This matches the policy AWS documents for Batch
// Operations job creators.
policyStatements.push({
Effect: "Allow",
Action: ["iam:PassRole"],
Resource: ["*"],
});
}
yield* host.bind`Allow(${host}, ${options.tag}())`({
policyStatements,
});
}
}
// Resolve the caller's account id lazily (first call inside the
// Lambda) and cache it for the life of the binding.
const accountId = yield* Effect.cached(
getCallerIdentity({}).pipe(Effect.map((r) => r.Account!)),
);
return Effect.fn(options.tag)(function* (request?: Omit) {
return yield* op({
...request,
AccountId: yield* accountId,
} as I);
});
});
});