diff --git a/server/curseforge-proxy/.env.example b/server/curseforge-proxy/.env.example index fced0a2..31421ca 100644 --- a/server/curseforge-proxy/.env.example +++ b/server/curseforge-proxy/.env.example @@ -19,9 +19,14 @@ HOST=0.0.0.0 # weiterlaufen, bis alle das Update haben – danach das alte streichen. # CF_PROXY_TOKEN=neues-token,altes-token # -# Leer lassen = keine Tokenprüfung. +# Pflicht: Ohne Token startet der Dienst nicht – sonst könnte jeder, der die +# Adresse kennt, deinen API-Key und dein Download-Kontingent benutzen. +# Ein langes Zufalls-Token erzeugen, z. B.: openssl rand -hex 32 CF_PROXY_TOKEN= +# Nur für ein abgeschottetes Netz: Dienst bewusst OHNE Token betreiben. +# CF_PROXY_OFFEN=1 + # Anfragen je Minute und Absender. 0 = keine Begrenzung. RATE_LIMIT_PER_MIN=60 diff --git a/server/curseforge-proxy/README.md b/server/curseforge-proxy/README.md index bc8139d..fd9f16b 100644 --- a/server/curseforge-proxy/README.md +++ b/server/curseforge-proxy/README.md @@ -101,7 +101,8 @@ Der Dienst steht offen im Netz. Eingebaut ist: die Host-Liste geprüft, höchstens vier Sprünge. - **Ratenbegrenzung** je Absender, einstellbar über `RATE_LIMIT_PER_MIN`. - **Größengrenze** für durchgereichte Dateien über `MAX_DOWNLOAD_MB`. -- **Optionales Token** über `CF_PROXY_TOKEN`. +- **Token** über `CF_PROXY_TOKEN` – Pflicht: ohne Token startet der Dienst nicht + (bewusst offen nur mit `CF_PROXY_OFFEN=1`, z. B. im abgeschotteten Netz). Zum Token eine ehrliche Einordnung: Es steckt dann auch im Launcher und ist dort auslesbar. Es hält Gelegenheitsnutzer ab, keine entschlossenen. Die diff --git a/server/curseforge-proxy/index.js b/server/curseforge-proxy/index.js index 733b727..92575d7 100644 --- a/server/curseforge-proxy/index.js +++ b/server/curseforge-proxy/index.js @@ -1,6 +1,7 @@ 'use strict'; const http = require('http'); +const crypto = require('crypto'); const { Readable } = require('stream'); const KEY = (process.env.CF_API_KEY || '').trim(); @@ -35,6 +36,17 @@ if (!KEY) { process.exit(1); } +// Ohne Token wäre der Dienst für jeden offen, der die Adresse kennt – und damit +// auch der API-Key und das Download-Kontingent dahinter. Deshalb startet er +// ohne CF_PROXY_TOKEN gar nicht erst. Wer ihn bewusst offen betreiben will +// (z. B. nur im eigenen Netz), muss das ausdrücklich sagen: CF_PROXY_OFFEN=1. +const OFFEN_ERLAUBT = process.env.CF_PROXY_OFFEN === '1'; +if (!TOKENS.size && !OFFEN_ERLAUBT) { + console.error('CF_PROXY_TOKEN fehlt. Ohne Token könnte jeder den Dienst (und deinen API-Key) benutzen.'); + console.error('Token setzen (siehe .env.example) – oder, nur für ein abgeschottetes Netz, CF_PROXY_OFFEN=1.'); + process.exit(1); +} + // --------------------------------------------------------------------------- // Ratenbegrenzung: einfaches Zeitfenster je Absender // --------------------------------------------------------------------------- @@ -88,9 +100,21 @@ function sendeJson(res, code, daten) { res.end(koerper); } +// Vergleich in gleichbleibender Zeit, damit sich ein Token nicht Zeichen für +// Zeichen über die Antwortdauer erraten lässt +function gleich(a, b) { + const x = crypto.createHash('sha256').update(a).digest(); + const y = crypto.createHash('sha256').update(b).digest(); + return crypto.timingSafeEqual(x, y); +} + function tokenOk(req) { - if (!TOKENS.size) return true; - return TOKENS.has(String(req.headers['x-aeromc-token'] || '').trim()); + if (!TOKENS.size) return OFFEN_ERLAUBT; // ohne Token nur mit ausdrücklicher Freigabe + const erhalten = String(req.headers['x-aeromc-token'] || '').trim(); + if (!erhalten) return false; + let ok = false; + for (const t of TOKENS) if (gleich(t, erhalten)) ok = true; + return ok; } function leseKoerper(req, maxBytes = 1_000_000) { @@ -308,7 +332,7 @@ server.listen(PORT, HOST, () => { console.log(`AeroMC CurseForge-Weiterleitung läuft auf ${HOST}:${PORT}`); console.log(` Ratenbegrenzung: ${LIMIT_PRO_MIN > 0 ? LIMIT_PRO_MIN + '/Minute je Absender' : 'aus'}`); // niemals die Token selbst ausgeben – Protokolle wandern schnell woanders hin - console.log(` Token verlangt : ${TOKENS.size ? 'ja (' + TOKENS.size + ' gültige)' : 'nein'}`); + console.log(` Token verlangt : ${TOKENS.size ? 'ja (' + TOKENS.size + ' gültige)' : 'NEIN – offen für jeden (CF_PROXY_OFFEN=1)'}`); console.log(` Proxy-Stufen : ${PROXY_STUFEN} (${PROXY_STUFEN ? 'Absender aus X-Forwarded-For' : 'Absender aus der Verbindung'})`); }); diff --git a/src/assets/design-aeromc.png b/src/assets/design-aeromc.png new file mode 100644 index 0000000..bb8bc31 Binary files /dev/null and b/src/assets/design-aeromc.png differ diff --git a/src/assets/design-minecraft.png b/src/assets/design-minecraft.png new file mode 100644 index 0000000..8f569b2 Binary files /dev/null and b/src/assets/design-minecraft.png differ diff --git a/src/assets/design-nachtlila.png b/src/assets/design-nachtlila.png new file mode 100644 index 0000000..b7b72b6 Binary files /dev/null and b/src/assets/design-nachtlila.png differ