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.
136 lines
4.5 KiB
TypeScript
136 lines
4.5 KiB
TypeScript
import { FastifyInstance } from "fastify"
|
||
import fp from "fastify-plugin"
|
||
import jwt from "jsonwebtoken"
|
||
import { secrets } from "../utils/secrets"
|
||
|
||
import {
|
||
authUserRoles,
|
||
authRolePermissions,
|
||
authUsers,
|
||
} from "../../db/schema"
|
||
|
||
import { eq, and, inArray } from "drizzle-orm"
|
||
|
||
export default fp(async (server: FastifyInstance) => {
|
||
server.addHook("preHandler", async (req, reply) => {
|
||
// 1️⃣ Token aus Header oder Cookie lesen
|
||
const cookieToken = req.cookies?.token
|
||
const authHeader = req.headers.authorization
|
||
|
||
const headerToken =
|
||
authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null
|
||
|
||
const token =
|
||
headerToken && headerToken.length > 10
|
||
? headerToken
|
||
: cookieToken || null
|
||
|
||
if (!token) {
|
||
return reply.code(401).send({ error: "Authentication required" })
|
||
}
|
||
|
||
try {
|
||
// 2️⃣ JWT verifizieren
|
||
const payload = jwt.verify(token, secrets.JWT_SECRET!) as {
|
||
user_id: string
|
||
email: string
|
||
tenant_id: number | null
|
||
}
|
||
|
||
if (!payload?.user_id) {
|
||
return reply.code(401).send({ error: "Invalid token" })
|
||
}
|
||
|
||
// Payload an Request hängen
|
||
req.user = payload
|
||
|
||
const [currentUser] = await server.db
|
||
.select({
|
||
is_admin: authUsers.is_admin,
|
||
})
|
||
.from(authUsers)
|
||
.where(eq(authUsers.id, payload.user_id))
|
||
.limit(1)
|
||
|
||
req.user.is_admin = Boolean(currentUser?.is_admin)
|
||
|
||
// Multi-Tenant Modus ohne ausgewählten Tenant → keine Rollenprüfung
|
||
if (!req.user.tenant_id) {
|
||
return
|
||
}
|
||
|
||
const tenantId = req.user.tenant_id
|
||
const userId = req.user.user_id
|
||
|
||
// --------------------------------------------------------
|
||
// 3️⃣ Rollen des Nutzers im Tenant holen
|
||
// --------------------------------------------------------
|
||
const roleRows = await server.db
|
||
.select({
|
||
role_id: authUserRoles.role_id,
|
||
})
|
||
.from(authUserRoles)
|
||
.where(
|
||
and(
|
||
eq(authUserRoles.user_id, userId),
|
||
eq(authUserRoles.tenant_id, tenantId)
|
||
)
|
||
)
|
||
|
||
if (roleRows.length === 0) {
|
||
if (req.user.is_admin) {
|
||
req.role = ""
|
||
req.permissions = []
|
||
req.hasPermission = () => false
|
||
return
|
||
}
|
||
|
||
return reply
|
||
.code(403)
|
||
.send({ error: "No role assigned for this tenant" })
|
||
}
|
||
|
||
const roleIds = Array.from(new Set(roleRows.map((role) => role.role_id)))
|
||
|
||
// --------------------------------------------------------
|
||
// 4️⃣ Berechtigungen der Rollen laden
|
||
// --------------------------------------------------------
|
||
const permissionRows = await server.db
|
||
.select()
|
||
.from(authRolePermissions)
|
||
.where(inArray(authRolePermissions.role_id, roleIds))
|
||
|
||
const permissions = Array.from(new Set(permissionRows.map((p) => p.permission)))
|
||
|
||
// --------------------------------------------------------
|
||
// 5️⃣ An Request hängen für spätere Nutzung
|
||
// --------------------------------------------------------
|
||
req.role = roleIds[0]
|
||
req.permissions = permissions
|
||
req.hasPermission = (perm: string) => permissions.includes(perm)
|
||
|
||
} catch (err) {
|
||
console.error("JWT verification error:", err)
|
||
return reply.code(401).send({ error: "Invalid or expired token" })
|
||
}
|
||
})
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Fastify TypeScript Erweiterungen
|
||
// ---------------------------------------------------------------------------
|
||
|
||
declare module "fastify" {
|
||
interface FastifyRequest {
|
||
user: {
|
||
user_id: string
|
||
email: string
|
||
tenant_id: number | null
|
||
is_admin?: boolean
|
||
}
|
||
role: string
|
||
permissions: string[]
|
||
hasPermission: (permission: string) => boolean
|
||
}
|
||
}
|