47 lines
2.1 KiB
TypeScript
47 lines
2.1 KiB
TypeScript
import { createHash, randomBytes } from 'node:crypto';
|
|
import { db } from '../db/index.js';
|
|
|
|
const digest = (value: string) => createHash('sha256').update(value).digest('hex');
|
|
|
|
/** Private controller state, reused only by the same immutable native instance. */
|
|
export function bankCredential(instanceId: string): string {
|
|
return db()
|
|
.transaction(() => {
|
|
const instance = db()
|
|
.prepare('SELECT server FROM native_instances WHERE id=? AND retired_at IS NULL')
|
|
.get(instanceId) as { server: string } | undefined;
|
|
if (!instance) throw new Error('Bank credentials require an active native instance.');
|
|
const saved = db()
|
|
.prepare('SELECT token FROM bank_credentials WHERE instance_id=?')
|
|
.get(instanceId) as { token: string } | undefined;
|
|
if (saved) return saved.token;
|
|
const token = 'ec_bank_' + randomBytes(32).toString('base64url');
|
|
db()
|
|
.prepare(
|
|
'INSERT INTO bank_credentials(instance_id,server,token,token_hash,created_at) VALUES (?,?,?,?,?)'
|
|
)
|
|
.run(instanceId, instance.server, token, digest(token), new Date().toISOString());
|
|
return token;
|
|
})
|
|
.immediate();
|
|
}
|
|
|
|
/** Retiring an instance immediately revokes its game credential, including reads. */
|
|
export function authenticateBank(
|
|
request: Request
|
|
): { ok: true; server: string; instanceId: string } | { ok: false; status: 401; code: string } {
|
|
const token = request.headers
|
|
.get('authorization')
|
|
?.match(/^Bearer (ec_bank_[A-Za-z0-9_-]{32,128})$/i)?.[1];
|
|
const matched = token
|
|
? (db()
|
|
.prepare(
|
|
`SELECT c.server, c.instance_id AS instanceId
|
|
FROM bank_credentials c JOIN native_instances n ON n.id=c.instance_id AND n.server=c.server
|
|
WHERE c.token_hash=? AND n.retired_at IS NULL`
|
|
)
|
|
.get(digest(token)) as { server: string; instanceId: string } | undefined)
|
|
: undefined;
|
|
return matched ? { ok: true, ...matched } : { ok: false, status: 401, code: 'unauthorized' };
|
|
}
|