Files
tessera-ctl/.planning/quick/261008-dts-modul-domains-autodns-anbindung-kontakte/261008-dts-RESEARCH.md
T
2026-10-08 11:34:16 +02:00

29 KiB

Quick 261008-dts: Modul "Domains" (AutoDNS) - Research

Researched: 2026-10-08 Domain: AutoDNS/InterNetX Domainrobot JSON API + Tessera-Modulmuster Confidence: MEDIUM (Endpunkte und Schemas HIGH aus OpenAPI; reale Antworten der Auftrags-Endpunkte und Demo-Kontext nicht ohne Zugangsdaten pruefbar)

Es gibt keine CONTEXT.md fuer diese Aufgabe. Es wird kein neues npm-Paket benoetigt (HTTP ueber undici 7.28.0, class-validator, @nestjs/schedule sind bereits in apps/api/package.json), daher entfaellt das Package Legitimacy Audit. [VERIFIED: apps/api/package.json Zeilen 25, 32, 52]

Project Constraints (aus CLAUDE.md / Memory)

  • UI-Texte Deutsch, siezen; keine "Mandant"-Begriffe in neuen UI-Texten; keine Lizenzierungs-/Mandantenfaehigkeits-Themen anschneiden.
  • Keine firmenspezifischen Werte hart in generische Admin-UI (Standard-Nameserver also nur als Einstellung, nicht als Konstante).
  • Neue Module: @UseModule(slug) auf Klassenebene ist Pflicht, Schreib-/Pruefrouten zusaetzlich @ModuleManage(slug); NIE ein Rollen-Decorator auf Verwalten-Handlern.
  • Statische Routen VOR :id-Routen (Unit-Tests fangen das nicht, Reihenfolge im Controller-Spec festschreiben).
  • Zugangsdaten nie an den Client zurueckgeben (LDAP-Muster: Maske '********').
  • Tests mit gemockter API; kein Docker-Deploy auf Testserver durch Claude.
  • Alle Aenderungen laufen ueber einen GSD-Workflow.

Summary

Die AutoDNS-JSON-API deckt alle vier Etappe-1-Funktionen ab. Es gibt KEINEN POST /domain/_check; die Verfuegbarkeitspruefung laeuft ueber POST /domainstudio (WHOIS-Dienst je Domain, optional PRICE). POST /domain ist asynchron und liefert einen Job; der Verlauf wird ueber GET /job/{id} verfolgt. Die Antwort-Huelle ist fuer alle Routen gleich (status/stid/object/data/messages).

Im Code ist das Nextcloud-Status-Modul (Controller/Seed/Modul) plus Handelsware-Datev (Einstellungs-Tab, Singleton-Konfiguration) das beste Vorbild; die AES-Verschluesselung kommt aus CryptoService. Der groesste fachliche Risikopunkt ist Geldsicherheit: POST /domain darf pro Bestellung hoechstens einmal abgeschickt werden, und ein Timeout heisst NICHT "nicht bestellt".

Primary recommendation: Zwei Umgebungen (DEMO/LIVE) mit je eigenem Zugang, feste Basis-URLs (keine freie URL-Eingabe), lokale Bestelltabelle mit atomarem Statuswechsel DRAFT -> SUBMITTING vor dem API-Aufruf, Kontakte und Domains live aus AutoDNS lesen und nur die Kundenzuordnung lokal halten.

Architectural Responsibility Map

Capability Primary Tier Secondary Rationale
AutoDNS-Aufrufe, Zugangsdaten API/Backend - Passwort darf nie in den Browser; Basic Auth serverseitig
Kundenzuordnung Kontakte Database API Nur lokal vorhanden, AutoDNS kennt "Kunde" nicht
Bestell-Zustand (Idempotenz) Database + API - Atomarer Statuswechsel in DB schuetzt vor Doppelbestellung
Job-Status-Nachverfolgung API (Abruf beim Oeffnen + Cron) Browser (Polling der eigenen API) Browser spricht nie mit AutoDNS
Filter/Gruppierung nach Kunde Browser API Kleine Datenmenge, Zusammenfuehren im API-Dienst
Berechtigung API (ModuleGuard) Browser (useCanManageModule, nur Anzeige) Bindend ist nur die API

AutoDNS JSON API (verifiziert)

Quelle der Schemas: OpenAPI 2.0 https://raw.githubusercontent.com/InterNetX/domainrobot-api/master/src/domainrobot.json (Info version v1, host api.autodns.com, basePath /v1), gelesen und ausgewertet am 2026-10-08. [VERIFIED: openapi domainrobot.json]

