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>
This commit is contained in:
parent
3240e6df79
commit
d033088223
10 changed files with 340 additions and 1 deletions
37
migrations/035_license_state.sql
Normal file
37
migrations/035_license_state.sql
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- Kundencenter-Lizenzierung (Meilenstein #113-#117): eigenes Lizenzsystem-Konto (licensing.flessinglabs.com),
|
||||
-- nicht zu verwechseln mit dem bestehenden "licensing"-Connector (der verwaltet KUNDEN-Lizenzen, die das
|
||||
-- Kundencenter weiterverkauft). Dies hier ist die Lizenz der Kundencenter-Installation selbst.
|
||||
CREATE TABLE license_state (
|
||||
id TINYINT PRIMARY KEY DEFAULT 1,
|
||||
license_key_enc TEXT NULL, -- verschlüsselt (wie andere Secrets), vom Betreiber selbst eingetragen
|
||||
instance_id CHAR(36) NOT NULL, -- einmal erzeugt, bleibt stabil (zählt sonst als neues Gerät)
|
||||
-- zuletzt erfolgreich geprüfter (und signaturgeprüfter) Entitlement-Snapshot
|
||||
valid BOOLEAN NULL,
|
||||
message VARCHAR(300) NULL,
|
||||
plan VARCHAR(100) NULL,
|
||||
modules_json JSON NULL,
|
||||
expires_at DATETIME NULL,
|
||||
user_limit INT NULL,
|
||||
customer_limit INT NULL,
|
||||
entitlement_version INT NULL,
|
||||
is_trial BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
addons_json JSON NULL,
|
||||
server_time DATETIME NULL,
|
||||
checked_at DATETIME(3) NULL, -- Zeitpunkt der letzten ERFOLGREICHEN Online-Prüfung (Basis fürs Offline-Fenster)
|
||||
last_attempt_at DATETIME(3) NULL,
|
||||
last_error VARCHAR(500) NULL,
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
||||
);
|
||||
INSERT INTO license_state (id, instance_id) VALUES (1, UUID());
|
||||
|
||||
-- Öffentliche Signaturschlüssel (GET /license/signing-keys), lokal zwischengespeichert statt bei jeder Prüfung
|
||||
-- abgerufen - sonst könnte ein kompromittierter/vorgetäuschter Server zu einer gefälschten Antwort auch gleich
|
||||
-- einen passenden Schlüssel mitliefern. Werden nur ergänzt, nie anhand der Liste selbst gelöscht.
|
||||
CREATE TABLE license_signing_keys (
|
||||
kid VARCHAR(100) PRIMARY KEY,
|
||||
alg VARCHAR(20) NOT NULL,
|
||||
n_hex TEXT NOT NULL,
|
||||
e_hex VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
fetched_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue