Files
FEDEO/backend/src/plugins/auth.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

136 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}