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 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-08-26 07:09:20 +02:00
1 parent c510304813
commit 8832081973
7 files changed
+28 -4

No files matched your search

@@ -1,5 +1,6 @@
import { BadRequestException, Controller, Get, Query } from '@nestjs/common'; import { BadRequestException, Controller, Get, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { RequirePermission } from '../auth/decorators/require-permission.decorator';
import { CalendarService } from './calendar.service'; import { CalendarService } from './calendar.service';
@ApiTags('calendar') @ApiTags('calendar')
@@ -8,6 +9,7 @@ import { CalendarService } from './calendar.service';
export class CalendarController { export class CalendarController {
constructor(private readonly calendarService: CalendarService) {} constructor(private readonly calendarService: CalendarService) {}
@RequirePermission('calendar:read')
@Get() @Get()
getEvents( getEvents(
@Query('from') from: string, @Query('from') from: string,
@@ -1,5 +1,6 @@
import { Controller, Get } from '@nestjs/common'; import { Controller, Get } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { RequirePermission } from '../auth/decorators/require-permission.decorator';
import { DashboardService } from './dashboard.service'; import { DashboardService } from './dashboard.service';
@ApiTags('dashboard') @ApiTags('dashboard')
@@ -8,11 +9,13 @@ import { DashboardService } from './dashboard.service';
export class DashboardController { export class DashboardController {
constructor(private readonly dashboardService: DashboardService) {} constructor(private readonly dashboardService: DashboardService) {}
@RequirePermission('dashboard:read')
@Get('summary') @Get('summary')
summary() { summary() {
return this.dashboardService.summary(); return this.dashboardService.summary();
} }
@RequirePermission('dashboard:read')
@Get('alerts') @Get('alerts')
alerts() { alerts() {
return this.dashboardService.alerts(); return this.dashboardService.alerts();
@@ -15,8 +15,8 @@ import {
import { FileInterceptor } from '@nestjs/platform-express'; import { FileInterceptor } from '@nestjs/platform-express';
import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger';
import type { Response } from 'express'; import type { Response } from 'express';
import { DocumentEntityType, LegacyRole as Role } from '../../generated/prisma'; import { DocumentEntityType } from '../../generated/prisma';
import { Roles } from '../auth/decorators/roles.decorator'; import { RequirePermission } from '../auth/decorators/require-permission.decorator';
import { CurrentUser } from '../auth/decorators/current-user.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator';
import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import type { AuthenticatedUser } from '../auth/types/authenticated-user';
import { isAllowedDocumentMimeType } from './document-mime-types'; import { isAllowedDocumentMimeType } from './document-mime-types';
@@ -35,6 +35,7 @@ function sanitizeFilenameHeader(fileName: string): string {
export class DocumentsController { export class DocumentsController {
constructor(private readonly documentsService: DocumentsService) {} constructor(private readonly documentsService: DocumentsService) {}
@RequirePermission('documents:read')
@Get() @Get()
findAll( findAll(
@Query('entityType') entityType: string, @Query('entityType') entityType: string,
@@ -56,7 +57,7 @@ export class DocumentsController {
); );
} }
@Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF) @RequirePermission('documents:upload')
@Post() @Post()
@ApiConsumes('multipart/form-data') @ApiConsumes('multipart/form-data')
@UseInterceptors( @UseInterceptors(
@@ -90,6 +91,7 @@ export class DocumentsController {
); );
} }
@RequirePermission('documents:read')
@Get(':id/download') @Get(':id/download')
async download(@Param('id') id: string, @Res() res: Response): Promise<void> { async download(@Param('id') id: string, @Res() res: Response): Promise<void> {
const { buffer, fileName, mimeType } = const { buffer, fileName, mimeType } =
@@ -102,7 +104,7 @@ export class DocumentsController {
res.send(buffer); res.send(buffer);
} }
@Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF) @RequirePermission('documents:delete')
@Delete(':id') @Delete(':id')
@HttpCode(204) @HttpCode(204)
delete(@Param('id') id: string, @CurrentUser() actor: AuthenticatedUser) { delete(@Param('id') id: string, @CurrentUser() actor: AuthenticatedUser) {
@@ -1,6 +1,7 @@
import { Controller, Get, Param, Query, Res } from '@nestjs/common'; import { Controller, Get, Param, Query, Res } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import type { Response } from 'express'; import type { Response } from 'express';
import { RequirePermission } from '../auth/decorators/require-permission.decorator';
import { ExportService } from './export.service'; import { ExportService } from './export.service';
@ApiTags('export') @ApiTags('export')
@@ -9,6 +10,7 @@ import { ExportService } from './export.service';
export class ExportController { export class ExportController {
constructor(private readonly exportService: ExportService) {} constructor(private readonly exportService: ExportService) {}
@RequirePermission('export:read')
@Get('colonies.csv') @Get('colonies.csv')
async colonies(@Res() res: Response): Promise<void> { async colonies(@Res() res: Response): Promise<void> {
const csv = await this.exportService.exportColoniesCsv(); const csv = await this.exportService.exportColoniesCsv();
@@ -17,6 +19,7 @@ export class ExportController {
res.send(csv); res.send(csv);
} }
@RequirePermission('export:read')
@Get('honey-batches.csv') @Get('honey-batches.csv')
async honeyBatches(@Res() res: Response): Promise<void> { async honeyBatches(@Res() res: Response): Promise<void> {
const csv = await this.exportService.exportHoneyBatchesCsv(); const csv = await this.exportService.exportHoneyBatchesCsv();
@@ -28,6 +31,7 @@ export class ExportController {
res.send(csv); res.send(csv);
} }
@RequirePermission('export:read')
@Get(':entity.json') @Get(':entity.json')
async entityJson( async entityJson(
@Param('entity') entity: string, @Param('entity') entity: string,
@@ -42,6 +46,7 @@ export class ExportController {
res.send(JSON.stringify(data, null, 2)); res.send(JSON.stringify(data, null, 2));
} }
@RequirePermission('export:read')
@Get(':report.pdf') @Get(':report.pdf')
async reportPdf( async reportPdf(
@Param('report') report: string, @Param('report') report: string,
+3
View File
@@ -1,6 +1,7 @@
import { Controller, Get, Param, Res } from '@nestjs/common'; import { Controller, Get, Param, Res } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import type { Response } from 'express'; import type { Response } from 'express';
import { RequirePermission } from '../auth/decorators/require-permission.decorator';
import { QrService } from './qr.service'; import { QrService } from './qr.service';
@ApiTags('qr') @ApiTags('qr')
@@ -9,6 +10,7 @@ import { QrService } from './qr.service';
export class QrController { export class QrController {
constructor(private readonly qrService: QrService) {} constructor(private readonly qrService: QrService) {}
@RequirePermission('qr:read')
@Get(':entityType/:id.svg') @Get(':entityType/:id.svg')
async svg( async svg(
@Param('entityType') entityType: string, @Param('entityType') entityType: string,
@@ -20,6 +22,7 @@ export class QrController {
res.send(svg); res.send(svg);
} }
@RequirePermission('qr:read')
@Get(':entityType/:id/link') @Get(':entityType/:id/link')
async link( async link(
@Param('entityType') entityType: string, @Param('entityType') entityType: string,
@@ -1,5 +1,6 @@
import { Controller, Get, Query } from '@nestjs/common'; import { Controller, Get, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { RequirePermission } from '../auth/decorators/require-permission.decorator';
import { SearchService } from './search.service'; import { SearchService } from './search.service';
@ApiTags('search') @ApiTags('search')
@@ -8,6 +9,7 @@ import { SearchService } from './search.service';
export class SearchController { export class SearchController {
constructor(private readonly searchService: SearchService) {} constructor(private readonly searchService: SearchService) {}
@RequirePermission('search:read')
@Get() @Get()
search(@Query('q') q?: string) { search(@Query('q') q?: string) {
return this.searchService.search(q ?? ''); return this.searchService.search(q ?? '');
@@ -1,5 +1,6 @@
import { Controller, Get } from '@nestjs/common'; import { Controller, Get } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { RequirePermission } from '../auth/decorators/require-permission.decorator';
import { StatisticsService } from './statistics.service'; import { StatisticsService } from './statistics.service';
@ApiTags('statistics') @ApiTags('statistics')
@@ -8,31 +9,37 @@ import { StatisticsService } from './statistics.service';
export class StatisticsController { export class StatisticsController {
constructor(private readonly statisticsService: StatisticsService) {} constructor(private readonly statisticsService: StatisticsService) {}
@RequirePermission('statistics:read')
@Get('colonies') @Get('colonies')
colonies() { colonies() {
return this.statisticsService.colonies(); return this.statisticsService.colonies();
} }
@RequirePermission('statistics:read')
@Get('queens') @Get('queens')
queens() { queens() {
return this.statisticsService.queens(); return this.statisticsService.queens();
} }
@RequirePermission('statistics:read')
@Get('apiaries') @Get('apiaries')
apiaries() { apiaries() {
return this.statisticsService.apiaries(); return this.statisticsService.apiaries();
} }
@RequirePermission('statistics:read')
@Get('varroa') @Get('varroa')
varroa() { varroa() {
return this.statisticsService.varroa(); return this.statisticsService.varroa();
} }
@RequirePermission('statistics:read')
@Get('honey') @Get('honey')
honey() { honey() {
return this.statisticsService.honey(); return this.statisticsService.honey();
} }
@RequirePermission('statistics:read')
@Get('feeding') @Get('feeding')
feeding() { feeding() {
return this.statisticsService.feeding(); return this.statisticsService.feeding();