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
Changes
@@@ -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;@@@ -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();
@@ -17,11 +17,29 @@ function getDb(req) { return req.app.locals.db; }
@@ -17,11 +17,29 @@ function getDb(req) { return req.app.locals.db; }
function getConfig(req) { return req.app.locals.config; }
function getConfig(req) { return req.app.locals.config; }
// ── Internal Key Middleware ──────────────────────────────────
// ── 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) {
function requireInternalKey(req, res, next) {
const key = req.headers['x-internal-key'];
const key = req.headers['x-internal-key'];
const config = getConfig(req);
const config = getConfig(req);
if (key && key === config.internalKey) {
if (key && key === config.internalKey) {
req.internalKeyOk = true;
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();
return next();
}
}
// Routes guarded by a capability also accept a service-principal token instead of the key;
// Routes guarded by a capability also accept a service-principal token instead of the key;
@@ -124,6 +124,10 @@ const server = http.createServer(app);
@@ -124,6 +124,10 @@ const server = http.createServer(app);
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);
r = await post('/internal/coins/credit', credit(), {});
r = await post('/internal/coins/credit', credit(), {});
assert.strictEqual(r.status, 403);
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 ──
// ── 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();
db.prepare("UPDATE principal_grants SET revoked_at = CURRENT_TIMESTAMP WHERE client_id = 'live' AND capability = 'network.coins.debit'").run();