Basis, Authentisierung, Limits

Punkt Wert Quelle
Live-URL https://api.autodns.com/v1 [CITED: help.internetx.com/x/cQbj "Interface addresses"]
Demo-URL https://api.demo.autodns.com/v1 [CITED: help.internetx.com/x/cQbj; java-domainrobot-sdk README]
Erreichbarkeit Beide Hosts antworten von hier mit HTTP 401 ohne Zugangsdaten [VERIFIED: curl-Probe 2026-10-08, ohne Zugangsdaten]
Auth HTTP Basic (Benutzer/Passwort) + Header X-Domainrobot-Context: <int>; Alternative Session (X-Domainrobot-SessionId), von InterNetX nicht empfohlen ausser bei Zwangs-Timeout [CITED: help.internetx.com Suchergebnis "Login and Authentication"; js-sdk DomainRobotService.js setzt Basic + Context-Header]
User-Agent Pflicht ("mandatory") - eigenen setzen, z. B. Tessera/<version> [CITED: help.internetx.com/x/cQbj]
Rate-Limit "Only 3 requests per second and IP" - Aufrufe serialisieren, Listen seitenweise, kein paralleles Fan-out [CITED: help.internetx.com/x/cQbj]
Verbindungstest GET /hello ("performs no operation, used only for testing the connection and authentication credentials") [CITED: help.internetx.com/x/cQbj; OpenAPI /hello]
Live-Context Standard 4 [CITED: wisecp-Doku und help.internetx.com (Plugin-Seite): "default context for AutoDNS is 4"]
Demo-Context UNKLAR: Drittanbieter nennt 1 fuer Demo, die offiziellen SDK-Beispiele nutzen die Demo-URL mit "4" [ASSUMED] - Kontext deshalb je Umgebung als freie Ganzzahl speichern, nicht hartkodieren
2FA OpenAPI kennt Header X-Domainrobot-2FA-Token; Hilfeseite sagt 2FA nur fuer XML [ASSUMED] Widerspruch - dedizierten API-Benutzer OHNE 2FA empfehlen
X-Domainrobot-Demo (boolean Header) in OpenAPI vorhanden, Wirkung undokumentiert NICHT verwenden; Umgebung nur ueber die Basis-URL waehlen

Fehlerhafte Anmeldung: Live liefert 401 mit {"messages":[{"code":"EF00202","text":"User does not exist or password incorrect.","status":"ERROR",...}],"status":{"code":null,"text":null,"type":"ERROR"},"stid":"..."}; Demo ohne Basic-Header liefert 401 EF1321001 "The authsession could not be found.". [VERIFIED: curl-Probe 2026-10-08] Wichtig: wiederholte falsche Anmeldungen koennen den Benutzer sperren [ASSUMED] - Verbindungstest nie in Schleife/Retry.

Antwort-Huelle

{ "status": {"code":"S0301","text":"...","type":"SUCCESS"},
  "stid": "20180915-app1",
  "object": {"type":"contact","value":"100101","summary":1},
  "messages": [ {"code":"...","text":"...","status":"ERROR","objects":[...]} ],
  "data": [ { } ] }
  • status.type Enum: SUCCESS, ERROR, NOTIFY, NOTICE, NICCOM_NOTIFY. [VERIFIED: OpenAPI StatusType]
  • Code-Praefixe: S Erfolg, E Fehler, N Benachrichtigung ("accepted, further processing required"). [CITED: help.internetx.com/x/cQbj]
  • Die Hilfeseite zeigt das Feld als resultCode, OpenAPI und die reale 401-Antwort nutzen code. Parser muss status.code ?? status.resultCode lesen. [VERIFIED: Probe + OpenAPI; Abweichung CITED]
  • object.summary = Gesamtzahl bei Listen (Paging). [VERIFIED: OpenAPI ResponseObject "amount of objects found in list tasks"]
  • HTTP-Status ist nicht verlaesslich fuer Fachfehler: IMMER status.type === 'ERROR' bzw. messages[].status pruefen, auch bei HTTP 200. [ASSUMED]
  • Fehlertexte an den Client nur gekuerzt und ohne Header/Zugangsdaten durchreichen (messages[].text).

Routen und minimale Nutzlasten

