From febecc408d87320aed1d67f743e7287922037971 Mon Sep 17 00:00:00 2001 From: Sara Thornsberry Date: Fri, 29 May 2026 11:16:51 -0400 Subject: [PATCH 1/3] feat(rbac): add developer and viewer roles with documentation Seed developer and viewer built-in roles, document the permission matrix in docs/rbac.md, and protect built-in roles from deletion in the UI. --- client/src/components/accounts/roles.vue | 9 +++- client/src/locale/en.ts | 7 +++ docs/rbac.md | 49 ++++++++++++++++++++ server/src/database/database.service.ts | 58 ++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 docs/rbac.md diff --git a/client/src/components/accounts/roles.vue b/client/src/components/accounts/roles.vue index c4d586244..6d32b60b6 100644 --- a/client/src/components/accounts/roles.vue +++ b/client/src/components/accounts/roles.vue @@ -136,7 +136,7 @@ class="ma-2" color="secondary" @click="openEditRoleDialog(item)" - :disabled="item.name === 'admin' || !writeUserPermission" + :disabled="isBuiltInRole(item.name) || !writeUserPermission" > mdi-pencil @@ -149,7 +149,7 @@ class="ma-2" color="secondary" @click="deleteRole(item)" - :disabled="item.name === 'admin' || item.name === 'guest' || item.name === 'member' || !writeUserPermission" + :disabled="isBuiltInRole(item.name) || !writeUserPermission" > mdi-delete @@ -519,6 +519,10 @@ export default defineComponent({ } } */ + const builtInRoleNames = ['admin', 'member', 'developer', 'viewer', 'guest'] + + const isBuiltInRole = (name: string) => builtInRoleNames.includes(name) + const getResourcePermissions = (permissions: any, resource: string) => { for (const permission of permissions) { if (permission.resource === resource) { @@ -560,6 +564,7 @@ export default defineComponent({ openCreateDialog, saveCreate, getResourcePermissions, + isBuiltInRole, writeUserPermission, } }, diff --git a/client/src/locale/en.ts b/client/src/locale/en.ts index ec83212c0..2788be79e 100644 --- a/client/src/locale/en.ts +++ b/client/src/locale/en.ts @@ -325,6 +325,13 @@ const messages = { name: 'Roles', search: 'Search for a Role', permission: 'Permission', + builtIn: { + admin: 'Administrator — full access to apps, pipelines, accounts, and settings', + member: 'Member — manage apps and pipelines; read account info', + developer: 'Developer — deploy and operate apps; no account or settings access', + viewer: 'Viewer — read-only access to apps, pipelines, and logs', + guest: 'Guest — legacy read-only role (prefer Viewer for new users)', + }, actions: { create: 'Create Role', edit: 'Edit Role', diff --git a/docs/rbac.md b/docs/rbac.md new file mode 100644 index 000000000..d46ad483b --- /dev/null +++ b/docs/rbac.md @@ -0,0 +1,49 @@ +# Role-based access control (RBAC) + +Kubero uses **roles**, **permissions**, and **teams** to control who can do what in the UI and API. + +## Concepts + +| Concept | Purpose | +|---------|---------| +| **Role** | Named set of permissions (stored in the Kubero database). | +| **Permission** | `resource:action` pair checked on API routes and in the UI (e.g. `app:write`). | +| **Team (user group)** | Groups users for **pipeline access** (`spec.access.teams` on a pipeline). Users in the `admin` team see all pipelines. | + +Permissions are loaded at login and embedded in the JWT. The API enforces them via `PermissionsGuard`; the Vue UI uses `authStore.hasPermission(...)`. + +## Built-in roles + +These roles are created on first startup (see `server/src/database/database.service.ts`). + +| Role | Intended use | Apps / pipelines | Accounts & settings | Logs / console / reboot | Security scans | +|------|----------------|------------------|---------------------|-------------------------|----------------| +| **admin** | Full administrators | write | write (users, config) | yes | write | +| **member** | Team members with broad access | write | read users; no config write | yes | write | +| **developer** | Build and operate workloads | write | no account or config access | yes | write | +| **viewer** | Read-only observers | read | no | logs only (no console/reboot) | read | +| **guest** | Legacy minimal role (prefer **viewer** for new users) | read | no | no | read | + +### Permission actions + +- **read** / **write** — used for `app`, `pipeline`, `user`, `config`, `security`. +- **ok** — used for `console`, `logs`, `reboot`, `token` (and audit read via `read`). +- **none** — stored in the database but does **not** satisfy API guards (guards require a positive permission such as `app:read`). + +## Pipeline scoping (teams) + +RBAC roles control *what operations* a user may perform. **Teams** control *which pipelines* they see: + +- Assign users to teams under **Accounts → Users**. +- On each pipeline, set **access teams** (or rely on the `admin` team for global access). +- The `admin` **user group** bypasses pipeline filters. + +## Managing roles + +Administrators can review and customize roles under **Accounts → Roles** (requires `user:read` / `user:write`). + +Built-in roles (`admin`, `member`, `developer`, `viewer`, `guest`) cannot be deleted from the UI. + +## Related issue + +This model implements the roles described in [kubero#545](https://github.com/kubero-dev/kubero/issues/545). diff --git a/server/src/database/database.service.ts b/server/src/database/database.service.ts index 7c5a96611..c7423e58d 100644 --- a/server/src/database/database.service.ts +++ b/server/src/database/database.service.ts @@ -393,6 +393,64 @@ export class DatabaseService { Logger.log('Role "guest" seeded successfully.', 'DatabaseService'); }); + // Developer: deploy and operate apps, no account or cluster settings access + await prisma.role + .upsert({ + where: { name: 'developer' }, + update: {}, + create: { + name: 'developer', + description: + 'Developer role — manage apps and pipelines, logs, console, and builds', + permissions: { + create: [ + { action: 'write', resource: 'app' }, + { action: 'write', resource: 'pipeline' }, + { action: 'none', resource: 'user' }, + { action: 'none', resource: 'config' }, + { action: 'ok', resource: 'console' }, + { action: 'ok', resource: 'logs' }, + { action: 'ok', resource: 'reboot' }, + { action: 'read', resource: 'audit' }, + { action: 'ok', resource: 'token' }, + { action: 'write', resource: 'security' }, + ], + }, + }, + }) + .then(() => { + Logger.log('Role "developer" seeded successfully.', 'DatabaseService'); + }); + + // Viewer: read-only access to apps, pipelines, metrics, and audit + await prisma.role + .upsert({ + where: { name: 'viewer' }, + update: {}, + create: { + name: 'viewer', + description: + 'Viewer role — read-only access to apps, pipelines, logs, and audit', + permissions: { + create: [ + { action: 'read', resource: 'app' }, + { action: 'read', resource: 'pipeline' }, + { action: 'none', resource: 'user' }, + { action: 'none', resource: 'config' }, + { action: 'none', resource: 'console' }, + { action: 'ok', resource: 'logs' }, + { action: 'none', resource: 'reboot' }, + { action: 'read', resource: 'audit' }, + { action: 'none', resource: 'token' }, + { action: 'read', resource: 'security' }, + ], + }, + }, + }) + .then(() => { + Logger.log('Role "viewer" seeded successfully.', 'DatabaseService'); + }); + // Ensure the 'everyone' user group exists prisma.userGroup .upsert({ From 4114bed0eff0937cab645bb42bfd7e8ec5c3e612 Mon Sep 17 00:00:00 2001 From: Sara Thornsberry Date: Fri, 29 May 2026 11:16:51 -0400 Subject: [PATCH 2/3] feat(rbac): enforce permissions on deployments, repo, and kubernetes APIs Apply PermissionsGuard to build, repository, and cluster helper endpoints. Git provider webhooks remain unauthenticated for external delivery. --- .../src/deployments/deployments.controller.ts | 20 +++++++++------ .../src/kubernetes/kubernetes.controller.ts | 14 ++++++++--- server/src/repo/repo.controller.ts | 25 ++++++++++++------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/server/src/deployments/deployments.controller.ts b/server/src/deployments/deployments.controller.ts index b196aa58a..208a3b900 100644 --- a/server/src/deployments/deployments.controller.ts +++ b/server/src/deployments/deployments.controller.ts @@ -21,6 +21,8 @@ import { IUser } from '../auth/auth.interface'; import { CreateBuild } from './dto/CreateBuild.dto'; import { OKDTO } from '../common/dto/ok.dto'; import { JwtAuthGuard } from '../auth/strategies/jwt.guard'; +import { PermissionsGuard } from '../auth/permissions.guard'; +import { Permissions } from '../auth/permissions.decorator'; import { ReadonlyGuard } from '../common/guards/readonly.guard'; @Controller({ path: 'api/deployments', version: '1' }) @@ -28,7 +30,8 @@ export class DeploymentsController { constructor(private readonly deploymentsService: DeploymentsService) {} @Get('/:pipeline/:phase/:app') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiForbiddenResponse({ description: 'Error: Unauthorized', type: OKDTO, @@ -49,8 +52,8 @@ export class DeploymentsController { } @Post('/build/:pipeline/:phase/:app') - @UseGuards(JwtAuthGuard) - @UseGuards(ReadonlyGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard) + @Permissions('app:write') @ApiForbiddenResponse({ description: 'Error: Unauthorized', type: OKDTO, @@ -91,8 +94,8 @@ export class DeploymentsController { } @Delete('/:pipeline/:phase/:app/:buildName') - @UseGuards(JwtAuthGuard) - @UseGuards(ReadonlyGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard) + @Permissions('app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -126,7 +129,8 @@ export class DeploymentsController { } @Get('/:pipeline/:phase/:app/:build/:container/history') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('logs:ok', 'app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -158,8 +162,8 @@ export class DeploymentsController { } @Put('/:pipeline/:phase/:app/:tag') - @UseGuards(JwtAuthGuard) - @UseGuards(ReadonlyGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard) + @Permissions('app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', diff --git a/server/src/kubernetes/kubernetes.controller.ts b/server/src/kubernetes/kubernetes.controller.ts index 03a264b05..de92f9251 100644 --- a/server/src/kubernetes/kubernetes.controller.ts +++ b/server/src/kubernetes/kubernetes.controller.ts @@ -14,13 +14,16 @@ import { } from './dto/kubernetes.dto'; import { OKDTO } from '../common/dto/ok.dto'; import { JwtAuthGuard } from '../auth/strategies/jwt.guard'; +import { PermissionsGuard } from '../auth/permissions.guard'; +import { Permissions } from '../auth/permissions.decorator'; @Controller({ path: 'api/kubernetes', version: '1' }) export class KubernetesController { constructor(private readonly kubernetesService: KubernetesService) {} @Get('events') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -41,7 +44,8 @@ export class KubernetesController { } @Get('storageclasses') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -59,7 +63,8 @@ export class KubernetesController { } @Get('domains') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -79,7 +84,8 @@ export class KubernetesController { } @Get('/contexts') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('pipeline:read', 'pipeline:write') @ApiForbiddenResponse({ description: 'Error: Unauthorized', type: OKDTO, diff --git a/server/src/repo/repo.controller.ts b/server/src/repo/repo.controller.ts index 24d740873..b4180d9cd 100644 --- a/server/src/repo/repo.controller.ts +++ b/server/src/repo/repo.controller.ts @@ -16,6 +16,8 @@ import { } from '@nestjs/swagger'; import { OKDTO } from '../common/dto/ok.dto'; import { JwtAuthGuard } from '../auth/strategies/jwt.guard'; +import { PermissionsGuard } from '../auth/permissions.guard'; +import { Permissions } from '../auth/permissions.decorator'; import { ReadonlyGuard } from '../common/guards/readonly.guard'; @Controller({ path: 'api/repo', version: '1' }) @@ -23,7 +25,8 @@ export class RepoController { constructor(private readonly repoService: RepoService) {} @Get('/providers') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('pipeline:read', 'pipeline:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -36,7 +39,8 @@ export class RepoController { } @Get('/:provider/repositories') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('pipeline:read', 'pipeline:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -56,7 +60,8 @@ export class RepoController { } @Get('/:provider/:gitrepob64/branches') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write', 'pipeline:read', 'pipeline:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -85,7 +90,8 @@ export class RepoController { } @Get('/:provider/:gitrepob64/pullrequests') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write', 'pipeline:read', 'pipeline:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -114,7 +120,8 @@ export class RepoController { } @Get('/:provider/:gitrepob64/references') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write', 'pipeline:read', 'pipeline:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -143,8 +150,8 @@ export class RepoController { } @Post('/:provider/connect') - @UseGuards(JwtAuthGuard) - @UseGuards(ReadonlyGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard) + @Permissions('pipeline:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -164,8 +171,8 @@ export class RepoController { } @Post('/:provider/disconnect') - @UseGuards(JwtAuthGuard) - @UseGuards(ReadonlyGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard) + @Permissions('pipeline:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', From 044894eb6bfaa0dda0cc8ee724ecee06cd471ccf Mon Sep 17 00:00:00 2001 From: Sara Thornsberry Date: Fri, 29 May 2026 11:16:51 -0400 Subject: [PATCH 3/3] feat(rbac): enforce permissions on addons, metrics, templates, and notifications Secure previously unguarded read/write routes with JwtAuthGuard and PermissionsGuard aligned with existing config and app permission checks. --- server/src/addons/addons.controller.ts | 8 +++-- server/src/metrics/metrics.controller.ts | 17 ++++++--- .../notifications/notifications.controller.ts | 36 +++++++++++++++++++ server/src/templates/templates.controller.ts | 5 ++- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/server/src/addons/addons.controller.ts b/server/src/addons/addons.controller.ts index 7d38b07d7..532493ca1 100644 --- a/server/src/addons/addons.controller.ts +++ b/server/src/addons/addons.controller.ts @@ -7,6 +7,8 @@ import { } from '@nestjs/swagger'; import { OKDTO } from '../common/dto/ok.dto'; import { JwtAuthGuard } from '../auth/strategies/jwt.guard'; +import { PermissionsGuard } from '../auth/permissions.guard'; +import { Permissions } from '../auth/permissions.decorator'; @Controller({ path: 'api/addons', version: '1' }) export class AddonsController { @@ -19,7 +21,8 @@ export class AddonsController { type: OKDTO, isArray: false, }) - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('pipeline:read', 'pipeline:write') @ApiBearerAuth('bearerAuth') async getAddons() { return this.addonsService.getAddonsList(); @@ -32,7 +35,8 @@ export class AddonsController { type: OKDTO, isArray: false, }) - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('pipeline:read', 'pipeline:write') @ApiBearerAuth('bearerAuth') async getOperators() { return this.addonsService.getOperatorsList(); diff --git a/server/src/metrics/metrics.controller.ts b/server/src/metrics/metrics.controller.ts index b6d931651..493d975cc 100644 --- a/server/src/metrics/metrics.controller.ts +++ b/server/src/metrics/metrics.controller.ts @@ -7,6 +7,8 @@ import { } from '@nestjs/swagger'; import { MetricsService } from './metrics.service'; import { JwtAuthGuard } from '../auth/strategies/jwt.guard'; +import { PermissionsGuard } from '../auth/permissions.guard'; +import { Permissions } from '../auth/permissions.decorator'; import { OKDTO } from '../common/dto/ok.dto'; @Controller({ path: 'api/metrics', version: '1' }) @@ -14,7 +16,8 @@ export class MetricsController { constructor(private metricsService: MetricsService) {} @Get('/resources/:pipeline/:phase/:app') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -34,7 +37,8 @@ export class MetricsController { } @Get('/uptimes/:pipeline/:phase') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -52,7 +56,8 @@ export class MetricsController { } @Get('/timeseries') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -65,7 +70,8 @@ export class MetricsController { } @Get('/timeseries/:type/:pipeline/:phase/:app') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', @@ -169,7 +175,8 @@ export class MetricsController { } @Get('/rules/:pipeline/:phase/:app') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', diff --git a/server/src/notifications/notifications.controller.ts b/server/src/notifications/notifications.controller.ts index 5390594aa..16d8ee4fa 100644 --- a/server/src/notifications/notifications.controller.ts +++ b/server/src/notifications/notifications.controller.ts @@ -9,9 +9,20 @@ import { HttpException, HttpStatus, Logger, + UseGuards, } from '@nestjs/common'; +import { + ApiBearerAuth, + ApiForbiddenResponse, + ApiOperation, +} from '@nestjs/swagger'; import { NotificationsDbService, CreateNotificationDto, UpdateNotificationDto } from './notifications-db.service'; import { INotificationConfig } from './notifications.interface'; +import { JwtAuthGuard } from '../auth/strategies/jwt.guard'; +import { PermissionsGuard } from '../auth/permissions.guard'; +import { Permissions } from '../auth/permissions.decorator'; +import { ReadonlyGuard } from '../common/guards/readonly.guard'; +import { OKDTO } from '../common/dto/ok.dto'; export interface ApiResponse { success: boolean; @@ -26,6 +37,11 @@ export class NotificationsController { constructor(private readonly notificationsDbService: NotificationsDbService) {} @Get() + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('config:read', 'config:write') + @ApiBearerAuth('bearerAuth') + @ApiForbiddenResponse({ description: 'Error: Unauthorized', type: OKDTO }) + @ApiOperation({ summary: 'List notification configurations' }) async findAll(): Promise> { try { const notifications = await this.notificationsDbService.getNotificationConfigs(); @@ -43,6 +59,11 @@ export class NotificationsController { } @Get(':id') + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('config:read', 'config:write') + @ApiBearerAuth('bearerAuth') + @ApiForbiddenResponse({ description: 'Error: Unauthorized', type: OKDTO }) + @ApiOperation({ summary: 'Get a notification configuration' }) async findOne(@Param('id') id: string): Promise> { try { const notification = await this.notificationsDbService.findById(id); @@ -67,6 +88,11 @@ export class NotificationsController { } @Post() + @UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard) + @Permissions('config:write') + @ApiBearerAuth('bearerAuth') + @ApiForbiddenResponse({ description: 'Error: Unauthorized', type: OKDTO }) + @ApiOperation({ summary: 'Create a notification configuration' }) async create(@Body() createNotificationDto: CreateNotificationDto): Promise> { try { // Validate required fields @@ -108,6 +134,11 @@ export class NotificationsController { } @Put(':id') + @UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard) + @Permissions('config:write') + @ApiBearerAuth('bearerAuth') + @ApiForbiddenResponse({ description: 'Error: Unauthorized', type: OKDTO }) + @ApiOperation({ summary: 'Update a notification configuration' }) async update( @Param('id') id: string, @Body() updateNotificationDto: Partial, @@ -152,6 +183,11 @@ export class NotificationsController { } @Delete(':id') + @UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard) + @Permissions('config:write') + @ApiBearerAuth('bearerAuth') + @ApiForbiddenResponse({ description: 'Error: Unauthorized', type: OKDTO }) + @ApiOperation({ summary: 'Delete a notification configuration' }) async remove(@Param('id') id: string): Promise { try { await this.notificationsDbService.delete(id); diff --git a/server/src/templates/templates.controller.ts b/server/src/templates/templates.controller.ts index bfe8aba8b..3a98219a3 100644 --- a/server/src/templates/templates.controller.ts +++ b/server/src/templates/templates.controller.ts @@ -8,6 +8,8 @@ import { import { TemplatesService } from './templates.service'; import { Response } from 'express'; import { JwtAuthGuard } from '../auth/strategies/jwt.guard'; +import { PermissionsGuard } from '../auth/permissions.guard'; +import { Permissions } from '../auth/permissions.decorator'; import { OKDTO } from '../common/dto/ok.dto'; @Controller({ path: 'api/templates', version: '1' }) @@ -16,7 +18,8 @@ export class TemplatesController { constructor(private readonly templatesService: TemplatesService) {} @Get('/:templateB64') - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, PermissionsGuard) + @Permissions('app:read', 'app:write') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized',