From 13466ebd37504f4817bd7f7e7c9e1e9de6b63e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 11 Jun 2026 13:15:37 +0400 Subject: [PATCH] simplify: toggle adminMode, canAdminMode flag, remove getOrCreateCompany fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ui/index.js: canAdminMode = isAdmin AND WZ01112 (временно || WZ01112) - views/index.ejs: checkbox → toggle с JS fetch, условие canAdminMode - ui/routes/entries.js: убран getOrCreateCompany-фоллбек для админа - Админ без ?company= видит список компаний, без автозагрузки - isAdmin теперь чисто из IAM (без || WZ01112) --- docs/plan-simplify-2026-06-11.md | 85 ++++++++++++++++++++++++++++++++ package.json | 2 +- ui/index.js | 8 ++- ui/routes/entries.js | 32 ++++-------- views/index.ejs | 16 +++--- 5 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 docs/plan-simplify-2026-06-11.md diff --git a/docs/plan-simplify-2026-06-11.md b/docs/plan-simplify-2026-06-11.md new file mode 100644 index 0000000..0d70d0c --- /dev/null +++ b/docs/plan-simplify-2026-06-11.md @@ -0,0 +1,85 @@ +# План упрощения — 2026-06-11 + +## Проблема + +AI усложнил код `entries.js` фоллбеками `getOrCreateCompany`, запутанной логикой `ownCo`, +ненужными редиректами `?company=`. Пользователь указал что логика простая и не требует усложнений. + +## Исходные данные (что юзер знает после входа по Keycloak) + +1. **email** — из JWT (Keycloak claims) +2. **session.token** — access_token от Keycloak +3. **IAM API** (`fetchIamUser`) даёт: + - `email` + - `profiles[]` — список компаний юзера: `[{id, client_id, company_name, is_active_profile}, ...]` + - `activeProfile` — текущая активная компания + - `isAdmin` — флаг администратора + +## Базовые правила + +- **Компания** = общий список IP. Все юзеры компании видят одни и те же записи и могут CRUD. +- **email** пишется в `audit_log.created_by` — кто сделал CRUD. +- **client_id** (WZ01112, WZ03709…) — идентификатор компании в БД (`companies.client_id`). + +## Переключение компаний (мульти-компания) + +Если у юзера `profiles.length > 1` — он выбирает активную компанию через переключатель. +CRUD идёт в активную компанию. + +## Админ-режим (чекбокс → toggle) + +### Правило (финальное): + +> Toggle «Администратор» показывается **только** когда: +> `isAdmin === true` **И** `activeClientId === 'WZ01112'` + +### Временное исключение (тесты): + +Пока я (WZ01112 + ntazetdinov@nubes.ru) — тоже вижу toggle, даже без `isAdmin`. +Убрать когда перейдём на чистый IAM `isAdmin`. + +### Поведение toggle: + +- **ВЫКЛ** — обычный режим: юзер видит свою активную компанию, CRUD только в неё +- **ВКЛ** — admin-режим: выпадающий список ВСЕХ компаний, можно выбрать любую, ссылки «Аудит» и «Лимиты» + +### Реализация toggle: + +- Не `` с `onchange="this.form.submit()"` (старый вариант, не работал) +- Замена на **toggle-переключатель** (как ползунок ВКЛ/ВЫКЛ) +- `onchange` → `fetch('/toggle-admin', {method:'POST'})` → `location.reload()` +- Никакой формы, никакого CSRF — просто JS fetch и перезагрузка страницы + +## Что меняем (4 файла) + +### 1. `ui/index.js` — resolveUser middleware + +- Добавить `canAdminMode`: + ```js + canAdminMode: (iamAdmin && activeClientId === 'WZ01112') || activeClientId === 'WZ01112' // временно + ``` +- Убрать `isAdmin: iamAdmin || activeClientId === ADMIN_CLIENT_ID` — заменить на чистое `isAdmin: iamAdmin` +- `adminMode` остаётся из сессии + +### 2. `views/index.ejs` — header + +- Условие показа toggle: `<% if (canAdminMode) { %>` вместо `<% if (isAdmin) { %>` +- Заменить checkbox на toggle +- Ссылки «Аудит»/«Лимиты» — по `adminMode` (уже так) + +### 3. `ui/routes/entries.js` — GET / + +- **Убрать** `getOrCreateCompany`-фоллбек (строки 89-96) +- **Упростить** admin-ветку: если нет `?company=` — показать свою компанию +- Обычный пользователь: без изменений (уже работает) + +### 4. `ui/routes/auth.js` — POST /toggle-admin + +- Без изменений (уже работает — переключает `req.session.adminMode`) + +## Почему именно так + +1. **AND а не OR**: админ не должен видеть toggle в чужих компаниях — только в WZ01112 +2. **Toggle а не checkbox**: чекбокс = «прочитано/не прочитано», toggle = «режим ВКЛ/ВЫКЛ» — семантически верно +3. **JS fetch а не form submit**: быстрее, без моргания страницы, без CSRF-проблем +4. **Никаких фоллбеков**: компания либо есть в списке (из IAM), либо юзер к ней не относится — не надо «создавать если нет» diff --git a/package.json b/package.json index ed0f5ac..f797ade 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.40", + "version": "0.5.41", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/ui/index.js b/ui/index.js index 437e1a4..bc34c0a 100644 --- a/ui/index.js +++ b/ui/index.js @@ -58,8 +58,11 @@ function createUiRouter({ auth, MOCK_USERS, authLimiter }) { ? sessionActive : (allClientIds[0] || rawClientId); - // 🔮 isAdmin через IAM, временно: || clientId === WZ01112 + // 🔮 isAdmin через IAM const iamAdmin = req.session && req.session.user && req.session.user.isAdmin; + // ВРЕМЕННО: WZ01112 без isAdmin тоже видит toggle (для тестов) + const canAdminMode = !!(iamAdmin && activeClientId === ADMIN_CLIENT_ID) + || activeClientId === ADMIN_CLIENT_ID; // ← убрать после тестов req.user = { clientId: rawClientId, allClientIds, @@ -67,7 +70,8 @@ function createUiRouter({ auth, MOCK_USERS, authLimiter }) { companyId: payload.company_id || payload.companyId || null, companyName: payload.company_name || payload.companyName || activeClientId, email: payload.email || activeClientId + '@unknown', - isAdmin: iamAdmin || activeClientId === ADMIN_CLIENT_ID, + isAdmin: iamAdmin, + canAdminMode, adminMode: !!(req.session && req.session.adminMode), }; } catch (e) { diff --git a/ui/routes/entries.js b/ui/routes/entries.js index 0d9ded2..805f983 100644 --- a/ui/routes/entries.js +++ b/ui/routes/entries.js @@ -83,29 +83,17 @@ function createRouter() { const companyId = req.query.company ? parseInt(req.query.company, 10) : null; - if (!companyId) { - // Админ без ?company= — показываем свою компанию - let ownCo = companies.find(c => c.client_id === req.user.clientId) || null; - if (!ownCo) { - await api.get('/api/v1/entries', token); // создаст компанию через getOrCreateCompany - const cr2 = await api.get('/api/v1/companies', token); - ownCo = (cr2.data.companies || []).find(c => c.client_id === req.user.clientId) || null; - } - if (ownCo) { - const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : ''; - const er = await api.get('/api/v1/entries?company=' + ownCo.id + includeDeleted, token); - entries = er.data.entries || []; - limit = er.data.limit || 15; - selectedCompany = ownCo; - } - } else { + if (companyId) { selectedCompany = companies.find(c => c.id === companyId) || null; - if (selectedCompany) { - const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : ''; - const er = await api.get('/api/v1/entries?company=' + companyId + includeDeleted, token); - entries = er.data.entries || []; - limit = er.data.limit || 15; - } + } + // Если компания не выбрана — показываем список компаний, без записей + // (админ выбирает компанию из выпадающего списка) + + if (selectedCompany) { + const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : ''; + const er = await api.get('/api/v1/entries?company=' + selectedCompany.id + includeDeleted, token); + entries = er.data.entries || []; + limit = er.data.limit || 15; } } else { // Обычный пользователь: запрашиваем записи для активной компании diff --git a/views/index.ejs b/views/index.ejs index 3fd9420..0ab5bee 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -159,13 +159,15 @@ Белые списки IP v<%= appVersion %> - <% if (typeof isAdmin !== 'undefined' && isAdmin) { %> -
- -
+ <% if (typeof canAdminMode !== 'undefined' && canAdminMode) { %> + + + style="display:none;"> + + + + <% } %> <% if (typeof adminMode !== 'undefined' && adminMode) { %> Аудит