Zweck Route Nutzlast / Hinweis
Kontakt anlegen POST /contact {"type":"PERSON","fname":"Max","lname":"Muster","address":["Musterstr. 1"],"pcode":"12345","city":"Berlin","country":"DE","email":"x@example.com","phone":"+49 30 123456"}; type Enum PERSON, ORG, ROLE; ORG braucht organization; address ist ein ARRAY; alias optional (wird generiert); Antwort data[0].id (Ganzzahl). Pflichtfelder sind in der OpenAPI nicht als required markiert -> obige Menge ist [ASSUMED], gegen Demo pruefen. extensions (z. B. general.gender, it.entityType) nur TLD-abhaengig, fuer .de nicht noetig [ASSUMED].
Kontakte listen POST /contact/_search Body Query: {"filters":[{"key":"lname","value":"%test%","operator":"LIKE"}],"view":{"limit":100,"offset":0},"orders":[{"key":"lname","type":"ASC"}]}; leere filters = alle. Filterbare Schluessel: country, pcode, city, type, title, lname, alias, state, id, email, fname, address, created, phone, organization, comment, updated ... Operatoren: EQUAL, NOT_EQUAL, LIKE, ILIKE, GREATER, LESS, IN, IS_NULL ... [VERIFIED: OpenAPI + js-sdk ContactList.js]
Kontakt lesen/aendern GET/PUT /contact/{id} Spaeter; DELETE /contact/{id} NICHT in Etappe 1 anbieten
Verfuegbarkeit POST /domainstudio Es gibt keine Route /domain/_check (alle /domain*-Pfade der OpenAPI durchgesehen). Body DomainEnvelopeSearchRequest: {"searchToken":"example","currency":"EUR","checkPortfolio":true,"sources":{"initial":{"tlds":["de"],"services":["WHOIS","PRICE"]}}}; alternativ sources.custom.domains:["example.de"]. Antwort data[] = DomainEnvelope mit domain, services.whois.data.status (Enum FREE, ASSIGNED, MARKET, PREMIUM, INVALID, ERROR, TIMEOUT, RESERVED, PREMIUM_CLAIM, CLAIM, OFFER) und services.price.data.prices[] (amount, currency, period). [VERIFIED: OpenAPI Schemas]. Die genaue Kombination initial.tlds + searchToken bzw. custom.domains liefert ein Ergebnis fuer genau die gewuenschte Domain: [ASSUMED], gegen Demo pruefen. Nur status === 'FREE' zaehlt als bestellbar; alles andere (auch ERROR/TIMEOUT) = nicht bestellbar.
Domain bestellen POST /domain "The operation is asynchronous and creates a job." Body (Domain): {"name":"beispiel.de","period":{"unit":"YEAR","period":1},"ownerc":{"id":123},"adminc":{"id":123},"techc":{"id":123},"zonec":{"id":123},"nameServers":[{"name":"ns1.example.com"},{"name":"ns2.example.com"}]}. Contacts als Objekte mit id (SDK-Beispiel reicht ganze Kontakt-Objekte; nur {id} genuegt vermutlich: [ASSUMED]). Query-Parameter ignoreWhois, nsCheck, replyTo - ignoreWhois NIE setzen. Antwort JsonResponseDataJob: data[0] = Job. Wo genau die Job-Id steht (data[0].id, ausserdem vermutlich object.type="job"/object.value): [ASSUMED], gegen Demo pruefen. confirmOrder (Nutzungsbedingungen mancher TLDs) und period je nach TLD: [VERIFIED: Feldbeschreibung OpenAPI]. Zusatz nameServerEntries nur fuer .de und schliesst nameServers aus.
Job lesen GET /job/{id} Antwort ObjectJob: data[0].job.status (Enum RUNNING, SUCCESS, FAILED, CANCELED, SUPPORT, DEFERRED, NOT_SET, WAIT), .job.subStatus, .job.action, .object (Domain-Name), .job.events[]. Terminal: SUCCESS, FAILED, CANCELED; SUPPORT = Eingriff durch InterNetX noetig (als "Rueckfrage noetig" anzeigen); WAIT/DEFERRED/RUNNING = offen. [VERIFIED: OpenAPI Enum; Terminal-Deutung ASSUMED]
Jobs suchen POST /job/_search Query mit Schluesseln id, status, object, type, action, created -> Abgleich nach Timeout (siehe Pitfall 1).
Domains listen POST /domain/_search?keys[]=expire&keys[]=ownerc Body Query wie bei Kontakten (view.limit/offset). Zusatzfelder per wiederholtem Query-Parameter keys[] (Format ?keys[]=a&keys[]=b): zulaessig u. a. expire, ownerc, adminc, techc, zonec, nserver, autorenew, cancelationStatus, authinfo, certificate. Statusfelder: registryStatus (ACTIVE, HOLD, LOCK, PENDING ...), cancelationStatus, action. [VERIFIED: OpenAPI + js-sdk DomainService.list + php-sdk DomainList.php]
Domain lesen GET /domain/{name} Detail/Abgleich nach Timeout.

