const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const loginAttempts = new Map(); const RATE_LIMIT_WINDOW = 15 * 60 * 1000; const MAX_LOGIN_ATTEMPTS = 5; const CLEANUP_INTERVAL = 15 * 60 * 1000; function checkRateLimit(ip) { const now = Date.now(); const record = loginAttempts.get(ip); if (!record) { loginAttempts.set(ip, { count: 1, firstAttempt: now }); return true; } if (now - record.firstAttempt > RATE_LIMIT_WINDOW) { loginAttempts.set(ip, { count: 1, firstAttempt: now }); return true; } if (record.count >= MAX_LOGIN_ATTEMPTS) { return false; } record.count++; return true; } function clearRateLimit(ip) { loginAttempts.delete(ip); } function cleanupLoginAttempts() { const now = Date.now(); for (const [ip, record] of loginAttempts.entries()) { if (now - record.firstAttempt > RATE_LIMIT_WINDOW) { loginAttempts.delete(ip); } } } setInterval(cleanupLoginAttempts, CLEANUP_INTERVAL); let db; let JWT_SECRET; function init(database, jwtSecret) { db = database; JWT_SECRET = jwtSecret; } function authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) return res.status(401).json({ error: 'Unauthorized' }); jwt.verify(token, JWT_SECRET, (err, user) => { if (err) return res.status(403).json({ error: 'Invalid or expired token' }); req.user = user; next(); }); } function requireAdmin(req, res, next) { if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admin access required' }); next(); } function login(req, res) { const clientIp = req.ip || req.connection.remoteAddress || 'unknown'; if (!checkRateLimit(clientIp)) { return res.status(429).json({ error: 'Слишком много попыток входа. Попробуйте через 15 минут.' }); } const { login, password } = req.body; if (!login || !password) return res.status(400).json({ error: 'Login and password required' }); db.get(`SELECT id, login, password_hash, full_name, email, role FROM users WHERE login = ?`, [login], (err, user) => { if (err) return res.status(500).json({ error: 'Database error' }); if (!user) return res.status(401).json({ error: 'Invalid credentials' }); const match = bcrypt.compareSync(password, user.password_hash); if (!match) return res.status(401).json({ error: 'Invalid credentials' }); clearRateLimit(clientIp); const token = jwt.sign({ id: user.id, login: user.login, role: user.role }, JWT_SECRET, { expiresIn: '24h' }); res.json({ token, user: { id: user.id, login: user.login, full_name: user.full_name, email: user.email, role: user.role } }); }); } function updateProfile(req, res) { const { full_name, email } = req.body; db.run(`UPDATE users SET full_name = COALESCE(?, full_name), email = COALESCE(?, email) WHERE id = ?`, [full_name || null, email || null, req.user.id], function(err) { if (err) return res.status(500).json({ error: 'Database error' }); db.get(`SELECT id, login, full_name, email, role FROM users WHERE id = ?`, [req.user.id], (err, row) => { if (err) return res.status(500).json({ error: 'Database error' }); res.json({ message: 'Profile updated', user: row }); }); }); } function changePassword(req, res) { const { current_password, new_password } = req.body; if (!current_password || !new_password) return res.status(400).json({ error: 'Current and new password required' }); if (new_password.length < 6) return res.status(400).json({ error: 'Password must be at least 6 characters' }); db.get(`SELECT password_hash FROM users WHERE id = ?`, [req.user.id], (err, row) => { if (err) return res.status(500).json({ error: 'Database error' }); if (!row) return res.status(404).json({ error: 'User not found' }); const match = bcrypt.compareSync(current_password, row.password_hash); if (!match) return res.status(401).json({ error: 'Current password is incorrect' }); const hash = bcrypt.hashSync(new_password, 10); db.run(`UPDATE users SET password_hash = ? WHERE id = ?`, [hash, req.user.id], (err) => { if (err) return res.status(500).json({ error: 'Database error' }); res.json({ message: 'Password changed successfully' }); }); }); } function setupRoutes(app) { app.post('/api/auth/login', login); app.put('/api/auth/me', authenticateToken, updateProfile); app.post('/api/auth/change-password', authenticateToken, changePassword); } module.exports = { init, setupRoutes, authenticateToken, requireAdmin, login };