import { HttpServerRequest } from "@effect/platform"; import type { WorkspaceId } from "#schema"; import { Actor, ActorForbiddenError, type ActorShape, ActorUnauthenticatedError, } from "#domain"; import * as Effect from "effect/Effect"; import { ControlPlaneActorResolver } from "./auth/actor-resolver"; import { ControlPlaneForbiddenError, ControlPlaneUnauthorizedError, } from "./errors"; const requestHeaders = Effect.map( HttpServerRequest.HttpServerRequest, (request) => request.headers, ); const readString = (value: unknown, fallback: string): string => typeof value === "string" && value.length > 0 ? value : fallback; const toForbiddenError = ( operation: string, cause: unknown, ): ControlPlaneForbiddenError => new ControlPlaneForbiddenError({ operation, message: "Access denied", details: `${readString((cause as { permission?: unknown })?.permission, "unknown")} on ${readString((cause as { scope?: unknown })?.scope, "unknown")}`, }); const toUnauthorizedError = ( operation: string, cause: unknown, ): ControlPlaneUnauthorizedError => new ControlPlaneUnauthorizedError({ operation, message: readString((cause as { message?: unknown })?.message, "Authentication required"), details: "Authentication required", }); export const resolveRequestActor = Effect.flatMap( ControlPlaneActorResolver, (actorResolver) => Effect.flatMap(requestHeaders, (headers) => actorResolver.resolveActor({ headers })), ); export const resolveWorkspaceRequestActor = (workspaceId: WorkspaceId) => Effect.flatMap(ControlPlaneActorResolver, (actorResolver) => Effect.flatMap(requestHeaders, (headers) => actorResolver.resolveWorkspaceActor({ workspaceId, headers }) )); const provideActor = ( actor: ActorShape, effect: Effect.Effect, ) => effect.pipe(Effect.provideService(Actor, actor)); export const mapActorHttpErrors = ( operation: string, effect: Effect.Effect, ): Effect.Effect => effect.pipe( Effect.catchTag("ActorUnauthenticatedError", (cause) => Effect.fail(toUnauthorizedError(operation, cause)), ), Effect.catchTag("ActorForbiddenError", (cause) => Effect.fail(toForbiddenError(operation, cause)), ), ); export const withRequestActor = ( operation: string, run: (actor: ActorShape) => Effect.Effect, ) => mapActorHttpErrors( operation, Effect.gen(function* () { const actor = yield* resolveRequestActor; return yield* provideActor(actor, run(actor)); }), ); export const withWorkspaceRequestActor = ( operation: string, workspaceId: WorkspaceId, run: (actor: ActorShape) => Effect.Effect, ) => mapActorHttpErrors( operation, Effect.gen(function* () { const actor = yield* resolveWorkspaceRequestActor(workspaceId); return yield* provideActor(actor, run(actor)); }), );