Spaeter (Architektur offenhalten, nicht bauen): Transfer POST /domain/_transfer, Kuendigung POST /domain/{name}/cancelation, Zonen GET/PUT /zone/{name}/{virtualNameServer}, Auftrag bestaetigen/abbrechen PUT /job/{id}/_confirm / _cancel, Poll-Nachrichten GET /poll. [VERIFIED: OpenAPI paths] Deshalb den AutoDNS-Client als eigene Klasse mit allgemeiner request(method, path, body, keys)-Methode plus duenner Fachschicht bauen.

.de-Besonderheiten (kurz)

  • Seit 25.05.2018 erhebt DENIC nur noch den Domaininhaber als personenbezogene Kontaktdaten; Admin-C/Tech-C/Zone-C entfallen bei DENIC, zusaetzlich zwei neutrale Adressen (General Request/Abuse). AutoDNS-Objekte kennen weiter ownerc/adminc/techc/zonec; sicher ist, den Inhaber vollstaendig mit Anschrift zu fuehren und fuer die uebrigen drei Rollen einen Kontakt zu referenzieren. [CITED: denic.de Pressemitteilung 25.05.2018 laut Suchergebnis; Abbildung auf AutoDNS-Pflichtfelder ASSUMED] - Empfehlung: alle vier IDs im Formular verlangen, Voreinstellung = Inhaber bzw. eigener Firmenkontakt als Technik-/Zonen-Kontakt.
  • .de verlangt 2 bis 6 funktionierende Nameserver; DENIC prueft sie bei der Registrierung (Nast-Check), unerreichbare/nicht eingerichtete Nameserver lassen den Auftrag scheitern. Parameter nsCheck existiert. [CITED: namecheap/ans.co.uk Suchergebnis; AutoDNS-Verhalten ASSUMED] - Auftrag FAILED mit messages verstaendlich anzeigen.
  • Regeln pro TLD unterscheiden sich stark (extensions); Etappe 1 auf Kern-TLDs (.de/.com/.net/.org) beschraenken oder bei fehlenden Pflichtangaben die API-Fehlermeldung durchreichen.

Codebase-Integration (verifiziert durch Lesen der Dateien)

Backend: Dateien als Vorlage kopieren

Zweck Vorlage Hinweis
Modul + Seed beim Start apps/api/src/nextcloud-status/nextcloud-status.module.ts, nextcloud-status.seed.ts seedModule({slug, name, version, category, description:{de,en}, isSystem:true}); onModuleInit mit try/catch. Kategorie: 'infrastructure' (existiert, proxmox/nextcloud-status nutzen sie) oder 'domain-tools' (existiert, domaincheck nutzt sie). [VERIFIED: nextcloud-status.seed.ts; module-categories.service.spec.ts Zeilen 17-20] Empfehlung: 'domain-tools' zu Domaincheck, Kategorien sind laut 261003-387 durch Admins umbenennbar.
Controller nextcloud-status.controller.ts @Controller('modules/<slug>') + @UseModule('<slug>') Klasse; requireTenantId(req) aus req.tenantId; Schreib-/Pruef-/Einstellungsrouten @ModuleManage('<slug>'); Lese-Routen ohne.
Singleton-Einstellungen + Tab handelsware-datev.controller.ts, handelsware-datev.service.ts, Web modules/handelsware-datev/page.tsx + components/SettingsTab.tsx Tab "Einstellungen" nur bei useCanManageModule.
Verschluesselung apps/api/src/crypto/crypto.service.ts (CryptoModule) encrypt(plain) -> iv:authTag:ciphertext (AES-256-GCM, Schluessel TESSERA_ENCRYPTION_KEY, 64 Hex). Entschluesseln-Fehler NICHT schlucken (kein stiller Wechsel auf "ohne Passwort"), wie ldap-config.service.ts decryptBindPassword. Passwort-Feld im API-Response als '********' bzw. hasPassword: boolean; leeres Feld beim Speichern = unveraendert (LDAP-Muster dto.bindPassword || config.bindPassword).
HTTP-Client apps/api/src/proxmox/proxmox-client.service.ts undiciFetch (NICHT globales fetch), AbortController-Timeout, Fehlerdetail gekuerzt (500 Zeichen), Fehlerklassifikation. Fuer AutoDNS: festes Basis-URL-Mapping DEMO/LIVE, kein Eintrag frei waehlbar (kein SSRF), 15-20 s Timeout, Basic-Header aus entschluesselten Daten.
Hintergrunddienst (Auftragsstatus) nextcloud-status-scheduler.service.ts OnApplicationBootstrap, SchedulerRegistry + require('cron').CronJob-Umgehung, Ueberlappungsschutz running; Systemkontext-Lesen nur ueber forSystem() (+ system_read_policy). Fuer Etappe 1 genuegt: Status beim Oeffnen/"Aktualisieren" abfragen, optional minuetlicher Cron nur fuer offene Auftraege.
DTOs nextcloud-status/dto/nextcloud-instance.dto.ts class-validator; globale ValidationPipe ist in main.ts aktiv.
DB-Zugriff forTenant(this.prisma, tenantId) aus prisma/prisma-tenant.extension.ts Jeder neue Zugriff in rls-access-inventory.spec.ts eintragen, wenn dort Klassifikation verlangt wird.
Migration apps/api/prisma/migrations/20261002130000_handelsware_datev/migration.sql Handgeschrieben mit Kopfkommentar; je Tabelle tenantId, ENABLE+FORCE ROW LEVEL SECURITY, tenant_isolation_policy USING ("tenantId" = current_tenant_id()); sonst faellt rls-coverage.spec.ts. Naechster Zeitstempel > 20261003120000.
Registrierungspunkte apps/api/src/app.module.ts (Importliste), module-manage-handlers.spec.ts (Pflicht-Test: jede Schreibroute verlangt Manage, Leseroute nicht)

