Files
FEDEO/backend/src/mcp/authz.ts
florianfederspiel a8450fc0c6 MCP-Server für Buchhaltung und Organisation ergänzen
Fügt einen geschützten MCP-JSON-RPC-Endpunkt mit Buchhaltungs-Tools und Aufgaben-Tools hinzu. Berechtigungen werden rollenbasiert pro Mandant geprüft und die Auth-Logik berücksichtigt nun alle Rollen eines Nutzers.
2026-05-11 12:43:58 +02:00

89 lines
2.4 KiB
TypeScript

import { FastifyInstance, FastifyRequest } from "fastify"
import { and, eq, or, isNull, inArray } from "drizzle-orm"
import {
authRoles,
authRolePermissions,
authUserRoles,
} from "../../db/schema"
import { McpContext, McpTool } from "./types"
export async function loadTenantPermissions(
server: FastifyInstance,
userId: string,
tenantId: number
) {
const roleRows = await server.db
.select({
roleId: authUserRoles.role_id,
})
.from(authUserRoles)
.innerJoin(
authRoles,
and(
eq(authRoles.id, authUserRoles.role_id),
or(isNull(authRoles.tenant_id), eq(authRoles.tenant_id, tenantId))
)
)
.where(
and(
eq(authUserRoles.user_id, userId),
eq(authUserRoles.tenant_id, tenantId)
)
)
const roleIds = Array.from(new Set(roleRows.map((row) => row.roleId)))
if (roleIds.length === 0) return []
const permissionRows = await server.db
.select({
permission: authRolePermissions.permission,
})
.from(authRolePermissions)
.where(inArray(authRolePermissions.role_id, roleIds))
return Array.from(new Set(permissionRows.map((row) => row.permission)))
}
export async function createMcpContext(
server: FastifyInstance,
request: FastifyRequest
): Promise<McpContext> {
const user = request.user
if (!user?.user_id) {
throw Object.assign(new Error("Authentication required"), { statusCode: 401 })
}
if (!user.tenant_id) {
throw Object.assign(new Error("MCP benötigt einen aktiven Mandanten"), { statusCode: 403 })
}
const permissions = await loadTenantPermissions(server, user.user_id, user.tenant_id)
return {
server,
request,
tenantId: user.tenant_id,
userId: user.user_id,
isAdmin: Boolean(user.is_admin),
permissions,
}
}
export function assertToolPermission(context: McpContext, tool: McpTool) {
if (context.isAdmin) return
const allowed = tool.requiredPermissions.every((permission) =>
context.permissions.includes(permission)
)
if (!allowed) {
throw Object.assign(
new Error(`Fehlende Berechtigung für ${tool.name}: ${tool.requiredPermissions.join(", ")}`),
{ statusCode: 403 }
)
}
}