182 lines
6.0 KiB
JavaScript
182 lines
6.0 KiB
JavaScript
// 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;
|