316 lines
9.8 KiB
Plaintext
316 lines
9.8 KiB
Plaintext
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;
|