API/Server/routes/auth.js
2026-01-24 16:47:19 -04:00

215 lines
5.8 KiB
JavaScript

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;