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
@@ -0,0 +1,45 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
@Injectable()
export class CreateViewFieldPermissionGuard implements CanActivate {
constructor(private readonly viewAccessService: ViewAccessService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let viewId: string | null = null;
// For GraphQL: extract from args.input or args.inputs
const args = gqlContext.getArgs();
if (typeof args?.input?.viewId === 'string') {
viewId = args.input.viewId;
}
if (!viewId && Array.isArray(args?.inputs) && args.inputs.length > 0) {
viewId =
typeof args.inputs[0]?.viewId === 'string'
? args.inputs[0].viewId
: null;
}
// For REST: extract from request body
if (!viewId && typeof request.body?.viewId === 'string') {
viewId = request.body.viewId;
}
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,38 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
@Injectable()
export class CreateViewFilterGroupPermissionGuard implements CanActivate {
constructor(private readonly viewAccessService: ViewAccessService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let viewId: string | null = null;
// For GraphQL: extract from args.input
const args = gqlContext.getArgs();
if (typeof args?.input?.viewId === 'string') {
viewId = args.input.viewId;
}
// For REST: extract from request body
if (!viewId && typeof request.body?.viewId === 'string') {
viewId = request.body.viewId;
}
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,38 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
@Injectable()
export class CreateViewFilterPermissionGuard implements CanActivate {
constructor(private readonly viewAccessService: ViewAccessService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let viewId: string | null = null;
// For GraphQL: extract from args.input
const args = gqlContext.getArgs();
if (typeof args?.input?.viewId === 'string') {
viewId = args.input.viewId;
}
// For REST: extract from request body
if (!viewId && typeof request.body?.viewId === 'string') {
viewId = request.body.viewId;
}
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,45 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
@Injectable()
export class CreateViewGroupPermissionGuard implements CanActivate {
constructor(private readonly viewAccessService: ViewAccessService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let viewId: string | null = null;
// For GraphQL: extract from args.input or args.inputs
const args = gqlContext.getArgs();
if (typeof args?.input?.viewId === 'string') {
viewId = args.input.viewId;
}
if (!viewId && Array.isArray(args?.inputs) && args.inputs.length > 0) {
viewId =
typeof args.inputs[0]?.viewId === 'string'
? args.inputs[0].viewId
: null;
}
// For REST: extract from request body
if (!viewId && typeof request.body?.viewId === 'string') {
viewId = request.body.viewId;
}
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,38 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
@Injectable()
export class CreateViewSortPermissionGuard implements CanActivate {
constructor(private readonly viewAccessService: ViewAccessService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let viewId: string | null = null;
// For GraphQL: extract from args.input
const args = gqlContext.getArgs();
if (typeof args?.input?.viewId === 'string') {
viewId = args.input.viewId;
}
// For REST: extract from request body
if (!viewId && typeof request.body?.viewId === 'string') {
viewId = request.body.viewId;
}
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DeleteViewFieldPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewField',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DeleteViewFilterGroupPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.id
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
entityId = args.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewFilterGroup',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DeleteViewFilterPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewFilter',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DeleteViewGroupPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewGroup',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,38 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
@Injectable()
export class DeleteViewPermissionGuard implements CanActivate {
constructor(private readonly viewAccessService: ViewAccessService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let viewId: string | null = null;
// For GraphQL: extract from args
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
viewId = args.id;
}
// For REST: extract from URL params
if (!viewId && typeof request.params?.id === 'string') {
viewId = request.params.id;
}
return this.viewAccessService.canUserModifyView(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DeleteViewSortPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.id
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
entityId = args.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewSort',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DestroyViewFieldPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewField',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DestroyViewFilterGroupPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.id
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
entityId = args.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewFilterGroup',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DestroyViewFilterPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewFilter',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DestroyViewGroupPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewGroup',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,38 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
@Injectable()
export class DestroyViewPermissionGuard implements CanActivate {
constructor(private readonly viewAccessService: ViewAccessService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let viewId: string | null = null;
// For GraphQL: extract from args
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
viewId = args.id;
}
// For REST: extract from URL params
if (!viewId && typeof request.params?.id === 'string') {
viewId = request.params.id;
}
return this.viewAccessService.canUserModifyView(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class DestroyViewSortPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.id
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
entityId = args.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewSort',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class UpdateViewFieldPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewField',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class UpdateViewFilterGroupPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.id
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
entityId = args.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewFilterGroup',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class UpdateViewFilterPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewFilter',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class UpdateViewGroupPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.input.id
const args = gqlContext.getArgs();
if (typeof args?.input?.id === 'string') {
entityId = args.input.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewGroup',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,38 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
@Injectable()
export class UpdateViewPermissionGuard implements CanActivate {
constructor(private readonly viewAccessService: ViewAccessService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let viewId: string | null = null;
// For GraphQL: extract from args
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
viewId = args.id;
}
// For REST: extract from URL params
if (!viewId && typeof request.params?.id === 'string') {
viewId = request.params.id;
}
return this.viewAccessService.canUserModifyView(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}
@@ -0,0 +1,50 @@
import {
Injectable,
type CanActivate,
type ExecutionContext,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ViewAccessService } from 'src/engine/metadata-modules/view-permissions/services/view-access.service';
import { ViewEntityLookupService } from 'src/engine/metadata-modules/view-permissions/services/view-entity-lookup.service';
@Injectable()
export class UpdateViewSortPermissionGuard implements CanActivate {
constructor(
private readonly viewAccessService: ViewAccessService,
private readonly viewEntityLookupService: ViewEntityLookupService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
let entityId = '';
// For GraphQL: extract from args.id
const args = gqlContext.getArgs();
if (typeof args?.id === 'string') {
entityId = args.id;
}
// For REST: extract from URL params
if (!entityId && typeof request.params?.id === 'string') {
entityId = request.params.id;
}
const viewId = entityId
? await this.viewEntityLookupService.findViewIdByEntityIdAndKind(
'viewSort',
entityId,
request.workspace.id,
)
: null;
return this.viewAccessService.canUserModifyViewByChildEntity(
viewId,
request.userWorkspaceId,
request.workspace.id,
);
}
}