Files
tessera-ctl/.planning/quick/260907-let-verbindungstest-fuer-das-postfach-im-aus/260907-let-PLAN.md
T
Schalli 5da58ad182 docs(quick-260907-let): Plan fuer Postfach-Verbindungstest im Ausschreibungs-Radar
Schliesst WINDOWS #16. Zwei Tasks: Endpunkt POST email-config/test
(Dienst + Route + Reihenfolge-Waechter) und Knopf 'Verbindung testen'
im Formular unter Meine Quellen samt Beschriftungen in beiden Sprachen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-07 15:33:54 +02:00

353 lines
24 KiB
Markdown

---
phase: quick-260907-let
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- apps/api/src/tenders/tender-email-config.service.ts
- apps/api/src/tenders/tenders.controller.ts
- apps/api/src/tenders/tender-email-config.service.spec.ts
- apps/api/src/tenders/tenders.controller.spec.ts
- apps/web/src/lib/tender-radar-api.ts
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx
- apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
autonomous: true
requirements: [WINDOWS-16]
estimate:
tokens: 55000
raw_tokens: 55000
tasks: 2
confidence: low
must_haves:
truths:
- "Ein angemeldeter Nutzer kann auf der Seite Meine Quellen die Verbindung zu seinem Postfach pruefen, ohne vorher zu speichern, und sieht entweder eine Erfolgsmeldung oder den Klartext-Fehler des Mailservers."
- "Ein bereits gespeichertes Postfach laesst sich erneut pruefen, ohne dass das Passwort neu eingetippt werden muss — der Server greift dann auf die verschluesselt hinterlegten Zugangsdaten des Nutzers zurueck."
- "Der Test laeuft ausschliesslich gegen das Postfach des angemeldeten Nutzers; eine im Rumpf mitgeschickte fremde Kennung kann daran nichts aendern."
- "Weder die Antwort des Servers noch seine Protokollzeilen enthalten Benutzername oder Passwort."
- "Die neuen Beschriftungen liegen in beiden Sprachdateien vor (Deutsch und Englisch)."
artifacts:
- "apps/api/src/tenders/tender-email-config.service.ts — Methode testConnection"
- "apps/api/src/tenders/tenders.controller.ts — Route POST email-config/test, deklariert vor @Get(':id')"
- "apps/web/src/lib/tender-radar-api.ts — Funktion testEmailConnection"
- "apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx — Knopf plus Rueckmeldung"
- "apps/web/src/messages/de.json und apps/web/src/messages/en.json — Schluessel unter tenderRadar.emailAlerts"
- "apps/api/src/tenders/tenders.controller.spec.ts — Deklarationsreihenfolge-Waechter um testEmailConnection erweitert"
key_links:
- "Controller-Handler -> extractTriageContext(req).userId -> Service-Aufruf (userId kommt nie aus dem Rumpf)"
- "Service -> ImapProvider.testConnection bzw. ExchangeInboxProvider.testConnection, ausgewaehlt nach dto.protocol"
- "Leeres Passwort im Rumpf -> entschluesselte encryptedInboxCreds derselben userId-Zeile"
- "EmailAlertConfigForm -> testEmailConnection -> POST /modules/tender-radar/email-config/test"
- "vi.mock-Fabriken fuer @/lib/tender-radar-api in EmailAlertConfigForm.test.tsx UND my-sources.test.tsx muessen testEmailConnection auffuehren — sonst wirft Vitest beim Import der Komponente"
---
<objective>
Der Ausschreibungs-Radar bekommt fuer das Postfach unter "Meine Quellen" einen
Verbindungstest — genau den, den das DKV-Modul seit Phase 7 hat. Schliesst
WINDOWS-Eintrag #16.
Purpose: Heute faellt ein Tippfehler in der EWS-Endpunkt-URL oder ein falsches
Passwort erst dadurch auf, dass dauerhaft nichts ankommt — und dann ist nicht
unterscheidbar, ob die Verbindung scheitert oder schlicht keine Alarm-Mail da
war. Das blockiert unmittelbar den offenen Live-Test WINDOWS #12, dessen ganzer
Zweck der Beleg des handgeschriebenen NTLM/SOAP-Wegs ist.
Output: Ein per-Nutzer-Endpunkt POST /modules/tender-radar/email-config/test,
ein Knopf "Verbindung testen" im Formular, Beschriftungen in beiden
Sprachdateien, und ein erweiterter Deklarationsreihenfolge-Waechter.
Die Faehigkeit selbst wird NICHT neu gebaut: beide Inbox-Provider bringen
testConnection() bereits mit (apps/api/src/inbox/imap.provider.ts:343,
apps/api/src/inbox/exchange-inbox.provider.ts:294, deklariert in
apps/api/src/inbox/inbox-provider.interface.ts:31). Dieser Plan verdrahtet sie
nur — das Vorbild ist DkvController.testConnection (dkv.controller.ts:106) und
DkvService.testConnection (dkv.service.ts:201).
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/STATE.md
@CLAUDE.md
Vorbild Backend (gemessen, nicht aus Notizen):
@apps/api/src/dkv/dkv.service.ts
@apps/api/src/tenders/tender-email-config.service.ts
@apps/api/src/tenders/tenders.controller.ts
Vorbild Frontend:
@apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx
@apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx
@apps/web/src/lib/tender-radar-api.ts
</context>
<tasks>
<task type="tracer" tdd="true">
<name>Task 1: Endpunkt POST email-config/test — vom Formularrumpf bis zum Mailserver</name>
<files>apps/api/src/tenders/tender-email-config.service.ts, apps/api/src/tenders/tenders.controller.ts, apps/api/src/tenders/tender-email-config.service.spec.ts, apps/api/src/tenders/tenders.controller.spec.ts</files>
<read_first>
apps/api/src/dkv/dkv.service.ts (Methode testConnection, Zeilen 195-235) — die Vorlage inklusive Rueckfall auf die gespeicherten Zugangsdaten bei leerem Passwort.
apps/api/src/tenders/tender-email-config.service.ts (Methode saveConfig) — dort steht die bereits erprobte Entschluessel-und-Ergaenzen-Logik dieses Dienstes.
apps/api/src/tenders/tenders.controller.ts Zeilen 328-365 (die beiden email-config-Handler) und Zeile 582 (@Get(':id')).
apps/api/src/tenders/tenders.controller.spec.ts Zeilen 91-93 (makeFakeRequest), 132-140 (makeFakeEmailConfigService), 411-423 (der bestehende email-config-Reihenfolgetest).
</read_first>
<behavior>
- TenderEmailConfigService.testConnection(userId, dto) mit gefuelltem Passwort im Rumpf: reicht genau dieses Passwort an den Provider durch, liest die Datenbank gar nicht erst.
- Mit leerem Passwort im Rumpf und vorhandener gespeicherter Zeile: entschluesselt encryptedInboxCreds derselben userId und uebergibt das gespeicherte Passwort an den Provider.
- Mit leerem Benutzernamen im Rumpf: ergaenzt analog den gespeicherten Benutzernamen (gleiche Semantik wie saveConfig in derselben Datei).
- dto.protocol 'exchange' waehlt den Exchange-Provider, alles andere den IMAP-Provider.
- Die Rueckgabe ist unveraendert das, was der Provider liefert: { success: true } oder { success: false, message }. Kein Feld mehr, kein Feld weniger.
- TendersController.testEmailConnection nimmt userId aus extractTriageContext(req) und reicht es als erstes Argument weiter; ein im Rumpf mitgeschicktes Fremdfeld erreicht den Dienst nicht.
- Der bestehende Reihenfolgetest fuer email-config schlaegt fehl, sobald testEmailConnection nach getTender deklariert wird.
</behavior>
<action>
Schritt 1 — Dienst. In apps/api/src/tenders/tender-email-config.service.ts eine
Methode testConnection(userId: string, dto: TenderEmailConfigDto) ergaenzen, die
{ success: boolean; message?: string } liefert. Sie baut ein InboxConfig-Objekt
(Typ aus '../inbox/inbox-provider.interface') aus den DTO-Feldern protocol, host,
port, username, password, encryption, folder, senderFilter, domain — mit denselben
Ersatzwerten wie DkvService.testConnection (host '' , port 993, folder 'INBOX').
Fehlen username oder password im DTO, wird die Zeile der uebergebenen userId gelesen
und encryptedInboxCreds entschluesselt; nur der jeweils fehlende Wert wird daraus
ergaenzt. Diese Rueckfall-Logik ist strukturell dieselbe, die saveConfig in derselben
Datei schon fuehrt — dort abschauen, nicht neu erfinden. Schlaegt das Entschluesseln
fehl, wird der Fehler still verschluckt und ohne Zugangsdaten weiterprobiert; eine
etwaige Protokollzeile nennt nur die userId, niemals einen Wert aus dem
Zugangsdatensatz (T-05-13, T-QT16-03). Anschliessend
protocol === 'exchange' ? exchangeProvider : imapProvider aufrufen und dessen
Ergebnis unveraendert zurueckgeben.
Die beiden Provider werden per Konstruktor injiziert. Sie MUESSEN als optionale
Parameter ANGEHAENGT werden (ImapProvider, ExchangeInboxProvider aus
'../inbox/imap.provider' bzw. '../inbox/exchange-inbox.provider') — genau nach dem
in diesem Repository dokumentierten Muster von TendersController.tenderIngestionService
(tenders.controller.ts Zeilen 95-103): so bleiben die acht bestehenden
new TenderEmailConfigService(prisma, crypto)-Aufrufe in der Spec ohne Aenderung
typkorrekt, waehrend Nest im Betrieb immer beide aufloest (InboxModule ist in
tenders.module.ts bereits importiert, ImapProvider und ExchangeInboxProvider werden
dort exportiert — nichts an der Modulverdrahtung ist anzufassen). Ist der benoetigte
Provider wider Erwarten nicht vorhanden, liefert testConnection
{ success: false, message: 'Inbox-Anbieter nicht verfuegbar' } statt zu werfen, damit
die Antwortform der Route in jedem Fall stabil bleibt.
Schritt 2 — Route. In apps/api/src/tenders/tenders.controller.ts unmittelbar nach
saveEmailConfig (also klar VOR @Get(':id') in Zeile 582) einen Handler
testEmailConnection mit @Post('email-config/test') und @UseModule('tender-radar')
ergaenzen. Er liest const { userId } = this.extractTriageContext(req) und gibt
this.tenderEmailConfig.testConnection(userId, dto) zurueck. Der Konstruktor der
Klasse bleibt unangetastet — tenderEmailConfig ist bereits injiziert; das ist der
Grund, warum die Logik in den Dienst und nicht in den Controller gehoert (42
Konstruktoraufrufe in der Spec wuerden sonst brechen).
Den Handler mit einem Kommentarblock im Stil der Nachbarhandler versehen. Er soll
zwei Dinge festhalten: erstens, dass userId ausschliesslich aus dem
Authentifizierungskontext stammt und der DTO gar kein Kennungsfeld traegt (T-17-01 /
T-14-03-05, IDOR); zweitens, warum der Handler trotzdem oberhalb von @Get(':id')
steht, obwohl Nest Routen je HTTP-Verb aufloest und ein GET-Platzhalter eine
POST-Route heute nicht verdecken kann — der ganze email-config-Block bleibt beisammen,
und der Waechter greift auch dann noch, wenn spaeter einmal ein Platzhalter fuer POST
hinzukaeme. Diese Einordnung bitte woertlich so treffen, nicht als schlichtes
"sonst 404".
Schritt 3 — Tests. In apps/api/src/tenders/tender-email-config.service.spec.ts drei
Faelle ergaenzen, die den bestehenden Fabriken (makeFakePrisma, makeFakeCrypto)
folgen und zusaetzlich zwei winzige Provider-Attrappen mit einer testConnection-Zusage
uebergeben: (a) getipptes Passwort geht unveraendert an den Provider,
(b) leeres Passwort holt das gespeicherte, (c) protocol 'exchange' waehlt die
Exchange-Attrappe. In apps/api/src/tenders/tenders.controller.spec.ts erstens
makeFakeEmailConfigService um eine testConnection-Attrappe erweitern, zweitens einen
Fall ergaenzen, der mit makeFakeRequest('u1', ...) belegt, dass der Dienst mit 'u1'
aufgerufen wird, auch wenn der Rumpf ein abweichendes Kennungsfeld mitfuehrt, und
drittens den vorhandenen Reihenfolgetest in Zeile 411 um den Index von
testEmailConnection erweitern (kleiner als der Index von getTender).
</action>
<verify>
<automated>cd apps/api &amp;&amp; npx vitest run src/tenders/tender-email-config.service.spec.ts src/tenders/tenders.controller.spec.ts</automated>
<automated>cd apps/api &amp;&amp; npx tsc --noEmit -p tsconfig.json</automated>
<automated>grep -n "email-config/test" apps/api/src/tenders/tenders.controller.ts</automated>
</verify>
<done>
Beide Vitest-Dateien laufen gruen (Ausgangsstand: 8 + 52 Faelle, jetzt mehr), der
API-Typecheck bleibt fehlerfrei, und die Route POST email-config/test steht in
tenders.controller.ts an einer Zeilennummer unterhalb von saveEmailConfig und
oberhalb von @Get(':id').
</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Knopf "Verbindung testen" im Postfach-Formular samt Beschriftungen</name>
<files>apps/web/src/lib/tender-radar-api.ts, apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json, apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx, apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx</files>
<read_first>
apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx Zeilen 140-205 (Zustandsfelder, update, handleTestConnection) und 520-580 (Knopfreihe und Rueckmeldung) — das nachzubildende Verhalten.
apps/web/src/lib/dkv-api.ts Zeilen 125-141 (testConnection) und apps/web/src/lib/tender-radar-api.ts Zeilen 518-571 (fetchEmailConfig/saveEmailConfig samt extractErrorMessage) — die Hausform des jeweiligen Moduls.
apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx Zeilen 14-22 — die vi.mock-Fabrik, die zwingend mitwachsen muss.
</read_first>
<behavior>
- Klick auf den Knopf ruft testEmailConnection genau einmal mit demselben Rumpf auf, den auch das Speichern erzeugt (formToPayload), also ohne Passwortfeld, solange keins getippt wurde.
- Waehrend der Anfrage tragen Test- und Speicherknopf den disabled-Zustand, und der Testknopf zeigt die Wartebeschriftung.
- Eine Antwort mit success false zeigt die Fehlermeldung des Servers sichtbar an.
- Eine Aenderung an irgendeinem Formularfeld raeumt die vorherige Rueckmeldung wieder weg.
- Die bestehenden fuenf Faelle in EmailAlertConfigForm.test.tsx bleiben unveraendert gruen.
</behavior>
<action>
Schritt 1 — Sprachdateien zuerst, damit die Komponente auf vorhandene Schluessel
zugreift. Unter tenderRadar.emailAlerts in apps/web/src/messages/de.json und
apps/web/src/messages/en.json jeweils vier Schluessel ergaenzen: testConnection,
testTesting, testSuccess und testFailed. Wortlaut und Platzhalter woertlich von
dkvFleet.form uebernehmen, wo dieselben vier Schluessel bereits stehen — deutsch
"Verbindung testen" / "Verbindung wird getestet..." / "Verbindung erfolgreich" /
"Verbindung fehlgeschlagen: {error}", englisch entsprechend. Deutsch ist die
Hauptsprache dieser Installation, aber beide Dateien muessen den Satz tragen.
Die 401-Hinweisliste des DKV-Formulars NICHT mitnehmen: sie gehoert dort zu einem
eigenen Hilfetext-Block, den dieses Formular nicht fuehrt.
Schritt 2 — Api-Klient. In apps/web/src/lib/tender-radar-api.ts eine exportierte
Funktion testEmailConnection ergaenzen, die denselben Nutzlast-Typ wie
saveEmailConfig entgegennimmt und Promise&lt;{ success: boolean; message?: string }&gt;
liefert. Sie sendet POST an ${API_URL}/modules/tender-radar/email-config/test mit
'Content-Type': 'application/json' und credentials: 'include'. Bei nicht-ok-Antwort
wird extractErrorMessage genutzt — so machen es die beiden Nachbarfunktionen in
derselben Datei; die knappere Fehlerbehandlung aus dkv-api.ts hier bewusst nicht
uebernehmen.
Schritt 3 — Formular. In EmailAlertConfigForm.tsx testEmailConnection importieren,
die Zustandsfelder isTesting und testResult ergaenzen (Form wie in InboxConfigForm),
in update() zusaetzlich setTestResult(null) aufrufen und einen Handler
handleTestConnection schreiben, der formToPayload(form) sendet und Erfolg wie
Fehlschlag in testResult ablegt; ein geworfener Fehler landet als
{ success: false, message } dort. In der Knopfreihe am Ende (heute nur "Speichern")
den Testknopf VOR dem Speicherknopf einsetzen, mit der Rahmen-Optik des DKV-Vorbilds,
disabled bei isTesting || isSaving; der Speicherknopf bekommt zusaetzlich isTesting
in seine disabled-Bedingung. Unter der Knopfreihe die Rueckmeldung rendern, gruen bei
Erfolg und in der Warnfarbe bei Fehlschlag, mit denselben Farbwerten, die das
Formular bereits fuer saveSuccess und saveError verwendet.
Der Beschreibungsblock ueber der Komponente (heute Zeilen 119-135) behauptet
ausdruecklich, dieses Formular fuehre keinen solchen Knopf und es gebe serverseitig
nichts, wogegen er pruefen koennte. Diese Passage ist ab jetzt falsch und muss
umgeschrieben werden: der Knopf ist da, er spricht die neue Route an, und die
Abrufhaeufigkeit bleibt weiterhin plattformweit geregelt (D-15) — nur Letzteres bleibt
als Begruendung stehen, warum es hier kein Intervallfeld gibt.
Schritt 4 — Attrappen nachziehen. Beide Testdateien, die
'@/lib/tender-radar-api' per vi.mock mit einer Fabrik ersetzen, muessen
testEmailConnection mitfuehren: EmailAlertConfigForm.test.tsx (Zeilen 8-11) und
my-sources.test.tsx (Zeilen 14-22). Fehlt der Eintrag, wirft Vitest bereits beim
Import der Komponente, weil der Named Export auf der Attrappe nicht existiert — das
ist der wahrscheinlichste Weg, diesen Task rot zu machen. In
EmailAlertConfigForm.test.tsx zusaetzlich die vier neuen Uebersetzungen in die
hand gepflegte Schluesseltabelle des next-intl-Mocks aufnehmen und zwei Faelle
ergaenzen: (a) Klick auf den Testknopf ruft testEmailConnection einmal mit einem
Rumpf ohne Passwortfeld auf, (b) eine Antwort { success: false, message: '401 ...' }
laesst die Fehlermeldung im Dokument erscheinen.
</action>
<verify>
<automated>cd apps/web &amp;&amp; npx vitest run "src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx" "src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx"</automated>
<automated>cd apps/web &amp;&amp; npx tsc --noEmit</automated>
<automated>node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');const k=['testConnection','testTesting','testSuccess','testFailed'];for(const key of k){if(!de.tenderRadar.emailAlerts[key])throw new Error('de fehlt '+key);if(!en.tenderRadar.emailAlerts[key])throw new Error('en fehlt '+key);}console.log('locale keys ok');"</automated>
<human-check>
Abnahme im Browser — der Test beweist sich nur an einem echten Mailserver, nie
an Attrappen. Der Neubau und Neustart der Container liegt beim Nutzer; dieser
Plan fasst weder Docker noch den Testserver an.
1. Portal oeffnen, Ausschreibungs-Radar, Seite "Meine Quellen", Abschnitt
"Mein Postfach". Unterhalb der Felder steht jetzt neben "Speichern" ein
zweiter Knopf "Verbindung testen".
2. Absichtlich falsch pruefen: einen unsinnigen Servernamen eintragen (zum
Beispiel gibtesnicht.example) und den Testknopf druecken. Erwartung: kurz
"Verbindung wird getestet...", danach in Rot "Verbindung fehlgeschlagen:"
gefolgt vom Klartext des Mailservers.
3. Richtig pruefen: echte Postfachdaten samt Passwort eintragen und erneut
druecken. Erwartung: gruene Zeile "Verbindung erfolgreich".
4. Ohne Passwort nachpruefen: speichern, Seite neu laden (das Passwortfeld ist
danach absichtlich leer) und nur den Testknopf druecken. Erwartung: wieder
"Verbindung erfolgreich" — der Server hat auf die gespeicherten Zugangsdaten
zurueckgegriffen. Das ist der Punkt, der den Test im Alltag brauchbar macht.
5. Kurzer Blick in die API-Protokollzeilen des Laufs: weder Benutzername noch
Passwort duerfen dort auftauchen.
Punkt 2 und 3 muessen sich unterscheiden — eine gruene Meldung bei absichtlich
falschem Server waere ein Fehlalarm und schlimmer als gar kein Knopf. Faellt
einer der Punkte durch, bleibt WINDOWS #16 offen; die gruenen Unit-Tests taugen
dann nicht als Gegenargument, sie pruefen ausschliesslich gegen Attrappen.
</human-check>
</verify>
<done>
Beide Web-Testdateien laufen gruen (Ausgangsstand EmailAlertConfigForm: 5 Faelle,
jetzt 7), der Web-Typecheck bleibt fehlerfrei, und der Sprachdatei-Check meldet
"locale keys ok" fuer beide Sprachen. Die Browser-Abnahme oben wandert in die
Abnahmeliste am Phasenende (human_verify_mode = end-of-phase).
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Browser -> API (POST /modules/tender-radar/email-config/test) | Nutzereingaben aus dem Formular, inklusive frei waehlbarem Servernamen und Zugangsdaten |
| API -> Mailserver (IMAP/EWS) | Ausgehende Verbindung mit entschluesselten Zugangsdaten |
| API -> Datenbank (encryptedInboxCreds) | Ruhende, verschluesselte Zugangsdaten eines einzelnen Nutzers |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-QT16-01 | Elevation of Privilege | TendersController.testEmailConnection | high | mitigate | userId stammt ausschliesslich aus extractTriageContext(req); TenderEmailConfigDto traegt gar kein Kennungsfeld, ein mitgeschicktes Fremdfeld erreicht den Dienst nicht (T-17-01 / T-14-03-05). Belegt durch den Controller-Testfall aus Task 1, Schritt 3 |
| T-QT16-02 | Information Disclosure | Antwort und Protokoll des Tests | high | mitigate | Rueckgabe ist unveraendert das { success, message? } des Providers — kein Durchreichen des Konfigurationsobjekts. Der Dienst protokolliert im Fehlerfall hoechstens die userId; die Provider-Protokollzeilen sind seit T-07-03 bereits zugangsdatenfrei |
| T-QT16-03 | Information Disclosure | entschluesselte gespeicherte Zugangsdaten | medium | mitigate | Entschluesselte Werte existieren nur im Methoden-Scope von testConnection, wandern ausschliesslich in das InboxConfig-Objekt und werden weder zurueckgegeben noch protokolliert (T-05-13) |
| T-QT16-04 | Spoofing | frei waehlbarer host / EWS-Endpunkt im Testrumpf | medium | accept | Identische Oberflaeche und identisches Risiko wie das bereits bestehende PUT /email-config, das denselben Wert entgegennimmt und dauerhaft speichert. Der Test fuegt keine neue Angriffsflaeche hinzu; das Modul-Gate @UseModule('tender-radar') bleibt unveraendert davor |
| T-QT16-05 | Denial of Service | wiederholtes Ausloesen des Tests | low | accept | Der Test bindet eine ausgehende Verbindung pro Klick, genau wie POST /dkv/test-connection seit Phase 7. Kein neues Muster, keine eigene Drosselung — dieselbe Bewertung wie beim Vorbild |
Keine neuen Pakete: dieser Plan installiert nichts ueber npm, das
Legitimitaets-Gate fuer Paketinstallationen ist daher nicht anwendbar.
</threat_model>
<verification>
Nach Abschluss beider Tasks, aus dem Repository-Wurzelverzeichnis:
```
cd apps/api && npx vitest run && npx tsc --noEmit -p tsconfig.json
cd apps/web && npx vitest run && npx tsc --noEmit
```
Gemessener Ausgangsstand vor Beginn dieses Plans: beide Typechecks fehlerfrei
(Exit 0), apps/api/src/tenders/tender-email-config.service.spec.ts 8 Faelle
gruen, apps/api/src/tenders/tenders.controller.spec.ts 52 Faelle gruen,
EmailAlertConfigForm.test.tsx 5 Faelle gruen. Die Zahlen duerfen nur steigen.
Der Reihenfolge-Waechter ist die dokumentierte Ausnahme von der
Reparatur-auf-Zuruf-Regel dieses Repositories (Entscheidung vom 2026-08-11):
er muss laut werden, wenn jemand die Handler spaeter umsortiert.
</verification>
<success_criteria>
- POST /modules/tender-radar/email-config/test existiert, ist mit
@UseModule('tender-radar') abgesichert und liefert { success, message? }.
- Die Kennung des Postfachbesitzers stammt nachweislich aus dem
Authentifizierungskontext, nicht aus dem Rumpf — durch einen Testfall belegt.
- Ein leer gelassenes Passwortfeld greift auf die gespeicherten,
verschluesselten Zugangsdaten desselben Nutzers zurueck.
- Der Knopf "Verbindung testen" steht im Postfach-Formular unter "Meine
Quellen", zeigt einen Wartezustand und eine sichtbare Rueckmeldung.
- Beide Sprachdateien tragen die vier neuen Schluessel.
- Der Deklarationsreihenfolge-Waechter deckt testEmailConnection mit ab.
- Alle vier Prueflaeufe (zwei Testsuiten, zwei Typechecks) bleiben gruen.
- Die Browser-Abnahme aus dem human-check-Block von Task 2 ist bestaetigt (sie laeuft am Phasenende, nicht mitten im Lauf).
</success_criteria>
<output>
Create `.planning/quick/260907-let-verbindungstest-fuer-das-postfach-im-aus/260907-let-SUMMARY.md` when done
</output>