// routes/authRoutes.js // Handles user registration, login, logout, and status check const express = require('express'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const db = require('../db'); // Import database query function require('dotenv').config(); const router = express.Router(); const JWT_SECRET = process.env.JWT_SECRET; const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '1h'; const SALT_ROUNDS = 10; // Cost factor for bcrypt hashing // Simple rate limiting for login attempts const loginAttempts = {}; const MAX_ATTEMPTS = 5; const LOCKOUT_TIME = 15 * 60 * 1000; // 15 minutes // Password validation function const isPasswordValid = (password) => { // At least 8 characters, containing a number and a letter return password.length >= 8 && /\d/.test(password) && /[a-zA-Z]/.test(password); }; // POST /api/auth/register - User Registration router.post('/register', async (req, res) => { const { username, password } = req.body; // Enhanced validation if (!username || !password) { return res.status(400).json({ message: 'Benutzername und Passwort sind erforderlich.' }); } if (username.length < 3 || username.length > 30) { return res.status(400).json({ message: 'Benutzername muss zwischen 3 und 30 Zeichen lang sein.' }); } if (!isPasswordValid(password)) { return res.status(400).json({ message: 'Passwort muss mindestens 8 Zeichen lang sein und mindestens eine Zahl und einen Buchstaben enthalten.' }); } try { // Check if user already exists const userCheck = await db.query('SELECT * FROM users WHERE username = $1', [username]); if (userCheck.rows.length > 0) { return res.status(409).json({ message: 'Benutzername bereits vergeben.' }); // 409 Conflict } // Hash the password const passwordHash = await bcrypt.hash(password, SALT_ROUNDS); // Insert new user into the database const newUser = await db.query( 'INSERT INTO users (username, password_hash) VALUES ($1, $2) RETURNING id, username', [username, passwordHash] ); console.log(`User registered: ${newUser.rows[0].username}`); // Respond with success message (or automatically log them in) // For simplicity, we just confirm registration here. User needs to login separately. res.status(201).json({ message: 'Registrierung erfolgreich. Bitte einloggen.' }); } catch (error) { console.error('Registration Error:', error); res.status(500).json({ message: 'Serverfehler bei der Registrierung.' }); } }); // POST /api/auth/login - User Login router.post('/login', async (req, res) => { const { username, password } = req.body; const ip = req.ip; // Check for login attempts rate limiting if (loginAttempts[ip] && loginAttempts[ip].count >= MAX_ATTEMPTS) { const timeElapsed = Date.now() - loginAttempts[ip].timestamp; if (timeElapsed < LOCKOUT_TIME) { const minutesLeft = Math.ceil((LOCKOUT_TIME - timeElapsed) / 60000); return res.status(429).json({ message: `Zu viele Anmeldeversuche. Bitte versuchen Sie es in ${minutesLeft} Minuten erneut.` }); } else { // Reset attempts after lockout period delete loginAttempts[ip]; } } if (!username || !password) { return res.status(400).json({ message: 'Benutzername und Passwort sind erforderlich.' }); } try { // Find user by username const result = await db.query('SELECT * FROM users WHERE username = $1', [username]); const user = result.rows[0]; // Check if user exists and password is correct if (!user || !(await bcrypt.compare(password, user.password_hash))) { // Track failed login attempts if (!loginAttempts[ip]) { loginAttempts[ip] = { count: 0, timestamp: Date.now() }; } loginAttempts[ip].count++; loginAttempts[ip].timestamp = Date.now(); return res.status(401).json({ message: 'Ungültiger Benutzername oder Passwort.' }); // 401 Unauthorized } // Reset login attempts on successful login delete loginAttempts[ip]; // User authenticated successfully, create JWT payload const payload = { id: user.id, username: user.username }; // Sign the JWT with a 24h expiration for testing purposes const token = jwt.sign(payload, JWT_SECRET, { expiresIn: '24h' }); // Set JWT as an HTTP-Only cookie with correct settings res.cookie('token', token, { httpOnly: true, secure: false, // Set to false for local development maxAge: 24 * 60 * 60 * 1000, // 24 hours in milliseconds sameSite: 'Lax' }); console.log(`User logged in: ${user.username}`); // Send success response res.status(200).json({ message: 'Login erfolgreich.', username: user.username }); } catch (error) { console.error('Login Error:', error); res.status(500).json({ message: 'Serverfehler beim Login.' }); } }); // POST /api/auth/logout - User Logout router.post('/logout', (req, res) => { // Clear the authentication cookie res.clearCookie('token', { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'Lax' }); console.log('User logged out'); res.status(200).json({ message: 'Logout erfolgreich.' }); }); // --- NEUE Route --- // GET /api/auth/status - Check login status without causing redirect/error router.get('/status', (req, res) => { const token = req.cookies.token; if (!token) { // No token cookie found return res.json({ loggedIn: false }); } // Verify the existing token jwt.verify(token, JWT_SECRET, (err, user) => { if (err) { // Token is invalid (e.g., expired, tampered) console.log('Status check: Invalid token found, clearing cookie.'); res.clearCookie('token'); // Clear the invalid cookie return res.json({ loggedIn: false }); } // Token is valid // Send back loggedIn status and username return res.json({ loggedIn: true, username: user.username }); }); }); module.exports = router;