215 lines
5.8 KiB
JavaScript
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;
|