Backend: Match-Lobbys, Map-Veto, Echtzeit und CS2-Serveradapter

Express + Socket.IO + SQLite. Veto-Engine mit konfigurierbaren Map-Pools
und validierten Abläufen, rollenbasierte Berechtigungen, Server-Lebenszyklus
mit Demo-, Docker- und Static-Pool-Adapter (RCON), automatisches Herunterfahren.
This commit is contained in:
mregosar committed 2026-10-25 16:50:22 +01:00
commit f08297dd0d
27 files changed
+6355

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
import crypto from 'node:crypto';
// Ohne leicht verwechselbare Zeichen (0/O, 1/I/L).
const CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
export function randomCode(length = 8) {
const bytes = crypto.randomBytes(length);
let out = '';
for (let i = 0; i < length; i++) out += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];
return out;
}
export function normalizeCode(input) {
return String(input ?? '')
.toUpperCase()
.replace(/[^A-Z0-9]/g, '');
}
export function formatCode(code) {
return code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;
}
export function randomToken() {
return crypto.randomBytes(32).toString('base64url');
}
export function randomId() {
return crypto.randomBytes(9).toString('base64url');
}
/** Alphanumerisches Passwort, sicher für Konsolenbefehle ohne Escaping. */
export function randomPassword(length = 16) {
const alphabet = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
const bytes = crypto.randomBytes(length);
let out = '';
for (let i = 0; i < length; i++) out += alphabet[bytes[i] % alphabet.length];
return out;
}
export function hashToken(token) {
return crypto.createHash('sha256').update(String(token)).digest('hex');
}
export function safeEqual(a, b) {
const ba = Buffer.from(String(a));
const bb = Buffer.from(String(b));
return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
}
/** Einfacher In-Memory-Ratenbegrenzer (Fenster pro Schlüssel). */
export function createRateLimiter({ windowMs, max }) {
const hits = new Map();
return function check(key) {
const now = Date.now();
const entry = hits.get(key);
if (!entry || now - entry.start > windowMs) {
hits.set(key, { start: now, count: 1 });
return true;
}
entry.count += 1;
if (hits.size > 10_000) {
for (const [k, v] of hits) if (now - v.start > windowMs) hits.delete(k);
}
return entry.count <= max;
};
}