Frontend: Registrierungspunkte (alle noetig, sonst erscheint das Modul nicht korrekt)

  • apps/web/src/lib/module-loader.ts: Eintrag '<slug>': { component: dynamic(() => import('@/app/(portal)/modules/<slug>/page'), { ssr: false }) }.
  • apps/web/src/lib/module-identity.ts: Symbol in ICONS (z. B. domaincheck: 'globe' existiert; neuer Slug braucht eigenen Eintrag; fehlt er, gilt 'tile').
  • apps/web/src/lib/stores/nav-store.ts: MODULE_TITLE_KEYS['<slug>'] = '<ns>.title'.
  • apps/web/src/app/(portal)/modules/<slug>/layout.tsx: <ModuleAccessGate moduleSlug="<slug>"> und Eintrag in module-layouts.test.tsx.
  • Seite: PageHeader aus @/components/layout/page-header, TabBar aus @/components/accounting/tab-bar (Handelsware-Muster: Tabs Kontakte / Domains / Registrieren / Einstellungen), useCanManageModule(slug). SettingsSection/ControlCenterNav (components/control-center/) gehoeren zum Administrationsbereich (/admin/...); fuer ein Modul reicht der Einstellungs-Tab nach Handelsware-Muster. [VERIFIED: ControlCenterNav listet nur /settings und /admin Seiten]
  • API-Client: apps/web/src/lib/<name>-api.ts nach nextcloud-status-api.ts (credentials: 'include', NEXT_PUBLIC_API_URL).
  • Texte in apps/web/src/messages/de.json UND en.json (Paritaets-Tests); deutsche Texte mit echten Umlauten, umlaut-guard.spec.ts laeuft ueber de.json - neue Woerter ggf. in umlaut-dictionary.ts.
  • Kein Dashboard-Widget in Etappe 1 (WIDGET_MODULE_SLUGS unveraendert).

Datenmodell-Empfehlung

Prinzip: AutoDNS bleibt die Quelle der Wahrheit fuer Kontakte und Domains; lokal liegt nur, was AutoDNS nicht kennt oder was Geld-/Idempotenz-Schutz braucht.

