feat: Authorization Code Flow (KK) + session middleware + dev-login

- src/auth.js: OIDC mode (KC_CLIENT_ID/KC_CLIENT_SECRET), session-based
  middleware, legacy jwt-cookie migration, httpGet/httpPost без внешних зав.
- src/routes/auth.js: GET /callback (OIDC), GET+POST /dev-login (backdoor),
  session-based login/logout для обоих режимов
- server.js: express-session (8h, httpOnly, sameSite lax), trust proxy в prod
- views/dev-login.ejs: форма с пресетами MOCK_USERS + произвольные поля
- views/login.ejs: ссылка на /dev-login если devLoginEnabled
- tests: +5 тестов (session middleware), 50 PASS / 0 FAIL

Env-переменные:
  KC_CLIENT_ID, KC_CLIENT_SECRET, KC_BASE_URL, APP_URL  — OIDC режим
  SESSION_SECRET                                         — секрет сессии
  DEV_MODE=true                                          — /dev-login без пароля
  DEV_SECRET=xyz                                         — /dev-login с ?key=xyz
This commit is contained in:
2026-05-30 14:37:29 +03:00
parent 876e03b78b
commit 86ca944c52
8 changed files with 624 additions and 169 deletions
+31 -18
View File
@@ -127,40 +127,53 @@ test('overlap exact match', overlaps('192.168.0.0/24', '192.168.0.0/24'), true);
test('no overlap public', overlaps('8.8.8.0/24', '9.9.9.0/24'), false);
// ────────────────────────────────────────────────────────────────
// 4. src/auth.js — DEV_MODE (без JWT, без БД)
// 4. src/auth.js — session middleware (без JWT, без БД)
// ────────────────────────────────────────────────────────────────
section('auth.js (DEV_MODE)');
section('auth.js (session middleware)');
process.env.DEV_MODE = 'true';
process.env.DEV_ADMIN = 'true';
process.env.ADMIN_CLIENT_ID = 'WZ01112';
const { initAuth, requireAdmin } = require('../src/auth');
try {
const auth = initAuth();
test('middleware is function', typeof auth.middleware, 'function');
test('jwksHandler present in DEV_MODE', typeof auth.jwksHandler, 'function');
test('middleware is function', typeof auth.middleware, 'function');
test('jwksHandler present (mock mode)', typeof auth.jwksHandler, 'function');
test('devLoginEnabled when DEV_MODE', auth.devLoginEnabled, true);
test('isOidc false (no KC creds)', auth.isOidc, false);
// DEV_MODE: middleware выставляет req.user без JWT
const req = { headers: {}, cookies: {} };
const res = { redirect: () => {} };
// ── Пользователь из session ──
const mockUser = { clientId: 'WZ01325', companyId: 'test-uuid', companyName: 'Test', email: 'test@test.local', isAdmin: false };
const reqSess = { headers: {}, cookies: {}, session: { user: mockUser } };
let nextCalled = false;
auth.middleware(req, res, () => { nextCalled = true; });
test('DEV_MODE: next() called', nextCalled, true);
test('DEV_MODE: req.user set', !!req.user, true);
test('DEV_MODE: req.user.isAdmin = true (DEV_ADMIN=true)', req.user.isAdmin, true);
auth.middleware(reqSess, {}, () => { nextCalled = true; });
test('session user: next() called', nextCalled, true);
test('session user: req.user set', !!reqSess.user, true);
test('session user: clientId match', reqSess.user.clientId, 'WZ01325');
// requireAdmin: пропускает admin
const reqAdmin = { user: { isAdmin: true } };
// ── Admin user из session ──
const adminUser = { clientId: 'WZ01112', isAdmin: true };
const reqAdmin = { headers: {}, cookies: {}, session: { user: adminUser } };
let adminNext = false;
auth.middleware(reqAdmin, {}, () => { adminNext = true; });
test('admin session: next() called', adminNext, true);
test('admin session: isAdmin=true', reqAdmin.user.isAdmin, true);
// ── Нет аутентификации → редирект ──
let redirectUrl = null;
const reqNone = { headers: {}, cookies: {}, session: {}, method: 'GET', originalUrl: '/some-page' };
auth.middleware(reqNone, { redirect: (url) => { redirectUrl = url; } }, () => {});
test('no auth: redirected to /login', redirectUrl !== null && redirectUrl.startsWith('/login'), true);
// ── requireAdmin: admin проходит ──
let adminPassed = false;
requireAdmin(reqAdmin, {}, () => { adminPassed = true; });
requireAdmin({ user: { isAdmin: true } }, {}, () => { adminPassed = true; });
test('requireAdmin: admin passes', adminPassed, true);
// requireAdmin: блокирует не-admin
const reqUser = { user: { isAdmin: false } };
// ── requireAdmin: не-admin блокируется ──
let blockedStatus = null;
requireAdmin(reqUser, {
requireAdmin({ user: { isAdmin: false } }, {
status: (s) => { blockedStatus = s; return { send: () => {} }; },
}, () => {});
test('requireAdmin: non-admin blocked (403)', blockedStatus, 403);