feat: adminMode — чек-бокс переключения админского интерфейса
Шаг 1: /toggle-admin роут в ui/routes/auth.js - переключает req.session.adminMode - canAdmin = isAdmin || clientId === WZ01112 (временно) Шаг 2: resolveUser в ui/index.js - req.user.adminMode из req.session.adminMode - isAdmin = IAM.isAdmin || clientId === WZ01112 (временно) Шаг 3: adminMode вместо isAdmin в интерфейсе - ui/routes/entries.js: adminMode решает админский view - ui/routes/admin.js: requireAdmin проверяет adminMode Шаг 4: views/index.ejs - чек-бокс «Администратор» (виден только если isAdmin) - ссылки Аудит/Лимиты видны только при adminMode - селектор компаний — при adminMode
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ipwhitelist",
|
"name": "ipwhitelist",
|
||||||
"version": "0.5.37",
|
"version": "0.5.38",
|
||||||
"description": "IP WhiteList microservice for cloud provider",
|
"description": "IP WhiteList microservice for cloud provider",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+4
-1
@@ -58,6 +58,8 @@ function createUiRouter({ auth, MOCK_USERS, authLimiter }) {
|
|||||||
? sessionActive
|
? sessionActive
|
||||||
: (allClientIds[0] || rawClientId);
|
: (allClientIds[0] || rawClientId);
|
||||||
|
|
||||||
|
// 🔮 isAdmin через IAM, временно: || clientId === WZ01112
|
||||||
|
const iamAdmin = req.session && req.session.user && req.session.user.isAdmin;
|
||||||
req.user = {
|
req.user = {
|
||||||
clientId: rawClientId,
|
clientId: rawClientId,
|
||||||
allClientIds,
|
allClientIds,
|
||||||
@@ -65,7 +67,8 @@ function createUiRouter({ auth, MOCK_USERS, authLimiter }) {
|
|||||||
companyId: payload.company_id || payload.companyId || null,
|
companyId: payload.company_id || payload.companyId || null,
|
||||||
companyName: payload.company_name || payload.companyName || activeClientId,
|
companyName: payload.company_name || payload.companyName || activeClientId,
|
||||||
email: payload.email || activeClientId + '@unknown',
|
email: payload.email || activeClientId + '@unknown',
|
||||||
isAdmin: activeClientId === ADMIN_CLIENT_ID,
|
isAdmin: iamAdmin || activeClientId === ADMIN_CLIENT_ID,
|
||||||
|
adminMode: !!(req.session && req.session.adminMode),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
req.session.destroy(() =>
|
req.session.destroy(() =>
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ const { Router } = require('express');
|
|||||||
const api = require('../api-client');
|
const api = require('../api-client');
|
||||||
|
|
||||||
function requireAdmin(req, res, next) {
|
function requireAdmin(req, res, next) {
|
||||||
if (!req.user || !req.user.isAdmin) {
|
if (!req.user || !req.user.adminMode) {
|
||||||
return res.status(403).render('error', {
|
return res.status(403).render('error', {
|
||||||
status: 403,
|
status: 403,
|
||||||
message: 'Доступ запрещён — требуются права администратора.',
|
message: 'Доступ запрещён — требуются права администратора.',
|
||||||
|
|||||||
@@ -166,6 +166,20 @@ function createRouter({ auth, MOCK_USERS, authLimiter }) {
|
|||||||
res.redirect(returnTo || '/');
|
res.redirect(returnTo || '/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Переключатель режима администратора ──────────────────────────────
|
||||||
|
// 🔮 isAdmin будет из IAM, временно: isAdmin || clientId === WZ01112
|
||||||
|
router.post('/toggle-admin', (req, res) => {
|
||||||
|
const canAdmin = req.session.user && (
|
||||||
|
req.session.user.isAdmin ||
|
||||||
|
req.session.user.clientId === (process.env.ADMIN_CLIENT_ID || 'WZ01112')
|
||||||
|
);
|
||||||
|
if (!canAdmin) return res.status(403).send('Forbidden');
|
||||||
|
|
||||||
|
req.session.adminMode = !req.session.adminMode;
|
||||||
|
// Редирект туда же откуда пришли
|
||||||
|
res.redirect(req.get('Referer') || '/');
|
||||||
|
});
|
||||||
|
|
||||||
// GET /logout
|
// GET /logout
|
||||||
router.get('/logout', (req, res) => {
|
router.get('/logout', (req, res) => {
|
||||||
req.session.destroy(() => res.redirect('/login'));
|
req.session.destroy(() => res.redirect('/login'));
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ function createRouter() {
|
|||||||
try {
|
try {
|
||||||
let entries = [], limit = 15, selectedCompany = null, companies = [];
|
let entries = [], limit = 15, selectedCompany = null, companies = [];
|
||||||
|
|
||||||
if (req.user.isAdmin) {
|
if (req.user.adminMode) {
|
||||||
const cr = await api.get('/api/v1/companies', token);
|
const cr = await api.get('/api/v1/companies', token);
|
||||||
companies = cr.data.companies || [];
|
companies = cr.data.companies || [];
|
||||||
|
|
||||||
@@ -120,6 +120,7 @@ function createRouter() {
|
|||||||
used: entries.length,
|
used: entries.length,
|
||||||
user: req.user,
|
user: req.user,
|
||||||
isAdmin: req.user.isAdmin,
|
isAdmin: req.user.isAdmin,
|
||||||
|
adminMode: req.user.adminMode,
|
||||||
companies,
|
companies,
|
||||||
selectedCompany,
|
selectedCompany,
|
||||||
allClientIds: hasMultiple ? req.user.allClientIds : null,
|
allClientIds: hasMultiple ? req.user.allClientIds : null,
|
||||||
@@ -135,7 +136,7 @@ function createRouter() {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.render('index', {
|
res.render('index', {
|
||||||
entries: [], limit: 15, used: 0,
|
entries: [], limit: 15, used: 0,
|
||||||
user: req.user, isAdmin: req.user.isAdmin,
|
user: req.user, isAdmin: req.user.isAdmin, adminMode: req.user.adminMode,
|
||||||
companies: [], selectedCompany: null,
|
companies: [], selectedCompany: null,
|
||||||
allClientIds: null, activeClientId: null,
|
allClientIds: null, activeClientId: null,
|
||||||
error: 'Ошибка загрузки: ' + e.message,
|
error: 'Ошибка загрузки: ' + e.message,
|
||||||
|
|||||||
+12
-4
@@ -157,9 +157,17 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<span style="font-weight:500;color:var(--text);">Белые списки IP v<%= appVersion %></span>
|
<span style="font-weight:500;color:var(--text);">Белые списки IP v<%= appVersion %></span>
|
||||||
<!-- Кнопки правой части header: admin-ссылки + выход -->
|
<!-- Кнопки правой части header: admin-ссылки + чекбокс + выход -->
|
||||||
<span style="margin-left:auto;display:flex;gap:.5rem;align-items:center;">
|
<span style="margin-left:auto;display:flex;gap:.5rem;align-items:center;">
|
||||||
<% if (isAdmin) { %>
|
<% if (typeof isAdmin !== 'undefined' && isAdmin) { %>
|
||||||
|
<form method="POST" action="/toggle-admin" style="display:inline;font-size:.8rem;color:var(--muted);padding:.2rem .6rem;border:1px solid var(--border);border-radius:5px;background:#fff;">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" onchange="this.form.submit()" <%= adminMode ? 'checked' : '' %>>
|
||||||
|
Администратор
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
<% } %>
|
||||||
|
<% if (typeof adminMode !== 'undefined' && adminMode) { %>
|
||||||
<a href="/audit" style="font-size:.8rem;color:var(--muted);text-decoration:none;padding:.2rem .6rem;border:1px solid var(--border);border-radius:5px;background:#fff;">Аудит</a>
|
<a href="/audit" style="font-size:.8rem;color:var(--muted);text-decoration:none;padding:.2rem .6rem;border:1px solid var(--border);border-radius:5px;background:#fff;">Аудит</a>
|
||||||
<a href="/admin" style="font-size:.8rem;color:var(--muted);text-decoration:none;padding:.2rem .6rem;border:1px solid var(--border);border-radius:5px;background:#fff;">Лимиты</a>
|
<a href="/admin" style="font-size:.8rem;color:var(--muted);text-decoration:none;padding:.2rem .6rem;border:1px solid var(--border);border-radius:5px;background:#fff;">Лимиты</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
@@ -179,8 +187,8 @@
|
|||||||
const companyPk = selectedCompany ? selectedCompany.id : null;
|
const companyPk = selectedCompany ? selectedCompany.id : null;
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<!-- Переключатель компании — для администратора -->
|
<!-- Переключатель компании — для администратора в режиме admin -->
|
||||||
<% if (isAdmin && companies) { %>
|
<% if (adminMode && companies) { %>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body" style="padding:.75rem 1rem;">
|
<div class="card-body" style="padding:.75rem 1rem;">
|
||||||
<form method="GET" action="/" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap;">
|
<form method="GET" action="/" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap;">
|
||||||
|
|||||||
Reference in New Issue
Block a user