Add administrator sign-in and private-safe room directory
Connect personal operator-provisioned admin keys, persisted roles and eight-hour sessions, current/all-session revocation and a filtered room directory. Additive schema protects final owner, revokes disabled accounts and records safe audit history. No game state changes; remaining administration scope stays explicit. Verified: typecheck, lint, 5196 offline tests, 52 HTTP tests, production build; authenticated local HTTP and desktop/mobile browser checks. Independent privacy/security review fixed malformed nested roster searches and read-time session revocation. Preserved 1,671 prior rooms and 3,538 seats. Container/deployed acceptance follows publication.
This commit is contained in:
1 parent
36a03bda9c
commit
c2d392e155
27 files changed
+3164
-13
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import type { AdminRole } from '../db/admin-access';
|
||||
|
||||
export function adminStore(path = ':memory:', migrate = true) {
|
||||
const sqlite = new DatabaseSync(path);
|
||||
sqlite.exec('PRAGMA foreign_keys = ON');
|
||||
if (migrate) {
|
||||
for (const file of readdirSync(new URL('../drizzle/', import.meta.url))
|
||||
.filter((file) => file.endsWith('.sql'))
|
||||
.sort())
|
||||
sqlite.exec(
|
||||
readFileSync(new URL('../drizzle/' + file, import.meta.url), 'utf8'),
|
||||
);
|
||||
}
|
||||
const hooks: { beforeBatch?: () => Promise<void> } = {};
|
||||
class Statement {
|
||||
values: (string | number | null)[] = [];
|
||||
constructor(readonly sql: string) {}
|
||||
bind(...values: (string | number | null)[]) {
|
||||
this.values = values;
|
||||
return this;
|
||||
}
|
||||
async first() {
|
||||
return sqlite.prepare(this.sql).get(...this.values) ?? null;
|
||||
}
|
||||
async all() {
|
||||
return {
|
||||
results: sqlite.prepare(this.sql).all(...this.values),
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
async run() {
|
||||
const statement = sqlite.prepare(this.sql);
|
||||
if (statement.columns().length) {
|
||||
return {
|
||||
results: statement.all(...this.values),
|
||||
meta: { changes: 0 },
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
const result = statement.run(...this.values);
|
||||
return {
|
||||
results: [],
|
||||
meta: { changes: Number(result.changes) },
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
const database = {
|
||||
prepare: (sql: string) => new Statement(sql),
|
||||
batch: async (statements: Statement[]) => {
|
||||
await hooks.beforeBatch?.();
|
||||
sqlite.exec('BEGIN');
|
||||
try {
|
||||
const results = [];
|
||||
for (const statement of statements) results.push(await statement.run());
|
||||
sqlite.exec('COMMIT');
|
||||
return results;
|
||||
} catch (error) {
|
||||
sqlite.exec('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
} as unknown as D1Database;
|
||||
function provision(role: AdminRole = 'owner', name = 'Admin') {
|
||||
const id = randomUUID();
|
||||
const key = `dune-admin.${id}.${randomBytes(32).toString('hex')}`;
|
||||
sqlite
|
||||
.prepare(`INSERT INTO admin_accounts
|
||||
(id,name,role,key_hash,created_at,updated_at) VALUES (?,?,?,?,?,?)`)
|
||||
.run(id, name, role, sha256(key), 1000, 1000);
|
||||
return { id, key, name, role };
|
||||
}
|
||||
return { sqlite, database, hooks, provision };
|
||||
}
|
||||
|
||||
export const sha256 = (value: string) =>
|
||||
createHash('sha256').update(value).digest('hex');
|
||||
|
||||
export function pauseAdminBatch(store: ReturnType<typeof adminStore>) {
|
||||
let entered!: () => void;
|
||||
let release!: () => void;
|
||||
const waiting = new Promise<void>((resolve) => (entered = resolve));
|
||||
const gate = new Promise<void>((resolve) => (release = resolve));
|
||||
store.hooks.beforeBatch = async () => {
|
||||
delete store.hooks.beforeBatch;
|
||||
entered();
|
||||
await gate;
|
||||
};
|
||||
return { waiting, release };
|
||||
}
|
||||
Reference in new issue
Block a user