Tabelle (alle mit tenantId, RLS) Inhalt Begruendung
DomainsConfig (Singleton je tenantId) environment (DEMO/LIVE, Standard DEMO), je Umgebung demoUser, demoEncryptedPassword, demoContext Int?, liveUser, liveEncryptedPassword, liveContext Int? (Standard 4), defaultNameServers (Text-Array/Json) Zwei getrennte Zugangssaetze: Umschalter kann nie versehentlich Live-Zugang in Demo tippen und umgekehrt; Wechsel verliert nichts. Passwort-Spalten iv:authTag:ciphertext.
DomainsCustomer id, name (je Mandant eindeutig), isOwnCompany Boolean Kunden gibt es im System noch nicht (Nextcloud nutzt nur Freitext customerName). Eigene kleine Tabelle erlaubt Gruppieren/Filtern ohne Tippfehler-Duplikate; die eigene Firma ist ein Eintrag mit Markierung. Kein Firmenname als Vorgabe.
DomainsContactAssignment environment, autodnsContactId Int, customerId (FK DomainsCustomer, onDelete: Restrict) ; @@unique([tenantId, environment, autodnsContactId]) AutoDNS-Kontakt-IDs von Demo und Live kollidieren zwangslaeufig, daher Umgebung im Schluessel. Kontakte selbst werden NICHT gespiegelt.
DomainsOrder id, environment, domainName, status (DRAFT, SUBMITTING, SUBMITTED, SUCCESS, FAILED, UNKNOWN, CANCELED), jobId Int?, payload Json (Kontakt-IDs, Nameserver, Laufzeit, angezeigter Preis/Waehrung), createdByUserId, confirmedByUserId, confirmedAt, errorText, lastCheckedAt ; Teil-Unique auf offene Bestellungen je (tenantId,environment,domainName) Idempotenz, Nachverfolgbarkeit, Audit "wer hat Geld ausgegeben".
Kein lokaler Domain-Cache in Etappe 1 Domainliste wird live seitenweise gelesen (view.limit), ownerc per keys[] mitgeholt; Kunde der Domain = Kunde des Inhaber-Kontakts (aus DomainsContactAssignment) Immer aktuell, keine Synchronisationsfehler. Bei vielen Hundert Domains oder AutoDNS-Ausfall spaeter Snapshot-Tabelle + Cron nachruesten. Kurzer In-Memory-Zwischenspeicher (z. B. 60 s) pro (tenant, environment) schuetzt das 3-Aufrufe-pro-Sekunde-Limit.

"Kunde" einer Domain, die der Inhaber-Kontakt nicht zuordenbar macht (Kontakt ohne Zuordnung): als "Nicht zugeordnet" anzeigen und im Filter anbieten. Spaetere Abweichung (Domain gehoert anderem Kunden als ihr Inhaber) ist eine eigene Zuordnungstabelle in Etappe 2 - Architektur laesst das zu.

Architektur-Muster fuer die Registrierung (Geldsicherheit)

Browser --1 Pruefen(name,tld)--> API --> POST /domainstudio (WHOIS+PRICE) --> Ergebnis FREE?
Browser --2 Entwurf(Kontakte,NS,Laufzeit)--> API: legt DomainsOrder DRAFT an, liefert Zusammenfassung + orderId
Browser --3 "Verbindlich registrieren" (orderId, Haken gesetzt)--> API:
      UPDATE ... SET status='SUBMITTING' WHERE id=? AND status='DRAFT' AND environment=<aktive>  (Zaehler == 1 ?)
        nein -> 409 "Bereits abgeschickt"
        ja   -> POST /domain (EINMAL, KEIN Retry) -> Job-Id speichern, status='SUBMITTED'
                 Netzwerkfehler/Timeout/unlesbare Antwort -> status='UNKNOWN' (nie zurueck auf DRAFT)
Browser/Cron --4 Aktualisieren--> GET /job/{id} -> SUCCESS/FAILED/... ; bei UNKNOWN: POST /job/_search bzw. GET /domain/{name} zum Abgleich

Don't Hand-Roll

Problem Nicht bauen Stattdessen Warum
Verschluesselung eigenes Krypto CryptoService AES-256-GCM, einheitlicher Schluessel
Moduleberechtigung eigene Rollenpruefung @UseModule + @ModuleManage Gruppen-/Direktfreigaben, Admin-Kurzschluss
Mandantenschutz WHERE tenantId von Hand forTenant() + RLS-Migration RLS-Tests erzwingen es
HTTP/Timeout neuer Client-Stil undici wie Proxmox bewaehrt, testbar
Domainnamen-Pruefung Regex-Eigenbau Pruefung per Muster ^[a-z0-9-]+\.[a-z.]{2,}$ nur als Vorfilter; Gueltigkeit entscheidet DomainStudio-Status INVALID IDN/TLD-Sonderfaelle

