From 4ab27b6bd5cc8d85aacd9da23befae67b35461d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 13 Jun 2026 07:37:07 +0400 Subject: [PATCH] =?UTF-8?q?v2:=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B8=20=D0=B2=D0=BE=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=D1=85=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=D1=85=20=E2=80=94?= =?UTF-8?q?=20=D1=87=D1=82=D0=BE,=20=D0=B7=D0=B0=D1=87=D0=B5=D0=BC,=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=B0=D0=BA=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- v2/server.js | 85 ++++---- v2/src/admin/index.js | 45 ++-- v2/src/auth/index.js | 423 +++++-------------------------------- v2/src/crud/index.js | 47 ++++- v2/src/db/index.js | 8 +- v2/src/db/queries.js | 21 +- v2/src/db/schema.js | 45 ++-- v2/src/router/index.js | 57 +++-- v2/src/test/index.js | 68 ++++-- v2/src/user/index.js | 46 ++-- v2/src/validators/index.js | 58 ++++- 12 files changed, 393 insertions(+), 512 deletions(-) diff --git a/package.json b/package.json index 21b18b0..9eb1f80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.93", + "version": "0.5.94", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/v2/server.js b/v2/server.js index 0f40145..ffe753e 100644 --- a/v2/server.js +++ b/v2/server.js @@ -1,16 +1,23 @@ // ═══════════════════════════════════════════════════════════════════════════════ -// V2 — роутер (монтируется в server.js как /v2) +// V2 — главный роутер (монтируется в server.js как /v2) // -// СЕЙЧАС: показывает IAM-данные из сессии основного приложения. -// Вход — через основной /login (production KC). +// ЭТО: createV2Router() — собирает все v2-роутеры в один. +// ЗАЧЕМ: единственная точка входа v2 в основное приложение. // -// ПОТОМ: будет свой /v2/login → /v2/callback через production KC. -// Тогда заработает login() из src/auth/index.js. +// МОНТАЖ В ОСНОВНОМ server.js: +// const { createV2Router } = require('./v2/server'); +// app.use('/v2', createV2Router()); // -// Модули: -// router/ — resolveContext: admin/user/impersonation → req.v2_* -// user/ — обычный CRUD (пустышка) -// admin/ — админка (пустышка) +// ПОРЯДОК МАРШРУТОВ (важен!): +// 1. /login — редирект на основной OIDC-вход +// 2. /iam — отладка IAM-данных из сессии +// 3. /app — CRUD юзера (resolveContext + user роутер) +// 4. /admin — админка (resolveContext + admin роутер) +// 5. /test — тестовый API (если ENABLE_TEST_API !== 'false') +// 6. /logout — редирект на основной выход +// +// ВАЖНО: роутеры /app и /admin используют resolveContext middleware, +// которое проверяет сессию и выставляет req.clientId, req.email, ... // ═══════════════════════════════════════════════════════════════════════════════ 'use strict'; @@ -25,26 +32,50 @@ const { pool } = require('./src/db'); const { ensureSchema } = require('./src/db/schema'); function createV2Router() { - // Автосоздание таблиц при старте + // Автосоздание таблиц v2_companies, v2_entries, v2_audit при старте ensureSchema(pool).catch(e => console.error('[v2:db] Schema error:', e.message)); const router = express.Router(); - // ── GET /v2/login — редирект на основной вход ────────────────────────── + // ── GET /v2/login — редирект на основной вход (KC OIDC) ───────────── router.get('/login', (req, res) => { res.redirect('/login?returnTo=' + encodeURIComponent('/v2/app')); }); - // ── GET /v2/app — показать IAM-данные из сессии ──────────────────────── + // ── GET /v2/iam — отладка: показать IAM-данные из сессии ──────────── router.get('/iam', (req, res) => { const u = req.session && req.session.user; if (!u) return res.send('

Нет данных

Войти

'); + res.send(debugPage(u, config.version)); + }); - res.send(` - + // ── /v2/app — пользовательский CRUD (только с сессией) ────────────── + router.use('/app', resolveContext, createUserRouter()); + + // ── /v2/admin — админка (только для isAdmin=true) ──────────────────── + router.use('/admin', resolveContext, createAdminRouter()); + + // ── GET /v2/ — редирект на /v2/app ────────────────────────────────── + router.get('/', (req, res) => res.redirect('/v2/app')); + + // ── /v2/test — тестовый API (отключить: ENABLE_TEST_API=false) ────── + // Код остаётся, роутер не монтируется при ENABLE_TEST_API=false. + if (process.env.ENABLE_TEST_API !== 'false') { + const { createTestRouter } = require('./src/test'); + router.use('/test', createTestRouter()); + } + + // ── GET /v2/logout — редирект на основной выход ───────────────────── + router.get('/logout', (req, res) => res.redirect('/logout')); + + return router; +} + +function debugPage(u, version) { + return ` V2 — IAM -

IAM Response (v${config.version})

+

IAM Response (v${version})

Email: ${u.email}

clientId: ${u.clientId}

Компания: ${u.companyName}

