Skip to content

Einstellungen: ACME-Kontaktadresse konfigurierbar (+ Wurzelfix am Token-Guard) - #232

Merged
CallMeTechie merged 10 commits into
masterfrom
feat/acme-email-setting
Jul 26, 2026
Merged

Einstellungen: ACME-Kontaktadresse konfigurierbar (+ Wurzelfix am Token-Guard)#232
CallMeTechie merged 10 commits into
masterfrom
feat/acme-email-setting

Conversation

@CallMeTechie

Copy link
Copy Markdown
Owner

Die Kontaktadresse für Let's Encrypt lässt sich in Einstellungen → Allgemein setzen und wirkt sofort, ohne Container-Neustart. Bisher war sie nur über GC_CADDY_EMAIL in der .env konfigurierbar.

Warum das mehr ist als Komfort

Ablaufwarnungen von Let's Encrypt sind der offensichtliche Nutzen — der eigentliche steckt tiefer. buildTlsAutomation steigt bei leerer Adresse komplett aus (caddyTlsAutomation.js:30). Damit verschwindet nicht nur der ACME-Kontakt, sondern der ganze TLS-Automation-Block, inklusive der Regeln, die interne Domains auf den internen Aussteller lenken. Genau daraus entstanden die dauerhaften ACME-Fehlversuche für gc-owner.invalid (PR #230 hat dort das Symptom behandelt).

Die leere Adresse ist also kein neutraler Zustand — sie schaltet still Verhalten ab, und sie ist der ausgelieferte Standard.

Umgesetzt

  • Auflöser effectiveAcmeEmail() in caddyConfig.js: gesetzte Einstellung schlägt die .env, leeres Feld fällt darauf zurück. buildTlsAutomation bleibt unverändert und seiteneffektfrei — aufgelöst wird am Aufrufpunkt.
  • Route PUT /api/v1/settings/acme-email im bestehenden Settings-Router. Ein fehlendes Feld ist ein Client-Fehler, kein Löschbefehl — sonst löschte ein abgeschnittener Request still eine gültige Konfiguration. Der Push gilt als gescheitert, wenn syncToCaddy wirft oder false liefert (Ownership-Guard); der Wert bleibt dann gespeichert und die Oberfläche warnt.
  • Nur mit Sitzung schreibbar, nicht per API-Token — der Wert bestimmt das Konto für alle öffentlichen Zertifikate.
  • Gemeinsamer Validator validateEmail in src/utils/validate.js, der die modulprivate Kopie in den Skoda-Konten ablöst. Streng, weil eine von Let's Encrypt abgewiesene Adresse Ausstellung und Erneuerung für alle Domains blockiert.
  • Oberfläche in allen drei Designs, mit Autosave. Der Platzhalter weist auf einen geerbten .env-Wert hin, ohne ihn zu zeigen — config.caddy.email steht in keiner API-Antwort, Klartext wäre eine neue Preisgabe.

Enthalten: ein Wurzelfix am Token-Guard (Security)

Beim Absichern der neuen Route fiel auf, dass TOKEN_FORBIDDEN umgehbar war: die Muster sind exakt verankert und werden gegen req.path geprüft, Express läuft aber mit strict: false / caseSensitive: false.

PUT /api/v1/settings/security          => 403
PUT /api/v1/settings/security/         => 200   ← geschrieben
PUT /api/v1/settings/ROUTE-BLOCK-DEFAULT => 200 ← geschrieben

Vorbestehend und systemisch, betraf alle 15 gesperrten Routen. Behoben wurde die Ursache im Guard (Pfadnormalisierung), nicht die eine Route. Der Reviewer hat 18 Umgehungsvarianten über rohe TCP-Sockets geprüft — superagent normalisiert clientseitig und hätte einen falschen Freibrief ausgestellt.

Wechselwirkung mit #231

Der Branch wurde auf den neuen master rebased. PR #231 fasst dieselbe Stelle an (Verwaltungs-Host in der TLS-Automation); der Konflikt wurde so aufgelöst, dass die Domainliste von #231 und die Adressauflösung dieses Branches beide bestehen bleiben. Der Abschlussreviewer hat alle vier Kombinationen durchgespielt: in der DB-Variante wird #231s Fix dadurch überhaupt erst wirksam, weil eine leere .env sonst die gesamte TLS-Automation gekippt hätte.

Prüfung

Volle Suite 2331 bestanden, 0 fehlgeschlagen. Spec und Plan durchliefen je einen Preflight (vier bzw. zwei unabhängige Prüfer), jede der sechs Tasks einen eigenen Review, dazu ein Abschlussreview über den ganzen Branch — Urteil merge-reif.

Was die Reviews gefangen haben, bevor es live ging: ein ReferenceError im Auflöser (settings ist in caddyConfig.js nicht auf Modulebene importiert), eine Push-Warnung, die der Autosave-Helfer sofort mit „Gespeichert" überschrieben hätte, und ein NUL-Byte in der Testdatei, das sie für Git zur Binärdatei machte — ausgerechnet den Test, der die Vertrauensgrenze absichert, hätte niemand im Diff lesen können.

Nebenbei behoben

Die Autosave-Bestätigung zeigte auch in der deutschen Oberfläche „Saved" — der Übersetzungsschlüssel fehlte in der Client-Whitelist aller drei Designs.

Nicht enthalten

Eine Rollenprüfung für /api/v1/settings/* — heute schützt dort nur requireAuth, und das betrifft alle Settings-Routen gleichermaßen. Ein einzelner Guard auf einer von zwanzig Routen würde Schutz vortäuschen, den die Nachbarn nicht haben. Eigener Vorgang.

Express runs with strict:false/caseSensitive:false, so `/acme-email/` and
`/ACME-EMAIL` reach the same route while the anchored TOKEN_FORBIDDEN
regexes only matched the exact spelling. Every session-only settings route
was writable with a plain API token (and token requests skip CSRF).
Lower-casing and stripping the trailing slash fixes all entries at once.
Review-Befunde: Feld hatte ohne .env-Placeholder keinen zugänglichen
Namen (aria-label ergänzt, gleicher Text wie Kartenüberschrift, kein
neuer i18n-Key). Fallback t['settings.acme_email.push_failed'] war tot
(beide Seiten derselbe Schlüssel) und hätte bei einer künftigen zweiten
Warnung fälschlich den push_failed-Text gezeigt statt der generischen
Autosave-Fehlermeldung.
…ted local part

tests/validate_email.test.js hielt ein NUL-Byte und einen vertikalen Tabulator
als rohe Bytes statt als Escape-Sequenzen -> Git stufte die Datei als binaer
ein, PR-Diff blieb unsichtbar, git grep uebersprang die Datei. Ersetzt durch
\0 / \v, gleicher geprüfter String.

validateEmail prüfte bisher nur die Domain auf führenden/abschließenden Punkt
und Doppelpunkt, nicht den Local Part vor dem @. '.me@example.com',
'me.@example.com' und 'me..you@example.com' galten faelschlich als gueltig,
obwohl RFC 5322 und Let's Encrypt sie ablehnen. Ergaenzt um dieselbe Pruefung
im Local Part, im Stil der bestehenden Domain-Pruefung (kein Regex).
@CallMeTechie
CallMeTechie merged commit 24c9e5c into master Jul 26, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant