feat: ATM10-Statistikportal
Statistikportal für den All-the-Mods-10-Server (Minecraft 1.21.1, NeoForge 21.1): - mod/: serverseitige NeoForge-Mod atmstats – Ereignisse, Live-Zähler, Servergesundheit, FTB-Dateien; Outbox auf der Festplatte, signierter gzip-Versand per HTTPS mit Backoff - backend/: Fastify + PostgreSQL – idempotenter Ingest, Projektionen, Parser für SNBT, FTB Quests/Teams, world/stats, world/advancements und latest.log; Ranglisten, Snapshots, Retention, öffentliche API, SSE-Live-Updates, Admin mit argon2/CSRF, SFTP-Worker - web/: Next.js-Dashboard – Übersicht, Ranglisten, Spielerprofile, ATM10, Server, Datenquellen, Verwaltung - shared/: Protokoll v1 und API-Typen; tools/: Simulator, Log-Anonymisierer, RCON-Client - deploy/: Docker Compose hinter Traefik, Backup; docs/: Architektur, Datenquellen, Betrieb Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
56d2078e6a
commit
e66f89f49e
214 files changed
+27077
No files matched your search
@@ -0,0 +1,34 @@
|
||||
import { createHash, createHmac } from 'node:crypto';
|
||||
import { gzipSync } from 'node:zlib';
|
||||
|
||||
export const TEST_SECRET = 'test-hmac-secret-0123456789abcdef0123';
|
||||
|
||||
export function signature(secret: string, ts: number, body: Buffer): string {
|
||||
const bodyHash = createHash('sha256').update(body).digest('hex');
|
||||
return createHmac('sha256', secret).update(`${ts}.${bodyHash}`).digest('hex');
|
||||
}
|
||||
|
||||
export interface SignOpts {
|
||||
token: string;
|
||||
secret?: string;
|
||||
ts?: number;
|
||||
gzip?: boolean;
|
||||
serverId?: string;
|
||||
}
|
||||
|
||||
/** Builds headers + payload exactly as the mod sends them: signature covers the transmitted (gzipped) bytes. */
|
||||
export function signedRequest(body: unknown, opts: SignOpts): { headers: Record<string, string>; payload: Buffer } {
|
||||
const raw = Buffer.from(typeof body === 'string' ? body : JSON.stringify(body));
|
||||
const gzip = opts.gzip ?? true;
|
||||
const payload = gzip ? gzipSync(raw) : raw;
|
||||
const ts = opts.ts ?? Date.now();
|
||||
const headers: Record<string, string> = {
|
||||
authorization: `Bearer ${opts.token}`,
|
||||
'content-type': 'application/json',
|
||||
'x-atm-server': opts.serverId ?? 'atm10',
|
||||
'x-atm-timestamp': String(ts),
|
||||
'x-atm-signature': signature(opts.secret ?? TEST_SECRET, ts, payload),
|
||||
};
|
||||
if (gzip) headers['content-encoding'] = 'gzip';
|
||||
return { headers, payload };
|
||||
}
|
||||
Reference in new issue
Block a user