'use strict'; const express = require('express'); const ldapClient = require('./ldapClient'); const config = require('./config'); const sessions = require('./sessions'); const adminSettings = require('./adminSettings'); const requests = require('./requests'); const mailer = require('./mailer'); const router = express.Router(); function errorResponse(res, err) { const status = err.status || 500; res.status(status).json({ success: false, error: err.message || 'Внутренняя ошибка сервера' }); } function getToken(req) { const header = req.headers.authorization || ''; if (header.startsWith('Bearer ')) return header.slice(7); return null; } function requireAuth(req, res, next) { const session = sessions.getSession(getToken(req)); if (!session) { return res.status(401).json({ success: false, error: 'Требуется авторизация' }); } req.session = session; next(); } router.post('/login', async (req, res) => { const { username, password } = req.body || {}; if (!username || !password) { return res.status(400).json({ success: false, error: 'Логин и пароль обязательны' }); } if (!config.adminLogin || !config.adminPassword) { return res.status(500).json({ success: false, error: 'Учётные данные администратора не заданы в .env (ADMIN_LOGIN/ADMIN_PASSWORD)' }); } const adminAliases = new Set( ldapClient.buildBindNames(config.adminLogin).map((n) => n.toLowerCase()) ); adminAliases.add(config.adminLogin.split('@')[0].split('\\').pop().toLowerCase()); if (!adminAliases.has(String(username).toLowerCase())) { return res.status(401).json({ success: false, error: 'Доступ разрешён только администратору' }); } try { const info = await ldapClient.authenticate(username, password, 'basic'); const token = sessions.createSession({ username: info.username, full_name: info.full_name, groups: info.groups }); res.json({ success: true, token, user: { username: info.username, full_name: info.full_name } }); } catch (err) { return errorResponse(res, err); } }); router.post('/logout', (req, res) => { sessions.destroySession(getToken(req)); res.json({ success: true }); }); router.get('/me', requireAuth, (req, res) => { res.json({ success: true, user: req.session.user }); }); router.get('/ous', requireAuth, async (req, res) => { try { const ous = await ldapClient.listOUs(); res.json({ success: true, ous }); } catch (err) { errorResponse(res, err); } }); router.get('/groups', requireAuth, async (req, res) => { try { const groups = await ldapClient.listGroups(); res.json({ success: true, groups }); } catch (err) { errorResponse(res, err); } }); router.get('/settings', requireAuth, (req, res) => { res.json({ success: true, settings: adminSettings.getSettings() }); }); router.put('/settings', requireAuth, (req, res) => { const body = req.body || {}; const patch = {}; if (typeof body.ouDn === 'string') patch.ouDn = body.ouDn; if (Array.isArray(body.defaultGroups)) { patch.defaultGroups = body.defaultGroups.filter((g) => typeof g === 'string'); } if (Object.keys(patch).length === 0) { return res.status(400).json({ success: false, error: 'Нет данных для сохранения' }); } try { const settings = adminSettings.updateSettings(patch); res.json({ success: true, settings }); } catch (err) { errorResponse(res, err); } }); router.get('/users', requireAuth, async (req, res) => { try { const users = await ldapClient.listUsers(); res.json({ success: true, users }); } catch (err) { errorResponse(res, err); } }); router.get('/users/:login', requireAuth, async (req, res) => { try { const user = await ldapClient.getUser(req.params.login); res.json({ success: true, user }); } catch (err) { errorResponse(res, err); } }); router.post('/users', requireAuth, async (req, res) => { const { ouDn, fio, login, password, email, phone, groupCNs } = req.body || {}; if (!login || !fio) { return res.status(400).json({ success: false, error: 'ФИО и логин обязательны' }); } try { const result = await ldapClient.createUser({ ouDn, fio, login, password, email, phone, groupCNs }); res.json(result); } catch (err) { errorResponse(res, err); } }); router.put('/users/:login', requireAuth, async (req, res) => { const { fio, email, phone, password, groupCNs } = req.body || {}; try { const result = await ldapClient.updateUser(req.params.login, { fio, email, phone, password, groupCNs }); res.json(result); } catch (err) { errorResponse(res, err); } }); router.post('/users/:login/disable', requireAuth, async (req, res) => { try { const result = await ldapClient.setUserEnabled(req.params.login, false); res.json(result); } catch (err) { errorResponse(res, err); } }); router.post('/users/:login/enable', requireAuth, async (req, res) => { try { const result = await ldapClient.setUserEnabled(req.params.login, true); res.json(result); } catch (err) { errorResponse(res, err); } }); // ================= Registration requests ================= async function getTakenLogins(excludeId) { const users = await ldapClient.listUsers(); const taken = users.map((u) => u.login).filter(Boolean); const all = requests.listRequests(); for (const r of [...all.pending, ...all.history]) { if (r.login && r.id !== excludeId) taken.push(r.login); } return taken; } async function getUsedEmails(excludeId) { const users = await ldapClient.listUsers(); const emails = users.map((u) => u.email).filter(Boolean).map((e) => e.toLowerCase()); const all = requests.listRequests(); for (const r of [...all.pending, ...all.history]) { if (r.id === excludeId || r.phase === 'denied') continue; if (r.email) emails.push(r.email.toLowerCase()); } return new Set(emails); } router.get('/requests', requireAuth, (req, res) => { res.json({ success: true, ...requests.listRequests() }); }); router.get('/requests/:id', requireAuth, (req, res) => { const record = requests.getRequest(req.params.id); if (!record) return res.status(404).json({ success: false, error: 'Заявка не найдена' }); res.json({ success: true, request: record }); }); router.put('/requests/:id', requireAuth, async (req, res) => { const { surname, name, patronymic, email, password, groupCNs } = req.body || {}; try { if (email !== undefined) { const used = await getUsedEmails(req.params.id); if (used.has(String(email).toLowerCase())) { return res.status(409).json({ success: false, error: 'Эта почта уже используется' }); } } const taken = await getTakenLogins(req.params.id); const record = requests.updateRequest(req.params.id, { surname, name, patronymic, email, password, groupCNs }, taken); res.json({ success: true, request: record }); } catch (err) { errorResponse(res, err); } }); router.post('/requests/:id/approve', requireAuth, async (req, res) => { const record = requests.getRequest(req.params.id); if (!record) return res.status(404).json({ success: false, error: 'Заявка не найдена' }); if (record.phase !== 'pending') { return res.status(409).json({ success: false, error: 'Заявка уже обработана' }); } try { const settings = adminSettings.getSettings(); if (!settings.ouDn) { return res.status(400).json({ success: false, error: 'Не задано подразделение — откройте Настройки' }); } const groupCNs = record.groupsEdited ? record.groups : (settings.defaultGroups || []); await ldapClient.createUser({ ouDn: settings.ouDn || undefined, fio: record.fio, login: record.login, password: record.password, email: record.email, groupCNs }); let warning = null; try { await mailer.sendApproved(record.email, { login: record.login, password: record.password, fio: record.fio }); requests.markProcessed(record.id, { phase: 'approved', emailSent: true }); } catch (mailErr) { requests.markProcessed(record.id, { phase: 'approved', emailSent: false }); warning = `Пользователь создан, но письмо не отправлено: ${mailErr.message}`; } res.json({ success: true, login: record.login, warning }); } catch (err) { errorResponse(res, err); } }); router.post('/requests/:id/deny', requireAuth, async (req, res) => { const reason = String((req.body || {}).reason || '').trim(); if (!reason) return res.status(400).json({ success: false, error: 'Укажите причину отказа' }); const record = requests.getRequest(req.params.id); if (!record) return res.status(404).json({ success: false, error: 'Заявка не найдена' }); if (record.phase !== 'pending') { return res.status(409).json({ success: false, error: 'Заявка уже обработана' }); } let warning = null; try { await mailer.sendDenied(record.email, { fio: record.fio, reason }); requests.markProcessed(record.id, { phase: 'denied', reason, emailSent: true }); } catch (mailErr) { requests.markProcessed(record.id, { phase: 'denied', reason, emailSent: false }); warning = `Заявка отклонена, но письмо не отправлено: ${mailErr.message}`; } res.json({ success: true, warning }); }); router.post('/requests/:id/resend', requireAuth, async (req, res) => { const record = requests.getRequest(req.params.id); if (!record) return res.status(404).json({ success: false, error: 'Заявка не найдена' }); if (record.phase !== 'approved' && record.phase !== 'denied') { return res.status(409).json({ success: false, error: 'Письмо можно отправить только по обработанной заявке' }); } try { if (record.phase === 'approved') { await mailer.sendApproved(record.email, { login: record.login, password: record.password, fio: record.fio }); } else { await mailer.sendDenied(record.email, { fio: record.fio, reason: record.reason }); } requests.markProcessed(record.id, { emailSent: true }); res.json({ success: true }); } catch (err) { errorResponse(res, err); } }); module.exports = router;