Files
hotell777_260507/modules/auth/index.js
2026-05-11 01:03:56 +05:00

126 lines
4.8 KiB
JavaScript

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 };