Architektur
Das Techeve Form Gateway kompiliert ein Go-Backend (Fiber v3), ein
Svelte-5-Admin-Frontend und das Client-Snippet in ein einziges Binary.
Das Frontend wird von Vite nach frontend/dist gebaut und per go:embed
eingebettet (frontend/embed.go); das Snippet
liefert das Gateway unter /form-gateway.js aus
(client/embed.go).
Die drei Flächen des Binaries
Abschnitt betitelt „Die drei Flächen des Binaries“- Public API (
/v1/challenge,/v1/submit) — für die statischen Kundenseiten, ohne Auth. Sicherheit = Prüf-Pipeline (unten). - Admin-API + UI (
/api/v1/...+ SPA) — echte Auth (argon2id, JWT, statisches RBAC), verwaltet Mandanten, Routen, Quarantäne, Blocklist. - Hintergrund-Worker (internal/worker) —
Versand-Queue (alle 15 s), Nonce-/Queue-Cleanup (stündlich),
Reputations-Refresh (täglich). Die ORM++-eigenen Worker (Projektionen,
Snapshots, Archivierung) startet
storage.Openseparat.
Schichtenmodell
Abschnitt betitelt „Schichtenmodell“HTTP-Request │ ▼Router + Middlewares internal/api/router, internal/api/middlewares │ (JWT/API-Key-Auth, RBAC, Logging, Recover; CORS für /v1) ▼Controller internal/api/controllers │ (JSON parsen, validieren, IDs als UUID-Strings via Views) ▼Service internal/core/services │ (Business-Logik — kennt kein HTTP; │ GatewayService orchestriert die Prüf-Pipeline) ▼Repository / ORM++ internal/storage/repositories (CRUD) │ orm.New/Load/Query direkt für Event-Sourcing (Submission) ▼ORM++ SQLite (Default) oder PostgreSQL — config-SchalterDaneben, bewusst DB- und HTTP-frei:
- internal/pipeline — die reinen Prüf-Schichten: Challenge-Token (HMAC), Proof-of-Work, Eingabe-Härtung, Spam-Score, Rate-Limiter. Vollständig unit-getestet.
- internal/reputation — IP-/ASN-Lookup (iptoasn.com), Tor-Exits, gespiegelte Admin-Blocklist (in-memory).
- internal/mailer — SMTP-Versand (STARTTLS/implicit), Routen-Auflösung (Mandanten-Route → geteilte Default-Route), Retry-Backoff über die DB-Queue.
Regeln:
- Controller enthalten keine Business-Logik und keine DB-Zugriffe;
orm.IDgeht nach außen immer als UUID-String (View-Structs). - Services importieren niemals
fiber. - Repositories kapseln CRUD; Fehler werden auf
repositories.ErrNotFoundnormalisiert. Für das event-sourcteSubmission-Modell nutzen Services die ORM++-API direkt (orm.New/Load/Queryim Tenant-Kontext). - Domain-Structs (
internal/core/domain) haben keine Abhängigkeiten außer ORM++-Tags/Typen.
Der Submit-Pfad (Kern des Produkts)
Abschnitt betitelt „Der Submit-Pfad (Kern des Produkts)“POST /v1/submit läuft durch GatewayService.Submit
(gateway_service.go) —
Reihenfolge „billig zuerst” (Konzept §5):
- Rate-Limits in-memory: global → IP → Site-Key
- Mandant per Site-Key + Kill-Switch + Origin-Allowlist (serverseitig!)
- Challenge-Token: HMAC-Signatur, TTL, Site-/Origin-Bindung, Mindest-Ausfüllzeit
- Honeypot (Treffer: Erfolg vortäuschen, intern
blockedprotokollieren) - IP-/ASN-Reputation (Tor, Blocklist, ASN-Sperren)
- Proof-of-Work-Verifikation (ein Hash)
- Nonce atomar einlösen (Unique-Insert ⇒ Replay-Schutz)
- Eingabe-Härtung (Feld-/Längen-Limits, CRLF-Schutz, Reply-To-Validierung)
- E-Mail-Blockmuster gegen Reply-To
- Tageskontingent (persistent, zählt pending + sent)
- Submission-Aggregat anlegen (
received-Event) + Spam-Score - Score ≥ Schwelle ⇒
quarantined(Client sieht trotzdem Erfolg), sonst Versand-Queue
Ablehnungen sind nach außen immer 400 {"ok":false,"reason":"rejected"} —
der echte Grund steht nur im Log und (als blocked-Submission) im Admin.
Mandanten-Datenmodell
Abschnitt betitelt „Mandanten-Datenmodell“Gateway-Mandanten (Tenant) sind globale Konfigurationszeilen; ihre
Formulardaten leben im jeweils eigenen ORM++-Tenant
(Tenant.OrmTenantID): Submissions und Nonces sind dadurch hart
getrennt, DSGVO-Export und -Purge gibt es pro Kundenseite über die
ORM++-Tenant-Registry. Submission ist event-sourced — die Historie
(received → scored → sent/quarantined → released/discarded) ist das
Audit-Log. Details: database.md.
Ein neues Feature bauen — Schritt für Schritt
Abschnitt betitelt „Ein neues Feature bauen — Schritt für Schritt“Immer von innen nach außen (Beispiel-Kette existiert komplett für
SmtpRoute):
-
Domain-Model —
internal/core/domain/xyz.go(ORM++-Tags, siehe database.md) -
Registrieren — in
internal/storage/database.go→registerModels(); bei nicht-additiven ÄnderungenSchemaVersionerhöhen -
Repository —
internal/storage/repositories/xyz_repository.go(Vorlage:smtproute_repository.go) -
Service —
internal/core/services/xyz_service.go; eigene Fehler alsvar ErrXyz = errors.New(...) -
Controller —
internal/api/controllers/xyz_controller.gomit View-Struct (Vorlage:route_controller.go) -
Route + Permission — Code in
domain/rbac.go+ Rollenkatalog, Route inrouter.go:xyz := api.Group("/xyz")xyz.Get("/", middlewares.RequirePermission(domain.PermXyzRead), ctrl.List)Wichtig (Fiber v3): Handler laufen in Angabe-Reihenfolge — Middlewares stehen vor dem Controller-Handler, sonst ist die Route ungeschützt. Regressionstest:
TestRBACMiddlewareRunsBeforeHandler. -
Frontend-API-Klasse + Page — siehe frontend_api.md (Vorlage:
api/routes.js+pages/Routes.svelte) -
Tests — Service-Test gegen Temp-SQLite (Vorlage:
gateway_service_test.go), Routen-Test inrouter_test.go
Sonderfall: Logik ohne Datenbank (Berechnungen, Versionsinfo): Repository-Schicht weglassen — Kette nur Controller → Service. Referenz:
SystemService/SystemController.
Verzeichnisstruktur
Abschnitt betitelt „Verzeichnisstruktur“cmd/app/main.go Einstiegspunkt: Config → DB → Seeding → Worker → Serverclient/ Client-Snippet (form-gateway.js) + Beispielseitedocs/ Diese Dokumentationinternal/api/ HTTP-Transport (Controller, Middlewares, Router)internal/core/domain/ Entitäten (ORM++-Models) + RBAC-Kataloginternal/core/services/ Business-Logik (inkl. GatewayService = Pipeline-Orchestrierung)internal/pipeline/ Reine Prüf-Schichten (Token, PoW, Härtung, Spam, Rate-Limits)internal/reputation/ IP-/ASN-/Tor-Daten (in-memory, täglicher Refresh)internal/mailer/ SMTP-Versand + Retry-Queue-Verarbeitunginternal/worker/ Hintergrund-Jobs (Versand, Cleanup, Refresh)internal/storage/ ORM++-Verbindung, Model-Registrierung, Seeding, Repositoriesinternal/config/ config.json-Managementfrontend/src/api/ API-Service-Klassen (fetch-Abstraktion)frontend/src/pages/ Eine Svelte-Datei pro Routefrontend/embed.go go:embed des dist-Ordnersagent.md Kontext für KI-AssistentenKonfiguration & Startablauf
Abschnitt betitelt „Konfiguration & Startablauf“Beim Start sucht das Binary eine config.json im Datenverzeichnis
(-data <dir> bzw. FORMGW_DATA; Default: Verzeichnis des Binaries).
Fehlt sie, wird sie mit sicheren Zufallswerten erzeugt — inklusive
JWT-Secret und Feld-Verschlüsselungs-Key (encryption_key, sichern!).
Details: security.md.
Danach: ORM++ öffnen (SQLite oder database_dsn = PostgreSQL),
Migration + ORM++-Worker, idempotentes Seeding (system- und
admin-User; das generierte Admin-Passwort erscheint einmalig in der
Konsole), Blocklist in den Reputations-Speicher spiegeln,
Gateway-Worker starten, Server binden.
Eine Installation besteht aus drei Dateien: Binary, config.json,
gateway.db — die Begleitdateien entstehen beim ersten Start von selbst.
Versionierung & Build-Nummer
Abschnitt betitelt „Versionierung & Build-Nummer“- Semantic Version: Datei
VERSION— pflegt der Release-Bot der CI-Pipeline (Conventional Commits ⇒ nächste Version), nicht von Hand. - Build-Nummer:
.buildnumberlokal (Makefilebump-build), Pipeline-Nummer in CI. - Beide werden per
-ldflags -Xin internal/version injiziert.
Abfragbar über: ./form-gateway -version, GET /api/v1/system/info,
Footer der Web-App und make version.
Build & Cross-Compiling
Abschnitt betitelt „Build & Cross-Compiling“make build läuft: npm audit → vite build → govulncheck →
go build. Schlägt ein Sicherheits-Check fehl, bricht der Build ab.
ORM++ ist ein privates Modul: einmalig
go env -w GOPRIVATE=gitlab.techeve.de (lokal; CI nutzt den Job-Token).
CGO-frei ⇒ Cross-Compiling ohne Toolchains:
make build-linux # bin/form-gateway-linux-amd64make build-linux-arm64 # bin/form-gateway-linux-arm64make build-windows # bin/form-gateway-windows-amd64.exemake build-macos # bin/form-gateway-darwin-arm64 (Apple Silicon)make build-macos-intel # bin/form-gateway-darwin-amd64make build-all # alle PlattformenBetrieb
Abschnitt betitelt „Betrieb“Docker: gehärtetes Alpine-Image (non-root, read-only) mit
./data-Volume — Anleitung: docker.md. Kurzform:
make docker-build && docker compose up -d.
systemd (/etc/systemd/system/form-gateway.service):
[Unit]Description=Techeve Form GatewayAfter=network.target
[Service]User=formgwWorkingDirectory=/opt/form-gatewayExecStart=/opt/form-gateway/form-gateway -data /var/lib/form-gatewayRestart=on-failure
[Install]WantedBy=multi-user.targetDavor gehört ein Reverse-Proxy (nginx) mit TLS; die Public API und das Admin-UI laufen über denselben Port. Das Binary behandelt SIGINT/SIGTERM mit Graceful Shutdown (stoppt auch die Worker sauber).
Betriebs-Voraussetzung Versand: mindestens eine SMTP-Route im Admin
anlegen — üblicherweise die geteilte Default-Route über den eigenen
Mailserver mit fester, DKIM-/SPF-signierter Absenderdomain (z. B.
forms.techeve.de). Der Test-Versand-Button prüft Verbindung + Login.