Add persistent cross-game player bank (Refs #146)

This commit is contained in:
2026-10-12 20:21:58 -04:00
parent bcd3b99ee4
commit e071e5b574
14 changed files with 866 additions and 1 deletions
+41
View File
@@ -0,0 +1,41 @@
import { createHash, timingSafeEqual } from 'node:crypto';
const digest = (value: string) => createHash('sha256').update(value).digest();
const valid = (value: unknown): value is string =>
typeof value === 'string' && /^ec_bank_[A-Za-z0-9_-]{32,128}$/.test(value);
/** Dedicated, unique credentials; telemetry and browser credentials never authorize spending. */
export function authenticateBank(
request: Request
): { ok: true; server: string } | { ok: false; status: 401 | 503; code: string } {
let entries: [string, string][];
try {
const config: unknown = JSON.parse(process.env.EC_BANK_TOKENS ?? '{}');
if (!config || typeof config !== 'object' || Array.isArray(config)) throw new Error();
const values = Object.entries(config);
if (
!values.length ||
values.some(
([server, token]) =>
!server || !valid(token) || token === process.env.EC_TELEMETRY_TOKEN
)
)
throw new Error();
entries = values as [string, string][];
if (new Set(entries.map(([, token]) => token)).size !== entries.length) throw new Error();
} catch {
return { ok: false, status: 503, code: 'unconfigured' };
}
const token = request.headers
.get('authorization')
?.match(/^Bearer (ec_bank_[A-Za-z0-9_-]{32,128})$/i)?.[1];
if (!token) return { ok: false, status: 401, code: 'unauthorized' };
const supplied = digest(token);
let matched: string | undefined;
for (const [server, expected] of entries) {
if (timingSafeEqual(supplied, digest(expected))) matched = server;
}
return matched
? { ok: true, server: matched }
: { ok: false, status: 401, code: 'unauthorized' };
}