feat(admin): count revoked html-view capabilities in the logout audit line

The admin forced logout already revoked the user's rendered-HTML
capabilities but recorded only the cookie sessions and web-tab
capabilities it retired. The `user.logout` audit entry now carries
`revokedHtmlViews` next to `revokedWebviews`, and the revocation test
reads it back from admin-audit.jsonl.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-13 17:14:00 +02:00
1 parent 7dd78d9d71
commit 7b4832b3e4
2 files changed
+15 -3

No files matched your search

+2 -2
View File
@@ -184,8 +184,8 @@ export function registerAdminRoutes(app: FastifyInstance, ctx: SessionPort & Aut
// Web-tab proxy capabilities are a second credential the cookie purge does not
// touch; a forced logout that left them alive would not be a logout.
const revokedWebviews = webviewCapabilities.revokeOwner(normalizeUsername(username));
htmlViewCapabilities.revokeOwner(normalizeUsername(username));
audit(req, 'user.logout', username, { revoked, revokedWebviews });
const revokedHtmlViews = htmlViewCapabilities.revokeOwner(normalizeUsername(username));
audit(req, 'user.logout', username, { revoked, revokedWebviews, revokedHtmlViews });
return { success: true, data: { revoked } };
});
+13 -1
View File
@@ -9,7 +9,9 @@
* The rendered-HTML capabilities (`htmlViewCapabilities`) are pinned at the same sites.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import { readFileSync } from 'node:fs';
import { dataPath } from '../src/config/instance.js';
import { WebviewCapabilityStore, webviewCapabilities } from '../src/webview-capabilities.js';
import { HtmlViewCapabilityStore, htmlViewCapabilities } from '../src/html-view-capabilities.js';
import { createRouteTestHarness, type RouteTestHarness } from './routes/_route-test-utils.js';
@@ -157,6 +159,16 @@ describe('admin routes (multi-user)', () => {
expect(htmlViewCapabilities.resolve(bossPage)).toBeDefined();
webviewCapabilities.revokeWebview('wv-boss-forced');
htmlViewCapabilities.revokeOwner('boss');
// The audit line counts both kinds of capability it retired.
await vi.waitFor(() => {
const entries = readFileSync(dataPath('admin-audit.jsonl'), 'utf8')
.trim()
.split('\n')
.map((line) => JSON.parse(line));
const logout = entries.filter((e) => e.action === 'user.logout' && e.target === 'PEON').pop();
expect(logout?.detail).toMatchObject({ revokedWebviews: 1, revokedHtmlViews: 1 });
});
});
it('deleting a user revokes whatever that user had open', async () => {