6 Commits
Author SHA1 Message Date
florian.wildeandClaude Opus 5 3657b60d16 feat(hosts): Doppelte Hosts erkennen und stilllegen (#041)
Zwei Eintraege desselben Namens beim selben Kunden entstehen, wenn sich die
Kennung aus /etc/machine-id aendert - Neuinstallation, geklonter Container,
zurueckgespielte Sicherung. Dieselbe Maschine meldet sich dann als neuer Host,
der alte Eintrag bleibt stehen. Zwei getrennte Maschinen mit gleichem Namen
sind aber genauso moeglich.

Neue dreizehnte Warnregel meldet den Fall und nennt die IP-Adressen zum
Unterscheiden; gleiche Namen bei verschiedenen Kunden bleiben still.

Dazu die bis dahin fehlende Moeglichkeit, einen Host loszuwerden: Stilllegen
blendet ihn aus Listen, Zahlen, Suche und Warnungen aus und schliesst seine
offenen Warnungen, laesst Laufzeiten und Rechnungen aber unberuehrt.
Endgueltig loeschen geht nur ohne Laufzeiten - Buchhaltung wirft man nicht weg.

Kein automatisches Zusammenfuehren: Es muesste raten, welcher Eintrag welche
Rechnung erbt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 17:57:18 +02:00
florian.wildeandClaude Opus 5 e50fb30ab3 feat(rechnungen): Rabatt je Position, Reverse Charge, Ablauf-Regel (#037)
Eine echte Proxmox-Rechnung zeigt je Subscription-Key eine Zeile mit
Listenpreis und eine mit "Client Discount". Unser Modell hing den Rabatt an
die Rechnung - bei einer Sammelrechnung ueber zwei Hosts liesse er sich damit
keinem Host zuordnen, und die Marge je Host waere falsch. Schlimmer: Er wurde
nirgends abgezogen.

Jetzt haengt der Rabatt an der Position, der Rechnungsbetrag ist die Summe
nach Abzug, und Marge wie Summen rechnen mit dem tatsaechlich gezahlten
Betrag. Die Migration verteilt vorhandene Rabatte anteilig auf die Positionen
und rechnet die Summen neu - nichts geht verloren.

Dazu ein eigener Steuersatz fuer den Einkauf mit Vorgabe 0: Proxmox rechnet
gegenueber Unternehmen mit USt-IdNr. ueber Reverse Charge ab.

Neue zwoelfte Warnregel aus Florians Ablauf: Der Kunde hat bezahlt, die
Proxmox-Rechnung ist noch offen - der Moment, an dem man selbst zahlt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 16:42:12 +02:00
florian.wildeandClaude Opus 5 2f45e33e20 feat(auth): Rollen, Benutzerverwaltung und Passkeys (#027, #028)
Drei Rollen: Administrator, Techniker, Kunde. Jede der 28 Aktionen nennt das
Recht, das sie verlangt; die Rechte stehen an einer Stelle. Kundenkonten
haengen an genau einem Kunden und sehen nur dessen Hosts und Rechnungen -
ohne Einkaufspreise und Marge. Die Einschraenkung sitzt in den Abfragen
selbst, damit keine Seite vergessen kann, sie weiterzugeben; sechs Tests
pruefen das.

Dabei gefunden: Neu ueber OIDC angelegte Konten haetten die Vorgabe des
Datenmodells geerbt - ADMIN. Jetzt gibt es dafuer eine Einstellung mit der
Vorgabe Techniker.

Passkeys (#028) als gleichwertiger, passwortloser Anmeldeweg neben Passwort
und OIDC. Mehrere je Konto, Aufgabe in einem kurzlebigen Cookie, rpID aus der
oeffentlichen Adresse. Ohne HTTPS sagt die Seite es, statt still zu scheitern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 00:46:29 +02:00
florian.wildeandClaude Opus 5 7775b6211f feat(security): Haertung nach OWASP Top 10 (#024)
Drei echte Luecken gefunden und geschlossen:

1. Die 27 Server Actions verliessen sich allein auf proxy.ts. Jede prueft die
   Sitzung jetzt selbst und schlaegt gegen die Datenbank nach, ob das Konto
   noch gilt - damit lassen sich Sitzungen ueberhaupt erst widerrufen.
2. Der Anmelde-Endpunkt prueft die Herkunft des Formulars; vorher war
   Login-CSRF moeglich.
3. Content-Security-Policy mit Nonce je Anfrage und strict-dynamic.

Dazu: npm audit ist ohne Befund (mysql2 und deepmerge-ts per overrides
angehoben statt Prisma zurueckzustufen), eine Benutzerverwaltung fuer die
Kommandozeile und docs/sicherheit.md mit allem, was bewusst offen bleibt -
keine Rollen, keine Zwei-Faktor-Anmeldung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 23:45:35 +02:00
florian.wildeandClaude Opus 5 7728fcb565 feat(cluster): Cluster-Knoten ohne Agent erkennen (#022, #018)
Feldtest auf zwei echten Proxmox-VE-9.2-Hosts (#018): Installation, Timer,
Zwischenspeicher und Nachsenden laufen wie vorgesehen. `pvesubscription get`
kennt kein JSON, ohne Subscription fehlen fast alle Felder, `pveversion`
enthaelt den Kernel doppelt - alles im Agent beruecksichtigt.

Dabei zeigte sich: in Proxmox hat jeder Knoten eine eigene Subscription.
Der Agent meldet darum die Cluster-Mitglieder aus `pvecm nodes`, und eine
elfte Warnregel meldet Knoten, von denen sich kein Agent meldet (#022).

Ausserdem behoben: die Bilder des Handbuchs zeigten allesamt die
Anmeldeseite, weil Firefox eine selbst erzeugte Cookie-Datei stillschweigend
verwirft. Das Skript nutzt jetzt einen lokalen Vermittler und bricht ab,
wenn alle Aufnahmen gleich gross sind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 18:05:08 +02:00
florian.wildeandClaude Opus 5 293bcf4b5a feat(db): Datenmodell, erste Migration und Seed (#003)
Prisma 7.10 mit Postgres-Adapter, 23 Modelle inklusive Rechnungen mit Positionen,
Seed fuer Administrator, Warnregeln und Demo-Daten, Testdatenbank fuer Vitest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 20:32:04 +02:00