Files
tessera-ctl/apps/api/src/groups/module-grants.controller.ts
T
schalli 7c9d7c1223 refactor(quick-260921-m34): Aufgabe 2b - getypte Anfrage in elf Controllern, zwei Befunde gemeldet
(req as any) und @Req() req: any durch AuthenticatedRequest ersetzt in
dashboard, favorites, calendar, groups, module-grants, module-registry,
tenders, dkv, ldap, settings; @CurrentUser() in user.controller auf AuthUser.
Die abwehrenden Pruefungen ("No tenant context", "No user context") bleiben
lebendig, weil user auf dem Anfragetyp wahlfrei ist - genau das beschreibt
den Zustand auf oeffentlichen Wegen.

Nebengewinn ohne neue Zusicherungen: req.tenantId as string | undefined
(dkv, settings), file.buffer as Buffer und file.mimetype as string
(dkv, user) sind weggefallen, weil der Typ sie jetzt traegt.

BEFUND 1 (D-03, gemeldet) dashboard.controller.ts:74 alt: der Handler las
req.user?.role NACH extractContext und gab sie an getWidgets(role: Role)
weiter, das eine Rolle zwingend verlangt. Die Annahme "hier gibt es immer
einen Aufrufer" stimmt - die Pruefung "No user context" erzwingt sie -, aber
sie stand in einer anderen Methode, wo der Compiler sie nicht sehen konnte.
extractContext gibt die Rolle jetzt mit zurueck: keine neue Pruefung, kein
erfundener Wert, gleiche Reihenfolge, gleiche Meldungen.

BEFUND 2 (D-03, gemeldet) tenders.controller.ts:142: resolveRequestingTenantId
erklaerte string | undefined, liest aber req.tenantId, das TenantGuard fuer
einen SUPER_ADMIN ohne Mandanten auf null setzt. Die Erklaerung war also nie
vollstaendig. Erweitert auf string | null | undefined, und buildTenderWhere
nimmt string | null - beides nur Erklaerung, kein Verhalten: die Funktion
entscheidet seit jeher ueber Wahrheitswert und faellt bei beiden zu
(nur global sichtbare Ausschreibungen).

Fixtures in user.controller.spec.ts ergaenzt (username, mustChangePassword,
originalname, size). Testzahlen unveraendert.

noExplicitAny in apps/api/src: 137 -> 66. type-check 4/4, lint 5/5,
apps/api 72/1143, apps/web 73/531, tenant.guard.ts unveraendert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 17:08:44 +02:00

92 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Body,
Controller,
Delete,
ForbiddenException,
Get,
Param,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { Role } from '@prisma/client';
import type { AuthenticatedRequest } from '../auth/types/auth-user';
import { Roles } from '../auth/decorators/roles.decorator';
import { RolesGuard } from '../auth/guards/roles.guard';
import { CreateModuleGrantDto } from './dto/create-module-grant.dto';
import { ModuleGrantsService } from './module-grants.service';
/**
* REST-Controller für die Schreibseite der Modul-Freigaben (PERM-03).
*
* tenantId kommt ausschließlich aus dem JWT (req.tenantId ?? req.user?.tenantId),
* niemals aus Body/Params (T-03-04) — identisch zum Muster in
* GroupsController/ModuleRegistryController. Jede Route ist rollengeschützt.
*
* Statische Segmente stehen vor Parameter-Routen: `matrix` ist vor
* `users/:userId` deklariert. Dieses Projekt hat den Beschattungsfehler
* schon einmal gehabt und Unit-Tests fangen ihn nicht.
*/
@Controller('module-grants')
export class ModuleGrantsController {
constructor(private readonly moduleGrantsService: ModuleGrantsService) {}
private getTenantId(req: AuthenticatedRequest): string {
const tenantId = req.tenantId ?? req.user?.tenantId;
if (!tenantId) {
throw new ForbiddenException('No tenant context');
}
return tenantId;
}
/**
* GET /module-grants/matrix
* Module × Gruppen mit den bestehenden Gruppen-Grants (D-15).
*/
@Get('matrix')
@UseGuards(RolesGuard)
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async matrix(@Req() req: AuthenticatedRequest) {
return this.moduleGrantsService.getMatrix(this.getTenantId(req));
}
/**
* GET /module-grants/users/:userId
* Liefert { groups, modules } (D-16). `groups` sind die tatsächlichen
* Gruppenmitgliedschaften des Benutzers (aus GroupMembership, unabhängig
* von Modul-Freigaben). `modules` ist je aktivem Modul, über welche
* Gruppen der Benutzer das Modul erbt, und ob zusätzlich ein
* Direkt-Grant besteht.
*/
@Get('users/:userId')
@UseGuards(RolesGuard)
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async userAccess(@Param('userId') userId: string, @Req() req: AuthenticatedRequest) {
return this.moduleGrantsService.getUserAccess(this.getTenantId(req), userId);
}
/**
* POST /module-grants
* Legt einen Grant für eine Gruppe oder einen Benutzer an.
*/
@Post()
@UseGuards(RolesGuard)
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async create(@Body() dto: CreateModuleGrantDto, @Req() req: AuthenticatedRequest) {
return this.moduleGrantsService.grant(this.getTenantId(req), dto);
}
/**
* DELETE /module-grants
* Entzieht einen Grant. Ziel im Body, weil die Kombination aus drei
* Feldern besteht und nicht sinnvoll in einen Pfadparameter passt.
*/
@Delete()
@UseGuards(RolesGuard)
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async remove(@Body() dto: CreateModuleGrantDto, @Req() req: AuthenticatedRequest) {
await this.moduleGrantsService.revoke(this.getTenantId(req), dto);
return { success: true };
}
}