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:
@@ -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) {}
|
||||
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+10
-4
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user