@@ -53,29 +84,7 @@ function createV2Router() {

Компаний: ${u.allClientIds ? u.allClientIds.length : 0} — ${(u.allClientIds || []).join(', ')}


${JSON.stringify(u, null, 2)}

Выйти | CRUD | Админка

-`); - }); - - // ── /v2/app — обычный CRUD (только для авторизованных) ───────────────── - router.use('/app', resolveContext, createUserRouter()); - - // ── /v2/admin — админка (только для авторизованных) ──────────────────── - router.use('/admin', resolveContext, createAdminRouter()); - - // ── GET /v2/ ───────────────────────────────────────────────────────────── - router.get('/', (req, res) => res.redirect('/v2/app')); - - // ── /v2/test — тестовый эндпоинт (отключить: ENABLE_TEST_API=false) ── - if (process.env.ENABLE_TEST_API !== 'false') { - const { createTestRouter } = require('./src/test'); - router.use('/test', createTestRouter()); - } - - // ── GET /v2/logout ─────────────────────────────────────────────────────── - - router.get('/logout', (req, res) => res.redirect('/logout')); - - return router; +`; } module.exports = { createV2Router }; diff --git a/v2/src/admin/index.js b/v2/src/admin/index.js index 1c2bb30..284e381 100644 --- a/v2/src/admin/index.js +++ b/v2/src/admin/index.js @@ -1,25 +1,42 @@ // ═══════════════════════════════════════════════════════════════════════════════ -// V2 — Админка (Dashboard, аудит, лимиты, все компании) +// V2 — Админка // -// Вход: req.isAdmin, req.email, req.clientId из resolveContext. -// Только для админов (isAdmin=true). -// Через crud/ для CRUD, через db/queries для admin-specific (аудит, лимиты). +// ЭТО: Express-роутер для администратора (isAdmin=true). +// НЕ: доступно обычным юзерам (middleware проверяет req.isAdmin). +// +// ЗАЧЕМ: +// 1. Видеть ВСЕ компании и их статистику (дашборд). +// 2. Смотреть аудит любой компании. +// 3. Устанавливать персональные лимиты. +// 4. Просматривать записи любой компании. +// +// ДАННЫЕ: +// crud.list(clientId) — записи компании (через API-слой) +// q.getAllCompanies() — все компании со счётчиками +// q.getAudit(companyId) — аудит (null = все компании) +// q.setLimit(id, limit) — персональный лимит +// +// МАРШРУТЫ: +// GET /admin — дашборд: таблица всех компаний +// GET /admin/audit — аудит (?companyId=X — фильтр) +// POST /admin/limit — установить лимит (body: companyId, limit) +// GET /admin/entries — записи компании (?companyId=X) // ═══════════════════════════════════════════════════════════════════════════════ const express = require('express'); -const q = require('../db/queries'); -const crud = require('../crud'); +const q = require('../db/queries'); // admin-specific: аудит, лимиты, все компании +const crud = require('../crud'); // CRUD для просмотра записей function createAdminRouter() { const router = express.Router(); - // ── middleware: только админы ─────────────────────────────────────────── + // ── middleware: только админы ─────────────────────────────────────── router.use((req, res, next) => { if (!req.isAdmin) return res.redirect('/v2/app'); next(); }); - // ── GET / — дашборд: все компании ────────────────────────────────────── + // ── GET / — дашборд: все компании ────────────────────────────────── router.get('/', async (req, res) => { try { const companies = await q.getAllCompanies(); @@ -30,7 +47,7 @@ function createAdminRouter() { } }); - // ── GET /audit?companyId=X ───────────────────────────────────────────── + // ── GET /audit?companyId=X — журнал действий ─────────────────────── router.get('/audit', async (req, res) => { try { const companyId = req.query.companyId ? parseInt(req.query.companyId) : null; @@ -42,7 +59,7 @@ function createAdminRouter() { } }); - // ── POST /limit ──────────────────────────────────────────────────────── + // ── POST /limit — установить персональный лимит ──────────────────── router.post('/limit', async (req, res) => { try { const companyId = parseInt(req.body.companyId); @@ -55,12 +72,13 @@ function createAdminRouter() { } }); - // ── GET /entries?companyId=X — записи любой компании ─────────────────── + // ── GET /entries?companyId=X — записи любой компании ─────────────── router.get('/entries', async (req, res) => { try { const companyId = parseInt(req.query.companyId); const company = companyId ? await q.getCompanyById(companyId) : null; if (!company) return res.redirect('/v2/admin'); + // crud.list принимает clientId (W-номер), не внутренний id const { entries, used, limit } = await crud.list(company.client_id, req.query.deleted === '1'); const version = require('../config').version; res.send(renderEntries({ entries, company, used, limit, includeDeleted: req.query.deleted === '1', version })); @@ -72,7 +90,9 @@ function createAdminRouter() { return router; } -// ── HTML-рендеринг ───────────────────────────────────────────────────────── +// ═══════════════════════════════════════════════════════════════════════════ +// HTML-рендеринг (временный, будет заменён на EJS) +// ═══════════════════════════════════════════════════════════════════════════ function renderDashboard({ companies, user, version }) { const rows = companies.map(c => ` @@ -183,6 +203,7 @@ function renderEntries({ entries, company, used, limit, includeDeleted, version `; } +// ── msgHtml() — скрипт для показа зелёных/красных плашек ────────────── function msgHtml() { return `