feat(auth): centralize ThothII permission enforcement

This commit is contained in:
2026-08-16 17:52:03 +02:00
parent 23ac75ce1d
commit 2e0489ce22
20 changed files with 330 additions and 93 deletions
+13 -3
View File
@@ -17,6 +17,7 @@ import {
} from "../workspaces/schema.js";
import type { RuntimeBindings } from "../workspaces/runtime-renderer.js";
import type { WorkspaceDiagnostics } from "../workspaces/diagnostics.js";
import { isPrincipalContext, requirePermission } from "../auth/authorization.js";
export type WorkspaceDiagnoser = (
workspace: WorkspaceDescriptor,
@@ -91,7 +92,8 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
};
};
app.get("/workspace-registry/status", async (_request, reply) => {
app.get("/workspace-registry/status", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "workspace.manage"))) return reply;
try {
return await deps.registry.bootstrap();
} catch (error) {
@@ -99,7 +101,8 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
}
});
app.post("/workspace-registry/pull", async (_request, reply) => {
app.post("/workspace-registry/pull", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "workspace.manage"))) return reply;
try {
return await deps.registry.pull();
} catch (error) {
@@ -107,7 +110,8 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
}
});
app.get("/workspaces", async (_request, reply) => {
app.get("/workspaces", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "session.use"))) return reply;
try {
const records = await deps.registry.listCatalog();
return await Promise.all(records.map(async (record) => {
@@ -129,6 +133,7 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
});
app.get("/workspaces/:id", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "session.use"))) return reply;
try {
const { id } = z.object({ id: workspaceId }).parse(request.params);
return await deps.registry.read(id);
@@ -138,6 +143,7 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
});
app.post("/workspaces/validate", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "workspace.manage"))) return reply;
try {
const { workspace } = workspacePayload.parse(request.body);
const canonical = validateWorkspaceDescriptor(workspace);
@@ -148,6 +154,7 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
});
app.get("/workspaces/:id/runtime-configuration", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "session.use"))) return reply;
try {
const { id } = z.object({ id: workspaceId }).parse(request.params);
return await runtimeConfiguration(id);
@@ -157,6 +164,7 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
});
app.put("/workspaces/:id/secrets", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "workspace.secrets.manage"))) return reply;
try {
const { id } = z.object({ id: workspaceId }).parse(request.params);
const { values } = secretValuesPayload.parse(request.body);
@@ -176,6 +184,7 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
});
app.delete("/workspaces/:id/secrets/:requirementId", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "workspace.secrets.manage"))) return reply;
try {
const { id, requirementId } = z.object({
id: workspaceId,
@@ -194,6 +203,7 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
});
app.post("/workspaces/:id/test", async (request, reply) => {
if (!isPrincipalContext(requirePermission(request, reply, "workspace.manage"))) return reply;
try {
const { id } = z.object({ id: workspaceId }).parse(request.params);
const { workspace } = await deps.registry.read(id);