Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x | /**
* Authentification interne service-to-service.
*
* Principe : un token signé unique dans le header `x-internal-token`.
* Le token contient la signature HMAC-SHA256 + horodatage (iat/exp) :
* token = base64url(header) + "." + base64url(payload) + "." + signature
* signature = HMAC-SHA256(secret, header.payload)
* payload = { iat, exp, svc }
*
* Le récepteur vérifie en une opération : signature valide (authenticité),
* exp non dépassée (anti-rejeu), iat pas dans le futur (horloge).
* Le secret partagé (INTERNAL_API_KEY) ne circule jamais — seule la
* signature HMAC dérivée transite.
*/
const crypto = require('crypto');
const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY || '';
const TOKEN_HEADER = 'x-internal-token';
const TTL_SECONDS = 60;
const MAX_CLOCK_SKEW = 30;
function b64url(buf) {
return Buffer.from(buf).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function sign(secret, data) {
return crypto.createHmac('sha256', secret).update(data).digest('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
/**
* Génère un token interne signé (à mettre dans le header x-internal-token).
*/
function createInternalToken(serviceName = 'unknown') {
const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'internal' }));
const now = Math.floor(Date.now() / 1000);
const payload = b64url(JSON.stringify({ iat: now, exp: now + TTL_SECONDS, svc: serviceName }));
const signature = sign(INTERNAL_API_KEY, `${header}.${payload}`);
return `${header}.${payload}.${signature}`;
}
/**
* Vérifie un token interne. Retourne le payload décodé ou null si invalide.
*/
function verifyInternalToken(token) {
if (!INTERNAL_API_KEY) {
console.error('[internalAuth] INTERNAL_API_KEY not configured');
return null;
}
if (!token || typeof token !== 'string') return null;
const parts = token.split('.');
if (parts.length !== 3) return null;
const [header, payload, signature] = parts;
const expected = sign(INTERNAL_API_KEY, `${header}.${payload}`);
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) return null;
let decoded;
try {
decoded = JSON.parse(Buffer.from(payload, 'base64').toString('utf8'));
} catch {
return null;
}
const now = Math.floor(Date.now() / 1000);
if (decoded.exp && now > decoded.exp + MAX_CLOCK_SKEW) return null;
if (decoded.iat && decoded.iat > now + MAX_CLOCK_SKEW) return null;
return decoded;
}
/**
* Middleware Express : exige un x-internal-token valide.
*/
function requireInternalKey(req, res, next) {
const token = req.headers[TOKEN_HEADER];
const decoded = verifyInternalToken(token);
if (!decoded) {
return res.status(401).json({ success: false, message: 'Unauthorized: invalid internal token' });
}
req.internalSvc = decoded.svc || 'unknown';
next();
}
/**
* Ajoute le header x-internal-token à une requête sortante (headers muté).
*/
function addInternalKey(headers = {}, serviceName = 'unknown') {
if (INTERNAL_API_KEY) headers[TOKEN_HEADER] = createInternalToken(serviceName);
return headers;
}
module.exports = {
requireInternalKey,
addInternalKey,
createInternalToken,
verifyInternalToken,
TOKEN_HEADER,
};
|