Deliver a working Domaincheck module end-to-end — user enters a domain name, backend checks TLD variants via DNS, frontend shows green/red results. This is the proof-of-concept module validating the entire module system.
Purpose: Per D-01, D-02, D-03 — validates the module system with a real, interactive module. Users get immediate value.
Output: DomaincheckModule (NestJS), domaincheck frontend page, module seed data.
As a user, I want to enter a domain name and see which TLD variants are available or registered, so that I can quickly check domain availability without leaving the platform.
Task 1: Domaincheck Backend — DNS Service + API Endpoint + Module Seed
apps/api/src/domaincheck/domaincheck.module.ts,
apps/api/src/domaincheck/domaincheck.service.ts,
apps/api/src/domaincheck/domaincheck.controller.ts,
apps/api/src/domaincheck/dto/check-domain.dto.ts,
apps/api/src/domaincheck/domaincheck.seed.ts,
apps/api/src/app.module.ts
apps/api/src/module-registry/module-registry.service.ts,
apps/api/src/module-registry/module.guard.ts,
apps/api/src/app.module.ts
1. Create apps/api/src/domaincheck/dto/check-domain.dto.ts:
- CheckDomainDto: domain (string, @IsString, @IsNotEmpty, @Matches regex for valid domain label — alphanumeric + hyphens, no dots)
- Optional tlds (string array) — if omitted, use defaults
2. Create apps/api/src/domaincheck/domaincheck.service.ts:
- DEFAULT_TLDS constant: ['de', 'com', 'net', 'org'] (per D-03 configurable, these are defaults)
- Method checkDomain(domain: string, tlds?: string[]): Promise of array of { tld: string, fqdn: string, status: 'available' | 'registered' }
- Implementation: use node:dns/promises — dns.resolve(fqdn) for each TLD variant
- If resolves (any record) -> status = 'registered'
- If throws with code ENOTFOUND or ENODATA -> status = 'available'
- If throws with other error -> status = 'registered' (safe default)
- Use Promise.allSettled for parallel lookups with 5-second timeout per lookup
- Per D-02: system checks all TLD variants automatically from the base domain name
3. Create apps/api/src/domaincheck/domaincheck.controller.ts:
- POST /modules/domaincheck/check
- Protected by UseModule('domaincheck') decorator from module.guard.ts
- Accepts CheckDomainDto body
- Returns { domain: string, results: array of { tld, fqdn, status } }
4. Create apps/api/src/domaincheck/domaincheck.module.ts:
- imports: PrismaModule, ModuleRegistryModule (for guard access)
- providers: DomaincheckService
- controllers: DomaincheckController
5. Create apps/api/src/domaincheck/domaincheck.seed.ts:
- Export async function seedDomaincheckModule(moduleRegistryService: ModuleRegistryService)
- Calls moduleRegistryService.seedModule with: slug='domaincheck', name='Domaincheck', version='1.0.0', category='domain-tools' (per D-09), description={ de: 'Domain-Verfuegbarkeit pruefen', en: 'Check domain availability' }, isSystem=true
6. Register DomaincheckModule in apps/api/src/app.module.ts imports.
7. Call the seed function from an OnModuleInit hook in DomaincheckModule (or add to an existing seeding mechanism) so the domaincheck module record exists in the database on startup.
cd /home/vicolab/projects/tessera-ctl && pnpm --filter api build 2>&1 | tail -3
- API build succeeds with DomaincheckModule
- POST /modules/domaincheck/check accepts { domain: "google" } and returns results array with tld, fqdn, status for each default TLD
- DNS check correctly identifies google.com as registered
- Module seed creates a "domaincheck" record in Module table on startup
- UseModule('domaincheck') guard protects the endpoint — returns 403 if module not activated for tenant
Domaincheck API endpoint works end-to-end: accepts domain, checks DNS for TLD variants, returns structured results
Task 2: Domaincheck Frontend — Input Form + Results Display
apps/web/src/app/(portal)/modules/domaincheck/page.tsx,
apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx,
apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx,
apps/web/src/app/(portal)/modules/domaincheck/actions.ts,
apps/web/messages/de.json,
apps/web/messages/en.json
apps/web/src/app/(portal)/page.tsx,
apps/web/src/app/(portal)/layout.tsx,
apps/web/messages/de.json,
apps/web/messages/en.json
1. Add i18n keys to apps/web/messages/de.json under "domaincheck" namespace:
- title: "Domaincheck"
- description: "Pruefe ob eine Domain verfuegbar ist"
- inputPlaceholder: "Domain eingeben (z.B. beispiel)"
- checkButton: "Pruefen"
- statusAvailable: "Verfuegbar"
- statusRegistered: "Registriert"
- noResults: "Gib einen Domain-Namen ein um die Verfuegbarkeit zu pruefen"
- checking: "Pruefe..."
- error: "Fehler bei der Pruefung"
2. Add same keys in English to apps/web/messages/en.json under "domaincheck" namespace:
- title: "Domain Check"
- description: "Check if a domain is available"
- inputPlaceholder: "Enter domain (e.g. example)"
- checkButton: "Check"
- statusAvailable: "Available"
- statusRegistered: "Registered"
- noResults: "Enter a domain name to check availability"
- checking: "Checking..."
- error: "Error checking domain"
3. Create apps/web/src/app/(portal)/modules/domaincheck/actions.ts:
- Server action or client-side fetch function checkDomainAction(domain: string): fetches POST to API_URL/modules/domaincheck/check with { domain }, passes auth cookie/token, returns typed results array
4. Create apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx:
- Client component ("use client")
- Input field (text) with placeholder from i18n
- Submit button
- Calls onSubmit prop with the domain value
- Shows loading state while checking (per i18n "checking" key)
5. Create apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx:
- Client component
- Receives results array prop: { tld, fqdn, status }[]
- Renders list/table of results
- Per D-01: green badge/pill for "available", red badge/pill for "registered"
- Per D-02: shows all TLD variants with their status
- Empty state shows "noResults" message
6. Create apps/web/src/app/(portal)/modules/domaincheck/page.tsx:
- Page component within (portal) route group (gets AppShell layout)
- Uses DomainInput and ResultList components
- Manages state with useState: domain input, results array, loading, error
- On submit: calls checkDomainAction, updates results
- Layout: title at top, input below, results list below input (per specifics in CONTEXT.md)
- Use Tailwind classes consistent with existing portal pages
- Use shadcn/ui Card component for the module panel (per D-05a)
cd /home/vicolab/projects/tessera-ctl && pnpm --filter web build 2>&1 | tail -5
- Web app builds successfully with the new domaincheck route
- /modules/domaincheck page renders within the portal AppShell
- Input field accepts text, submit triggers API call
- Results display with color-coded status badges (green=available, red=registered)
- All visible strings use next-intl translations (no hardcoded text)
- Works in both DE and EN locale
User can navigate to /modules/domaincheck, enter a domain name, and see color-coded availability results for .de, .com, .net, .org TLD variants
<threat_model>
Trust Boundaries
Boundary
Description
client -> POST /modules/domaincheck/check
User-supplied domain string
API -> DNS
External DNS resolution (network)
STRIDE Threat Register
Threat ID
Category
Component
Disposition
Mitigation Plan
T-03-05
Tampering
domain input
mitigate
Regex validation in DTO — only alphanumeric + hyphens, max 63 chars, no dots allowed
T-03-06
Denial of Service
DNS lookups
mitigate
5-second timeout per lookup, max 10 TLDs per request, Promise.allSettled prevents hanging
T-03-07
Information Disclosure
DNS results
accept
Domain availability is public information, no sensitive data exposed
T-03-08
Spoofing
module access
mitigate
UseModule('domaincheck') guard + JwtAuthGuard ensure only authenticated users with activated module can access
</threat_model>
- pnpm --filter api build && pnpm --filter web build both succeed
- POST /modules/domaincheck/check returns correct results for known domains
- Frontend page renders and submits successfully
- Module guard blocks access when module not activated for tenant
<success_criteria>
User can open /modules/domaincheck in browser (DCHK-01)
System checks TLD variants via DNS and returns status (DCHK-02)
Results show clear green/red status indicators (DCHK-03, D-01)
TLD list uses defaults ['de','com','net','org'] per D-03
</success_criteria>