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
This commit is contained in:
+352
@@ -0,0 +1,352 @@
|
|||||||
|
---
|
||||||
|
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 && npx vitest run src/tenders/tender-email-config.service.spec.ts src/tenders/tenders.controller.spec.ts</automated>
|
||||||
|
<automated>cd apps/api && 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<{ success: boolean; message?: string }>
|
||||||
|
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 && 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 && 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>
|
||||||
Reference in New Issue
Block a user