wa-dev-tools/routes/auth.js
treamz 5c7066b7d3 Security hardening: helmet, rate limits, session fixes, admin auth
Critical fixes:
- helmet middleware (X-Frame-Options, HSTS, X-Content-Type, CSP, etc)
- Remove /admin from PUBLIC_PREFIXES (double auth: session + admin role)
- Session cookie: httpOnly, sameSite=lax, 1-day expiry (was 30 days)
- Session regeneration on login (prevent session fixation)
- Blocked user check on login

Rate limiting:
- /auth/login: 10 req / 15 min (brute force protection)
- /auth/register: 10 req / 15 min (spam protection)
- /parse, /metadata, /text, /preview: 20 req / min (DDoS via server)

Input sanitization:
- Strip HTML tags from display_name (stored XSS prevention)
2026-03-22 00:39:38 +03:00

142 lines
4.4 KiB
JavaScript

const express = require('express');
const bcrypt = require('bcrypt');
const path = require('path');
const rateLimit = require('express-rate-limit');
const db = require('../lib/db');
const log = require('../lib/logger');
const router = express.Router();
const SALT_ROUNDS = 10;
// Rate limiting for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 min
max: 10, // 10 attempts per window
message: { error: 'Слишком много попыток. Попробуйте через 15 минут.' },
standardHeaders: true,
legacyHeaders: false,
});
// Pages
router.get('/login', (req, res) => {
if (req.session.user) return res.redirect('/dashboard');
res.sendFile(path.join(__dirname, '..', 'public', 'login.html'));
});
router.get('/register', (req, res) => {
if (req.session.user) return res.redirect('/dashboard');
res.sendFile(path.join(__dirname, '..', 'public', 'register.html'));
});
// Login API
router.post('/login', authLimiter, express.json(), express.urlencoded({ extended: true }), async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email и пароль обязательны' });
}
try {
const user = await db.findUserByEmail(email.toLowerCase().trim());
if (!user) {
return res.status(401).json({ error: 'Неверный email или пароль' });
}
if (user.is_blocked) {
return res.status(403).json({ error: 'Аккаунт заблокирован' });
}
const valid = await bcrypt.compare(password, user.password_hash);
if (!valid) {
return res.status(401).json({ error: 'Неверный email или пароль' });
}
await db.updateLastLogin(user.id);
// Session regeneration (prevent session fixation)
const returnTo = req.session.returnTo;
req.session.regenerate((err) => {
if (err) {
log.error('Session regenerate error', { error: err.message });
return res.status(500).json({ error: 'Ошибка сервера' });
}
req.session.user = {
id: user.id,
email: user.email,
name: user.display_name || user.email.split('@')[0],
role: user.role || 'user',
};
log.info(`Login: ${user.email}`);
res.json({ ok: true, redirect: returnTo || '/dashboard' });
});
} catch (err) {
log.error('Login error', { error: err.message });
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Sanitize display name (strip HTML tags)
function sanitizeName(name) {
if (!name) return '';
return name.replace(/<[^>]*>/g, '').trim().slice(0, 100);
}
// Register API
router.post('/register', authLimiter, express.json(), express.urlencoded({ extended: true }), async (req, res) => {
const { email, password, password2 } = req.body;
const name = sanitizeName(req.body.name);
if (!email || !password) {
return res.status(400).json({ error: 'Email и пароль обязательны' });
}
const emailClean = email.toLowerCase().trim();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailClean)) {
return res.status(400).json({ error: 'Некорректный email' });
}
if (password.length < 6) {
return res.status(400).json({ error: 'Пароль минимум 6 символов' });
}
if (password2 && password !== password2) {
return res.status(400).json({ error: 'Пароли не совпадают' });
}
try {
const existing = await db.findUserByEmail(emailClean);
if (existing) {
return res.status(409).json({ error: 'Этот email уже зарегистрирован' });
}
const hash = await bcrypt.hash(password, SALT_ROUNDS);
const displayName = (name || '').trim() || emailClean.split('@')[0];
const userId = await db.createUser(emailClean, hash, displayName);
req.session.user = { id: userId, email: emailClean, name: displayName };
log.info(`Register: ${emailClean}`);
res.json({ ok: true, redirect: '/dashboard' });
} catch (err) {
log.error('Register error', { error: err.message });
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Logout
router.get('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
// Current user info
router.get('/me', (req, res) => {
if (!req.session.user) return res.status(401).json({ error: 'Not authenticated' });
res.json(req.session.user);
});
module.exports = router;