Common Pitfalls

  1. Timeout bei POST /domain ist unbekannter Ausgang. Nie automatisch wiederholen. Status UNKNOWN; Abgleich per GET /domain/{name} (existiert -> bestellt) oder POST /job/_search; dem Benutzer "Ergebnis ungeklaert, bitte pruefen" zeigen. Kein globaler Retry-Wrapper fuer POST.
  2. Doppelklick / zwei Browser-Tabs / Wiederholen nach Fehler. Schutz nur serverseitig durch atomaren Compare-and-Set in der DB (updateMany ... where status='DRAFT', Zaehler pruefen); Button-Sperre im Browser ist nur Komfort. Eine neue Bestellung derselben Domain erst, wenn die alte FAILED/CANCELED ist. AutoDNS bietet keinen dokumentierten Idempotenz-Schluessel; Header X-Domainrobot-Ctid existiert (js-sdk Headers.js), eine serverseitige Dublettenerkennung darauf ist [ASSUMED] - nur zur Korrelation im Log verwenden.
  3. Demo/Live verwechselt. Umgebung ist Teil jeder Bestellung und jeder Zuordnung; beim Bestaetigen muss order.environment === config.environment gelten (sonst 409). Deutlicher, dauerhafter Banner "DEMO-System" bzw. "LIVE - kostenpflichtig" in Modulkopf und Bestaetigungsdialog; Live-Registrierung verlangt ausdrueckliches Haeckchen "verbindlich registrieren (kostenpflichtig)". Umgebungswechsel nur mit Verwalten und mit Bestaetigungsdialog; Standard fuer neue Installation: DEMO. Basis-URL ist Aufzaehlung, nie Freitext.
  4. Geheimnisse. Passwort nie in Responses, Logs oder Fehlertexten (Basic-Header, Request-Body-Logging aus; request-log.ts protokolliert nur Methode/Pfad/Status). Fehlermeldungen gekuerzt; AutoDNS-messages[].text ist unkritisch, aber ohne stid-Zwang anzeigen. Entschluesselungsfehler lautstark loggen, nicht als "kein Passwort" werten.
  5. NestJS-Routenreihenfolge. contacts/import, contacts/availability, registrations/preview, domains/check, connection-test stehen VOR contacts/:id, registrations/:id; im Controller-Spec die Deklarationsreihenfolge pruefen (Vorbild handelsware-datev.controller.spec.ts). Jede neue Verwalten-Route in module-manage-handlers.spec.ts eintragen.
  6. Rate-Limit 3 Anfragen/s/IP. Einen Limiter (Warteschlange mit 350 ms Abstand je Prozess) im AutoDNS-Client, Listen in Seiten zu je 100-250; die Domainliste nicht pro Zeile mit GET /domain/{name} anreichern.
  7. HTTP 200 mit Fachfehler. status.type==='ERROR' immer auswerten (auch messages[]), sonst gilt eine fehlgeschlagene Bestellung als erfolgreich.
  8. Kontakt-IDs ueber Umgebungen. Siehe Datenmodell; Zuordnungen nie ohne environment auswerten.
  9. Kontakte aendern kann Domains beeinflussen (confirmOwnerConsent bei Inhaberwechsel gTLDs). Etappe 1: Kontakte nur anlegen/lesen, nicht aendern.
  10. Nameserver-Check .de. Standard-Nameserver in den Einstellungen muessen vorab bei AutoDNS/Anbieter eingerichtet sein, sonst scheitert .de mit FAILED.

Validation Architecture

Property Value
Framework Vitest 3.2.6 (apps/api), 4.1.9 (apps/web) [VERIFIED: CLAUDE.md Stack-Tabelle]
API-Mock AutoDNS-Client hinter Schnittstelle (AutodnsClient), im Test durch Fake ersetzt; HTTP-Schicht mit undici MockAgent pruefen: Header (Authorization, X-Domainrobot-Context, User-Agent), URL je Umgebung, Timeout
Quick run pnpm --filter api exec vitest run src/autodns-domains
Pflicht-Tests Controller-Reihenfolge; module-manage-handlers.spec.ts erweitern; Passwort nie in Response ('********'/hasPassword); Bestellung: zweiter Bestaetigungsaufruf -> 409 und POST /domain genau einmal; Timeout -> UNKNOWN ohne Retry; Umgebungswechsel zwischen DRAFT und Bestaetigung -> 409; HTTP 200 + status.type=ERROR -> Fehler; rls-coverage.spec.ts und RLS-Inventar; de/en-Paritaet der Texte
Echtprobe Erst nach Eintrag von Demo-Zugangsdaten durch den User: Verbindungstest, Kontakt anlegen, Verfuegbarkeit, Bestellung, Job-Antwortform (schliesst die [ASSUMED]-Punkte)

Security Domain

ASVS Gilt Kontrolle
V4 Zugriffskontrolle ja @UseModule Klasse, @ModuleManage auf Registrieren/Kontakt anlegen/Einstellungen/Verbindungstest; tenantId nur aus req.tenantId
V5 Eingabevalidierung ja class-validator-DTOs; Domainname normalisieren (klein, ohne Protokoll/Pfad); Kontakt-/Job-Ids als Integer
V6 Kryptografie ja CryptoService, nichts Eigenes
V9 Kommunikation ja nur https, feste Hosts (kein SSRF), Zertifikatspruefung an
V7 Protokollierung ja Bestellungen mit Benutzer-Id und Zeitpunkt in DomainsOrder (Audit)

