Files
ldap-auth-service/lib/adminApi.js

288 lines
10 KiB
JavaScript

'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();
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;