Files
ipwhitelist-app/src/auth.js
T

137 lines
4.6 KiB
JavaScript

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 };