Assumptions Log

# Annahme Abschnitt Risiko wenn falsch
A1 Demo-Context ist unklar (1 laut Drittanbieter, 4 in SDK-Beispielen) Basis Verbindungstest Demo scheitert; deshalb frei eintragbar
A2 Pflichtfelder Kontakt (type, Name/Organisation, address[], pcode, city, country, email, phone) Routen Demo-Fehler beim Anlegen; Formular nachbessern
A3 POST /domainstudio mit initial.tlds+searchToken bzw. custom.domains liefert genau die gewuenschte Domain mit WHOIS-Status Routen Verfuegbarkeitspruefung anders aufbauen
A4 Job-Id in Antwort von POST /domain unter data[0].id Routen Statusnachverfolgung muss Antwortform nachziehen; Fallback POST /job/_search
A5 { "id": n } genuegt als Kontaktverweis in ownerc/adminc/techc/zonec Routen ganzes Kontakt-Objekt aus GET /contact/{id} mitsenden
A6 Dedizierter API-Benutzer ohne 2FA; 2FA-Token-Header fuer JSON unsicher Basis Anmeldung scheitert bei 2FA-Benutzer
A7 HTTP 200 mit Fachfehler moeglich; 429 bei Rate-Limit Pitfalls schlimmstenfalls zu vorsichtige Auswertung
A8 DENIC/AutoDNS-.de-Pflichten fuer adminc/techc/zonec und Nameserver-Pruefung .de Bestellung scheitert mit klarer API-Meldung
A9 Falsche Anmeldungen koennen den Benutzer sperren Basis Verbindungstest nur einmal je Klick, nie Schleife

Open Questions

  1. Existiert beim User ein dedizierter AutoDNS-API-Benutzer (ohne 2FA) und welcher Context gilt fuer Live/Demo? Klaeren beim Eintragen der Demo-Zugangsdaten; Einstellungsmaske nimmt beides auf.
  2. Preisanzeige in der Zusammenfassung: PRICE-Dienst liefert Betrag/Waehrung; falls er fehlt oder FAILED ist, Bestellung nur mit Hinweis "Preis nicht ermittelbar" zulassen, nicht stillschweigend ohne Preis.
  3. Taeglicher Abgleich der Auftraege: Entscheidung Etappe 1 ohne Cron (Abfrage beim Oeffnen/Aktualisieren) oder mit minuetlichem Cron nur fuer offene Auftraege. Empfehlung: Cron nachruesten, sobald die Demo-Antwortform bekannt ist.

Sources

Primary (HIGH)

  • OpenAPI 2.0 https://raw.githubusercontent.com/InterNetX/domainrobot-api/master/src/domainrobot.json - Pfade, Schemas, Enums
  • https://help.internetx.com/x/cQbj (JSON API Basics) - URLs live/demo, Limits, /hello, Statuscodes S/E/N
  • InterNetX SDK-Beispiele: js-domainrobot-sdk (examples/contact|domain|domainstudio, src/services/DomainService.js, src/lib/Headers.js), php-domainrobot-sdk (example/domain/DomainList.php), java-domainrobot-sdk README (Demo-URL)
  • Curl-Proben ohne Zugangsdaten gegen beide Hosts am 2026-10-08
  • Codebase: apps/api/src/nextcloud-status/*, apps/api/src/crypto/crypto.service.ts, apps/api/src/ldap/ldap-config.service.ts, apps/api/src/module-registry/module.guard.ts, apps/api/src/handelsware-datev/*, apps/api/prisma/migrations/20261002130000_handelsware_datev, apps/web/src/lib/module-loader.ts|module-identity.ts|stores/nav-store.ts

Secondary/Tertiary (MEDIUM/LOW)

  • docs.wisecp.com/en/internetx.md (Demo-Context 1, Drittanbieter)
  • DENIC Pressemitteilung 25.05.2018 und Registrar-Wissensdatenbanken (.de-Regeln, ueber Websuche, nicht im Volltext geprueft)

Metadata

Confidence: Standard-Stack HIGH (nichts Neues); AutoDNS-Routen/Schemas HIGH; Antwortformen der Bestell-/Job-Routen MEDIUM; .de-Regeln LOW-MEDIUM Research date: 2026-10-08, gueltig ca. 30 Tage (OpenAPI aendert sich selten, aber vor Implementierung gegen Demo gegenpruefen)