// One-time migration: replace each seat line's raw token with its SHA-256, // so no ledger on disk or in a backup holds a live seat key. Idempotent; a // line already hashed is left alone. Run ON the droplet from the server // directory (tsx is installed there): // cd /opt/hnefatafl/app/server && npx tsx ../deploy/hash-tokens.ts /var/lib/hnefatafl/rooms // Browsers keep their raw tokens; the server hashes what they send and // compares, so nobody loses a seat. import { createHash } from 'node:crypto'; import { readdirSync, readFileSync, renameSync, statSync, writeFileSync, chownSync } from 'node:fs'; import { join } from 'node:path'; const dir = process.argv[2]; if (!dir) { console.error('usage: hash-tokens.ts '); process.exit(2); } let files = 0; let lines = 0; for (const file of readdirSync(dir).filter((f) => f.endsWith('.jsonl'))) { const path = join(dir, file); const raw = readFileSync(path, 'utf8'); let changed = 0; const out = raw .split('\n') .map((line) => { if (!line.trim()) return line; const entry = JSON.parse(line) as Record; if (entry.t !== 'seat' || typeof entry.token !== 'string') return line; const { token, ...rest } = entry; changed += 1; return JSON.stringify({ ...rest, tokenHash: token ? createHash('sha256').update(token).digest('hex') : '' }); }) .join('\n'); if (!changed) continue; const { uid, gid } = statSync(path); writeFileSync(path + '.tmp', out); chownSync(path + '.tmp', uid, gid); renameSync(path + '.tmp', path); files += 1; lines += changed; } console.log(`${files} ledger${files === 1 ? '' : 's'} rewritten, ${lines} seat line${lines === 1 ? '' : 's'} hashed`);