56 lines
2.4 KiB
TypeScript
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
|
|
);
|
|
}
|