require('dotenv').config(); const express = require('express'); const crypto = require('crypto'); const path = require('path'); const session = require('express-session'); const SQLiteStore = require('connect-sqlite3')(session); const { initDb, getDb } = require('./src/db'); const authService = require('./src/auth'); const app = express(); const PORT = process.env.PORT || 3457; app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname, 'public'))); app.use(session({ store: new SQLiteStore({ db: 'sessions.db', dir: path.join(__dirname, 'data'), table: 'sessions' }), secret: process.env.SESSION_SECRET || 'fallback_secret_change_in_production', resave: false, saveUninitialized: false, cookie: { secure: false, maxAge: 7 * 24 * 60 * 60 * 1000, httpOnly: true, sameSite: 'lax' } })); app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); app.use((req, res, next) => { res.locals.csrfToken = req.session ? req.session.csrfToken || '' : ''; res.locals.user = req.session ? req.session.user || null : null; next(); }); app.use((req, res, next) => { const mutating = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method); const isLoginPath = req.path === '/api/login'; if (!isLoginPath && req.session && req.session.user && !req.session.csrfToken) { req.session.csrfToken = crypto.randomBytes(32).toString('hex'); } if (mutating) { if (isLoginPath) return next(); if (!req.session || !req.session.user) { return res.status(401).json({ error: 'Требуется аутентификация' }); } const clientToken = req.headers['x-csrf-token'] || req.headers['csrf-token']; if (!req.session.csrfToken) { req.session.csrfToken = crypto.randomBytes(32).toString('hex'); } if (!clientToken || clientToken !== req.session.csrfToken) { return res.status(403).json({ error: 'CSRF token missing or invalid' }); } } next(); }); const requireAuth = (req, res, next) => { if (!req.session.user) { if (req.headers.accept && req.headers.accept.includes('application/json')) { return res.status(401).json({ error: 'Требуется аутентификация' }); } return res.redirect('/login'); } next(); }; app.post('/api/login', async (req, res) => { const { login, password } = req.body; if (!login || !password) { return res.status(400).json({ error: 'Логин и пароль обязательны' }); } try { const user = await authService.authenticate(login, password); if (user) { const sessionUser = { id: user.id, login: user.login, name: user.name, email: user.email, role: user.role, auth_type: user.auth_type, groups: user.groups ? (typeof user.groups === 'string' ? JSON.parse(user.groups) : user.groups) : [] }; if (sessionUser.login === process.env.USER_1_LOGIN) { sessionUser.role = 'admin'; } req.session.user = sessionUser; req.session.csrfToken = crypto.randomBytes(32).toString('hex'); req.session.save((err) => { if (err) return res.status(500).json({ error: 'Ошибка сохранения сессии' }); console.log(`Вход: ${user.name} (${user.login})`); res.json({ success: true, user: sessionUser, csrfToken: req.session.csrfToken }); }); } else { res.status(401).json({ error: 'Неверный логин или пароль' }); } } catch (err) { console.error('Ошибка аутентификации:', err.message); res.status(500).json({ error: 'Ошибка сервера при авторизации' }); } }); app.post('/api/logout', (req, res) => { req.session.destroy((err) => { if (err) return res.status(500).json({ error: 'Ошибка при выходе' }); res.json({ success: true }); }); }); app.get('/api/user', (req, res) => { if (req.session.user) { if (!req.session.csrfToken) { req.session.csrfToken = crypto.randomBytes(32).toString('hex'); } return res.json({ success: true, user: req.session.user, csrfToken: req.session.csrfToken }); } res.status(401).json({ error: 'Не авторизован' }); }); app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); async function start() { await initDb(); authService.setDatabase(getDb()); const apiRoutes = require('./src/routes/api'); const pageRoutes = require('./src/routes/pages'); app.use('/api', apiRoutes); app.use('/', pageRoutes); app.listen(PORT, () => { console.log(`Сервер запущен: http://localhost:${PORT}`); }); } start().catch(err => { console.error('Ошибка запуска:', err); process.exit(1); });