v
This commit is contained in:
151
server.js
Normal file
151
server.js
Normal file
@@ -0,0 +1,151 @@
|
||||
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 || 3000;
|
||||
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user