137 lines
4.6 KiB
JavaScript
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 };
|