Files
electric-cherry/src/lib/server/moderation/credentials.ts
T
2026-10-15 21:33:50 -04:00

56 lines
2.4 KiB
TypeScript

import { createHash, randomBytes } from 'node:crypto';
import { db } from '../db/index.js';
import type { ModerationGame } from '../../moderation.js';
const digest = (value: string) => createHash('sha256').update(value).digest('hex');
export function moderationCredential(instanceId: string, generation: number): string {
return db()
.transaction(() => {
if (
!Number.isSafeInteger(generation) ||
!db()
.prepare(
'SELECT 1 FROM native_instances WHERE id=? AND generation IN (?,?) AND retired_at IS NULL'
)
.get(instanceId, generation, generation - 1)
)
throw new Error('Moderation credentials require an active instance generation.');
const existing = db()
.prepare(
'SELECT token FROM moderation_credentials WHERE instance_id=? AND generation=?'
)
.get(instanceId, generation) as { token: string } | undefined;
if (existing) return existing.token;
const token = 'ec_moderation_' + randomBytes(32).toString('base64url');
db()
.prepare('INSERT INTO moderation_credentials VALUES(?,?,?,?,?)')
.run(instanceId, generation, token, digest(token), new Date().toISOString());
return token;
})
.immediate();
}
export interface ModerationRuntimeIdentity {
instanceId: string;
generation: number;
server: string;
game: ModerationGame;
serverCreatedAt: string;
credentialHash: string;
}
export function authenticateModerationRuntime(request: Request): ModerationRuntimeIdentity | null {
const token = request.headers
.get('authorization')
?.match(/^Bearer (ec_moderation_[A-Za-z0-9_-]{43})$/)?.[1];
if (!token) return null;
return (
(db()
.prepare(
`SELECT n.id AS instanceId,n.generation,n.server,s.game,s.created_at AS serverCreatedAt,c.token_hash AS credentialHash
FROM moderation_credentials c JOIN native_instances n ON n.id=c.instance_id AND n.generation=c.generation
JOIN servers s ON s.name=n.server
WHERE c.token_hash=? AND n.retired_at IS NULL AND s.destroyed_at IS NULL AND s.game IN ('t5','t6')`
)
.get(digest(token)) as ModerationRuntimeIdentity | undefined) ?? null
);
}