/** * ADR-0027: Admin routes for organization-scoped capability connections. * GET /api/org/:orgSlug/capability-connections — list all * GET /api/org/:orgSlug/capability-connections/:capId — read one * PUT /api/org/:orgSlug/capability-connections/:capId — rotate/create * DELETE /api/org/:orgSlug/capability-connections/:capId — disable */ import type { PrismaClient } from "@prisma/client"; import type { FastifyInstance } from "fastify"; import { CapabilityConnectionService } from "../../capability/capabilityConnectionService.js"; import { CapabilityReadinessError, type CapabilityReadinessProbe } from "../../capability/capabilityReadiness.js"; import type { LocalSecretEnvelope } from "../../security/secretEnvelope.js"; import { requireOrgRole, type GuardDeps } from "../auth/guards.js"; import { handleRouteError } from "../errors.js"; export interface CapabilityConnectionRouteConfig { readonly prisma: PrismaClient; readonly sessionSecret: string; readonly secretEnvelope: LocalSecretEnvelope; readonly readinessProbe?: CapabilityReadinessProbe; } export async function registerCapabilityConnectionRoutes( app: FastifyInstance, config: CapabilityConnectionRouteConfig, ): Promise { const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret }; const connections = new CapabilityConnectionService( config.prisma, config.secretEnvelope, config.readinessProbe, ); app.get("/api/org/:orgSlug/capability-connections", async (request, reply) => { try { const { orgSlug } = request.params as { orgSlug: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; return { connections: await connections.list(auth.organization.id) }; } catch (error) { request.log.error({ requestId: request.id, operation: "capability_connection.list" }, "list failed"); return handleRouteError(reply, error); } }); app.get("/api/org/:orgSlug/capability-connections/:capabilityId", async (request, reply) => { try { const { orgSlug, capabilityId } = request.params as { orgSlug: string; capabilityId: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; return { connection: await connections.read(auth.organization.id, capabilityId) }; } catch (error) { request.log.error({ requestId: request.id, operation: "capability_connection.read" }, "read failed"); return handleRouteError(reply, error); } }); app.put("/api/org/:orgSlug/capability-connections/:capabilityId", async (request, reply) => { try { const { orgSlug, capabilityId } = request.params as { orgSlug: string; capabilityId: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; const body = parseBody(request.body); const result = await connections.rotate({ organizationId: auth.organization.id, capabilityId, actorUserId: auth.user.id, ...body, }); request.log.info({ organizationId: auth.organization.id, capabilityId, connectionId: result.id, status: result.status, secretVersion: result.activeVersion, }, result.created ? "Capability Connection created" : "Capability Connection rotated"); const { created, ...metadata } = result; return reply.status(created ? 201 : 200).send(metadata); } catch (error) { const facts = error instanceof CapabilityReadinessError ? { errorCode: error.code, failureCategory: error.category, ...(error.upstreamStatus !== undefined ? { upstreamStatus: error.upstreamStatus } : {}), } : { errorCode: "capability_connection_write_failed" }; request.log.error({ requestId: request.id, operation: "capability_connection.rotate", ...facts }, "rotate failed"); return handleRouteError(reply, error); } }); app.delete("/api/org/:orgSlug/capability-connections/:capabilityId", async (request, reply) => { try { const { orgSlug, capabilityId } = request.params as { orgSlug: string; capabilityId: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; const result = await connections.disable({ organizationId: auth.organization.id, capabilityId, actorUserId: auth.user.id, }); request.log.info({ organizationId: auth.organization.id, capabilityId, connectionId: result.id, status: result.status, }, "Capability Connection disabled"); return reply.send(result); } catch (error) { request.log.error({ requestId: request.id, operation: "capability_connection.disable" }, "disable failed"); return handleRouteError(reply, error); } }); } function parseBody(value: unknown): { readonly accessKeyId: string; readonly accessKeySecret: string; readonly endpoint: string } { if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new Error("invalid capability credential body"); } const body = value as Record; for (const name of ["accessKeyId", "accessKeySecret", "endpoint"] as const) { if (typeof body[name] !== "string" || (body[name] as string).trim() === "") { throw new Error(`${name} is required`); } } return { accessKeyId: body["accessKeyId"] as string, accessKeySecret: body["accessKeySecret"] as string, endpoint: body["endpoint"] as string, }; }