Skip to content

feat(portal): VPN Landing Portal (Roadmap #6) — Phase 1 - #175

Merged
CallMeTechie merged 16 commits into
masterfrom
feature/vpn-landing-portal
Jun 24, 2026
Merged

CallMeTechie merged 16 commits into
masterfrom
feature/vpn-landing-portal

Conversation

@CallMeTechie

Copy link
Copy Markdown
Owner

VPN Landing Portal (Roadmap #6) — Phase 1

Personalisierte, zero-login VPN-Startseite unter dem internen home.<vpn-domain> (Captive-Portal-Stil). Ein verbundener Peer sieht nur seine eigenen Daten: Gerätestatus, Traffic-Verlauf und die Dienste, die er erreichen darf. Eigenes Design-System (Dark / Light „Papier"), self-hosted Fonts.

Umgesetzt strikt im Phase-1-Scope (TDD, subagent-getrieben, zweistufiges Review pro Task + finales Whole-Branch-Review).

Scope-Entscheidungen (vom Owner während der Umsetzung getroffen)

  • Pi-hole-Widget → Phase 2 verschoben. Grund: der Live-Pi-hole-Cache liefert pro Gerät nur Gesamt-Anfragen, keine blockiert-Zahl/Blockrate (der ursprüngliche Plan-Spike A war faktisch falsch). Eine mockup-treue Per-Gerät-Pi-hole-Ansicht braucht eine Sync-Layer-Änderung → Phase 2. Der Pi-hole-Sync wurde nicht angefasst.
  • Login-Button sichtbar, aber inert in Phase 1. Verlinkt den bestehenden Login; schaltet noch keine Portal-Aktion frei (Pi-hole-Pause + Haushalts-/Cross-Device-Ansicht kommen in Phase 2).
  • Nur direkt verbundene Peers; Gateway-/unbekannte Quellen → fail-safe generische Fallback-Ansicht (nie fremde Gerätedaten).

Widgets (3)

  • Gerät — Status, letzter Handshake, VPN-Adresse, DNS, RX/TX
  • Traffic — 24h/7d/30d Zeitreihen-Diagramm + Summen
  • Meine Dienste — Kacheln für HTTP-Routen, die der Peer per ACL erreichen darf (L4/RDP ausgeschlossen)

Pro Widget: Loading-Skeleton / unidentified-Fallback / Error+Retry-Zustände.

Security-Modell (make-or-break)

Identität wird ausschließlich etabliert, wenn der Request über das interne home.<domain>-Caddy-Site kommt:

  • Das Site ist auf die VPN-Subnetz-Range beschränkt (remote_ip), strippt jede vom Client gesetzte X-GC-Portal-Peer-IP und setzt sie aus der echten TCP-Quelle ({http.request.remote.host}).
  • portalIdentity verlangt zusätzlich eine Loopback-Verbindung UND req.hostname === home.<domain>.
  • req.ip / X-Forwarded-For werden nie für Identität vertraut.
  • Defense-in-depth: der Management-UI-vhost strippt den Header ebenfalls (per Test abgesichert).
  • Zustandsändernde Aktionen erfordern immer Login — Source-IP allein autorisiert keine Mutation.
  • Die token-authentifizierten /api/v1/client/* bleiben unverändert; die Identitäts-Brücke gilt nur auf /api/v1/portal/*.

Weitere Bausteine

  • Admin-Toggles: Master-Schalter portal.enabled + Per-Widget-Toggles (alle 3 Themes), server-seitig gegated (deaktiviert → 404).
  • Auto-Appear „C" (Baseline): dnsmasq publiziert home.<domain> → Gateway-IP.
  • i18n vollständig en + de (inkl. client-seitiger i18n-Map für portal.js).
  • TLS: home.<domain> nutzt für interne TLDs den internen Caddy-CA-Issuer.

Finales Whole-Branch-Review (behoben in diesem PR)

  • Critical — Identitäts-Fälschung über Nicht-home-vhosts geschlossen (Host-Gate in portalIdentity + Header-Strip auf dem Management-vhost; Regressionstests).
  • Importanthome.<domain> jetzt in der TLS-Automation; portal.js-injiziertes <style> (CSP-blockiert) in portal.css verschoben; Portal-API durch portalConfig gegated.

Bekannte Follow-up-Minors (nicht blockierend)

  • Wenn GC_DNS_DOMAIN eine öffentliche TLD ist, würde home.<domain> öffentliches ACME versuchen, ist aber internal-only → ggf. internen Issuer für den home-Host erzwingen (Default gc.internal ist unkritisch).
  • Toter i18n-Key fallbackGateway; /device nutzt getAll({limit})-Sentinel.

Tests

  • 36 Portal-Tests grün (identity/api/settings/page/css/js/dns-caddy) inkl. Security-/Forgery-Regressionstests.
  • Keine neuen Regressionen: der lokale Full-Suite-Fehlerstand ist identisch zur master-Baseline (umgebungsbedingte Fehler ohne echtes WG/Netz). CI ist das maßgebliche Gate.

Doku

3-teiliges Batch unter /root/Dokumentation/gatecontrol/vpn-landing-portal/ (FAQ + Dokumentation + Knowledge-Base), local-only.

Hinweis: Nicht deployen ohne Freigabe (User-Vorgabe). CI grün abwarten.

🤖 Generated with Claude Code

Comment thread public/js/portal.js Fixed
Comment thread public/js/portal.js Fixed
Comment thread public/js/portal.js Fixed
Comment thread public/js/portal.js Fixed
Comment thread public/js/portal.js Fixed
Comment thread tests/portal_api.test.js Fixed
Comment thread tests/portal_api.test.js Fixed
Comment thread tests/portal_api.test.js Fixed
Comment thread tests/portal_api.test.js Fixed
Comment thread tests/portal_api.test.js Fixed
Comment thread tests/portal_dns_caddy.test.js Fixed
Comment thread tests/portal_tls_home_internal.test.js Fixed
Comment thread tests/portal_tls_home_internal.test.js Fixed
Comment thread tests/portal_tls_home_internal.test.js Fixed
@CallMeTechie
CallMeTechie merged commit 1d64fe6 into master Jun 24, 2026
8 checks passed
@CallMeTechie
CallMeTechie deleted the feature/vpn-landing-portal branch June 24, 2026 03:39
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.

2 participants