fix: auth.js на jsonwebtoken (CJS) вместо jose (ESM)

This commit is contained in:
2026-05-30 08:45:21 +03:00
parent 28b8e7bd27
commit 2fb4548bf6
3 changed files with 152 additions and 82 deletions
+48 -74
View File
@@ -1,51 +1,41 @@
const jose = require('jose');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
// ── Конфигурация ──
const ISSUER = process.env.JWT_ISSUER || 'mock-auth-api';
const JWKS_URL = process.env.JWKS_URL || ''; // пусто → локальные ключи
const JWKS_URL = process.env.JWKS_URL || '';
let keyPair = null; // { publicKey, privateKey } — CryptoKeyPair
let jwks = null; // предсобранный JWKS
let keyPair = null;
let jwks = null;
// ── Инициализация ──
async function initAuth() {
// Если задан внешний JWKS — используем его (продакшен)
function initAuth() {
if (JWKS_URL) {
console.log(`🔐 Auth: внешний JWKS ${JWKS_URL}`);
return { verify: verifyJWT, middleware: createMiddleware(), jwksHandler: null };
console.log('Auth: external JWKS ' + JWKS_URL);
return { verify: verifyJWT, middleware: createMiddleware(), jwksHandler: null, issue: null };
}
// Mock: генерируем локальную ключевую пару
console.log('🔐 Auth: MOCK — локальный ключ RS256');
keyPair = await crypto.subtle.generateKey(
{
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['sign', 'verify']
);
console.log('Auth: MOCK - local RS256 key');
keyPair = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
});
const publicJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
jwks = { keys: [{ ...publicJwk, alg: 'RS256', use: 'sig', kid: 'mock-1' }] };
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: (req, res) => res.json(jwks),
jwksHandler: function(req, res) { res.json(jwks); },
};
}
// ── Middleware ──
function createMiddleware() {
return async function authMiddleware(req, res, next) {
// DEV_MODE — полный обход (разработка)
return function authMiddleware(req, res, next) {
if (process.env.DEV_MODE === 'true' && process.env.NODE_ENV !== 'production') {
req.user = {
email: 'dev@test.local',
@@ -56,21 +46,18 @@ function createMiddleware() {
return next();
}
// Извлекаем JWT из cookie или заголовка
const token =
req.cookies?.jwt ||
var token = (req.cookies && req.cookies.jwt) ||
(req.headers.authorization || '').replace('Bearer ', '');
if (!token) {
// Если нет токена — редиректим на логин (только GET, не API)
if (req.method === 'GET' && !req.path.startsWith('/.well-known')) {
return res.redirect(`/login?returnTo=${encodeURIComponent(req.originalUrl)}`);
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 {
const payload = await verifyJWT(token);
var payload = verifyJWT(token);
req.user = {
email: payload.email || 'unknown',
clientId: payload.ClientID,
@@ -79,9 +66,8 @@ function createMiddleware() {
};
next();
} catch (e) {
// Токен невалиден → чистим cookie и на логин
res.clearCookie('jwt');
if (req.method === 'GET' && !req.path.startsWith('/.well-known')) {
if (req.method === 'GET' && req.path.indexOf('/.well-known') !== 0) {
return res.redirect('/login');
}
return res.status(401).send('Unauthorized');
@@ -89,47 +75,35 @@ function createMiddleware() {
};
}
// ── JWT-верификация ──
async function verifyJWT(token) {
function verifyJWT(token) {
if (JWKS_URL) {
// Продакшен: JWKS от auth-api
const { payload } = await jose.jwtVerify(token, jose.createRemoteJWKSet(new URL(JWKS_URL)), {
issuer: ISSUER,
});
return payload;
throw new Error('JWKS_URL verification not implemented yet');
}
// Mock: локальный публичный ключ
const { payload } = await jose.jwtVerify(token, keyPair.publicKey, {
return jwt.verify(token, keyPair.publicKey, {
algorithms: ['RS256'],
issuer: ISSUER,
});
return payload;
}
// ── JWT-выпуск (только mock) ──
async function issueJWT(claims) {
if (!keyPair) throw new Error('JWKS_URL задан — выпуск токенов невозможен');
const privateKey = keyPair.privateKey;
// Для jose.SignJWT нужен ключ в формате, который он понимает
// Экспортируем как JWK и импортируем обратно как KeyLike
const jwk = await crypto.subtle.exportKey('jwk', privateKey);
return new jose.SignJWT({
ClientID: claims.clientId,
company_id: claims.companyId,
company_name: claims.companyName,
email: claims.email,
login: claims.email,
})
.setProtectedHeader({ alg: 'RS256', kid: 'mock-1' })
.setIssuedAt()
.setIssuer(ISSUER)
.setExpirationTime('24h')
.setSubject(claims.companyId)
.sign(await jose.importJWK(jwk, 'RS256'));
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 };