feat(quick-260911-nke): Benutzer an 34 Aufrufstellen gesetzt, zehn Tabellen gemessen, sechs Pruefungen umgedreht
- 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
This commit is contained in:
@@ -108,13 +108,19 @@ const CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
* The three ownership checks (`updateSource`/`deleteSource`/
|
||||
* `testConnection`, comparing `existing.userId` against the calling user)
|
||||
* are kept UNCHANGED alongside the binding, not replaced by it: the RLS
|
||||
* policy on `CalendarSource` carries no user dimension (measured
|
||||
* 260911-cwh, Aufgabe 1 — `calendarsource-fremder-nutzer-desselben-mandanten-gebunden-sichtbar`),
|
||||
* policy on `CalendarSource` carried no user dimension when measured
|
||||
* 260911-cwh, Aufgabe 1 (`calendarsource-fremder-nutzer-desselben-mandanten-gebunden-sichtbar`),
|
||||
* so a colleague of the SAME tenant would otherwise see and modify a
|
||||
* fellow user's encrypted Exchange/CalDAV credentials. Until the RLS
|
||||
* policy itself gains a user dimension (Etappe-3-Entscheidung (2)), these
|
||||
* application-level checks remain the only protection between users of the
|
||||
* same tenant.
|
||||
* fellow user's encrypted Exchange/CalDAV credentials.
|
||||
*
|
||||
* Nachtrag (260911-nke, Etappe 3b): seit Migration 20260911120000 traegt die
|
||||
* `tenant_isolation_policy` auf `CalendarSource` die Benutzerdimension
|
||||
* (`current_user_id() IS NULL OR "userId" = current_user_id()`) — jeder
|
||||
* `forTenant()`-Aufruf oben reicht `userId` als drittes Argument durch. Die
|
||||
* drei anwendungsseitigen Besitzpruefungen bleiben trotzdem UNVERAENDERT
|
||||
* bestehen: die Datenbankregel ist ein ZWEITES Netz, kein Ersatz dafuer, und
|
||||
* ein Aufrufer, der `userId` vergisst, saehe ohne sie den ganzen Mandanten
|
||||
* (siehe .planning/WINDOWS.md).
|
||||
*
|
||||
* Credentials encrypted at rest via CryptoService (T-05-10).
|
||||
*/
|
||||
@@ -155,7 +161,7 @@ export class CalendarService {
|
||||
* Adds a `hasCredentials` boolean so the UI knows if credentials are set.
|
||||
*/
|
||||
async getSources(userId: string, tenantId: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const sources = await tenantPrisma.calendarSource.findMany({
|
||||
where: { userId },
|
||||
select: {
|
||||
@@ -195,7 +201,7 @@ export class CalendarService {
|
||||
data.encryptedPassword = this.crypto.encrypt(dto.password);
|
||||
}
|
||||
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const created = await tenantPrisma.calendarSource.create({
|
||||
data: data as any,
|
||||
select: SOURCE_SAFE_SELECT,
|
||||
@@ -209,7 +215,7 @@ export class CalendarService {
|
||||
* Re-encrypts password if provided; T-05-12 ownership enforcement.
|
||||
*/
|
||||
async updateSource(id: string, userId: string, tenantId: string, dto: UpdateCalendarSourceDto) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const existing = await tenantPrisma.calendarSource.findUnique({
|
||||
where: { id },
|
||||
select: { userId: true, type: true },
|
||||
@@ -261,7 +267,7 @@ export class CalendarService {
|
||||
* Deletes a calendar source. Ownership check enforced (T-05-12).
|
||||
*/
|
||||
async deleteSource(id: string, userId: string, tenantId: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const existing = await tenantPrisma.calendarSource.findUnique({
|
||||
where: { id },
|
||||
select: { userId: true },
|
||||
@@ -283,7 +289,7 @@ export class CalendarService {
|
||||
* Updates lastSyncAt/lastSyncError on the source record.
|
||||
*/
|
||||
async testConnection(id: string, userId: string, tenantId: string): Promise<{ success: boolean; error?: string }> {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const source = await tenantPrisma.calendarSource.findUnique({ where: { id } });
|
||||
if (!source) throw new NotFoundException('Calendar source not found');
|
||||
if (source.userId !== userId) throw new ForbiddenException('Not your calendar source');
|
||||
@@ -399,7 +405,7 @@ export class CalendarService {
|
||||
to: Date,
|
||||
cacheKey: string,
|
||||
): Promise<CalendarEvent[]> {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const sources = await tenantPrisma.calendarSource.findMany({
|
||||
where: { userId, isVisible: true },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user