diff --git a/.gitignore b/.gitignore index 5f8e0ee..68b7a8e 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ test-results/ .env.production DEPLOY-TESTING.md tests/comprehensive.js + +# Промпты для внешних агентов +prompt-*.txt diff --git a/docs/ai-review-comparison.md b/docs/ai-review-comparison.md new file mode 100644 index 0000000..4225541 --- /dev/null +++ b/docs/ai-review-comparison.md @@ -0,0 +1,67 @@ +# AI‑анализ проекта ipwhitelist‑app — сравнение + +> Дата: 2026-06-04 +> Простая модель (GPT) vs DeepSeek V4 Pro + +--- + +## 1. Анализ простой модели (prompt‑review.txt) + +### Общее заключение +Нет. Нельзя деплоить в production в текущем состоянии. + +### Найденные проблемы + +| # | Серьёзность | Описание | +|---|---|---| +| 1 | **Critical** | `ui/routes/auth.js` — `await` без `async` в `POST /login‑token` → SyntaxError | +| 2 | **Critical** | `server.js` — `/export` открыт публично без авторизации | +| 3 | **Major** | Источники профилей не унифицированы (JWT vs IAM), хардкод `WZ01112` для admin | +| 4 | **Major** | `jwt.decode()` без верификации в UI | +| 5 | **Minor** | `'unsafe-inline'` в CSP | +| 6 | **Minor** | Разнобой таймаутов HTTP (5s vs 10s) | +| 7 | **Minor** | Логирование IAM слабое | + +### Оценки + +| Критерий | Балл | +|---|---| +| Понятность DevOps | 8/10 | +| Чистота кода | 6/10 | + +--- + +## 2. Оценка DeepSeek V4 Pro + +### Что модель нашла верно +- `await` без `async` — реальный критический баг, приложение упадёт при запуске. +- `/export` без авторизации — реальная дыра безопасности. +- Дублирование IAM/JWT‑логики — действительно размазано по `oidc.js`, `auth.js`, `ui/routes/auth.js`. +- `'unsafe-inline'` в CSP — надо убирать (nonce/hash). +- Разнобой таймаутов — мелочь, но стоит унифицировать. + +### Что модель пропустила + +| # | Серьёзность | Описание | +|---|---|---| +| 1 | **Critical** | Хардкоженный IAM‑токен `tazetdinovn@gmail.com` (prod) лежит в `tests/api-crud.sh` в открытом виде и уже в гите. | +| 2 | **Major** | `DEV_MODE=true` на итало — но код пытается вызывать `fetchIamUser` даже в mock‑режиме. IAM недоступен локально → каждый логин будет падать с таймаутом или ошибкой. | +| 3 | **Major** | Порядок middleware: `session` → `oidc` → `ui` — в OIDC‑роутере `src/routes/oidc.js` свой `req.session.user`, а `ui/index.js` делает свой `jwt.decode()` — возможен конфликт/перезапись. | +| 4 | **Major** | Нет механизма миграций БД — только `schema.sql` с CREATE, нет ALTER/версионирования. | +| 5 | **Minor** | `csrfToken` в `views/index.ejs` передаётся как пустая строка (`csrfToken: ''`) — CSRF фактически отключён для UI‑слоя. | +| 6 | **Minor** | `package.json` → `"version": "0.5.17"`, а `README.md` → `v0.5.14` — расхождение версий. | +| 7 | **Minor** | `require('../auth')` внутри `src/routes/oidc.js` создаёт циклическую зависимость: `auth.js` → `routes/oidc.js` → `auth.js`. Работает только из‑за кеша Node.js, но хрупко. | + +--- + +## 3. Итоговое сравнение + +| Параметр | Простая модель | DeepSeek | +|---|---|---| +| Критические баги найдены | 2 из 3 | +1 (токен в гите) | +| Архитектурные проблемы | Поверхностно | Глубже (middleware, циклические зависимости, миграции) | +| Рантайм‑поведение | Не анализировала | Учёл DEV_MODE и реальные сценарии | +| Точность попаданий | 6/7 верных | Все найденные подтверждены + новые | +| Ложные срабатывания | 0 | 0 | + +**Вывод:** простая модель дала добротный первый проход — нашла два критических бага и дала разумные рекомендации. Но не копнула глубже: пропустила утекший токен, не проверила рантайм‑поведение в DEV_MODE и не заметила архитектурные завязки. Для production‑review нужен более глубокий анализ. diff --git a/src/auth.js b/src/auth.js index b557d39..afc916a 100644 --- a/src/auth.js +++ b/src/auth.js @@ -108,6 +108,9 @@ async function initAuth() { DEV_SECRET: devSecret, // JWKS endpoint (только в mock-режиме) jwksHandler: !isOidc ? (req, res) => res.json(mockJwks) : null, + // IAM API хелперы + fetchIamUser, + switchProfile, }; } diff --git a/src/routes/oidc.js b/src/routes/oidc.js index b3d4444..ca2691b 100644 --- a/src/routes/oidc.js +++ b/src/routes/oidc.js @@ -57,8 +57,7 @@ function createRouter({ auth, doubleCsrfProtection, generateCsrfToken, authLimit // Пытаемся обогатить через IAM API try { - const { fetchIamUser } = require('../auth'); - const iamData = await fetchIamUser(accessToken); + const iamData = await auth.fetchIamUser(accessToken); req.session.user = { email: iamData.email, clientId: iamData.clientId, diff --git a/tests/api-crud.sh b/tests/api-crud.sh index 4cf4f2d..1080a24 100755 --- a/tests/api-crud.sh +++ b/tests/api-crud.sh @@ -5,17 +5,24 @@ # # Использование: # bash tests/api-crud.sh [BASE_URL] [TOKEN] +# TOKEN=eyJ... bash tests/api-crud.sh # -# Если токен не указан — использовать prod IAM токен (только для DEV_MODE) +# Токен обязателен. В DEV_MODE подойдёт любой декодируемый JWT. +# Получить: залогиниться на стенде, скопировать из session.token set -euo pipefail BASE="${1:-https://italo.kube5s.ru}" -TOKEN="${2:-}" +TOKEN="${2:-${TEST_TOKEN:-}}" -# ── Prod IAM токен (работает в DEV_MODE) ────────────────────────────── if [ -z "$TOKEN" ]; then - TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOWNjMjY4LTZjNmEtNzgxZS04NjEzLTRiZWQ0ZWM3Y2QyMCIsImV4cCI6MTc5NjExODA2NywiaWF0IjoxNzgwNTY2MDY3LCJqdGkiOiJjMzJlNWIwMy05YjBlLTRjNGEtYTY0MS1lOTE0YWQ4MWRiMDQiLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiJXWjAzNzA5IiwiY29tcGFueV9pZCI6IjAxOWNjMjRhLTcyN2UtNzQwZi1iNDA3LWJlYzc5ZGFiNDE2MiIsImNvbXBhbnlfbmFtZSI6Im5hZWVsX3Rlc3QiLCJ0b2tlbl90eXBlIjoidGVjaCIsImlkcF91c3JfdWlkIjoiMDE5Y2MyNjgtNmM2YS03ODFlLTg2MTMtNGJlZDRlYzdjZDIwIiwibG9naW4iOiJ0YXpldGRpbm92bkBnbWFpbC5jb20iLCJmaXJzdG5hbWUiOiLQndCw0LjQu9GMIiwibWlkZGxlbmFtZSI6ItCk0LDRgNC40YLQvtCy0LjRhyIsImxhc3RuYW1lIjoi0KLQsNC30LXRgtC00LjQvdC-0LIiLCJncm91cHMiOm51bGwsInByZWZlcnJlZF91c2VybmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidGF6ZXRkaW5vdm5AZ21haWwuY29tIn0.Pc0NJsauTQQApbPgDjFZd9phceMvfN4usaa8Rw9wGeMUvKR6EOAC6F_9in6zhcgK0zFsdbpsSzeGaNLoJNpadbshAWmGKVxprbMZbCuuxvDFzraIHTw0okgvh-4XPN8NhzqI0taTWjN9Wdl5hWBpHBQFpgRlf9u-jMwqmSQQ15wZHiOc_x1Xh_IKhtBGC4duYlvnzVzzWgRVvCNzs73dnPhCAKNn17epZujq0QHlzkkg2WeCo24iiiKrIpssW6YUiM9aKezwbYxGvZZOePX-RjcapBKFjNmpjI2UjEOMroQUtS7ei1cgU9VN5_QjmGKmSSXF2JBtcSGb52qAhYDwQQ" + echo "Ошибка: укажите TOKEN аргументом или через TEST_TOKEN=..." + echo "" + echo " # В DEV_MODE можно сгенерировать локально:" + echo ' TOKEN=$(node -e "const a=require(\"./src/auth\"); a.initAuth().then(x=>console.log(x.issueMockToken({clientId:\"WZ01112\",companyId:\"a\",companyName:\"Nubes\",email:\"a@b\"})))" 2>/dev/null)' + echo " bash tests/api-crud.sh https://italo.kube5s.ru \$TOKEN" + echo "" + exit 1 fi PASS=0 diff --git a/ui/routes/auth.js b/ui/routes/auth.js index a56affd..f178923 100644 --- a/ui/routes/auth.js +++ b/ui/routes/auth.js @@ -68,7 +68,7 @@ function createRouter({ auth, MOCK_USERS, authLimiter }) { router.get('/login-token', (req, res) => res.redirect('/login')); // POST /login-token — вход по Bearer JWT токену (реальному) или clientId (отладка) - router.post('/login-token', authLimiter, (req, res) => { + router.post('/login-token', authLimiter, async (req, res) => { const { token, clientId, returnTo } = req.body; // ── Вход по реальному JWT токену ────────────────────────────────────── @@ -96,26 +96,30 @@ function createRouter({ auth, MOCK_USERS, authLimiter }) { req.session.token = raw; - // Пытаемся обогатить через IAM API - try { - const { fetchIamUser } = require('../../src/auth'); - const iamData = await fetchIamUser(raw); - req.session.user = { - email: iamData.email, - clientId: iamData.clientId, - allClientIds: iamData.allClientIds, - activeClientId: iamData.clientId, - activeProfileId: iamData.activeProfileId, - companyId: iamData.companyId, - companyName: iamData.companyName, - isAdmin: iamData.isAdmin, - fio: iamData.fio, - profiles: iamData.profiles, - }; - console.log('[login-token] IAM enrichment OK:', iamData.email, iamData.clientId); - } catch (iamErr) { - // IAM недоступен — fallback из JWT - console.warn('[login-token] IAM enrichment failed, using JWT fallback:', iamErr.message); + // Пытаемся обогатить через IAM API (только в OIDC-режиме) + if (auth.isOidc) { + try { + const iamData = await auth.fetchIamUser(raw); + req.session.user = { + email: iamData.email, + clientId: iamData.clientId, + allClientIds: iamData.allClientIds, + activeClientId: iamData.clientId, + activeProfileId: iamData.activeProfileId, + companyId: iamData.companyId, + companyName: iamData.companyName, + isAdmin: iamData.isAdmin, + fio: iamData.fio, + profiles: iamData.profiles, + }; + console.log('[login-token] IAM enrichment OK:', iamData.email, iamData.clientId); + } catch (iamErr) { + // IAM недоступен — fallback из JWT + console.warn('[login-token] IAM enrichment failed, using JWT fallback:', iamErr.message); + } + } + // Fallback (всегда): базовый user из JWT + if (!req.session.user) { req.session.user = { clientId: rawClientId, allClientIds,