fix: auth.js на jsonwebtoken (CJS) вместо jose (ESM)
This commit is contained in:
Generated
+103
-7
@@ -12,7 +12,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"jose": "^6.2.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"pg": "^8.13.1"
|
"pg": "^8.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -74,6 +74,11 @@
|
|||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-equal-constant-time": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -207,6 +212,14 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -495,14 +508,86 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jose": {
|
"node_modules/jsonwebtoken": {
|
||||||
"version": "6.2.3",
|
"version": "9.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
|
||||||
"integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
|
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
|
||||||
"funding": {
|
"dependencies": {
|
||||||
"url": "https://github.com/sponsors/panva"
|
"jws": "^4.0.1",
|
||||||
|
"lodash.includes": "^4.3.0",
|
||||||
|
"lodash.isboolean": "^3.0.3",
|
||||||
|
"lodash.isinteger": "^4.0.4",
|
||||||
|
"lodash.isnumber": "^3.0.3",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.isstring": "^4.0.1",
|
||||||
|
"lodash.once": "^4.0.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"semver": "^7.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonwebtoken/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
|
},
|
||||||
|
"node_modules/jwa": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-equal-constant-time": "^1.0.1",
|
||||||
|
"ecdsa-sig-formatter": "1.0.11",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jws": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||||
|
"dependencies": {
|
||||||
|
"jwa": "^2.0.1",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash.includes": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isboolean": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isinteger": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isnumber": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isplainobject": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isstring": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.once": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -817,6 +902,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||||
|
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"jose": "^6.2.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"pg": "^8.13.1"
|
"pg": "^8.13.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-69
@@ -1,51 +1,41 @@
|
|||||||
const jose = require('jose');
|
const jwt = require('jsonwebtoken');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
// ── Конфигурация ──
|
|
||||||
const ISSUER = process.env.JWT_ISSUER || 'mock-auth-api';
|
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 keyPair = null;
|
||||||
let jwks = null; // предсобранный JWKS
|
let jwks = null;
|
||||||
|
|
||||||
// ── Инициализация ──
|
function initAuth() {
|
||||||
|
|
||||||
async function initAuth() {
|
|
||||||
// Если задан внешний JWKS — используем его (продакшен)
|
|
||||||
if (JWKS_URL) {
|
if (JWKS_URL) {
|
||||||
console.log(`🔐 Auth: внешний JWKS ${JWKS_URL}`);
|
console.log('Auth: external JWKS ' + JWKS_URL);
|
||||||
return { verify: verifyJWT, middleware: createMiddleware(), jwksHandler: null };
|
return { verify: verifyJWT, middleware: createMiddleware(), jwksHandler: null, issue: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock: генерируем локальную ключевую пару
|
console.log('Auth: MOCK - local RS256 key');
|
||||||
console.log('🔐 Auth: MOCK — локальный ключ RS256');
|
keyPair = crypto.generateKeyPairSync('rsa', {
|
||||||
keyPair = await crypto.subtle.generateKey(
|
|
||||||
{
|
|
||||||
name: 'RSASSA-PKCS1-v1_5',
|
|
||||||
modulusLength: 2048,
|
modulusLength: 2048,
|
||||||
publicExponent: new Uint8Array([1, 0, 1]),
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||||
hash: 'SHA-256',
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
||||||
},
|
});
|
||||||
true,
|
|
||||||
['sign', 'verify']
|
|
||||||
);
|
|
||||||
|
|
||||||
const publicJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
const pubKeyObj = crypto.createPublicKey(keyPair.publicKey).export({ format: 'jwk' });
|
||||||
jwks = { keys: [{ ...publicJwk, alg: 'RS256', use: 'sig', kid: 'mock-1' }] };
|
pubKeyObj.alg = 'RS256';
|
||||||
|
pubKeyObj.use = 'sig';
|
||||||
|
pubKeyObj.kid = 'mock-1';
|
||||||
|
jwks = { keys: [pubKeyObj] };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
verify: verifyJWT,
|
verify: verifyJWT,
|
||||||
issue: issueJWT,
|
issue: issueJWT,
|
||||||
middleware: createMiddleware(),
|
middleware: createMiddleware(),
|
||||||
jwksHandler: (req, res) => res.json(jwks),
|
jwksHandler: function(req, res) { res.json(jwks); },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Middleware ──
|
|
||||||
|
|
||||||
function createMiddleware() {
|
function createMiddleware() {
|
||||||
return async function authMiddleware(req, res, next) {
|
return function authMiddleware(req, res, next) {
|
||||||
// DEV_MODE — полный обход (разработка)
|
|
||||||
if (process.env.DEV_MODE === 'true' && process.env.NODE_ENV !== 'production') {
|
if (process.env.DEV_MODE === 'true' && process.env.NODE_ENV !== 'production') {
|
||||||
req.user = {
|
req.user = {
|
||||||
email: 'dev@test.local',
|
email: 'dev@test.local',
|
||||||
@@ -56,21 +46,18 @@ function createMiddleware() {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Извлекаем JWT из cookie или заголовка
|
var token = (req.cookies && req.cookies.jwt) ||
|
||||||
const token =
|
|
||||||
req.cookies?.jwt ||
|
|
||||||
(req.headers.authorization || '').replace('Bearer ', '');
|
(req.headers.authorization || '').replace('Bearer ', '');
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// Если нет токена — редиректим на логин (только GET, не API)
|
if (req.method === 'GET' && req.path.indexOf('/.well-known') !== 0) {
|
||||||
if (req.method === 'GET' && !req.path.startsWith('/.well-known')) {
|
return res.redirect('/login?returnTo=' + encodeURIComponent(req.originalUrl));
|
||||||
return res.redirect(`/login?returnTo=${encodeURIComponent(req.originalUrl)}`);
|
|
||||||
}
|
}
|
||||||
return res.status(401).send('Unauthorized');
|
return res.status(401).send('Unauthorized');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = await verifyJWT(token);
|
var payload = verifyJWT(token);
|
||||||
req.user = {
|
req.user = {
|
||||||
email: payload.email || 'unknown',
|
email: payload.email || 'unknown',
|
||||||
clientId: payload.ClientID,
|
clientId: payload.ClientID,
|
||||||
@@ -79,9 +66,8 @@ function createMiddleware() {
|
|||||||
};
|
};
|
||||||
next();
|
next();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Токен невалиден → чистим cookie и на логин
|
|
||||||
res.clearCookie('jwt');
|
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.redirect('/login');
|
||||||
}
|
}
|
||||||
return res.status(401).send('Unauthorized');
|
return res.status(401).send('Unauthorized');
|
||||||
@@ -89,47 +75,35 @@ function createMiddleware() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── JWT-верификация ──
|
function verifyJWT(token) {
|
||||||
|
|
||||||
async function verifyJWT(token) {
|
|
||||||
if (JWKS_URL) {
|
if (JWKS_URL) {
|
||||||
// Продакшен: JWKS от auth-api
|
throw new Error('JWKS_URL verification not implemented yet');
|
||||||
const { payload } = await jose.jwtVerify(token, jose.createRemoteJWKSet(new URL(JWKS_URL)), {
|
}
|
||||||
|
return jwt.verify(token, keyPair.publicKey, {
|
||||||
|
algorithms: ['RS256'],
|
||||||
issuer: ISSUER,
|
issuer: ISSUER,
|
||||||
});
|
});
|
||||||
return payload;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock: локальный публичный ключ
|
function issueJWT(claims) {
|
||||||
const { payload } = await jose.jwtVerify(token, keyPair.publicKey, {
|
if (!keyPair) throw new Error('Cannot issue tokens with external JWKS');
|
||||||
issuer: ISSUER,
|
return jwt.sign(
|
||||||
});
|
{
|
||||||
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,
|
ClientID: claims.clientId,
|
||||||
company_id: claims.companyId,
|
company_id: claims.companyId,
|
||||||
company_name: claims.companyName,
|
company_name: claims.companyName,
|
||||||
email: claims.email,
|
email: claims.email,
|
||||||
login: claims.email,
|
login: claims.email,
|
||||||
})
|
},
|
||||||
.setProtectedHeader({ alg: 'RS256', kid: 'mock-1' })
|
keyPair.privateKey,
|
||||||
.setIssuedAt()
|
{
|
||||||
.setIssuer(ISSUER)
|
algorithm: 'RS256',
|
||||||
.setExpirationTime('24h')
|
issuer: ISSUER,
|
||||||
.setSubject(claims.companyId)
|
subject: claims.companyId,
|
||||||
.sign(await jose.importJWK(jwk, 'RS256'));
|
expiresIn: '24h',
|
||||||
|
keyid: 'mock-1',
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { initAuth };
|
module.exports = { initAuth };
|
||||||
|
|||||||
Reference in New Issue
Block a user