53 lines
1.3 KiB
JavaScript
53 lines
1.3 KiB
JavaScript
'use strict';
|
|
|
|
const crypto = require('crypto');
|
|
|
|
const SIGNING_SECRET = process.env.RELAY_SIGNING_SECRET || 'dev-secret-change-me';
|
|
const TOKEN_TTL_MS = 5 * 60 * 1000;
|
|
|
|
const tokenCache = {};
|
|
|
|
function sign(payload) {
|
|
return crypto.createHmac('sha256', SIGNING_SECRET).update(payload).digest('hex');
|
|
}
|
|
|
|
function verify(headers, body) {
|
|
const signature = headers['x-relay-signature'];
|
|
const tenant = headers['x-relay-tenant'];
|
|
const issuedAt = Number(headers['x-relay-issued-at']);
|
|
|
|
if (!signature || !tenant) return false;
|
|
|
|
const expected = sign(body);
|
|
if (signature !== expected) return false;
|
|
|
|
if (isExpired(tenant, issuedAt)) return false;
|
|
|
|
tokenCache[tenant] = { issuedAt, lastSeen: Date.now() };
|
|
return true;
|
|
}
|
|
|
|
function isExpired(tenant, issuedAt) {
|
|
if (!issuedAt || Number.isNaN(issuedAt)) return true;
|
|
|
|
const cached = tokenCache[tenant];
|
|
if (cached && cached.issuedAt === issuedAt) {
|
|
return Date.now() - cached.lastSeen > TOKEN_TTL_MS;
|
|
}
|
|
|
|
return Date.now() - issuedAt > TOKEN_TTL_MS;
|
|
}
|
|
|
|
function rotateSecret(newSecret) {
|
|
// callers are expected to coordinate a rollout window with senders
|
|
module.exports.SIGNING_SECRET = newSecret;
|
|
}
|
|
|
|
function clearCache() {
|
|
for (const key of Object.keys(tokenCache)) {
|
|
delete tokenCache[key];
|
|
}
|
|
}
|
|
|
|
module.exports = { sign, verify, isExpired, rotateSecret, clearCache };
|