diff --git a/backend/src/middleware/auth.ts b/backend/src/middleware/auth.ts new file mode 100644 index 0000000..395f0aa --- /dev/null +++ b/backend/src/middleware/auth.ts @@ -0,0 +1,53 @@ +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; + +// In Produktion MUSS JWT_SECRET über eine Server-Umgebungsvariable gesetzt sein +// (siehe .env auf dem VPS). Der Fallback ist nur für die lokale Entwicklung. +export const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-me-in-production'; +export const JWT_EXPIRES_IN = '30d'; + +export interface AuthUser { + id: string; + email: string; + role: string; +} + +export interface AuthedRequest extends Request { + user?: AuthUser; +} + +export function signAuthToken(user: AuthUser): string { + return jwt.sign({ sub: user.id, email: user.email, role: user.role }, JWT_SECRET, { + expiresIn: JWT_EXPIRES_IN, + }); +} + +/** Erfordert einen gültigen Bearer-JWT im Authorization-Header. */ +export function requireAuth(req: AuthedRequest, res: Response, next: NextFunction) { + const header = req.headers.authorization; + if (!header || !header.startsWith('Bearer ')) { + return res.status(401).json({ error: 'Nicht authentifiziert' }); + } + const token = header.slice('Bearer '.length); + try { + const payload = jwt.verify(token, JWT_SECRET) as jwt.JwtPayload; + req.user = { + id: String(payload.sub), + email: String(payload.email), + role: String(payload.role), + }; + next(); + } catch { + return res.status(401).json({ error: 'Token ungültig oder abgelaufen' }); + } +} + +/** Erfordert zusätzlich eine der angegebenen Rollen (nach requireAuth einsetzen). */ +export function requireRole(...roles: string[]) { + return (req: AuthedRequest, res: Response, next: NextFunction) => { + if (!req.user || !roles.includes(req.user.role)) { + return res.status(403).json({ error: 'Keine Berechtigung für diese Aktion' }); + } + next(); + }; +}