2026-09-27 00:51:32 +02:00
|
|
|
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
|
|
|
import { join } from 'node:path';
|
|
|
|
|
|
|
|
|
|
/** Lädt *.env aus KC_ENV_DIR (Standard /etc/kundencenter), ohne bereits gesetzte Variablen zu überschreiben. */
|
|
|
|
|
function loadEnvDir(dir: string): void {
|
|
|
|
|
if (!existsSync(dir)) return;
|
|
|
|
|
for (const f of readdirSync(dir).filter((n) => n.endsWith('.env')).sort()) {
|
|
|
|
|
for (const line of readFileSync(join(dir, f), 'utf8').split('\n')) {
|
|
|
|
|
const m = /^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/.exec(line);
|
|
|
|
|
if (m && m[1] && process.env[m[1]] === undefined) process.env[m[1]] = m[2] ?? '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
loadEnvDir(process.env.KC_ENV_DIR ?? '/etc/kundencenter');
|
|
|
|
|
|
|
|
|
|
function req(name: string): string {
|
|
|
|
|
const v = process.env[name];
|
|
|
|
|
if (!v) throw new Error(`Konfiguration fehlt: ${name}`);
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const config = {
|
|
|
|
|
env: process.env.KC_NODE_ENV ?? 'development',
|
|
|
|
|
isProd: (process.env.KC_NODE_ENV ?? 'development') === 'production',
|
|
|
|
|
port: Number(process.env.KC_API_PORT ?? 4100),
|
|
|
|
|
baseUrl: process.env.KC_BASE_URL ?? 'http://localhost:4101',
|
|
|
|
|
/** Erlaubte Browser-Origins (CSRF/Origin-Prüfung): baseUrl plus optional KC_ALLOWED_ORIGINS (kommagetrennt). */
|
|
|
|
|
allowedOrigins: new Set([process.env.KC_BASE_URL ?? 'http://localhost:4101', ...(process.env.KC_ALLOWED_ORIGINS ?? '').split(',').map((o) => o.trim()).filter(Boolean)]),
|
|
|
|
|
secretKey: Buffer.from(req('KC_SECRET_KEY'), 'base64'),
|
feat(license): Kundencenter-Lizenzierung gegen licensing.flessinglabs.com (#116)
Milestone "Kundencenter lizenzierbar machen": die Installation prüft jetzt
ihre eigene Lizenz beim Lizenzsystem (licensing.flessinglabs.com, Programm
"Kundencenter") und setzt die Kundengrenze serverseitig durch - nicht zu
verwechseln mit dem bestehenden "licensing"-Connector, der KUNDEN-Lizenzen
verwaltet, die das Kundencenter weiterverkauft.
- Migration 035: license_state (ein Lizenzschlüssel, verschlüsselt wie
andere Secrets, stabile instance_id, zuletzt geprüfter Entitlement-
Snapshot), license_signing_keys (öffentliche Signaturschlüssel, werden
nur ergänzt, nie anhand einer Serverantwort gelöscht).
- @kc/platform/license: checkLicenseNow() ruft POST /license/verify mit
X-Program-Key auf, prüft die FLS1-Signatur (RSA-2048, PKCS#1 v1.5,
SHA-256) und schreibt das Ergebnis in license_state. getEntitlement()
liefert den nutzbaren Stand inkl. Offline-Fenster (Standard 7 Tage,
verlängert nie eine tatsächlich abgelaufene Lizenz). assertCustomerCapacity()
sperrt neue/reaktivierte Kunden transaktional (GET_LOCK gegen den letzten
freien Platz) bei erreichter Kundengrenze oder ungültiger Lizenz - aber
NICHT, solange die Lizenzierung noch gar nicht konfiguriert oder noch nie
geprüft wurde (sonst wäre jede frische Installation von Anfang an blockiert).
- Durchsetzung an allen drei Stellen, die einen neuen Kundenplatz belegen:
Kundenanlage, Reaktivierung (closed→active) und KeyHelp-Import neuer
(nicht verknüpfter) Kunden.
- Worker prüft alle 12 h automatisch (zusätzlich zur Prüfung beim Speichern
eines neuen Schlüssels). API: GET/PUT /admin/license/settings,
POST /admin/license/check. UI: Einstellungen → Lizenz (Status, Module,
Kundengrenze, Schlüssel eingeben, manuell prüfen).
Beim Testen einen echten Bug gefunden und behoben: Buffer.from(hex,'hex')
verwirft bei ungerader Hex-Länge stillschweigend die letzte Ziffer (der
Exponent "10001"/65537 wurde fälschlich zu 0x1000) - hätte jede Signatur-
prüfung unbemerkt scheitern lassen. Mit geradem Padding gegen echte
Serverantworten (gültig und manipuliert) verifiziert.
Gegen die echte Produktions-API getestet (Program-Key aus LICENSE_PROGRAM_KEY):
Status/Settings/Check-Endpunkte, Signaturprüfung mit echten Schlüsseln
erfolgreich, Kundenanlage korrekt blockiert bei geprüfter ungültiger Lizenz,
danach wieder sauber in den unkonfigurierten (nicht blockierenden) Zustand
zurückgesetzt, da noch kein echter Lizenzschlüssel für diese Installation
vorliegt. Testsuite 58/59 (eine vorbestehende, unabhängige datumsabhängige
Flakiness in orders.test.ts, auch ohne diese Änderungen reproduzierbar).
Audit-Kette und Backup+Wiederherstellungstest danach weiterhin grün.
Offen: Lizenzschlüssel für diese Installation selbst anlegen (Admin-Panel
licensing.flessinglabs.com) und unter Einstellungen > Lizenz eintragen.
Modul-Gates (billing/domains/provisioning/discord/automation) an den
einzelnen Funktionsbereichen sowie die Lizenzübersicht-Admin-UI (#117)
sind noch zu bauen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 10:48:48 +02:00
|
|
|
// Identifiziert das Programm "Kundencenter" selbst gegenüber licensing.flessinglabs.com (kein echtes
|
|
|
|
|
// Geheimnis, steckt in jeder Installation gleich). Der je Installation unterschiedliche Lizenzschlüssel
|
|
|
|
|
// liegt dagegen verschlüsselt in der DB (Einstellungen > Lizenz), nicht hier.
|
|
|
|
|
licenseProgramKey: process.env.LICENSE_PROGRAM_KEY || null,
|
|
|
|
|
licenseApiBaseUrl: process.env.LICENSE_API_BASE_URL || 'https://licensing.flessinglabs.com/api',
|
2026-09-27 00:51:32 +02:00
|
|
|
db: {
|
|
|
|
|
host: process.env.DB_HOST ?? '127.0.0.1',
|
|
|
|
|
port: Number(process.env.DB_PORT ?? 3306),
|
|
|
|
|
database: req('DB_NAME'),
|
|
|
|
|
user: req('DB_USER'),
|
|
|
|
|
password: req('DB_PASSWORD'),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
if (config.secretKey.length !== 32) throw new Error('KC_SECRET_KEY muss 32 Byte (base64) sein');
|