95d625bd25
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
173 lines
16 KiB
Markdown
173 lines
16 KiB
Markdown
---
|
||
phase: quick-261008-dts
|
||
plan: 01
|
||
subsystem: domains
|
||
tags: [autodns, domains, nestjs, nextjs, prisma, rls, money-safety]
|
||
status: complete
|
||
requires:
|
||
- module-registry (ModuleGuard, UseModule, ModuleManage)
|
||
- crypto (CryptoService, global)
|
||
- prisma tenant extension (forTenant)
|
||
provides:
|
||
- Modul Domains (Slug domains) mit AutoDNS-Anbindung Demo/Live
|
||
- Kunden, Kontakt-Zuordnung, Domainliste, Verfügbarkeit, Registrierung, Aufträge
|
||
affects:
|
||
- docs/mandantentrennung-zugriffsklassifikation.md
|
||
- CHANGELOG.md, docs/anleitung-anwender.md, docs/anleitung-administration.md
|
||
tech-stack:
|
||
added: []
|
||
patterns:
|
||
- atomarer Anspruch (updateMany where status DRAFT, count === 1) vor dem Netzaufruf
|
||
- ein Client für alle AutoDNS-Aufrufe (feste Hosts, Takt 350 ms, kein Retry)
|
||
- Pull-Abgleich offener Aufträge statt Cron
|
||
key-files:
|
||
created:
|
||
- apps/api/src/domains/autodns-client.ts
|
||
- apps/api/src/domains/autodns-parse.ts
|
||
- apps/api/src/domains/domain-name.ts
|
||
- apps/api/src/domains/domains-settings.service.ts
|
||
- apps/api/src/domains/domains-directory.service.ts
|
||
- apps/api/src/domains/domains-orders.service.ts
|
||
- apps/api/prisma/migrations/20261008120000_domains_autodns/migration.sql
|
||
- apps/web/src/app/(portal)/modules/domains/page.tsx
|
||
- apps/web/src/app/(portal)/modules/domains/components/RegisterTab.tsx
|
||
- apps/web/src/app/(portal)/modules/domains/components/OrdersTab.tsx
|
||
modified:
|
||
- apps/api/src/domains/domains.controller.ts
|
||
- CHANGELOG.md
|
||
decisions:
|
||
- "Aufträge des nicht aktiven Systems werden beim Abgleich nicht angefasst (Zugangsdaten gehören zum aktiven System); nur der SUBMITTING-zu-UNKNOWN-Übergang gilt für alle."
|
||
- "Beim Abschicken wird der Entwurf VOR dem Anspruch gelesen, damit genau der bestätigte Inhalt hinausgeht; der Zugang wird vor dem Anspruch geladen, damit ein fehlender Zugang nie einen Auftrag in SUBMITTING stehen lässt."
|
||
- "HTTP-Antwort ohne Umschlag (Gateway 502/504) zählt als UNKNOWN, nicht als FAILED; FAILED nur bei ausdrücklicher AutoDNS-Ablehnung (auth, forbidden, rate-limit, business)."
|
||
- "UNKNOWN wird beim Abschicken ohne lastCheckedAt gespeichert; erst ein Abgleich mit Ergebnis setzt es und schaltet das Verwerfen frei. Ein Fehler beim Nachsehen setzt es nicht."
|
||
- "Auftragssuche beim Abgleich nimmt nur Aufträge mit lesbarer Anlagezeit ab 5 Minuten vor der Bestätigung; ohne Zeit zählt ein Auftrag nicht (lieber UNKNOWN als ein falscher Treffer)."
|
||
metrics:
|
||
tasks: 3
|
||
completed: 2026-10-08
|
||
actuals:
|
||
tokens: 109600
|
||
tasks: 3
|
||
commits: 3
|
||
plan_head_before: 6b0f840c1cf1ad09d593e9f607b5230541fdfb2b
|
||
plan_head_after: d332246bdead2b5ec87156dd610945687c672016
|
||
---
|
||
|
||
# Phase quick-261008-dts Plan 01: Modul Domains (AutoDNS) Summary
|
||
|
||
Neues Modul „Domains“ mit verschlüsselter Demo-/Live-Anbindung an AutoDNS, Kunden- und Kontaktzuordnung, Domainliste und einer Registrierung, die pro Auftrag höchstens einmal an AutoDNS geht und bei unklarem Ausgang auf „Ergebnis ungeklärt“ stehen bleibt, statt zu raten.
|
||
|
||
## Commits
|
||
|
||
| Aufgabe | Commit | Inhalt |
|
||
|---|---|---|
|
||
| 1 (Tracer) | 1f1c984 | Migration (4 Tabellen mit Zeilenschutz), AutoDNS-Client, Einstellungen, Verbindungstest, Modulseite mit Reiter Einstellungen, Registrierungen (Loader, Icon, Navigationstitel) |
|
||
| 2 | 450218f | Kunden, Kontakte live aus AutoDNS (Seiten, 60-s-Zwischenspeicher), Zuordnung, Kontakt anlegen, Domainliste, Reiter Domains/Kontakte/Kunden |
|
||
| 3 | d332246 | Verfügbarkeit, Entwurf, verbindliches Abschicken mit atomarem Anspruch, Auftragsverfolgung und Abgleich, Reiter Registrieren/Aufträge, Changelog, Anwender- und Administrationshandbuch, Mandantenschutz-Inventar |
|
||
|
||
Nichts gepusht. Docs-Artefakte (PLAN, RESEARCH, SUMMARY, STATE) sind nicht committet.
|
||
|
||
## Aufgabe 3 im Detail (Geldsicherheit)
|
||
|
||
- **Anspruch vor dem Netz:** `submitOrder` führt `updateMany` mit `id, tenantId, status: 'DRAFT', environment: <aktiv>` aus. Nur bei `count === 1` geht genau ein `POST /domain` (ohne Query, ohne Wiederholung) hinaus. Wer `count !== 1` bekommt, lädt die Zeile neu und erhält 404, 409 `environmentChanged` oder 409 `alreadySubmitted`.
|
||
- **Ausgang:** Auftragsnummer vorhanden gleich SUBMITTED. Ausdrückliche AutoDNS-Ablehnung (auth, forbidden, rate-limit, business, auch HTTP 200 mit `status.type` ERROR) gleich FAILED mit `openKey` null. Zeitablauf, Netz, TLS, unlesbare oder umschlaglose Antwort gleich UNKNOWN, nie zurück auf DRAFT.
|
||
- **Eindeutigkeit:** `openKey` (Domain) mit `@@unique([tenantId, environment, openKey])`; FAILED und CANCELED geben ihn frei, SUCCESS behält ihn bewusst.
|
||
- **Abgleich:** Pull beim Öffnen des Reiters „Aufträge“, bei „Aktualisieren“ und alle 30 s bei offenen Aufträgen und sichtbarer Seite; höchstens 20 Aufträge je Lauf, am längsten ungeprüfte zuerst; ein Fehler bei einem Auftrag hält die anderen nicht auf. SUBMITTING älter als 120 s wird UNKNOWN. UNKNOWN: erst `GET /domain/{name}`, dann `POST /job/_search`.
|
||
- **Verwerfen:** Entwurf jederzeit; UNKNOWN erst nach einem Abgleich mit Ergebnis (`lastCheckedAt` gesetzt), im Browser mit zusätzlicher Warnung.
|
||
- **Browser:** `useRef`-Sperre gegen Doppelklick; nach einem Fehler beim Abschicken bleibt der Knopf gesperrt, bis „Zurück“ gewählt wird (der Auftrag könnte angekommen sein).
|
||
|
||
## Messwerte der Prüfkette (Aufgabe 3)
|
||
|
||
| Segment | Ergebnis |
|
||
|---|---|
|
||
| `pnpm --filter @tessera/api test` | 133 Dateien, 2423 Tests grün |
|
||
| `pnpm --filter @tessera/web test` | 132 Dateien, 1455 Tests grün |
|
||
| `tsc --noEmit` api und web | grün |
|
||
| `biome check` (alle angefassten Dateien) | grün |
|
||
| rls-coverage, rls-access-inventory | 35 Tests grün (domains: 0/30/0, Paare 104) |
|
||
| Rollen-Decorator im Controller | keiner |
|
||
| de/en-Schlüsselgleichheit `domains.*`, Wortprüfung (Mandant, Lizenz) | 207 Schlüssel, gleich, ohne Treffer |
|
||
| CHANGELOG-/Handbuch-Greps | ok |
|
||
| Neuaufbau `docker compose up -d --build api web` | api, web, db laufen; „Domains module seeded in registry“; Routen `orders/refresh` vor `orders/:id/submit`; keine ausstehenden Migrationen; `GET customers` und `GET orders` geben `[]`; `POST availability` ohne Zugang gibt 409 `notConfigured` |
|
||
| Gegenprobe Doppelbestellung | Anspruchsprüfung testweise ausgebaut: Test „zwei gleichzeitige Bestätigungen“ und Test „Systemwechsel zwischen Lesen und Anspruch“ schlugen fehl; danach zurückgebaut |
|
||
|
||
## Abweichungen vom Plan
|
||
|
||
### Automatisch behoben und ergänzt
|
||
|
||
**1. [Regel 2 - Fehlende Absicherung] Kontakt-Prüfung beim Entwurf**
|
||
- **Ort:** `createOrder`
|
||
- **Problem:** Ohne Prüfung könnte ein Entwurf Kontaktnummern enthalten, die bei AutoDNS nicht (mehr) existieren.
|
||
- **Lösung:** Alle vier Kontaktnummern werden gegen die Kontaktliste geprüft (`findContactsByIds`), sonst 400 `contactNotFound`. Grenze: bei mehr als 2000 Kontakten kann ein vorhandener Kontakt außerhalb der gelesenen Liste fälschlich abgelehnt werden; die Meldung rät zum Neu-Einlesen.
|
||
|
||
**2. [Regel 3 - Blockierend] Zeitlimit im Test einstellbar**
|
||
- `DomainsSettingsService.transport` bekam das Feld `timeoutMs` (nur für Tests), damit der Zeitablauf-Fall mit 20 ms getestet werden kann; im Betrieb bleibt es leer (20 s). `HOSTNAME_PATTERN` wird exportiert und im Auftragsdienst wiederverwendet.
|
||
|
||
**3. [Regel 1 - Fehler im eigenen Entwurf] Zusätzliche Absicherungen im Abgleich**
|
||
- Ein Fehler beim Nachsehen (Anmeldung, Zeitablauf, Gateway) setzt `lastCheckedAt` nicht, damit „Verwerfen“ nie auf einer Prüfung beruht, die nichts geprüft hat. Nur eine ausdrückliche AutoDNS-Antwort „gibt es nicht“ (Fehlerart business) zählt als Ergebnis.
|
||
- Alle Schreibzugriffe des Abgleichs sind an den erwarteten Zustand gebunden (`status` in der `where`-Klausel), damit zwei gleichzeitige Abgleiche sich nicht überschreiben.
|
||
|
||
**4. [Hinweis] Reiter Aufträge für alle, auch ohne Einrichtung**
|
||
- Der Reiter ist immer sichtbar (Liste kommt aus der Datenbank); der Abgleich überspringt ihn still, wenn das aktive System nicht eingerichtet ist.
|
||
|
||
**5. [Aufräumen] Versehentliche Fremdformatierung rückgängig gemacht**
|
||
- Ein Formatierungslauf hatte `module-manage-handlers.spec.ts` in unbeteiligten Zeilen umgebrochen; die Datei wurde zurückgesetzt und nur um die sechs Handlernamen ergänzt.
|
||
|
||
### Nicht behoben (vorbestehend)
|
||
|
||
Keine fehlschlagenden Tests. `biome organizeImports` meldet in `module-manage-handlers.spec.ts` eine bereits vor dieser Arbeit vorhandene Importreihenfolge; bewusst nicht angefasst.
|
||
|
||
## Threat-Status
|
||
|
||
| ID | Stand |
|
||
|---|---|
|
||
| T-dts-01 Passwort | umgesetzt (Aufgabe 1); Antworten tragen nur `hasPassword` |
|
||
| T-dts-02 Rechte | alle Schreib- und Verwalten-Wege tragen `@ModuleManage('domains')`, kein Rollen-Decorator; Controller- und Handler-Spec grün |
|
||
| T-dts-03 Doppelbestellung | umgesetzt und mit Gegenprobe belegt (siehe oben) |
|
||
| T-dts-04 SSRF | feste Hosts, `redirect: 'error'`, Pfadprüfung |
|
||
| T-dts-05 Organisationstrennung | `forTenant` überall, RLS-Gates grün |
|
||
| T-dts-06 Abfragegrenze | Takt 350 ms, höchstens 20 Aufträge je Abgleich, Verbindungstest ein Aufruf |
|
||
| T-dts-07 Demo/Live | System in Anspruch und Zuordnung, Wechsel nur mit Bestätigung |
|
||
| T-dts-08 Routen-Reihenfolge | `orders/refresh` vor `orders/:id/...`, Spec prüft |
|
||
| T-dts-09 Eingaben | `normalizeDomainName`, Validatoren, `ParseUUIDPipe` |
|
||
| T-dts-10 Fehlertexte | nur `messages[].text`, gekürzt; AutoDNS 401/403 als 502 |
|
||
|
||
## Bekannte Stubs
|
||
|
||
Keine. Die Anzeige „Preis nicht ermittelbar“ ist gewollt (AutoDNS nennt keinen Preis).
|
||
|
||
## Prüfung gegen AutoDNS-Demo
|
||
|
||
Voraussetzung: Demo-Zugangsdaten eintragen (Domains, Einstellungen, Zugang Demo-System, Benutzername, Passwort, Kontext). System bleibt auf „Demo-System“. Jeder Punkt schließt eine offene Annahme aus der Recherche.
|
||
|
||
| Nr. | Annahme | Prüfung in der Oberfläche | Wenn es abweicht |
|
||
|---|---|---|---|
|
||
| 1 | A1: Welcher Demo-Kontext funktioniert (1 oder 4) | Einstellungen, Zugang Demo-System: Kontext 4 eintragen, speichern, „Verbindung testen“; bei Fehler 1 versuchen. Je Versuch genau einmal klicken (Sperrgefahr) | Wert merken und in die Handbücher übernehmen |
|
||
| 2 | A6/A9: Anmeldung ohne Zwei-Faktor, Test gelingt | „Verbindung testen“ zeigt „Verbindung erfolgreich“ | bei Anmeldefehler Benutzer ohne 2FA verwenden |
|
||
| 3 | A2: Kontaktfelder und Telefonformat | Reiter Kontakte, „Neuer Kontakt“: erst Person, dann Organisation anlegen (Telefon +49 30 123456). Meldung „Der Kontakt wurde bei AutoDNS angelegt“, Kontakt erscheint nach „Aus AutoDNS neu einlesen“ | Fehlermeldung von AutoDNS lesen (steht im Formular); Pflichtfelder oder Telefonformat anpassen |
|
||
| 4 | A3: Verfügbarkeitsprüfung liefert genau die gefragte Domain mit WHOIS-Status und Preis | Reiter Registrieren: eine sicher freie Domain (zufälliger Name) und eine belegte (zum Beispiel denic.de) prüfen. Frei zeigt „… ist frei“ mit Preis; belegt zeigt „nicht frei“ mit Status. Außerdem eine Umlautdomain prüfen | Steht immer „nicht frei“ mit Status „NO_RESULT“, liefert DomainStudio den Namen anders (Suchwort, Endung) und `parseDomainStudio` oder die Anfrage ist anzupassen. Fehlt nur der Preis, erscheint „Preis nicht ermittelbar“: Preisform prüfen |
|
||
| 5 | A4/A5: Registrierung liefert Auftragsnummer, `{ id }` genügt als Kontaktverweis | Zusammenfassung erstellen, Häkchen setzen, „Jetzt verbindlich registrieren“. Erwartet: „wird bearbeitet“, Reiter Aufträge zeigt „In Bearbeitung“ | „Fehlgeschlagen“ mit Text von AutoDNS: Text lesen (Kontaktverweis, Nameserver, Pflichtfelder). Steht ein Auftrag ohne Nummer auf „In Bearbeitung“ und ändert sich nie, ist die Fundstelle der Nummer anders (`extractJobFromSubmit`) |
|
||
| 6 | Auftrag erreicht SUCCESS, Tessera folgt | Reiter Aufträge, „Aktualisieren“ (oder 30 s warten): Stand wechselt auf „Registriert“ | bleibt er „In Bearbeitung“, in AutoDNS den Auftrag ansehen; „Rückfrage nötig“ heißt Status SUPPORT |
|
||
| 7 | D-J: Domainliste mit Inhaber, Ablaufdatum, `registryStatus` | Reiter Domains, „Aus AutoDNS neu laden“: die neue Domain erscheint mit Inhaber, Ablaufdatum und Status (Aktiv, In Bearbeitung …). Kunde nach Zuordnung des Inhaber-Kontakts | Status als Rohwert angezeigt: `registryStatus` hat andere Werte, Beschriftungen nachziehen. Ablaufdatum „–“: Feld `expire` fehlt in der Antwort |
|
||
| 8 | A8: `.de` mit Standard-Nameservern besteht die Nameserver-Prüfung | Standard-Nameserver in den Einstellungen eintragen (bei AutoDNS eingerichtete), eine freie `.de`-Domain registrieren | „Fehlgeschlagen“ mit Nameserver-Meldung: Nameserver einrichten oder korrigieren |
|
||
| 9 | Neu in Aufgabe 3: Auftragssuche (`POST /job/_search`, Filter `object`, Anlagezeit) und `GET /domain/{name}` für nicht Gefundenes | Schwer künstlich auszulösen. Wenn ein Auftrag je auf „Ergebnis ungeklärt“ steht: „Aktualisieren“ klicken. Erwartet: Tessera findet die Domain (Registriert), einen Auftrag (In Bearbeitung) oder setzt die Prüfung (dann ist „Verwerfen“ möglich) | Bleibt „Verwerfen“ trotz Abgleich gesperrt, liefert `GET /domain/{name}` für Unbekanntes keinen business-Fehler; Antwortform prüfen |
|
||
| 10 | Neu in Aufgabe 3: Umlautdomain wird als Punycode gesucht und bestellt | Umlautdomain prüfen; Anzeige zeigt die Umlautform | DomainStudio erwartet ggf. die Unicode-Form im Suchwort |
|
||
| 11 | D-J: Wert für „Kündigung vorgemerkt“ | Eine Domain mit vorgemerkter Kündigung in der Liste ansehen (falls vorhanden) | Werte von `cancelationStatus` prüfen (NONE und NOT_SET gelten als „keine“) |
|
||
|
||
## Browser-Prüfschritte für den Orchestrator (dunkler Modus bevorzugt)
|
||
|
||
Lokal: `http://localhost:3000`, Anmeldung `admin` / `admin123`. Playwright-Prüfung mit Theme-Knopf auf dunkel.
|
||
|
||
1. **Aktivieren:** Marktplatz, Modul „Domains“ aktivieren. Erwartet: Modul in der Seitenleiste (Gruppe Domains), Erde-Symbol.
|
||
2. **Freigabe Benutzen gegen Verwalten:** Mit einem normalen Benutzer mit „Benutzen“ anmelden: Reiter Domains, Kontakte, Kunden, Aufträge sichtbar; kein Registrieren, kein Einstellungen, kein „Neuer Kontakt“, keine Zuordnung, keine Kunden-Knöpfe. Mit Admin oder „Verwalten“: zusätzlich Registrieren und Einstellungen.
|
||
3. **Einstellungen:** Ohne Zugang zeigt die Kopfzeile „AutoDNS nicht eingerichtet“ mit Hinweis und Knopf „Zu den Einstellungen“. Passwort eintragen, speichern, Seite neu laden: Passwortfeld leer mit Platzhalter „Gespeichert – leer lassen …“. Live-Kontext ist mit 4 vorbelegt. Auswahl „Live-System“: Bestätigungsfeld erscheint; „Abbrechen“ stellt Demo zurück; erst „Live-System verwenden“ speichert, Kopfzeile zeigt dann „Live-System – Registrierungen kosten Geld“. Wieder auf Demo zurückstellen. Nameserver (zwei) eintragen und speichern.
|
||
4. **Kontakte:** Liste, Suche, Filter „Nicht zugeordnet“, „Nach Kunde gruppieren“; Kontakte ankreuzen, Kunden wählen, „Zuordnen“; „Zuordnung entfernen“; „Neuer Kontakt“ mit ungültiger E-Mail und mit Telefon ohne „+“ (Fehler sichtbar), dann gültig speichern.
|
||
5. **Kunden:** Kunden anlegen, einen als „Eigene Firma“ markieren (Kennzeichnung erscheint, bei einem zweiten wandert sie); Kunden mit Kontakten löschen: Meldung „Diesem Kunden sind noch Kontakte zugeordnet …“.
|
||
6. **Domains:** Liste, Gruppen pro Kunde mit „Nicht zugeordnet“ zuletzt, Kundenfilter blendet andere Gruppen aus, Datum als TT.MM.JJJJ.
|
||
7. **Registrieren:** Ungültiger Name zeigt Meldung; freie und belegte Domain; Kontaktauswahl gruppiert nach Kunde, Voreinstellung Admin gleich Inhaber, Technik/Zone gleich eigene Firma; Nameserver hinzufügen/entfernen (2 bis 6); „Zusammenfassung anzeigen“ zeigt System-Kennzeichnung, Preis oder „Preis nicht ermittelbar“; „Jetzt verbindlich registrieren“ bleibt gesperrt bis zum Häkchen; **Doppelklick auf den Knopf** darf nur einen Auftrag erzeugen (Reiter Aufträge zeigt eine Zeile, Server-Log zeigt ein einziges „Registrierung abgeschickt“). Vorsicht: Mit Demo-Zugang ohne echte Daten antwortet AutoDNS mit einem Fehler, die Zeile steht dann auf „Fehlgeschlagen“ oder „Ergebnis ungeklärt“; beides ist ein gültiger Ausgang.
|
||
8. **Aufträge:** Reiter zeigt Zeilen mit System (Demo/Live), Stand, Bestätigt von/am, Fehlertext; „Aktualisieren“; „Verwerfen“ nur für Verwalter bei Entwurf und geprüftem unklarem Auftrag, mit Rückfrage.
|
||
|
||
## Self-Check: PASSED
|
||
|
||
- Dateien vorhanden: domain-name.ts, domains-orders.service.ts, dto/domains-order.dto.ts, RegisterTab.tsx, OrdersTab.tsx, order-status.ts (alle von `git status` als hinzugefügt bestätigt).
|
||
- Commits vorhanden: 1f1c984, 450218f, d332246 liegen in der Historie von HEAD (`git rev-list --count 6b0f840..HEAD` gleich 3).
|