crazylizenz
v1.0.0
Published
Lizenzschlüssel ausstellen und prüfen — ein fertiger Lizenzserver zum Selbsthosten, ohne Shop und ohne Datenbank.
Maintainers
Readme
crazylizenz
Lizenzschlüssel ausstellen und prüfen. Mehr nicht — kein Shop, keine Zahlungen, keine Kundenkonten, nichts, was über der Lizenz stünde.
Ein fertiger Lizenzserver zum Selbsthosten: Node.js + Express, Ablage in einer JSON-Datei, keine Datenbank. Port 4280.
npx crazylizenzDas war es. Beim ersten Start entsteht ein Zugang; das Passwort steht einmalig in der Konsole und danach nirgends mehr — schreib es dir auf.
Die Lizenzen landen in ./signet-daten, also dort, wo du den Befehl aufrufst.
Ruf ihn künftig aus demselben Verzeichnis auf, sonst fängt er von vorn an;
oder gib den Ort fest an:
crazylizenz --daten /var/lib/crazylizenz --host 0.0.0.0 --port 4280Dauerhaft installieren:
npm install -g crazylizenzOder aus dem Quelltext, wenn du daran arbeiten willst:
npm install && npm startIm Quelltext heißt das Ganze Signet — Paket und Programm sind dasselbe.
Wofür das da ist
Du hast ein Plugin, ein Programm, einen Dienst — und willst wissen, wer ihn benutzen darf. crazylizenz gibt dir Schlüssel wie
AGP-M7FEB-7XV4L-UM6XZ-5QDTRund einen Endpunkt, den dein Programm beim Start fragt:
GET /api/v1/check?key=AGP-…&machine=mc-01&nonce=a1b2c3d4Wer verkauft, lässt die Schlüssel über eine Schnittstelle aus seinem Shop oder Bot heraus erzeugen.
Was es nicht macht: verkaufen, abrechnen, Kunden verwalten. Wer das zusammen will, braucht einen Shop davor — dieser Server macht nur Lizenzen.
Nur Lizenzen
Es gibt genau eine Sorte Ding hier: die Lizenz. Ein Schlüssel, ein Inhaber, ein paar Plätze, vielleicht ein Ablaufdatum. Nichts darüber — keine Gruppe, zu der sie gehört, und nichts, wofür sie gilt außer dem einen Programm, das fragt.
Wer mehrere Programme getrennt lizenzieren will, startet einen zweiten
Server daneben — anderer Port, anderes SIGNET_DATA, anderes Vorzeichen. Zwei
Prozesse mit je einer JSON-Datei sind billiger als eine Hierarchie, die man
danach in jeder Abfrage, jeder Unterschrift und jeder Oberfläche mitschleppt.
Eine Ablage aus einer früheren Fassung wird beim Start umgestellt; die Lizenzen behalten ihre Schlüssel. Was dabei nicht mehr passt, sagt die Konsole beim ersten Start.
Ein Protokoll, zwei Server
crazylizenz spricht dasselbe Protokoll wie der CrazyDevelopment Hub. Dieselben Clients funktionieren gegen beide; man setzt nur die Adresse um:
const license = new License({
secret: process.env.LICENSE_SECRET,
server: 'mc-01',
url: 'https://licenses.crazyde.de'
});Das ist alles, was der Client hier braucht. (Der Hub bedient viele Programme und
verlangt deshalb zusätzlich, welches gemeint ist; diese Angabe geht dort in die
Unterschrift ein. Wer sie hier mitschickt, bekommt signature zurück — was
richtig ist, denn sie gehört nicht hierher.)
Deshalb gibt es die Hub-Pfade /api/license/check und /api/license/release
zusätzlich zu /api/v1/….
Die Schlüssel
AGP-4522Z-NH2FU-QEFVG-AU86E — vier Gruppen zu fünf Zeichen, mit einem
Vorzeichen davor.
Warum keine UUID: Schlüssel werden abgetippt — vom Zettel, aus einer Mail,
am Telefon. Deshalb kein I, O, 0 und 1 (die verwechselt man
gegenseitig), Gruppen zu fünf (man verliert die Stelle nicht), und
Groß-/Kleinschreibung sowie Bindestriche sind egal.
Der Zufall ist gleichverteilt: Werte, die beim Rest-Rechnen die ersten Zeichen bevorzugen würden, werden verworfen statt gebogen. Der Test rechnet das nach.
Das Vorzeichen lässt sich nur setzen, solange keine Lizenz ausgestellt ist. Danach würde eine Änderung alle ausgegebenen Schlüssel unlesbar machen — die Prüfung erwartet das eingestellte Vorzeichen. Die Oberfläche zeigt das Feld dann gar nicht mehr an, sondern schreibt hin, warum.
Die Unterschrift
Jede Antwort wird mit HMAC-SHA256 unterschrieben:
HMAC-SHA256(geheimnis, key + "|" + (valid ? "1" : "0") + "|" + time + "|" + nonce)Ohne sie genügt ein Eintrag in der hosts-Datei, um einen eigenen „Lizenzserver" unterzuschieben, der immer ja sagt.
Drei Entscheidungen dazu:
- Ein Geheimnis für alles. Es gibt nur einen Server und eine Sorte Schlüssel; ein Geheimnis je Gruppe hätte nichts zu trennen. Wer trennen will, nimmt eine zweite Instanz — die hat dann auch ein eigenes Geheimnis.
- Auch eine Absage wird unterschrieben. Sonst könnte jemand eine Absage unterwegs wegwerfen und durch nichts ersetzen; der Client sähe eine fehlende Antwort und ginge in die Schonfrist.
- Der Zufallswert (
nonce) kommt vom Client und wird zurückgegeben. Ohne ihn ließe sich eine einmal mitgeschnittene Ja-Antwort beliebig oft wieder einspielen.
Ein neues Geheimnis lässt sich setzen — dann lehnt aber jeder ausgelieferte Client die Antworten ab, bis er es hat. Die Oberfläche sagt das, bevor man klickt.
Plätze
Jede Lizenz hat eine Zahl von Plätzen. Meldet sich eine Maschine mit machine=,
belegt sie einen; ist keiner mehr frei, kommt seat_limit zurück. 0 heißt
unbegrenzt.
Ein Platz lässt sich freigeben — in der Verwaltung oder über
POST /api/v1/release. Das braucht keinen API-Schlüssel: Wer den
Lizenzschlüssel hat, darf seinen eigenen Platz auch räumen.
Ohne machine= wird gar nicht gezählt. Das ist die Prüfung ohne Anspruch auf
einen Platz — etwa aus einem Verwaltungswerkzeug.
API-Schlüssel
Für Programme statt Menschen: Authorization: Bearer sk_…
Ein API-Schlüssel darf Lizenzen ausstellen, nachschlagen und sperren — mehr nicht. Das Geheimnis erneuern oder das Vorzeichen setzen geht nur in der Verwaltung. Ein Schlüssel, der in einem Bot steckt, soll nicht dieselben Rechte haben wie jemand, der vor der Verwaltung sitzt.
curl -X POST http://localhost:4280/api/v1/licenses \
-H "Authorization: Bearer sk_…" \
-H "Content-Type: application/json" \
-d '{"owner":"Lena Berg","email":"[email protected]","seats":1,"days":365}'Der Schlüssel wird genau einmal angezeigt. Danach steht in der Liste nur noch, worauf er endet.
Gründe
Alle englisch — sie gehen an fremde Programme. Die Oberfläche bleibt deutsch.
| reason | Bedeutung |
|---|---|
| ok | Alles in Ordnung |
| format | Kein Schlüssel von hier — fremdes Vorzeichen oder verschrieben |
| unknown | Den Schlüssel gibt es nicht |
| blocked | Gesperrt |
| expired | Abgelaufen |
| not_yet | Gilt erst später |
| seat_limit | Alle Plätze belegt |
| missing_key | Der Parameter key fehlt (HTTP 400) |
Was die Antwort nicht verrät: Ein Schlüssel mit fremdem Vorzeichen bekommt
format, und die Ablage wird dafür gar nicht erst durchsucht. Das bestätigt
nicht einmal, dass es einen solchen Schlüssel geben könnte.
Was bewusst fehlt
Kein Kundenkonto. Wer eine Lizenz hat, hat einen Schlüssel; wer keinen hat, hat nichts zu suchen. Ein Login je Kunde wäre eine zweite Benutzerverwaltung für nichts.
Geschrieben wird asynchron. Eine Änderung steht sofort im Speicher und wandert kurz darauf auf die Platte — atomar, über eine Nebendatei. Wer den Prozess in genau diesem Moment hart abschießt, verliert die letzte Änderung. Bei einem normalen Beenden passiert das nicht.
Keine Lizenz wird je gelöscht, nur gesperrt. Ein gelöschter Schlüssel heißt: Der Kunde steht plötzlich ohne da, und niemand kann nachvollziehen, dass er je einen hatte. Die Ablage weigert sich sogar zu speichern, wenn die Liste schrumpfen würde.
Noch nicht da: Mailversand an den Kunden, Ablauf-Erinnerungen, ein Selbstbedienungs-Portal zum Umziehen von Plätzen.
Aufbau
server.js Routen, Prüfung, Verwaltung
src/schluessel.js Erzeugen, Lesen, Unterschreiben
src/pruefung.js Die Entscheidung — ohne Seiteneffekte, einzeln testbar
src/db.js JSON-Ablage, atomar geschrieben
src/auth.js scrypt-Passwörter, Sitzungen im Arbeitsspeicher
public/ Oberfläche — ein CSS, ein JS, kein Aufbauwerkzeug
test/alles.js 95 Prüfungensrc/pruefung.js trifft die Entscheidung und ändert dabei nichts. Das Buchen
des Platzes und das Hochzählen der Zähler macht der Aufrufer. So lässt sich die
Entscheidung lesen und einzeln prüfen, ohne dass dabei etwas passiert.
Die Seiten: / sind die Lizenzen (die Startseite ist die Liste — eine
Übersicht, die auf eine zweite Seite verweist, wäre ein Klick für nichts),
/api die API-Schlüssel, /einstellungen Vorzeichen, Geheimnis und Protokoll,
/doku die Schnittstelle.
Prüfen
npm testEigene Instanz auf 4281, eigene Ablage in einem Temp-Verzeichnis. Der letzte
Abschnitt fährt die echten Clients aus ../clients gegen den Server —
inklusive
der Fälle, dass ein Client mit falschem Geheimnis die Antwort verwerfen muss und
dass einer, der für den Hub eingestellt ist, hier nicht durchkommt.
