Files
FEDEO/backend/src/plugins/auth.ts
florianfederspiel 7e0a2f5e4f
Some checks failed
Build and Push Docker Images / build-backend (push) Failing after 25s
Build and Push Docker Images / build-frontend (push) Successful in 1m22s
New Admin Dashboard
2026-03-18 18:34:02 +01:00

135 lines
4.3 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 } 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⃣ Rolle des Nutzers im Tenant holen
// --------------------------------------------------------
const roleRows = await server.db
.select()
.from(authUserRoles)
.where(
and(
eq(authUserRoles.user_id, userId),
eq(authUserRoles.tenant_id, tenantId)
)
)
.limit(1)
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 roleId = roleRows[0].role_id
// --------------------------------------------------------
// 4⃣ Berechtigungen der Rolle laden
// --------------------------------------------------------
const permissionRows = await server.db
.select()
.from(authRolePermissions)
.where(eq(authRolePermissions.role_id, roleId))
const permissions = permissionRows.map((p) => p.permission)
// --------------------------------------------------------
// 5⃣ An Request hängen für spätere Nutzung
// --------------------------------------------------------
req.role = roleId
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
}
}