Enhance role-check system with stricter checks (#15392)

## Overview

This PR strengthens our permission system by introducing more granular
role-based access control across the platform.

## Changes

### New Permissions Added
- **Applications** - Control who can install and manage applications
- **Layouts** - Control who can customize page layouts and UI structure
- **AI** - Control access to AI features and agents
- **Upload File** - Separate permission for file uploads
- **Download File** - Separate permission for file downloads (frontend
visibility)

### Security Enhancements
- Implemented whitelist-based validation for workspace field updates
- Added explicit permission guards to core entity resolvers
- Enhanced ESLint rule to enforce permission checks on all mutations
- Created `CustomPermissionGuard` and `NoPermissionGuard` for better
code documentation

### Affected Components
- Core entity resolvers: webhooks, files, domains, applications,
layouts, postgres credentials
- Workspace update mutations now use whitelist validation
- Settings UI updated with new permission controls

### Developer Experience
- ESLint now catches missing permission guards during development
- Explicit guard markers make permission requirements clear in code
review
- Comprehensive test coverage for new permission logic

## Testing
-  All TypeScript type checks pass
-  ESLint validation passes
-  New permission guards properly enforced
-  Frontend UI displays new permissions correctly

## Migration Notes
Existing workspaces will need to assign the new permissions to roles as
needed. By default, all new permissions are set to `false` for non-admin
roles.
This commit is contained in:
Félix Malfait
2025-11-07 15:37:17 +01:00
committed by GitHub
parent 44d6ec2594
commit cff17db6cb
306 changed files with 13863 additions and 976 deletions
@@ -194,6 +194,7 @@ export const mockedUserData: MockedUser = {
currentWorkspace: mockCurrentWorkspace,
currentUserWorkspace: {
permissionFlags: [PermissionFlagType.WORKSPACE_MEMBERS],
twoFactorAuthenticationMethodSummary: [],
objectsPermissions: generatedMockObjectMetadataItems.map((item) => ({
objectMetadataId: item.id,
canReadObjectRecords: true,
@@ -265,7 +266,7 @@ export const mockedOnboardingUserData = (
},
locale: 'en',
workspaces: [{ workspace: mockCurrentWorkspace }],
onboardingStatdeus: onboardingStatus || null,
onboardingStatus: onboardingStatus || null,
userVars: {},
availableWorkspaces: {
availableWorkspacesForSignIn: [],
@@ -8,6 +8,8 @@ import {
ViewKey as CoreViewKey,
ViewOpenRecordIn as CoreViewOpenRecordIn,
ViewType as CoreViewType,
ViewVisibility as CoreViewVisibility,
ViewVisibility,
} from '~/generated-metadata/graphql';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
@@ -37,6 +39,7 @@ export const mockedViewsData: View[] = [
viewFields: [],
viewFilters: [],
viewSorts: [],
visibility: ViewVisibility.WORKSPACE,
__typename: 'View',
},
{
@@ -57,6 +60,7 @@ export const mockedViewsData: View[] = [
viewFields: [],
viewFilters: [],
viewSorts: [],
visibility: ViewVisibility.WORKSPACE,
__typename: 'View',
},
{
@@ -77,6 +81,7 @@ export const mockedViewsData: View[] = [
viewFields: [],
viewFilters: [],
viewSorts: [],
visibility: ViewVisibility.WORKSPACE,
__typename: 'View',
},
{
@@ -97,6 +102,7 @@ export const mockedViewsData: View[] = [
viewFields: [],
viewFilters: [],
viewSorts: [],
visibility: ViewVisibility.WORKSPACE,
__typename: 'View',
},
];
@@ -119,6 +125,8 @@ export const mockedCoreViewsData: CoreViewWithRelations[] = [
viewFields: [],
viewFilters: [],
viewSorts: [],
visibility: CoreViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
__typename: 'CoreView',
},
{
@@ -138,6 +146,8 @@ export const mockedCoreViewsData: CoreViewWithRelations[] = [
viewFields: [],
viewFilters: [],
viewSorts: [],
visibility: CoreViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
__typename: 'CoreView',
},
{
@@ -157,6 +167,8 @@ export const mockedCoreViewsData: CoreViewWithRelations[] = [
viewFields: [],
viewFilters: [],
viewSorts: [],
visibility: CoreViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
__typename: 'CoreView',
},
{
@@ -176,6 +188,8 @@ export const mockedCoreViewsData: CoreViewWithRelations[] = [
viewFields: [],
viewFilters: [],
viewSorts: [],
visibility: CoreViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
__typename: 'CoreView',
},
];