openvibers/openvibe.network Commits

Wave 22 telemetry: every X-Internal-Key call is counted per route (principal_usage, auth internal-key-route) so the key can be retired route by route

OP OpenVibers committed 23 Sept 2026 · bd965db878 · 2 files, +22 −0


Changes

ddiff --git a/server/internal/routes.js b/server/internal/routes.js
iindex f84ed7b3..1fee65f1 100644
---- a/server/internal/routes.js
++++ b/server/internal/routes.js
@@@ -17,11 +17,29 @@ function getDb(req) { return req.app.locals.db; }
function getConfig(req) { return req.app.locals.config; }
// ── Internal Key Middleware ──────────────────────────────────
++let legacyStmt = null;
++function countLegacyKey(req, res) {
++ res.on('finish', () => {
++ try {
++ const db = getDb(req);
++ if (!legacyStmt) {
++ legacyStmt = db.prepare(`INSERT INTO principal_usage (principal, route, capability, auth, allowed, code, count) VALUES ('legacy-key', ?, '-', 'internal-key-route', ?, '', 1)
++ ON CONFLICT(principal, route, auth, allowed, code) DO UPDATE SET count = count + 1, last_at = CURRENT_TIMESTAMP`);
++ }
++ const route = `${req.method} ${req.baseUrl || ''}${req.route ? req.route.path : req.path}`;
++ legacyStmt.run(route, res.statusCode < 400 ? 1 : 0);
++ } catch { /* telemetry is best effort */ }
++ });
++}
function requireInternalKey(req, res, next) {
const key = req.headers['x-internal-key'];
const config = getConfig(req);
if (key && key === config.internalKey) {
req.internalKeyOk = true;
++ // Retirement telemetry (roadmap Wave 22): count every legacy-key call per route, so the key can
++ // be removed route by route once nothing uses it. Capability-guarded routes also record their
++ // decision; this covers the routes that only know the key.
++ countLegacyKey(req, res);
return next();
}
// Routes guarded by a capability also accept a service-principal token instead of the key;
ddiff --git a/test/principals.test.js b/test/principals.test.js
iindex dc3ed849..bdb432b7 100644
---- a/test/principals.test.js
++++ b/test/principals.test.js
@@@ -124,6 +124,10 @@ const server = http.createServer(app);
assert.strictEqual(r.status, 200);
r = await post('/internal/coins/credit', credit(), {});
assert.strictEqual(r.status, 403);
++ // Every legacy-key call is counted per route (Wave 22 retirement telemetry).
++ await new Promise((resolve) => setTimeout(resolve, 20));
++ const legacy = db.prepare("SELECT route, count FROM principal_usage WHERE principal = 'legacy-key' AND auth = 'internal-key-route' AND allowed = 1").all();
++ assert.ok(legacy.some(row => row.route === 'POST /internal/coins/credit' && row.count >= 1), JSON.stringify(legacy));
// ── Revocation: new tokens stop carrying a revoked grant ──
db.prepare("UPDATE principal_grants SET revoked_at = CURRENT_TIMESTAMP WHERE client_id = 'live' AND capability = 'network.coins.debit'").run();