const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const Joi = require('joi'); const { RateLimiterMemory } = require('rate-limiter-flexible'); const Player = require('../models/Player'); const logger = require('../utils/logger'); const router = express.Router(); // Rate limiting for auth routes const authLimiter = new RateLimiterMemory({ keyGenerator: (req) => req.ip, points: 5, // Number of requests duration: 900, // Per 15 minutes (900 seconds) blockDuration: 900, // Block for 15 minutes message: 'Too many authentication attempts, please try again later.' }); // Validation schemas const registerSchema = Joi.object({ username: Joi.string().min(3).max(30).required(), email: Joi.string().email().required(), password: Joi.string().min(6).required() }); const loginSchema = Joi.object({ email: Joi.string().email().required(), password: Joi.string().required() }); // Register route router.post('/register', async (req, res) => { try { // Rate limiting check const resLimiter = await authLimiter.consume(req.ip); if (!resLimiter.remainingPoints) { return res.status(429).json({ error: 'Too many authentication attempts, please try again later.' }); } const { error } = registerSchema.validate(req.body); if (error) { return res.status(400).json({ error: error.details[0].message }); } const { username, email, password } = req.body; // Check if user already exists const existingUser = await Player.findOne({ $or: [{ email }, { username }] }); if (existingUser) { return res.status(400).json({ error: 'User with this email or username already exists' }); } // Hash password const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt); // Create new player const player = new Player({ userId: `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, username, email, password: hashedPassword, createdAt: new Date(), lastLogin: new Date() }); await player.save(); // Create JWT token const token = jwt.sign( { userId: player.userId, email: player.email }, process.env.JWT_SECRET || 'fallback_secret', { expiresIn: '24h' } ); logger.info(`New user registered: ${email}`); res.status(201).json({ message: 'User registered successfully', token, user: { userId: player.userId, username: player.username, email: player.email, stats: player.stats } }); } catch (error) { logger.error('Registration error:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Login route router.post('/login', async (req, res) => { try { // Rate limiting check const resLimiter = await authLimiter.consume(req.ip); if (!resLimiter.remainingPoints) { return res.status(429).json({ error: 'Too many authentication attempts, please try again later.' }); } const { error } = loginSchema.validate(req.body); if (error) { return res.status(400).json({ error: error.details[0].message }); } const { email, password } = req.body; // Find user const player = await Player.findOne({ email }).select('+password'); if (!player) { return res.status(401).json({ error: 'Invalid credentials' }); } // Check if password exists (for backward compatibility with existing users) if (!player.password) { logger.error('Player password field is missing for user:', email); return res.status(401).json({ error: 'Account migration required. Please re-register your account.', requiresMigration: true }); } // Check password const isPasswordValid = await bcrypt.compare(password, player.password); if (!isPasswordValid) { return res.status(401).json({ error: 'Invalid credentials' }); } // Update last login player.stats.lastLogin = new Date(); await player.save(); // Create JWT token const token = jwt.sign( { userId: player.userId, email: player.email }, process.env.JWT_SECRET || 'fallback_secret', { expiresIn: '24h' } ); logger.info(`User logged in: ${email}`); res.json({ message: 'Login successful', token, user: { userId: player.userId, username: player.username, email: player.email, stats: player.stats, info: player.info } }); } catch (error) { logger.error('Login error:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Verify token route router.get('/verify', async (req, res) => { try { const token = req.header('Authorization')?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ error: 'No token provided' }); } const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret'); const player = await Player.findOne({ userId: decoded.userId }); if (!player) { return res.status(401).json({ error: 'Invalid token' }); } res.json({ valid: true, user: { userId: player.userId, username: player.username, email: player.email, stats: player.stats, info: player.info } }); } catch (error) { logger.error('Token verification error:', error); res.status(401).json({ error: 'Invalid token' }); } }); // Logout route router.post('/logout', async (req, res) => { try { // In a real implementation, you might want to blacklist the token // For now, we'll just return success res.json({ message: 'Logout successful' }); } catch (error) { logger.error('Logout error:', error); res.status(500).json({ error: 'Internal server error' }); } }); module.exports = router;