From 883208197310d3795244a5effde64ecefc443f0f Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Wed, 26 Aug 2026 07:09:20 +0200 Subject: [PATCH] feat: RBAC Phase 2d - Auswertung & Ausgabe auf @RequirePermission umgestellt Dashboard, Search, Calendar, Statistics, Qr, Export, Documents: @Roles(...) durch @RequirePermission(...) ersetzt bzw. neu eingefuehrt (alle diese Controller hatten zuvor gar keine Rollenpruefung - jeder eingeloggte Nutzer konnte lesen). Documents behaelt STAFF-Zugriff auf Upload und Loeschen. Verifiziert: tsc, lint, volle e2e-Suite 158/159 gruen (1 getrackter Skip). Co-Authored-By: Claude Sonnet 5 --- ApiCore-Backend/src/calendar/calendar.controller.ts | 2 ++ ApiCore-Backend/src/dashboard/dashboard.controller.ts | 3 +++ ApiCore-Backend/src/documents/documents.controller.ts | 10 ++++++---- ApiCore-Backend/src/export/export.controller.ts | 5 +++++ ApiCore-Backend/src/qr/qr.controller.ts | 3 +++ ApiCore-Backend/src/search/search.controller.ts | 2 ++ .../src/statistics/statistics.controller.ts | 7 +++++++ 7 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ApiCore-Backend/src/calendar/calendar.controller.ts b/ApiCore-Backend/src/calendar/calendar.controller.ts index 1cfbc27..7146e3b 100644 --- a/ApiCore-Backend/src/calendar/calendar.controller.ts +++ b/ApiCore-Backend/src/calendar/calendar.controller.ts @@ -1,5 +1,6 @@ import { BadRequestException, Controller, Get, Query } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CalendarService } from './calendar.service'; @ApiTags('calendar') @@ -8,6 +9,7 @@ import { CalendarService } from './calendar.service'; export class CalendarController { constructor(private readonly calendarService: CalendarService) {} + @RequirePermission('calendar:read') @Get() getEvents( @Query('from') from: string, diff --git a/ApiCore-Backend/src/dashboard/dashboard.controller.ts b/ApiCore-Backend/src/dashboard/dashboard.controller.ts index 375de51..5401b25 100644 --- a/ApiCore-Backend/src/dashboard/dashboard.controller.ts +++ b/ApiCore-Backend/src/dashboard/dashboard.controller.ts @@ -1,5 +1,6 @@ import { Controller, Get } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { DashboardService } from './dashboard.service'; @ApiTags('dashboard') @@ -8,11 +9,13 @@ import { DashboardService } from './dashboard.service'; export class DashboardController { constructor(private readonly dashboardService: DashboardService) {} + @RequirePermission('dashboard:read') @Get('summary') summary() { return this.dashboardService.summary(); } + @RequirePermission('dashboard:read') @Get('alerts') alerts() { return this.dashboardService.alerts(); diff --git a/ApiCore-Backend/src/documents/documents.controller.ts b/ApiCore-Backend/src/documents/documents.controller.ts index 36f1b88..08b68be 100644 --- a/ApiCore-Backend/src/documents/documents.controller.ts +++ b/ApiCore-Backend/src/documents/documents.controller.ts @@ -15,8 +15,8 @@ import { import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger'; import type { Response } from 'express'; -import { DocumentEntityType, LegacyRole as Role } from '../../generated/prisma'; -import { Roles } from '../auth/decorators/roles.decorator'; +import { DocumentEntityType } from '../../generated/prisma'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import { isAllowedDocumentMimeType } from './document-mime-types'; @@ -35,6 +35,7 @@ function sanitizeFilenameHeader(fileName: string): string { export class DocumentsController { constructor(private readonly documentsService: DocumentsService) {} + @RequirePermission('documents:read') @Get() findAll( @Query('entityType') entityType: string, @@ -56,7 +57,7 @@ export class DocumentsController { ); } - @Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF) + @RequirePermission('documents:upload') @Post() @ApiConsumes('multipart/form-data') @UseInterceptors( @@ -90,6 +91,7 @@ export class DocumentsController { ); } + @RequirePermission('documents:read') @Get(':id/download') async download(@Param('id') id: string, @Res() res: Response): Promise { const { buffer, fileName, mimeType } = @@ -102,7 +104,7 @@ export class DocumentsController { res.send(buffer); } - @Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF) + @RequirePermission('documents:delete') @Delete(':id') @HttpCode(204) delete(@Param('id') id: string, @CurrentUser() actor: AuthenticatedUser) { diff --git a/ApiCore-Backend/src/export/export.controller.ts b/ApiCore-Backend/src/export/export.controller.ts index 479b50d..8c46738 100644 --- a/ApiCore-Backend/src/export/export.controller.ts +++ b/ApiCore-Backend/src/export/export.controller.ts @@ -1,6 +1,7 @@ import { Controller, Get, Param, Query, Res } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import type { Response } from 'express'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { ExportService } from './export.service'; @ApiTags('export') @@ -9,6 +10,7 @@ import { ExportService } from './export.service'; export class ExportController { constructor(private readonly exportService: ExportService) {} + @RequirePermission('export:read') @Get('colonies.csv') async colonies(@Res() res: Response): Promise { const csv = await this.exportService.exportColoniesCsv(); @@ -17,6 +19,7 @@ export class ExportController { res.send(csv); } + @RequirePermission('export:read') @Get('honey-batches.csv') async honeyBatches(@Res() res: Response): Promise { const csv = await this.exportService.exportHoneyBatchesCsv(); @@ -28,6 +31,7 @@ export class ExportController { res.send(csv); } + @RequirePermission('export:read') @Get(':entity.json') async entityJson( @Param('entity') entity: string, @@ -42,6 +46,7 @@ export class ExportController { res.send(JSON.stringify(data, null, 2)); } + @RequirePermission('export:read') @Get(':report.pdf') async reportPdf( @Param('report') report: string, diff --git a/ApiCore-Backend/src/qr/qr.controller.ts b/ApiCore-Backend/src/qr/qr.controller.ts index 2d07689..11c9c3d 100644 --- a/ApiCore-Backend/src/qr/qr.controller.ts +++ b/ApiCore-Backend/src/qr/qr.controller.ts @@ -1,6 +1,7 @@ import { Controller, Get, Param, Res } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import type { Response } from 'express'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { QrService } from './qr.service'; @ApiTags('qr') @@ -9,6 +10,7 @@ import { QrService } from './qr.service'; export class QrController { constructor(private readonly qrService: QrService) {} + @RequirePermission('qr:read') @Get(':entityType/:id.svg') async svg( @Param('entityType') entityType: string, @@ -20,6 +22,7 @@ export class QrController { res.send(svg); } + @RequirePermission('qr:read') @Get(':entityType/:id/link') async link( @Param('entityType') entityType: string, diff --git a/ApiCore-Backend/src/search/search.controller.ts b/ApiCore-Backend/src/search/search.controller.ts index cd48c28..d2ffd7f 100644 --- a/ApiCore-Backend/src/search/search.controller.ts +++ b/ApiCore-Backend/src/search/search.controller.ts @@ -1,5 +1,6 @@ import { Controller, Get, Query } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { SearchService } from './search.service'; @ApiTags('search') @@ -8,6 +9,7 @@ import { SearchService } from './search.service'; export class SearchController { constructor(private readonly searchService: SearchService) {} + @RequirePermission('search:read') @Get() search(@Query('q') q?: string) { return this.searchService.search(q ?? ''); diff --git a/ApiCore-Backend/src/statistics/statistics.controller.ts b/ApiCore-Backend/src/statistics/statistics.controller.ts index d9ee809..b34b078 100644 --- a/ApiCore-Backend/src/statistics/statistics.controller.ts +++ b/ApiCore-Backend/src/statistics/statistics.controller.ts @@ -1,5 +1,6 @@ import { Controller, Get } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { StatisticsService } from './statistics.service'; @ApiTags('statistics') @@ -8,31 +9,37 @@ import { StatisticsService } from './statistics.service'; export class StatisticsController { constructor(private readonly statisticsService: StatisticsService) {} + @RequirePermission('statistics:read') @Get('colonies') colonies() { return this.statisticsService.colonies(); } + @RequirePermission('statistics:read') @Get('queens') queens() { return this.statisticsService.queens(); } + @RequirePermission('statistics:read') @Get('apiaries') apiaries() { return this.statisticsService.apiaries(); } + @RequirePermission('statistics:read') @Get('varroa') varroa() { return this.statisticsService.varroa(); } + @RequirePermission('statistics:read') @Get('honey') honey() { return this.statisticsService.honey(); } + @RequirePermission('statistics:read') @Get('feeding') feeding() { return this.statisticsService.feeding();