const jwt = require('jsonwebtoken'); const crypto = require('crypto'); const ISSUER = process.env.JWT_ISSUER || 'mock-auth-api'; const JWKS_URL = process.env.JWKS_URL || ''; // clientId платформы, которому выдаётся роль администратора. // Задаётся через .env, по умолчанию WZ01112 (Nubes) согласно ТЗ. const ADMIN_CLIENT_ID = process.env.ADMIN_CLIENT_ID || 'WZ01112'; let keyPair = null; let jwks = null; function initAuth() { if (JWKS_URL) { console.log('Auth: external JWKS ' + JWKS_URL); return { verify: verifyJWT, middleware: createMiddleware(), jwksHandler: null, issue: null }; } console.log('Auth: MOCK - local RS256 key'); keyPair = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, }); const pubKeyObj = crypto.createPublicKey(keyPair.publicKey).export({ format: 'jwk' }); pubKeyObj.alg = 'RS256'; pubKeyObj.use = 'sig'; pubKeyObj.kid = 'mock-1'; jwks = { keys: [pubKeyObj] }; return { verify: verifyJWT, issue: issueJWT, middleware: createMiddleware(), jwksHandler: function(req, res) { res.json(jwks); }, }; } function createMiddleware() { return function authMiddleware(req, res, next) { // DEV_MODE: обход авторизации для локальной разработки. // Активен только если NODE_ENV != 'production' — в prod всегда требует токен. // DEV_ADMIN=true позволяет тестировать admin-функционал в dev-режиме. if (process.env.DEV_MODE === 'true' && process.env.NODE_ENV !== 'production') { req.user = { email: 'dev@test.local', clientId: 'WZ01325', companyId: '00000000-0000-0000-0000-000000000000', companyName: 'DEV', // DEV_ADMIN=true → имитировать admin-права в dev-окружении isAdmin: process.env.DEV_ADMIN === 'true', }; return next(); } // Токен извлекаем из httpOnly-cookie (браузер) или Authorization header (API) var token = (req.cookies && req.cookies.jwt) || (req.headers.authorization || '').replace('Bearer ', ''); if (!token) { if (req.method === 'GET' && req.path.indexOf('/.well-known') !== 0) { return res.redirect('/login?returnTo=' + encodeURIComponent(req.originalUrl)); } return res.status(401).send('Unauthorized'); } try { var payload = verifyJWT(token); req.user = { email: payload.email || 'unknown', clientId: payload.ClientID, companyId: payload.company_id, companyName: payload.company_name || payload.ClientID, // Роль admin определяется по clientId платформы согласно ТЗ. // В проде: можно расширить проверкой отдельного claim из Keycloak. isAdmin: payload.ClientID === ADMIN_CLIENT_ID, }; next(); } catch (e) { res.clearCookie('jwt'); if (req.method === 'GET' && req.path.indexOf('/.well-known') !== 0) { return res.redirect('/login'); } return res.status(401).send('Unauthorized'); } }; } /** * Middleware-защита для admin-only маршрутов. * Использовать после app.use(auth.middleware): * app.get('/admin', requireAdmin, handler) */ function requireAdmin(req, res, next) { if (!req.user || !req.user.isAdmin) { return res.status(403).send('403 Forbidden: требуется роль администратора'); } next(); } function verifyJWT(token) { if (JWKS_URL) { // TODO: реализовать верификацию через внешний JWKS-endpoint. // Нужен URL от команды Keycloak: JWKS_URL=https://auth-api.../jwks throw new Error('JWKS_URL verification not implemented yet'); } return jwt.verify(token, keyPair.publicKey, { algorithms: ['RS256'], issuer: ISSUER, }); } function issueJWT(claims) { if (!keyPair) throw new Error('Cannot issue tokens with external JWKS'); return jwt.sign( { ClientID: claims.clientId, company_id: claims.companyId, company_name: claims.companyName, email: claims.email, login: claims.email, }, keyPair.privateKey, { algorithm: 'RS256', issuer: ISSUER, subject: claims.companyId, expiresIn: '24h', keyid: 'mock-1', } ); } module.exports = { initAuth, requireAdmin };