1import * as Lambda from "@/AWS/Lambda";
2import * as RolesAnywhere from "@/AWS/RolesAnywhere";
3import * as Duration from "effect/Duration";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8import path from "pathe";
9
10const main = path.resolve(import.meta.dirname, "handler.ts");
11
12// A well-formed-but-nonexistent subject id — drives the typed error path for
13// GetSubject (proving the IAM grant + typed union; an IAM gap would surface
14// AccessDeniedException instead).
15const NONEXISTENT_SUBJECT_ID = "00000000-0000-0000-0000-000000000000";
16
17export class RolesAnywhereTestFunction extends Lambda.Function<Lambda.Function>()(
18 "RolesAnywhereTestFunction",
19) {}
20
21export default RolesAnywhereTestFunction.make(
22 {
23 main,
24 functionUrl: true,
25 timeout: Duration.seconds(30),
26 },
27 Effect.gen(function* () {
28 // --- account-level bindings (subjects are the audit records of every
29 // certificate identity that authenticated via Roles Anywhere) ---
30 const getSubject = yield* RolesAnywhere.GetSubject();
31 const listSubjects = yield* RolesAnywhere.ListSubjects();
32
33 const bound = { getSubject, listSubjects };
34
35 return {
36 fetch: Effect.gen(function* () {
37 const request = yield* HttpServerRequest;
38 const url = new URL(request.originalUrl);
39 const pathname = url.pathname;
40
41 if (request.method === "GET" && pathname === "/bindings") {
42 return yield* HttpServerResponse.json({
43 bound: Object.keys(bound),
44 });
45 }
46
47 if (request.method === "GET" && pathname === "/subjects") {
48 // The testing account has no external workloads authenticating via
49 // Roles Anywhere, so an empty list still proves the
50 // rolesanywhere:ListSubjects grant end-to-end.
51 const result = yield* listSubjects().pipe(
52 Effect.map((r) => ({
53 ok: true as const,
54 count: r.subjects?.length ?? 0,
55 })),
56 );
57 return yield* HttpServerResponse.json(result);
58 }
59
60 if (request.method === "GET" && pathname === "/subject-not-found") {
61 const tag = yield* getSubject({
62 subjectId: NONEXISTENT_SUBJECT_ID,
63 }).pipe(
64 Effect.map((r) =>
65 r.subject === undefined ? "NoSubject" : "Found",
66 ),
67 Effect.catchTag("ResourceNotFoundException", (e) =>
68 Effect.succeed(e._tag),
69 ),
70 );
71 return yield* HttpServerResponse.json({ tag });
72 }
73
74 return yield* HttpServerResponse.json(
75 { error: "Not found", method: request.method, pathname },
76 { status: 404 },
77 );
78 }).pipe(Effect.orDie),
79 };
80 }).pipe(
81 Effect.provide(
82 Layer.mergeAll(
83 RolesAnywhere.GetSubjectHttp,
84 RolesAnywhere.ListSubjectsHttp,
85 ),
86 ),
87 ),
88);