Files
twenty/tools/eslint-rules/rules/graphql-resolvers-should-be-guarded.spec.ts
T
Félix Malfait 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

224 lines
4.2 KiB
TypeScript

import { TSESLint } from '@typescript-eslint/utils';
import { rule, RULE_NAME } from './graphql-resolvers-should-be-guarded';
const ruleTester = new TSESLint.RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
});
ruleTester.run(RULE_NAME, rule, {
valid: [
{
code: `
class TestResolver {
@Query()
@UseGuards(UserAuthGuard)
testQuery() {}
}
`,
},
{
code: `
class TestResolver {
@Query()
@UseGuards(WorkspaceAuthGuard)
testQuery() {}
}
`,
},
{
code: `
class TestResolver {
@Query()
@UseGuards(PublicEndpointGuard)
testQuery() {}
}
`,
},
{
code: `
class TestResolver {
@Query()
@UseGuards(CaptchaGuard, PublicEndpointGuard)
testQuery() {}
}
`,
},
{
code: `
@UseGuards(UserAuthGuard)
class TestResolver {
@Query()
testQuery() {}
}
`,
},
{
code: `
@UseGuards(WorkspaceAuthGuard)
class TestResolver {
@Query()
testQuery() {}
}
`,
},
{
code: `
@UseGuards(PublicEndpointGuard)
class TestResolver {
@Query()
testQuery() {}
}
`,
},
{
code: `
class TestResolver {
regularMethod() {}
}
`,
},
{
code: `
class TestResolver {
@ResolveField()
testField() {}
}
`,
},
{
code: `
@UseGuards(WorkspaceAuthGuard, SettingsPermissionsGuard(PermissionFlagType.WORKSPACE))
class TestResolver {
@Mutation(() => String)
async createSomething() {}
}
`,
},
{
code: `
class TestResolver {
@Mutation(() => String)
@UseGuards(WorkspaceAuthGuard, SettingsPermissionsGuard(PermissionFlagType.WORKSPACE))
async createSomething() {}
}
`,
},
{
code: `
class TestResolver {
@Mutation(() => String)
@UseGuards(WorkspaceAuthGuard, CustomPermissionGuard)
async createSomething() {}
}
`,
},
{
code: `
@UseGuards(WorkspaceAuthGuard, CustomPermissionGuard)
class TestResolver {
@Mutation(() => String)
async createSomething() {}
}
`,
},
],
invalid: [
{
code: `
class TestResolver {
@Query()
testQuery() {}
}
`,
errors: [
{
messageId: 'graphqlResolversShouldBeGuarded',
},
],
},
{
code: `
class TestResolver {
@Mutation()
testMutation() {}
}
`,
errors: [
{
messageId: 'graphqlResolversShouldBeGuarded',
},
],
},
{
code: `
class TestResolver {
@Subscription()
testSubscription() {}
}
`,
errors: [
{
messageId: 'graphqlResolversShouldBeGuarded',
},
],
},
{
code: `
class TestResolver {
@Query()
@UseGuards(CaptchaGuard)
testQuery() {}
}
`,
errors: [
{
messageId: 'graphqlResolversShouldBeGuarded',
},
],
},
{
code: `
@UseGuards(CaptchaGuard)
class TestResolver {
@Query()
testQuery() {}
}
`,
errors: [
{
messageId: 'graphqlResolversShouldBeGuarded',
},
],
},
{
code: `
class TestResolver {
@Mutation(() => String)
@UseGuards(WorkspaceAuthGuard)
async createSomething() {}
}
`,
errors: [
{
messageId: 'graphqlResolversShouldBeGuarded',
},
],
},
{
code: `
@UseGuards(WorkspaceAuthGuard)
class TestResolver {
@Mutation(() => String)
async createSomething() {}
}
`,
errors: [
{
messageId: 'graphqlResolversShouldBeGuarded',
},
],
},
],
});