'use strict'; const express = require('express'); const ldapClient = require('./ldapClient'); const sessions = require('./sessions'); const zupAccess = require('./zupAccess'); const zupLog = require('./zupLog'); const audit = require('./auditLog'); const rateLimit = require('./rateLimit'); const router = express.Router(); function getToken(req) { const header = req.headers.authorization || ''; if (header.startsWith('Bearer ')) return header.slice(7); return null; } function errorResponse(res, err) { const status = err.status || 500; res.status(status).json({ success: false, error: err.message || 'Внутренняя ошибка сервера' }); } function requireZupAuth(req, res, next) { const session = sessions.getSession(getToken(req)); if (!session) { return res.status(401).json({ success: false, error: 'Требуется авторизация' }); } const username = (session.user && session.user.username) || ''; if (!zupAccess.isAdminLogin(username) && !zupAccess.has(username)) { return res.status(403).json({ success: false, error: 'Нет доступа к ЗУП' }); } req.session = session; next(); } function requireAdmin(req, res, next) { const session = sessions.getSession(getToken(req)); if (!session) { return res.status(401).json({ success: false, error: 'Требуется авторизация' }); } if (!zupAccess.isAdminLogin((session.user && session.user.username) || '')) { return res.status(403).json({ success: false, error: 'Требуются права администратора' }); } req.session = session; next(); } router.post('/login', async (req, res) => { const { username, password } = req.body || {}; const ip = rateLimit.getClientIp(req); if (rateLimit.isBlocked(ip)) { return res.status(429).json({ success: false, error: 'Слишком много попыток, попробуйте через час' }); } if (!username || !password) { return res.status(400).json({ success: false, error: 'Логин и пароль обязательны' }); } try { const info = await ldapClient.authenticate(username, password, 'basic'); rateLimit.reset(ip); const isAdmin = zupAccess.isAdminLogin(info.username); if (!isAdmin && !zupAccess.has(info.username)) { audit.logAdminEntry({ ip, username: info.username, success: false, reason: 'нет доступа к ЗУП' }); return res.status(403).json({ success: false, error: 'Нет доступа к ЗУП — обратитесь к администратору' }); } audit.logAdminEntry({ ip, username: info.username, success: true, reason: 'вход в ЗУП' }); const token = sessions.createSession({ username: info.username, full_name: info.full_name }); res.json({ success: true, token, isAdmin, user: { username: info.username, full_name: info.full_name } }); } catch (err) { if (err.status === 401) { rateLimit.recordFailure(ip); audit.logAdminEntry({ ip, username, success: false, reason: err.message || 'неверный пароль' }); } errorResponse(res, err); } }); router.post('/logout', (req, res) => { sessions.destroySession(getToken(req)); res.json({ success: true }); }); router.get('/me', requireZupAuth, (req, res) => { res.json({ success: true, isAdmin: zupAccess.isAdminLogin((req.session.user && req.session.user.username) || ''), user: req.session.user }); }); router.get('/users', requireZupAuth, async (req, res) => { try { const users = await ldapClient.listUsers(); res.json({ success: true, users: users.map((u) => ({ login: u.login, fio: u.full_name || '', phone: u.phone || '', email: u.email || '', position: u.position || '', disabled: !!u.disabled, isAdmin: zupAccess.isAdminLogin(u.login) })) }); } catch (err) { errorResponse(res, err); } }); router.post('/users/:login/block', requireZupAuth, async (req, res) => { const login = String(req.params.login || '').trim(); const actor = (req.session.user && req.session.user.username) || ''; try { if (!login) return res.status(400).json({ success: false, error: 'Логин не указан' }); if (String(login).toLowerCase() === String(actor).toLowerCase()) { return res.status(400).json({ success: false, error: 'Нельзя заблокировать самого себя' }); } if (zupAccess.isAdminLogin(login)) { return res.status(400).json({ success: false, error: 'Нельзя блокировать учётную запись администратора' }); } const user = await ldapClient.getUser(login); const result = await ldapClient.setUserEnabled(login, false); zupLog.add({ actor, target: login, targetFio: user.full_name || '', action: 'block' }); audit.logAdminEntry({ ip: req.ip, username: actor, success: true, reason: `zup block: ${login}` }); res.json(result); } catch (err) { errorResponse(res, err); } }); router.post('/users/:login/unblock', requireZupAuth, async (req, res) => { const login = String(req.params.login || '').trim(); const actor = (req.session.user && req.session.user.username) || ''; try { if (!login) return res.status(400).json({ success: false, error: 'Логин не указан' }); if (String(login).toLowerCase() === String(actor).toLowerCase()) { return res.status(400).json({ success: false, error: 'Нельзя разблокировать самого себя' }); } if (zupAccess.isAdminLogin(login)) { return res.status(400).json({ success: false, error: 'Нельзя разблокировать учётную запись администратора' }); } const user = await ldapClient.getUser(login); const result = await ldapClient.setUserEnabled(login, true); zupLog.add({ actor, target: login, targetFio: user.full_name || '', action: 'unblock' }); audit.logAdminEntry({ ip: req.ip, username: actor, success: true, reason: `zup unblock: ${login}` }); res.json(result); } catch (err) { errorResponse(res, err); } }); router.get('/users/:login/history', requireZupAuth, (req, res) => { res.json({ success: true, entries: zupLog.forTarget(req.params.login) }); }); // ================= Управление доступом (только администратор) ================= router.get('/access', requireAdmin, (req, res) => { res.json({ success: true, items: zupAccess.list() }); }); router.post('/access', requireAdmin, async (req, res) => { const { login } = req.body || {}; const actor = (req.session.user && req.session.user.username) || ''; try { const item = await zupAccess.add(login, actor); res.json({ success: true, item }); } catch (err) { errorResponse(res, err); } }); router.delete('/access/:login', requireAdmin, (req, res) => { zupAccess.remove(req.params.login); res.json({ success: true }); }); module.exports = router;