npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

crazylizenz

v1.0.0

Published

Lizenzschlüssel ausstellen und prüfen — ein fertiger Lizenzserver zum Selbsthosten, ohne Shop und ohne Datenbank.

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 crazylizenz

Das 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 4280

Dauerhaft installieren:

npm install -g crazylizenz

Oder aus dem Quelltext, wenn du daran arbeiten willst:

npm install && npm start

Im 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-5QDTR

und einen Endpunkt, den dein Programm beim Start fragt:

GET /api/v1/check?key=AGP-…&machine=mc-01&nonce=a1b2c3d4

Wer 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üfungen

src/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 test

Eigene 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.