197 lines
6.5 KiB
JavaScript
197 lines
6.5 KiB
JavaScript
import { Router } from 'express';
|
|
import bcrypt from 'bcryptjs';
|
|
import jwt from 'jsonwebtoken';
|
|
import crypto from 'crypto';
|
|
import {
|
|
getUserByUsername, getUserById, createUser, updateUserCredentials, deleteUser,
|
|
createInviteToken, getInviteToken, useInviteToken,
|
|
getInvitesByUser, getAllUsers, getInviteStats
|
|
} from '../db.js';
|
|
import { authMiddleware, adminMiddleware } from '../middleware.js';
|
|
import { trackEvent } from '../analytics-db.js';
|
|
|
|
const router = Router();
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'tiktok-rezepte-secret-key-change-in-production';
|
|
const JWT_EXPIRES_IN = '7d';
|
|
|
|
router.post('/register', async (req, res) => {
|
|
try {
|
|
const { username, password, inviteToken } = req.body;
|
|
|
|
if (!username || !password || !inviteToken) {
|
|
return res.status(400).json({ error: 'Benutzername, Passwort und Einladungscode erforderlich' });
|
|
}
|
|
|
|
if (username.length < 3) {
|
|
return res.status(400).json({ error: 'Benutzername muss mindestens 3 Zeichen lang sein' });
|
|
}
|
|
|
|
if (password.length < 6) {
|
|
return res.status(400).json({ error: 'Passwort muss mindestens 6 Zeichen lang sein' });
|
|
}
|
|
|
|
const invite = getInviteToken(inviteToken);
|
|
if (!invite) {
|
|
return res.status(400).json({ error: 'Ungültiger Einladungscode' });
|
|
}
|
|
if (invite.used_by) {
|
|
return res.status(400).json({ error: 'Einladungscode wurde bereits verwendet' });
|
|
}
|
|
|
|
const existing = getUserByUsername(username);
|
|
if (existing) {
|
|
return res.status(409).json({ error: 'Benutzername ist bereits vergeben' });
|
|
}
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
const user = createUser(username, hashedPassword);
|
|
useInviteToken(inviteToken, user.id);
|
|
|
|
trackEvent('user_register', user.id, { username: user.username });
|
|
|
|
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
|
|
|
res.status(201).json({ user, token });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Registrierung fehlgeschlagen' });
|
|
}
|
|
});
|
|
|
|
router.post('/login', async (req, res) => {
|
|
try {
|
|
const { username, password } = req.body;
|
|
|
|
if (!username || !password) {
|
|
return res.status(400).json({ error: 'Benutzername und Passwort erforderlich' });
|
|
}
|
|
|
|
const user = getUserByUsername(username);
|
|
if (!user) {
|
|
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
|
}
|
|
|
|
const valid = await bcrypt.compare(password, user.password);
|
|
if (!valid) {
|
|
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
|
}
|
|
|
|
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
|
|
|
trackEvent('user_login', user.id, { username: user.username });
|
|
|
|
res.json({
|
|
user: { id: user.id, username: user.username, is_admin: !!user.is_admin, created_at: user.created_at },
|
|
token,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Anmeldung fehlgeschlagen' });
|
|
}
|
|
});
|
|
|
|
router.get('/me', (req, res) => {
|
|
const authHeader = req.headers.authorization;
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
|
}
|
|
try {
|
|
const token = authHeader.split(' ')[1];
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
const user = getUserByUsername(decoded.username);
|
|
if (!user) return res.status(401).json({ error: 'Benutzer nicht gefunden' });
|
|
res.json({ id: user.id, username: user.username, is_admin: !!user.is_admin, created_at: user.created_at });
|
|
} catch {
|
|
res.status(401).json({ error: 'Ungültiges Token' });
|
|
}
|
|
});
|
|
|
|
router.post('/invite', authMiddleware, adminMiddleware, (req, res) => {
|
|
try {
|
|
const { count = 1 } = req.body;
|
|
const amount = Math.min(Math.max(parseInt(count) || 1, 1), 20);
|
|
const tokens = [];
|
|
|
|
for (let i = 0; i < amount; i++) {
|
|
const token = crypto.randomBytes(16).toString('hex');
|
|
createInviteToken(token, req.user.id);
|
|
tokens.push(token);
|
|
}
|
|
|
|
res.json({ tokens });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Einladung fehlgeschlagen' });
|
|
}
|
|
});
|
|
|
|
router.get('/invites', authMiddleware, adminMiddleware, (req, res) => {
|
|
try {
|
|
const invites = getInvitesByUser(req.user.id);
|
|
const stats = getInviteStats();
|
|
res.json({ invites, stats });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Fehler beim Laden der Einladungen' });
|
|
}
|
|
});
|
|
|
|
router.get('/users', authMiddleware, adminMiddleware, (req, res) => {
|
|
try {
|
|
const users = getAllUsers();
|
|
res.json(users);
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Fehler beim Laden der Benutzer' });
|
|
}
|
|
});
|
|
|
|
router.delete('/users/:id', authMiddleware, adminMiddleware, (req, res) => {
|
|
try {
|
|
const userId = Number(req.params.id);
|
|
if (userId === req.user.id) {
|
|
return res.status(400).json({ error: 'Du kannst dich nicht selbst löschen' });
|
|
}
|
|
const user = getUserById(userId);
|
|
if (!user) return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
|
deleteUser(userId);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Fehler beim Löschen' });
|
|
}
|
|
});
|
|
|
|
router.put('/credentials', authMiddleware, async (req, res) => {
|
|
try {
|
|
const { currentPassword, newUsername, newPassword } = req.body;
|
|
|
|
const user = getUserByUsername(req.user.username);
|
|
if (!user) return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
|
|
|
if (currentPassword) {
|
|
const valid = await bcrypt.compare(currentPassword, user.password);
|
|
if (!valid) return res.status(401).json({ error: 'Aktuelles Passwort ist falsch' });
|
|
}
|
|
|
|
if (newUsername && newUsername !== user.username) {
|
|
const exists = getUserByUsername(newUsername);
|
|
if (exists) return res.status(409).json({ error: 'Benutzername ist bereits vergeben' });
|
|
}
|
|
|
|
const updates = {};
|
|
if (newUsername) updates.username = newUsername;
|
|
if (newPassword) {
|
|
if (newPassword.length < 6) return res.status(400).json({ error: 'Neues Passwort muss mindestens 6 Zeichen lang sein' });
|
|
updates.password = await bcrypt.hash(newPassword, 10);
|
|
}
|
|
|
|
if (Object.keys(updates).length === 0) {
|
|
return res.status(400).json({ error: 'Keine Änderungen angegeben' });
|
|
}
|
|
|
|
const updated = updateUserCredentials(user.id, updates);
|
|
const token = jwt.sign({ id: updated.id, username: updated.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
|
|
|
res.json({ user: updated, token });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Fehler beim Aktualisieren' });
|
|
}
|
|
});
|
|
|
|
export default router;
|