Files
twenty/packages/twenty-server/test/integration/graphql/utils/view-data-factory.util.ts
T
Félix MalfaitandGitHub cff17db6cb 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.
2025-11-07 15:37:17 +01:00

124 lines
3.5 KiB
TypeScript

import { ViewFilterOperand } from 'twenty-shared/types';
import { type UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator';
import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
import { type ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
import { ViewVisibility } from 'src/engine/metadata-modules/view/enums/view-visibility.enum';
export const createViewData = (overrides: Partial<ViewEntity> = {}) => ({
name: 'Test View',
icon: 'IconTable',
type: ViewType.TABLE,
key: null,
position: 0,
isCompact: false,
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
visibility: ViewVisibility.WORKSPACE,
...overrides,
});
export const updateViewData = (overrides: Partial<ViewEntity> = {}) => ({
name: 'Updated View',
type: ViewType.KANBAN,
isCompact: true,
...overrides,
});
export const createViewFieldData = (
viewId: string,
overrides: Partial<ViewFieldEntity> = {},
) => ({
viewId,
position: 0,
isVisible: true,
size: 150,
...overrides,
});
export const updateViewFieldData = (
overrides: Partial<UpdateViewFieldInput['update']> = {},
) => ({
position: 5,
isVisible: false,
size: 300,
...overrides,
});
export const createViewSortData = (
viewId: string,
overrides: Partial<ViewSortEntity> = {},
) => ({
viewId,
direction: ViewSortDirection.ASC,
...overrides,
});
export const updateViewSortData = (
overrides: Partial<ViewSortEntity> = {},
) => ({
direction: ViewSortDirection.DESC,
...overrides,
});
export const createViewFilterData = (
viewId: string,
overrides: Partial<ViewFilterEntity> = {},
) => ({
viewId,
operand: ViewFilterOperand.IS,
value: 'test-value',
...overrides,
});
export const updateViewFilterData = (
overrides: Partial<ViewFilterEntity> = {},
) => ({
operand: ViewFilterOperand.IS_NOT,
value: 'updated-value',
...overrides,
});
export const createViewGroupData = (
viewId: string,
overrides: Partial<ViewGroupEntity> = {},
) => ({
viewId,
fieldValue: 'test-group-value',
isVisible: true,
position: 0,
...overrides,
});
export const updateViewGroupData = (
overrides: Partial<ViewGroupEntity> = {},
) => ({
fieldValue: 'updated-group-value',
isVisible: false,
position: 1,
...overrides,
});
export const createViewFilterGroupData = (
viewId: string,
overrides: Partial<ViewFilterGroupEntity> = {},
) => ({
viewId,
logicalOperator: ViewFilterGroupLogicalOperator.AND,
...overrides,
});
export const updateViewFilterGroupData = (
overrides: Partial<ViewFilterGroupEntity> = {},
) => ({
logicalOperator: ViewFilterGroupLogicalOperator.OR,
...overrides,
});