Add owner-only manual database integrity check

This commit is contained in:
Quintine committed 2026-09-30 13:04:00 +10:00
1 parent ebfae0f803
commit ddc303542a
8 files changed
+162 -21

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ void test('admin HTTP denies anonymous, forged admin, room-host and spoofed iden
assert.ok(cookie);
const room = (await created.json() as { code: string }).code;
for (const headers of [{}, { cookie }, { cookie: 'dune_admin_session=' + 'a'.repeat(64) }, { 'oai-authenticated-user-id': 'owner', 'oai-authenticated-user-email': 'owner@example.test' }] as Record<string, string>[]) {
for (const path of ['/api/admin/session', '/api/admin/accounts', '/api/admin/audit', '/api/admin/rooms', `/api/admin/rooms/${room}/control`, `/api/admin/rooms/${room}/lobby`, `/api/admin/rooms/${room}/removal`, `/api/admin/rooms/${room}/closure`, `/api/admin/rooms/${room}/archive`, `/api/admin/rooms/${room}/seat-ai`, `/api/admin/rooms/${room}/discussion`]) {
for (const path of ['/api/admin/session', '/api/admin/accounts', '/api/admin/audit', '/api/admin/operations?integrity=1', '/api/admin/rooms', `/api/admin/rooms/${room}/control`, `/api/admin/rooms/${room}/lobby`, `/api/admin/rooms/${room}/removal`, `/api/admin/rooms/${room}/closure`, `/api/admin/rooms/${room}/archive`, `/api/admin/rooms/${room}/seat-ai`, `/api/admin/rooms/${room}/discussion`]) {
const result = await fetch(base + path, { headers, signal: AbortSignal.timeout(15000) });
assert.equal(result.status, 401, path);
assert.equal(result.headers.get('cache-control'), 'no-store');
+41 -1
View File
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';
import test from 'node:test';
import { adminLogin, requireAdmin, AdminError } from '../db/admin-access';
import { readAdminOperations } from '../db/admin-operations';
import { readAdminIntegrity, readAdminOperations } from '../db/admin-operations';
import { adminStore } from './admin-access-fixture';
const now = 10_000;
@@ -35,6 +35,9 @@ void test('owner operations sample counts saved states and backup provenance wit
});
assert.equal(JSON.stringify(result).includes('secret'), false);
assert.deepEqual(store.sqlite.prepare('SELECT code,state,version FROM rooms ORDER BY code').all(), before);
assert.deepEqual(await readAdminIntegrity(store.database, identity, now),
{ observedAt: now, sqlite: 'passed', foreignKeys: 'passed' });
assert.deepEqual(store.sqlite.prepare('SELECT code,state,version FROM rooms ORDER BY code').all(), before);
} finally { store.sqlite.close(); }
});
@@ -53,3 +56,40 @@ void test('stale owner identity, demotion and revoked session cannot sample oper
await assert.rejects(readAdminOperations(store.database, identity, now), denied);
} finally { store.sqlite.close(); }
});
void test('manual integrity check detects orphaned foreign keys without exposing their rows', async () => {
const store = adminStore();
try {
const owner = store.provision('owner');
const login = await adminLogin(store.database, owner.key, now);
const identity = await requireAdmin(store.database, login.token, ['owner'], now);
store.sqlite.exec('PRAGMA foreign_keys=OFF');
store.sqlite.prepare('INSERT INTO seats(token_hash,room_code,player_id,revoked) VALUES(?,?,?,0)')
.run('private-orphan-token', 'GHOSTABC', 'private-player');
store.sqlite.exec('PRAGMA foreign_keys=ON');
const before = store.sqlite.prepare('SELECT * FROM seats').all();
const result = await readAdminIntegrity(store.database, identity, now);
assert.deepEqual(result, { observedAt: now, sqlite: 'passed', foreignKeys: 'failed' });
assert.equal(JSON.stringify(result).includes('private'), false);
assert.deepEqual(store.sqlite.prepare('SELECT * FROM seats').all(), before);
store.provision('owner', 'Backup owner');
store.sqlite.prepare("UPDATE admin_accounts SET role='operator' WHERE id=?").run(owner.id);
await assert.rejects(readAdminIntegrity(store.database, identity, now),
(error: unknown) => error instanceof AdminError && error.status === 403);
} finally { store.sqlite.close(); }
});
void test('manual integrity check revalidates owner authority before returning results', async () => {
const store = adminStore();
try {
const owner = store.provision('owner');
store.provision('owner', 'Remaining owner');
const login = await adminLogin(store.database, owner.key, now);
const identity = await requireAdmin(store.database, login.token, ['owner'], now);
store.hooks.beforeBatch = async () => {
store.sqlite.prepare("UPDATE admin_accounts SET role='viewer' WHERE id=?").run(owner.id);
};
await assert.rejects(readAdminIntegrity(store.database, identity, now),
(error: unknown) => error instanceof AdminError && error.status === 403);
} finally { store.sqlite.close(); }
});