- 30 verbleibende forTenant()-Aufrufstellen in sieben Diensten (calendar 6,
dashboard 9, favorites 5, tender-email-config 3, tender-notification-pref 2,
tender-rss-feed 2, tender-triage 3) reichen userId als drittes Argument
durch. tender-digest.scheduler.ts bleibt zweistellig (Hintergrunddienst,
Etappe 3c), mit Begruendung im Kommentar. Keine Methodensignatur, kein
Controller angefasst, keine anwendungsseitige userId-Filterung entfernt.
- rls-scratch-check.mjs: zwoelf Extraktionsstellen auf die neue Migration
umgeleitet (TenderEmailConfig/TenderNotificationPref/TenderSavedSearch/
TenderTriage/TenderRssFeedSource in runTendersAreaChecks, SearchProvider in
runSearchProviderAreaChecks/runDashboardAreaChecks, DashboardLayout/
WidgetInstance, CalendarSource/FavoriteLink samt regelstand-eindeutig-Gates).
SearchProvider/TenderRssFeedSource jetzt mit extractAllPolicySql (4 Regeln).
runUserDimensionChecks() um die uebrigen neun Tabellen erweitert (neue
Routine runCommandSeparatedPersonalTableCheck fuer die zwei NULL-faehigen
Tabellen inkl. gemeinsame-Zeile-Pruefungen).
- Sechs Loch-Pruefungen umgedreht (dashboardlayout, widgetinstance,
searchprovider, calendarsource, favoritelink-Doppelaussage getrennt) —
alte Messung ohne Benutzer bleibt unter neuem Namen, Umkehrung MIT
Benutzer erwartet das Gegenteil; kein alter Name mehr als Kennung.
- Baseline: 1020/62 Tests weiterhin gruen, Typpruefung sauber, Werkzeug
203/203 bestanden (vorher 146).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- calendar.service.spec.ts: NEU, Zwei-Klienten-Nachweis ueber __makeBoundClient
(Muster dkv.service.spec.ts), Attrappen fuer CryptoService und die drei
Provider, 23 Testfaelle: getSources/addSource, updateSource inkl. drei
Erhaltungsfaelle, alle drei Besitzpruefungen je Ausnahmeart, testConnection
Erfolgs-/Fehlerpfad, aggregateEvents/fetchAndCacheEvents inkl. beider
Synchronstatus-Rueckschreibungen, Cache-Verhalten inkl. Nutzer-Trennung,
testConnectionFromConfig ohne DB-Zugriff, Wachhund fuer genau einen
gebundenen Klienten je Aufruf
- calendar.service.ts: alle 12 Zugriffe auf forTenant() umgestellt, ein
tenantPrisma-Klient je Methode (getSources, addSource, updateSource,
deleteSource, testConnection, fetchAndCacheEvents); Cache-Schluessel-Urteil
und die kein-sechster-Hintergrunddienst-Begruendung als Kommentare
festgehalten
- calendar.controller.ts: alle sechs kontextnutzenden Handler reichen
Benutzer- UND Mandantenkennung aus extractContext durch, keine neue
Vertrauensquelle
- Falsifizierungsnachweis durchgefuehrt: eine Rueckschreibung testweise
entbunden, benannter Test ging rot ("aggregateEvents, Erfolgspfad"), Fund
bestaetigt, zurueckgenommen
- docs/mandantentrennung-zugriffsklassifikation.md: Bestandsaufnahme-Zeile
calendar.service.ts/calendarSource auf gebunden gezogen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
CALENDAR_ENCRYPTION_KEY was named after the calendar module because that
module needed encryption first, in Phase 5. Every feature since has shared the
same key -- SMTP, the DKV and tender mailboxes, and as of today the LDAP bind
password -- so the name has been describing one of five users rather than the
thing itself, and each new feature inherited the confusion.
TESSERA_ENCRYPTION_KEY is the name now. The old one is still read, because
renaming outright would stop every existing installation at the next start:
their .env carries the old name, and compose was just made to fail hard on a
missing key. When only the old name is present the API logs a deprecation
warning naming both, and when both are set the new one wins -- otherwise a
half-migrated .env would encrypt with one key and decrypt with the other.
CalendarCryptoService becomes CryptoService in its own global CryptoModule.
Four modules used to import CalendarModule purely to reach the provider, which
read as a dependency on calendars where there was none; that import is gone.
Compose keeps the hard failure: without either name the stack refuses to
start. Verified in both files for all three cases -- neither name set (abort),
only the old name (starts), only the new name (starts).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- SSRF check skipped for Exchange type (internal EWS servers are common)
- testConnectionFromConfig catches SSRF/validation errors, returns {success:false,error} instead of throwing 403
- updateSource reads existing.type to determine effective type for SSRF check
- Panel shows saveError/editSaveError on failed add/update
- Edit form initialValues now includes domain field
- i18n: calendar.saveError key added (de+en)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Prisma: domain String? added to CalendarSource model (db push applied)
- DTOs: domain in CreateCalendarSourceDto, UpdateCalendarSourceDto, new TestCalendarSourceConfigDto
- Service: domain in SOURCE_SAFE_SELECT, addSource, updateSource; new testConnectionFromConfig method
- Controller: POST /calendar/sources/test-config (before :id routes to avoid collision)
- ExchangeProvider: domain in all source interfaces; passed as 3rd arg to EWS WebCredentials
- Frontend: domain in CalendarSource/CreateSourcePayload/UpdateSourcePayload; testSourceConfig API fn
- Form: domain field (Exchange-only), "Test connection" button with idle/loading/success/error states
- i18n: de+en keys for formFieldDomain, formFieldDomainHint, formTestConnection, formTesting, formTestSuccess, formTestFailed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ExchangeInboxProvider rewritten to use httpntlm + raw EWS SOAP:
FindItem / GetItem / GetAttachment via NTLM challenge-response.
No longer requires Basic Auth on Exchange EWS virtual directory.
Folder name mapped to EWS DistinguishedFolderId (Inbox/SentItems/etc).
- CalendarCryptoService: move key init from onModuleInit to constructor
so MailModule.forRootAsync() factory can call decrypt() before NestJS
lifecycle hooks execute (startup crash when SmtpConfig row has password).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Implement aggregateEvents with Promise.allSettled across visible sources
- Dispatch to ICS/CalDAV/Exchange providers by source.type with credential decryption
- In-memory per-user event cache with 5-minute TTL (Pitfall 4)
- Background cache refresh when close to expiry
- Implement testConnection with lastSyncAt/lastSyncError updates
- Default window: now to now+30 days
- Events sorted by start ascending with source color included