const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const { Pool } = require('pg'); const router = express.Router(); const pool = new Pool({ host: process.env.DB_HOST || 'localhost', port: process.env.DB_PORT || 5432, database: process.env.DB_NAME || 'buchhaltung', user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'postgres', }); const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production'; const BCRYPT_ROUNDS = 11; // Helper: Check if any users exist async function hasUsers() { const result = await pool.query('SELECT COUNT(*) FROM users'); return parseInt(result.rows[0].count) > 0; } // POST /api/auth/setup - Erste Anmeldung (Admin erstellen) router.post('/setup', async (req, res) => { try { // Prüfen ob schon User existieren const existingUsers = await hasUsers(); if (existingUsers) { return res.status(400).json({ error: 'Setup bereits abgeschlossen. Bitte einloggen.' }); } const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Username und Passwort erforderlich' }); } if (password.length < 6) { return res.status(400).json({ error: 'Passwort muss mindestens 6 Zeichen lang sein' }); } const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS); const result = await pool.query( `INSERT INTO users (username, password_hash, role, is_active) VALUES ($1, $2, $3, true) RETURNING id, username, role, created_at`, [username, passwordHash, 'admin'] ); const user = result.rows[0]; const token = jwt.sign( { userId: user.id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: '7d' } ); res.status(201).json({ success: true, message: 'Admin-Account erstellt', user: { id: user.id, username: user.username, role: user.role }, token }); } catch (error) { console.error('Setup Error:', error); if (error.code === '23505') { return res.status(400).json({ error: 'Username bereits vergeben' }); } res.status(500).json({ error: 'Server-Fehler bei der Einrichtung' }); } }); // POST /api/auth/login - JWT-Login router.post('/login', async (req, res) => { try { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Username und Passwort erforderlich' }); } const result = await pool.query( 'SELECT * FROM users WHERE username = $1 AND is_active = true', [username] ); if (result.rows.length === 0) { return res.status(401).json({ error: 'Ungültige Anmeldedaten' }); } const user = result.rows[0]; const validPassword = await bcrypt.compare(password, user.password_hash); if (!validPassword) { return res.status(401).json({ error: 'Ungültige Anmeldedaten' }); } // Update last_login await pool.query('UPDATE users SET last_login = NOW() WHERE id = $1', [user.id]); const token = jwt.sign( { userId: user.id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: '7d' } ); res.json({ success: true, message: 'Login erfolgreich', user: { id: user.id, username: user.username, role: user.role }, token }); } catch (error) { console.error('Login Error:', error); res.status(500).json({ error: 'Server-Fehler beim Login' }); } }); // POST /api/auth/logout - Logout (Client-seitig Token entfernen, hier nur Bestätigung) router.post('/logout', async (req, res) => { res.json({ success: true, message: 'Logout erfolgreich' }); }); // GET /api/auth/me - Aktueller User router.get('/me', async (req, res) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Kein Token vorhanden' }); } const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, JWT_SECRET); const result = await pool.query( 'SELECT id, username, role, created_at, last_login FROM users WHERE id = $1 AND is_active = true', [decoded.userId] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'User nicht gefunden' }); } res.json({ success: true, user: result.rows[0] }); } catch (error) { console.error('Auth/me Error:', error); if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Token ungültig oder abgelaufen' }); } res.status(500).json({ error: 'Server-Fehler' }); } }); // POST /api/auth/users - Neuen User anlegen (nur Admin) router.post('/users', async (req, res) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Kein Token vorhanden' }); } const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, JWT_SECRET); if (decoded.role !== 'admin') { return res.status(403).json({ error: 'Nur Admins können User anlegen' }); } const { username, password, role = 'user' } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Username und Passwort erforderlich' }); } if (!['admin', 'user'].includes(role)) { return res.status(400).json({ error: 'Ungültige Rolle' }); } const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS); const result = await pool.query( `INSERT INTO users (username, password_hash, role, is_active) VALUES ($1, $2, $3, true) RETURNING id, username, role, created_at`, [username, passwordHash, role] ); res.status(201).json({ success: true, message: 'User erstellt', user: result.rows[0] }); } catch (error) { console.error('Create User Error:', error); if (error.code === '23505') { return res.status(400).json({ error: 'Username bereits vergeben' }); } if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Token ungültig oder abgelaufen' }); } res.status(500).json({ error: 'Server-Fehler' }); } }); // GET /api/auth/users - Alle User auflisten (nur Admin) router.get('/users', async (req, res) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Kein Token vorhanden' }); } const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, JWT_SECRET); if (decoded.role !== 'admin') { return res.status(403).json({ error: 'Nur Admins können User-Liste sehen' }); } const result = await pool.query( 'SELECT id, username, role, is_active, created_at, last_login FROM users ORDER BY created_at DESC' ); res.json({ success: true, users: result.rows }); } catch (error) { console.error('List Users Error:', error); if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Token ungültig oder abgelaufen' }); } res.status(500).json({ error: 'Server-Fehler' }); } }); // DELETE /api/auth/users/:id - User löschen (nur Admin) router.delete('/users/:id', async (req, res) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Kein Token vorhanden' }); } const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, JWT_SECRET); if (decoded.role !== 'admin') { return res.status(403).json({ error: 'Nur Admins können User löschen' }); } // Cannot delete yourself if (req.params.id === decoded.userId) { return res.status(400).json({ error: 'Kann eigenen Account nicht löschen' }); } await pool.query('DELETE FROM users WHERE id = $1', [req.params.id]); res.json({ success: true, message: 'User gelöscht' }); } catch (error) { console.error('Delete User Error:', error); if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Token ungültig oder abgelaufen' }); } res.status(500).json({ error: 'Server-Fehler' }); } }); // PUT /api/auth/users/:id/password - Passwort zurücksetzen (nur Admin) router.put('/users/:id/password', async (req, res) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Kein Token vorhanden' }); } const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, JWT_SECRET); if (decoded.role !== 'admin') { return res.status(403).json({ error: 'Nur Admins können Passwörter zurücksetzen' }); } const { newPassword } = req.body; if (!newPassword || newPassword.length < 6) { return res.status(400).json({ error: 'Passwort muss mindestens 6 Zeichen lang sein' }); } const passwordHash = await bcrypt.hash(newPassword, BCRYPT_ROUNDS); await pool.query('UPDATE users SET password_hash = $1 WHERE id = $2', [passwordHash, req.params.id]); res.json({ success: true, message: 'Passwort zurückgesetzt' }); } catch (error) { console.error('Reset Password Error:', error); if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Token ungültig oder abgelaufen' }); } res.status(500).json({ error: 'Server-Fehler' }); } }); module.exports = router;