Add comprehensive permission guard coverage across GraphQL and REST endpoints (#15739)

This PR enhances our security model by ensuring all GraphQL resolvers
and REST API endpoints have appropriate permission guards.

## Changes

### ESLint Rules
- Enhanced `graphql-resolvers-should-be-guarded` to require permission
guards on all resolvers (Query, Mutation, Subscription), not just
mutations
- Enhanced `rest-api-methods-should-be-guarded` to require permission
guards on all REST endpoints (GET, POST, PUT, PATCH, DELETE), not just
mutating methods
- Both rules now enforce consistent security: authentication guards +
permission guards for all endpoints

### Permission Guards Added

**Public Endpoints** - Added `NoPermissionGuard`:
- Auth-related queries (checkUserExists, findWorkspaceFromInviteHash,
validatePasswordResetToken)
- Billing webhooks (Stripe callbacks)
- SSO callbacks (SAML authentication)
- Workflow webhooks
- Cloudflare webhooks
- Route trigger endpoints
- GraphQL subscriptions
- Current workspace queries
- Geo-map address autocomplete
- View-related read operations (view-field, view-filter, view-group,
view-sort, view-filter-group)

**Settings Permission Guards** - Added `SettingsPermissionGuard`:
- API Keys management: `PermissionFlagType.API_KEYS_AND_WEBHOOKS`
- Webhooks management: `PermissionFlagType.API_KEYS_AND_WEBHOOKS`
- Page Layouts (write operations): `PermissionFlagType.LAYOUTS`
- REST Metadata API: `PermissionFlagType.DATA_MODEL`
- Agent operations: `PermissionFlagType.AI`
- Remote servers: `PermissionFlagType.DATA_MODEL`
- Remote tables: `PermissionFlagType.DATA_MODEL`
- Serverless functions: `PermissionFlagType.WORKFLOWS`

**Custom Permission Guards** - Added `CustomPermissionGuard`:
- REST Core API (permissions checked at query execution layer)
- Timeline calendar events (permission checks in service layer)
- Timeline messaging (permission checks in service layer)
- Search operations (permission checks in service layer)
- View operations (permission checks via dedicated view permission
guards)

### View Permission Guards
- Created dedicated `FindManyViewsPermissionGuard` and
`FindOneViewPermissionGuard` for reading views
- Created `CreateViewPermissionGuard` for view creation with
visibility-based permission checks
- All view child entities (view-field, view-filter, view-sort,
view-group, view-filter-group) use `NoPermissionGuard` for reads
- Write operations on view child entities use dedicated permission
guards that check parent view access

### Page Layout Permissions
- Read operations (GET/Query) now use `NoPermissionGuard` - users can
view layouts without LAYOUTS permission
- Write operations (POST/PATCH/DELETE/Mutation) require
`SettingsPermissionGuard(PermissionFlagType.LAYOUTS)`
- Applied consistently across page-layout, page-layout-tab, and
page-layout-widget endpoints

## Security Model
All endpoints now follow a consistent pattern:
1. **Authentication**: `UserAuthGuard`, `WorkspaceAuthGuard`, or
`PublicEndpointGuard`
2. **Authorization**: One of:
- `SettingsPermissionGuard(PermissionFlagType.XXX)` - for settings/admin
operations
- `CustomPermissionGuard` - when permissions are checked in service/data
layer
   - `NoPermissionGuard` - for public or non-sensitive read operations

The ESLint rules automatically enforce this pattern going forward.

## Stats
- 47 files changed
- 603 insertions, 163 deletions
- 3 new guard files created
This commit is contained in:
Félix Malfait
2025-11-10 12:17:38 +01:00
committed by GitHub
parent ae2d399d6e
commit 9a80164cf3
62 changed files with 727 additions and 147 deletions
@@ -2,14 +2,15 @@ import { Controller, Post, Req, UseGuards } from '@nestjs/common';
import { Request } from 'express';
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { MCPMetadataService } from 'src/engine/api/mcp/services/mcp-metadata.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@Controller('mcp/metadata')
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard, NoPermissionGuard)
export class McpMetadataController {
constructor(private readonly mCPMetadataService: MCPMetadataService) {}
@@ -17,11 +17,12 @@ import { Response } from 'express';
import { RestApiCoreService } from 'src/engine/api/rest/core/services/rest-api-core.service';
import { RestApiExceptionFilter } from 'src/engine/api/rest/rest-api-exception.filter';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@Controller('rest')
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard, CustomPermissionGuard)
@UseFilters(RestApiExceptionFilter)
export class RestApiCoreController {
private readonly logger = new Logger(RestApiCoreController.name);
@@ -1,12 +1,12 @@
import {
Controller,
Get,
Delete,
Get,
Patch,
Post,
Put,
Req,
Res,
Patch,
Put,
UseGuards,
} from '@nestjs/common';
@@ -15,10 +15,16 @@ import { Request, Response } from 'express';
import { RestApiMetadataService } from 'src/engine/api/rest/metadata/rest-api-metadata.service';
import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils';
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
@Controller('rest/metadata/*')
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
@UseGuards(
JwtAuthGuard,
WorkspaceAuthGuard,
SettingsPermissionGuard(PermissionFlagType.DATA_MODEL),
)
export class RestApiMetadataController {
constructor(
private readonly restApiMetadataService: RestApiMetadataService,
@@ -8,6 +8,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
import { RestApiCoreModule } from 'src/engine/api/rest/core/rest-api-core.module';
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
import { RestApiMetadataController } from 'src/engine/api/rest/metadata/rest-api-metadata.controller';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@Module({
imports: [
@@ -16,6 +17,7 @@ import { RestApiMetadataController } from 'src/engine/api/rest/metadata/rest-api
AuthModule,
HttpModule,
RestApiCoreModule,
PermissionsModule,
],
controllers: [RestApiMetadataController],
providers: [RestApiService, RestApiMetadataService],