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],
|
||||
|
||||
@@ -18,6 +18,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
interface ChatRequest {
|
||||
@@ -27,7 +28,7 @@ interface ChatRequest {
|
||||
}
|
||||
|
||||
@Controller('chat')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, CustomPermissionGuard)
|
||||
export class AiController {
|
||||
constructor(
|
||||
private readonly aiService: AiService,
|
||||
|
||||
@@ -8,18 +8,19 @@ import {
|
||||
ValidationPipe,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { AuthApiKey } from 'src/engine/decorators/auth/auth-api-key.decorator';
|
||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||
import { RestApiExceptionFilter } from 'src/engine/api/rest/rest-api-exception.filter';
|
||||
import { JsonRpc } from 'src/engine/core-modules/ai/dtos/json-rpc';
|
||||
import { McpService } from 'src/engine/core-modules/ai/services/mcp.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthApiKey } from 'src/engine/decorators/auth/auth-api-key.decorator';
|
||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
|
||||
import { RestApiExceptionFilter } from 'src/engine/api/rest/rest-api-exception.filter';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Controller('mcp')
|
||||
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
|
||||
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard, NoPermissionGuard)
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
export class McpController {
|
||||
constructor(private readonly mcpService: McpService) {}
|
||||
|
||||
+7
-1
@@ -20,14 +20,20 @@ import { UpdateApiKeyInput } from 'src/engine/core-modules/api-key/dtos/update-a
|
||||
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 { 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';
|
||||
|
||||
/**
|
||||
* rest/apiKeys is deprecated, use rest/metadata/apiKeys instead
|
||||
* rest/apiKeys will be removed in the future
|
||||
*/
|
||||
@Controller(['rest/apiKeys', 'rest/metadata/apiKeys'])
|
||||
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
|
||||
@UseGuards(
|
||||
JwtAuthGuard,
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS),
|
||||
)
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
export class ApiKeyController {
|
||||
constructor(private readonly apiKeyService: ApiKeyService) {}
|
||||
|
||||
@@ -125,7 +125,7 @@ export class AuthResolver {
|
||||
private readonly permissionsService: PermissionsService,
|
||||
) {}
|
||||
|
||||
@UseGuards(CaptchaGuard, PublicEndpointGuard)
|
||||
@UseGuards(CaptchaGuard, PublicEndpointGuard, NoPermissionGuard)
|
||||
@Query(() => CheckUserExistOutput)
|
||||
async checkUserExists(
|
||||
@Args() checkUserExistsInput: EmailAndCaptchaInput,
|
||||
@@ -147,7 +147,7 @@ export class AuthResolver {
|
||||
}
|
||||
|
||||
@Query(() => WorkspaceInviteHashValidOutput)
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async checkWorkspaceInviteHashIsValid(
|
||||
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
|
||||
): Promise<WorkspaceInviteHashValidOutput> {
|
||||
@@ -157,7 +157,7 @@ export class AuthResolver {
|
||||
}
|
||||
|
||||
@Query(() => WorkspaceEntity)
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async findWorkspaceFromInviteHash(
|
||||
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
|
||||
): Promise<WorkspaceEntity> {
|
||||
@@ -833,7 +833,7 @@ export class AuthResolver {
|
||||
}
|
||||
|
||||
@Query(() => ValidatePasswordResetTokenOutput)
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async validatePasswordResetToken(
|
||||
@Args() args: ValidatePasswordResetTokenInput,
|
||||
): Promise<ValidatePasswordResetTokenOutput> {
|
||||
|
||||
+12
-3
@@ -23,12 +23,13 @@ import { GoogleAPIsOauthRequestCodeGuard } from 'src/engine/core-modules/auth/gu
|
||||
import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { GoogleAPIsRequest } from 'src/engine/core-modules/auth/types/google-api-request.type';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
|
||||
@Controller('auth/google-apis')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
@@ -45,14 +46,22 @@ export class GoogleAPIsAuthController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(GoogleAPIsOauthRequestCodeGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
GoogleAPIsOauthRequestCodeGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async googleAuth() {
|
||||
// As this method is protected by Google Auth guard, it will trigger Google SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('get-access-token')
|
||||
@UseGuards(GoogleAPIsOauthExchangeCodeForTokenGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
GoogleAPIsOauthExchangeCodeForTokenGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async googleAuthGetAccessToken(
|
||||
@Req() req: GoogleAPIsRequest,
|
||||
@Res() res: Response,
|
||||
|
||||
+13
-2
@@ -16,6 +16,7 @@ import { GoogleProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { GoogleRequest } from 'src/engine/core-modules/auth/strategies/google.auth.strategy';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller('auth/google')
|
||||
@@ -24,14 +25,24 @@ export class GoogleAuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(GoogleProviderEnabledGuard, GoogleOauthGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
GoogleProviderEnabledGuard,
|
||||
GoogleOauthGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async googleAuth() {
|
||||
// As this method is protected by Google Auth guard, it will trigger Google SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('redirect')
|
||||
@UseGuards(GoogleProviderEnabledGuard, GoogleOauthGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
GoogleProviderEnabledGuard,
|
||||
GoogleOauthGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
@UseFilters(AuthOAuthExceptionFilter)
|
||||
async googleAuthRedirect(@Req() req: GoogleRequest, @Res() res: Response) {
|
||||
return res.redirect(
|
||||
|
||||
+11
-2
@@ -28,6 +28,7 @@ import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/ser
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller('auth/microsoft-apis')
|
||||
@@ -45,14 +46,22 @@ export class MicrosoftAPIsAuthController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(MicrosoftAPIsOauthRequestCodeGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
MicrosoftAPIsOauthRequestCodeGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async MicrosoftAuth() {
|
||||
// As this method is protected by Microsoft Auth guard, it will trigger Microsoft SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('get-access-token')
|
||||
@UseGuards(MicrosoftAPIsOauthExchangeCodeForTokenGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
MicrosoftAPIsOauthExchangeCodeForTokenGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async MicrosoftAuthGetAccessToken(
|
||||
@Req() req: MicrosoftAPIsRequest,
|
||||
@Res() res: Response,
|
||||
|
||||
+3
@@ -15,6 +15,7 @@ import { MicrosoftProviderEnabledGuard } from 'src/engine/core-modules/auth/guar
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller('auth/microsoft')
|
||||
@@ -27,6 +28,7 @@ export class MicrosoftAuthController {
|
||||
MicrosoftProviderEnabledGuard,
|
||||
MicrosoftOAuthGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async microsoftAuth() {
|
||||
// As this method is protected by Microsoft Auth guard, it will trigger Microsoft SSO flow
|
||||
@@ -38,6 +40,7 @@ export class MicrosoftAuthController {
|
||||
MicrosoftProviderEnabledGuard,
|
||||
MicrosoftOAuthGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async microsoftAuthRedirect(
|
||||
@Req() req: MicrosoftRequest,
|
||||
|
||||
+34
-9
@@ -21,10 +21,6 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import {
|
||||
WorkspaceSSOIdentityProviderEntity,
|
||||
IdentityProviderType,
|
||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter';
|
||||
import { EnterpriseFeaturesEnabledGuard } from 'src/engine/core-modules/auth/guards/enterprise-features-enabled.guard';
|
||||
import { OIDCAuthGuard } from 'src/engine/core-modules/auth/guards/oidc-auth.guard';
|
||||
@@ -36,9 +32,14 @@ import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/l
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import {
|
||||
IdentityProviderType,
|
||||
WorkspaceSSOIdentityProviderEntity,
|
||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller('auth')
|
||||
@@ -56,7 +57,11 @@ export class SSOAuthController {
|
||||
) {}
|
||||
|
||||
@Get('saml/metadata/:identityProviderId')
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
EnterpriseFeaturesEnabledGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async generateMetadata(@Req() req: any): Promise<string | void> {
|
||||
return generateServiceProviderMetadata({
|
||||
@@ -73,27 +78,47 @@ export class SSOAuthController {
|
||||
}
|
||||
|
||||
@Get('oidc/login/:identityProviderId')
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
EnterpriseFeaturesEnabledGuard,
|
||||
OIDCAuthGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async oidcAuth() {
|
||||
// As this method is protected by OIDC Auth guard, it will trigger OIDC SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('saml/login/:identityProviderId')
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
EnterpriseFeaturesEnabledGuard,
|
||||
SAMLAuthGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async samlAuth() {
|
||||
// As this method is protected by SAML Auth guard, it will trigger SAML SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('oidc/callback')
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
EnterpriseFeaturesEnabledGuard,
|
||||
OIDCAuthGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async oidcAuthCallback(@Req() req: OIDCRequest, @Res() res: Response) {
|
||||
return await this.authCallback(req, res);
|
||||
}
|
||||
|
||||
@Post('saml/callback/:identityProviderId')
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard, PublicEndpointGuard)
|
||||
@UseGuards(
|
||||
EnterpriseFeaturesEnabledGuard,
|
||||
SAMLAuthGuard,
|
||||
PublicEndpointGuard,
|
||||
NoPermissionGuard,
|
||||
)
|
||||
async samlAuthCallback(@Req() req: SAMLRequest, @Res() res: Response) {
|
||||
try {
|
||||
return await this.authCallback(req, res);
|
||||
|
||||
+3
-2
@@ -21,8 +21,8 @@ import { BillingWebhookEntitlementService } from 'src/engine/core-modules/billin
|
||||
import { BillingWebhookInvoiceService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-invoice.service';
|
||||
import { BillingWebhookPriceService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-price.service';
|
||||
import { BillingWebhookProductService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-product.service';
|
||||
import { BillingWebhookSubscriptionService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-subscription.service';
|
||||
import { BillingWebhookSubscriptionScheduleService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-subscription-schedule.service';
|
||||
import { BillingWebhookSubscriptionService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-subscription.service';
|
||||
import {
|
||||
BillingException,
|
||||
BillingExceptionCode,
|
||||
@@ -31,6 +31,7 @@ import { BillingWebhookEvent } from 'src/engine/core-modules/billing/enums/billi
|
||||
import { BillingRestApiExceptionFilter } from 'src/engine/core-modules/billing/filters/billing-api-exception.filter';
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { StripeWebhookService } from 'src/engine/core-modules/billing/stripe/services/stripe-webhook.service';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller()
|
||||
@@ -52,7 +53,7 @@ export class BillingWebhookController {
|
||||
) {}
|
||||
|
||||
@Post(['webhooks/stripe'])
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async handleWebhooks(
|
||||
@Headers('stripe-signature') signature: string,
|
||||
@Req() req: RawBodyRequest<Request>,
|
||||
|
||||
@@ -246,7 +246,7 @@ export class BillingResolver {
|
||||
}
|
||||
|
||||
@Query(() => [BillingPlanOutput])
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, NoPermissionGuard)
|
||||
async listPlans(): Promise<BillingPlanOutput[]> {
|
||||
const plans = await this.billingPlanService.listPlans();
|
||||
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import { TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE } from 'src/engine/core-modules/
|
||||
import { TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
|
||||
import { TimelineCalendarEventService } from 'src/engine/core-modules/calendar/timeline-calendar-event.service';
|
||||
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
||||
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@ArgsType()
|
||||
@@ -49,7 +50,7 @@ class GetTimelineCalendarEventsFromOpportunityIdArgs {
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, CustomPermissionGuard)
|
||||
@Resolver(() => TimelineCalendarEventsWithTotalDTO)
|
||||
export class TimelineCalendarEventResolver {
|
||||
constructor(
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||
|
||||
import { type ClientConfig } from 'src/engine/core-modules/client-config/client-config.entity';
|
||||
import { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller('/client-config')
|
||||
@@ -9,7 +10,7 @@ export class ClientConfigController {
|
||||
constructor(private readonly clientConfigService: ClientConfigService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async getClientConfig(): Promise<ClientConfig> {
|
||||
return this.clientConfigService.getClientConfig();
|
||||
}
|
||||
|
||||
+4
-3
@@ -5,11 +5,12 @@ import { Controller, Post, Req, UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
|
||||
import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { DnsManagerExceptionFilter } from 'src/engine/core-modules/dns-manager/exceptions/dns-manager-exception-filter';
|
||||
import { CloudflareSecretMatchGuard } from 'src/engine/core-modules/cloudflare/guards/cloudflare-secret.guard';
|
||||
import { DnsCloudflareService } from 'src/engine/core-modules/cloudflare/services/dns-cloudflare.service';
|
||||
import { DnsManagerExceptionFilter } from 'src/engine/core-modules/dns-manager/exceptions/dns-manager-exception-filter';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller()
|
||||
@UseFilters(AuthRestApiExceptionFilter, DnsManagerExceptionFilter)
|
||||
@@ -20,7 +21,7 @@ export class DnsCloudflareController {
|
||||
) {}
|
||||
|
||||
@Post(['cloudflare/custom-hostname-webhooks', 'webhooks/cloudflare'])
|
||||
@UseGuards(CloudflareSecretMatchGuard, PublicEndpointGuard)
|
||||
@UseGuards(CloudflareSecretMatchGuard, PublicEndpointGuard, NoPermissionGuard)
|
||||
async customHostnameWebhooks(@Req() req: Request) {
|
||||
const hostname = req.body?.data?.data?.hostname;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { FileApiExceptionFilter } from 'src/engine/core-modules/file/filters/fil
|
||||
import { FilePathGuard } from 'src/engine/core-modules/file/guards/file-path-guard';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { extractFileInfoFromRequest } from 'src/engine/core-modules/file/utils/extract-file-info-from-request.utils';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller('files')
|
||||
@@ -32,7 +33,7 @@ export class FileController {
|
||||
constructor(private readonly fileService: FileService) {}
|
||||
|
||||
@Get('*/:filename')
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async getFile(
|
||||
@Param() _params: string[],
|
||||
@Res() res: Response,
|
||||
|
||||
@@ -4,10 +4,11 @@ import { Args, Query, Resolver } from '@nestjs/graphql';
|
||||
import { AutocompleteResultDTO } from 'src/engine/core-modules/geo-map/dtos/autocomplete-result.dto';
|
||||
import { PlaceDetailsResultDTO } from 'src/engine/core-modules/geo-map/dtos/place-details-result.dto';
|
||||
import { GeoMapService } from 'src/engine/core-modules/geo-map/services/geo-map.service';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Resolver()
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, NoPermissionGuard)
|
||||
export class GeoMapResolver {
|
||||
constructor(private readonly geoMapService: GeoMapService) {}
|
||||
|
||||
|
||||
+3
-2
@@ -13,6 +13,7 @@ import { ConnectedAccountHealth } from 'src/engine/core-modules/health/indicator
|
||||
import { DatabaseHealthIndicator } from 'src/engine/core-modules/health/indicators/database.health';
|
||||
import { RedisHealthIndicator } from 'src/engine/core-modules/health/indicators/redis.health';
|
||||
import { WorkerHealthIndicator } from 'src/engine/core-modules/health/indicators/worker.health';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller('healthz')
|
||||
@@ -27,14 +28,14 @@ export class HealthController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
@HealthCheck()
|
||||
check() {
|
||||
return this.health.check([]);
|
||||
}
|
||||
|
||||
@Get(':indicatorId')
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
@HealthCheck()
|
||||
checkService(@Param('indicatorId') indicatorId: HealthIndicatorId) {
|
||||
const checks = {
|
||||
|
||||
+2
-1
@@ -12,6 +12,7 @@ import { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@@ -54,7 +55,7 @@ class GetTimelineThreadsFromOpportunityIdArgs {
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, CustomPermissionGuard)
|
||||
@Resolver(() => TimelineThreadsWithTotalDTO)
|
||||
export class TimelineMessagingResolver {
|
||||
constructor(
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
import { OpenApiService } from 'src/engine/core-modules/open-api/open-api.service';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
|
||||
@Controller()
|
||||
@@ -10,7 +11,7 @@ export class OpenApiController {
|
||||
constructor(private readonly openApiService: OpenApiService) {}
|
||||
|
||||
@Get(['open-api/core', 'rest/open-api/core'])
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async generateOpenApiSchemaCore(
|
||||
@Req() request: Request,
|
||||
@Res() res: Response,
|
||||
@@ -21,7 +22,7 @@ export class OpenApiController {
|
||||
}
|
||||
|
||||
@Get(['open-api/metadata', 'rest/open-api/metadata'])
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async generateOpenApiSchemaMetaData(
|
||||
@Req() request: Request,
|
||||
@Res() res: Response,
|
||||
|
||||
+8
@@ -26,7 +26,10 @@ import { PageLayoutTabRestApiExceptionFilter } from 'src/engine/core-modules/pag
|
||||
import { PageLayoutTabService } from 'src/engine/core-modules/page-layout/services/page-layout-tab.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.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/pageLayoutTabs')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@@ -35,6 +38,7 @@ export class PageLayoutTabController {
|
||||
constructor(private readonly pageLayoutTabService: PageLayoutTabService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('pageLayoutId') pageLayoutId: string,
|
||||
@@ -55,6 +59,7 @@ export class PageLayoutTabController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -63,6 +68,7 @@ export class PageLayoutTabController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async create(
|
||||
@Body() input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -71,6 +77,7 @@ export class PageLayoutTabController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdatePageLayoutTabInput,
|
||||
@@ -80,6 +87,7 @@ export class PageLayoutTabController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+8
@@ -26,7 +26,10 @@ import { PageLayoutWidgetRestApiExceptionFilter } from 'src/engine/core-modules/
|
||||
import { PageLayoutWidgetService } from 'src/engine/core-modules/page-layout/services/page-layout-widget.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.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/pageLayoutWidgets')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@@ -37,6 +40,7 @@ export class PageLayoutWidgetController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('pageLayoutTabId') pageLayoutTabId: string,
|
||||
@@ -57,6 +61,7 @@ export class PageLayoutWidgetController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -65,6 +70,7 @@ export class PageLayoutWidgetController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async create(
|
||||
@Body() input: CreatePageLayoutWidgetInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -73,6 +79,7 @@ export class PageLayoutWidgetController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdatePageLayoutWidgetInput,
|
||||
@@ -82,6 +89,7 @@ export class PageLayoutWidgetController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+8
@@ -21,7 +21,10 @@ import { PageLayoutRestApiExceptionFilter } from 'src/engine/core-modules/page-l
|
||||
import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/page-layout.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.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/pageLayouts')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@@ -30,6 +33,7 @@ export class PageLayoutController {
|
||||
constructor(private readonly pageLayoutService: PageLayoutService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('objectMetadataId') objectMetadataId?: string,
|
||||
@@ -45,6 +49,7 @@ export class PageLayoutController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -53,6 +58,7 @@ export class PageLayoutController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async create(
|
||||
@Body() input: CreatePageLayoutInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -61,6 +67,7 @@ export class PageLayoutController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdatePageLayoutInput,
|
||||
@@ -76,6 +83,7 @@ export class PageLayoutController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -11,6 +11,7 @@ import { PageLayoutTabService } from 'src/engine/core-modules/page-layout/servic
|
||||
import { PageLayoutGraphqlApiExceptionFilter } from 'src/engine/core-modules/page-layout/utils/page-layout-graphql-api-exception.filter';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.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';
|
||||
@@ -23,6 +24,7 @@ export class PageLayoutTabResolver {
|
||||
constructor(private readonly pageLayoutTabService: PageLayoutTabService) {}
|
||||
|
||||
@Query(() => [PageLayoutTabDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutTabs(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('pageLayoutId', { type: () => String }) pageLayoutId: string,
|
||||
@@ -34,6 +36,7 @@ export class PageLayoutTabResolver {
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutTabDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -18,6 +18,7 @@ import { injectWidgetConfigurationDiscriminator } from 'src/engine/core-modules/
|
||||
import { PageLayoutGraphqlApiExceptionFilter } from 'src/engine/core-modules/page-layout/utils/page-layout-graphql-api-exception.filter';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.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';
|
||||
@@ -32,6 +33,7 @@ export class PageLayoutWidgetResolver {
|
||||
) {}
|
||||
|
||||
@Query(() => [PageLayoutWidgetDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutWidgets(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('pageLayoutTabId', { type: () => String }) pageLayoutTabId: string,
|
||||
@@ -43,6 +45,7 @@ export class PageLayoutWidgetResolver {
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutWidgetDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutWidget(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+10
-4
@@ -13,16 +13,14 @@ import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/
|
||||
import { PageLayoutGraphqlApiExceptionFilter } from 'src/engine/core-modules/page-layout/utils/page-layout-graphql-api-exception.filter';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.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';
|
||||
|
||||
@Resolver(() => PageLayoutDTO)
|
||||
@UseFilters(PageLayoutGraphqlApiExceptionFilter)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.LAYOUTS),
|
||||
)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
export class PageLayoutResolver {
|
||||
constructor(
|
||||
@@ -31,6 +29,7 @@ export class PageLayoutResolver {
|
||||
) {}
|
||||
|
||||
@Query(() => [PageLayoutDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayouts(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('objectMetadataId', { type: () => String, nullable: true })
|
||||
@@ -47,6 +46,7 @@ export class PageLayoutResolver {
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutDTO, { nullable: true })
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -55,6 +55,7 @@ export class PageLayoutResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async createPageLayout(
|
||||
@Args('input') input: CreatePageLayoutInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -63,6 +64,7 @@ export class PageLayoutResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async updatePageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdatePageLayoutInput,
|
||||
@@ -72,6 +74,7 @@ export class PageLayoutResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async deletePageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -85,6 +88,7 @@ export class PageLayoutResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async destroyPageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -98,6 +102,7 @@ export class PageLayoutResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async restorePageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -106,6 +111,7 @@ export class PageLayoutResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async updatePageLayoutWithTabsAndWidgets(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdatePageLayoutWithTabsInput,
|
||||
|
||||
@@ -11,12 +11,14 @@ import { SearchApiExceptionFilter } from 'src/engine/core-modules/search/filters
|
||||
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
@Resolver()
|
||||
@UseFilters(SearchApiExceptionFilter, PreventNestToAutoLogGraphqlErrorsFilter)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseGuards(WorkspaceAuthGuard, CustomPermissionGuard)
|
||||
export class SearchResolver {
|
||||
constructor(
|
||||
private readonly searchService: SearchService,
|
||||
@@ -24,7 +26,6 @@ export class SearchResolver {
|
||||
) {}
|
||||
|
||||
@Query(() => SearchResultConnectionDTO)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
async search(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args()
|
||||
|
||||
+7
-1
@@ -18,14 +18,20 @@ import { WebhookService } from 'src/engine/core-modules/webhook/webhook.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 { 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';
|
||||
|
||||
/**
|
||||
* rest/webhooks is deprecated, use rest/metadata/webhooks instead
|
||||
* rest/webhooks will be removed in the future
|
||||
*/
|
||||
@Controller(['rest/webhooks', 'rest/metadata/webhooks'])
|
||||
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
|
||||
@UseGuards(
|
||||
JwtAuthGuard,
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS),
|
||||
)
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
export class WebhookController {
|
||||
constructor(private readonly webhookService: WebhookService) {}
|
||||
|
||||
+3
-2
@@ -12,6 +12,7 @@ import { Request } from 'express';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkflowTriggerRestApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-rest-api-exception.filter';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
@@ -40,7 +41,7 @@ export class WorkflowTriggerController {
|
||||
) {}
|
||||
|
||||
@Post('workflows/:workspaceId/:workflowId')
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async runWorkflowByPostRequest(
|
||||
@Param('workspaceId') workspaceId: string,
|
||||
@Param('workflowId') workflowId: string,
|
||||
@@ -54,7 +55,7 @@ export class WorkflowTriggerController {
|
||||
}
|
||||
|
||||
@Get('workflows/:workspaceId/:workflowId')
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async runWorkflowByGetRequest(
|
||||
@Param('workspaceId') workspaceId: string,
|
||||
@Param('workflowId') workflowId: string,
|
||||
|
||||
@@ -104,7 +104,7 @@ export class WorkspaceResolver {
|
||||
) {}
|
||||
|
||||
@Query(() => WorkspaceEntity)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, NoPermissionGuard)
|
||||
async currentWorkspace(@AuthWorkspace() { id }: WorkspaceEntity) {
|
||||
const workspace = await this.workspaceService.findById(id);
|
||||
|
||||
@@ -317,7 +317,7 @@ export class WorkspaceResolver {
|
||||
}
|
||||
|
||||
@Query(() => PublicWorkspaceDataOutput)
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async getPublicWorkspaceDataByDomain(
|
||||
@OriginHeader() originHeader: string,
|
||||
@Args('origin', { nullable: true }) origin?: string,
|
||||
|
||||
@@ -15,8 +15,10 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
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 { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
|
||||
import { AgentChatService } from './agent-chat.service';
|
||||
import { AgentStreamingService } from './agent-streaming.service';
|
||||
@@ -31,6 +33,7 @@ export class AgentChatController {
|
||||
) {}
|
||||
|
||||
@Post('stream')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.AI))
|
||||
async streamAgentChat(
|
||||
@Body()
|
||||
body: {
|
||||
|
||||
@@ -23,7 +23,11 @@ import { AgentDTO } from './dtos/agent.dto';
|
||||
import { CreateAgentInput } from './dtos/create-agent.input';
|
||||
import { UpdateAgentInput } from './dtos/update-agent.input';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
FeatureFlagGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.AI),
|
||||
)
|
||||
@Resolver()
|
||||
export class AgentResolver {
|
||||
constructor(
|
||||
|
||||
+4
-1
@@ -17,7 +17,10 @@ import { type RemoteServerType } from 'src/engine/metadata-modules/remote-server
|
||||
import { RemoteServerService } from 'src/engine/metadata-modules/remote-server/remote-server.service';
|
||||
import { remoteServerGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/remote-server/utils/remote-server-graphql-api-exception-handler.util';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.DATA_MODEL),
|
||||
)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
|
||||
@Resolver()
|
||||
|
||||
+4
-1
@@ -14,7 +14,10 @@ import { RemoteTableDTO } from 'src/engine/metadata-modules/remote-server/remote
|
||||
import { RemoteTableService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.service';
|
||||
import { remoteTableGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/remote-server/remote-table/utils/remote-table-graphql-api-exception-handler.util';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.DATA_MODEL),
|
||||
)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
|
||||
@Resolver()
|
||||
|
||||
+3
-2
@@ -12,13 +12,14 @@ import {
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { RouteTriggerRestApiExceptionFilter } from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger-rest-api-exception-filter';
|
||||
import { HTTPMethod } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
|
||||
import { RouteTriggerService } from 'src/engine/metadata-modules/route-trigger/route-trigger.service';
|
||||
import { RouteTriggerRestApiExceptionFilter } from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger-rest-api-exception-filter';
|
||||
|
||||
@Controller('s')
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
@UseFilters(RouteTriggerRestApiExceptionFilter)
|
||||
export class RouteTriggerController {
|
||||
constructor(private readonly routeTriggerService: RouteTriggerService) {}
|
||||
|
||||
+5
-1
@@ -25,7 +25,11 @@ import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
import { serverlessFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
FeatureFlagGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.WORKFLOWS),
|
||||
)
|
||||
@Resolver()
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
|
||||
|
||||
+3
@@ -17,6 +17,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
|
||||
import { UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
|
||||
@@ -47,6 +48,7 @@ export class ViewFieldController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('viewId') viewId?: string,
|
||||
@@ -59,6 +61,7 @@ export class ViewFieldController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
|
||||
import { DeleteViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/delete-view-field.input';
|
||||
@@ -35,6 +36,7 @@ export class ViewFieldResolver {
|
||||
) {}
|
||||
|
||||
@Query(() => [ViewFieldDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewFields(
|
||||
@Args('viewId', { type: () => String }) viewId: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -43,6 +45,7 @@ export class ViewFieldResolver {
|
||||
}
|
||||
|
||||
@Query(() => ViewFieldDTO, { nullable: true })
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewField(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -15,6 +15,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewFilterGroupInput } from 'src/engine/metadata-modules/view-filter-group/dtos/inputs/create-view-filter-group.input';
|
||||
import { UpdateViewFilterGroupInput } from 'src/engine/metadata-modules/view-filter-group/dtos/inputs/update-view-filter-group.input';
|
||||
@@ -41,6 +42,7 @@ export class ViewFilterGroupController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('viewId') viewId?: string,
|
||||
@@ -53,6 +55,7 @@ export class ViewFilterGroupController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewFilterGroupInput } from 'src/engine/metadata-modules/view-filter-group/dtos/inputs/create-view-filter-group.input';
|
||||
import { UpdateViewFilterGroupInput } from 'src/engine/metadata-modules/view-filter-group/dtos/inputs/update-view-filter-group.input';
|
||||
@@ -25,6 +26,7 @@ export class ViewFilterGroupResolver {
|
||||
) {}
|
||||
|
||||
@Query(() => [ViewFilterGroupDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewFilterGroups(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('viewId', { type: () => String, nullable: true })
|
||||
@@ -38,6 +40,7 @@ export class ViewFilterGroupResolver {
|
||||
}
|
||||
|
||||
@Query(() => ViewFilterGroupDTO, { nullable: true })
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewFilterGroup(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -17,6 +17,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input';
|
||||
import { UpdateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/update-view-filter.input';
|
||||
@@ -46,6 +47,7 @@ export class ViewFilterController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('viewId') viewId?: string,
|
||||
@@ -58,6 +60,7 @@ export class ViewFilterController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input';
|
||||
import { DeleteViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/delete-view-filter.input';
|
||||
@@ -30,6 +31,7 @@ export class ViewFilterResolver {
|
||||
) {}
|
||||
|
||||
@Query(() => [ViewFilterDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewFilters(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('viewId', { type: () => String, nullable: true })
|
||||
@@ -43,6 +45,7 @@ export class ViewFilterResolver {
|
||||
}
|
||||
|
||||
@Query(() => ViewFilterDTO, { nullable: true })
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewFilter(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -17,6 +17,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
|
||||
import { UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input';
|
||||
@@ -46,6 +47,7 @@ export class ViewGroupController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('viewId') viewId?: string,
|
||||
@@ -58,6 +60,7 @@ export class ViewGroupController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
|
||||
import { DeleteViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/delete-view-group.input';
|
||||
@@ -34,6 +35,7 @@ export class ViewGroupResolver {
|
||||
) {}
|
||||
|
||||
@Query(() => [ViewGroupDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewGroups(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('viewId', { type: () => String, nullable: true })
|
||||
@@ -47,6 +49,7 @@ export class ViewGroupResolver {
|
||||
}
|
||||
|
||||
@Query(() => ViewGroupDTO, { nullable: true })
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewGroup(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
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 { ViewVisibility } from 'src/engine/metadata-modules/view/enums/view-visibility.enum';
|
||||
|
||||
@Injectable()
|
||||
export class CreateViewPermissionGuard implements CanActivate {
|
||||
constructor(private readonly viewAccessService: ViewAccessService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const gqlContext = GqlExecutionContext.create(context);
|
||||
const request = gqlContext.getContext().req;
|
||||
|
||||
let visibility: ViewVisibility = ViewVisibility.WORKSPACE;
|
||||
|
||||
// For GraphQL: extract from args.input
|
||||
const args = gqlContext.getArgs();
|
||||
|
||||
if (args?.input?.visibility) {
|
||||
visibility = args.input.visibility as ViewVisibility;
|
||||
}
|
||||
|
||||
// For REST: extract from request body
|
||||
if (!args?.input && request.body?.visibility) {
|
||||
visibility = request.body.visibility as ViewVisibility;
|
||||
}
|
||||
|
||||
return this.viewAccessService.canUserCreateView(
|
||||
visibility,
|
||||
request.userWorkspaceId,
|
||||
request.workspace.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
+35
@@ -71,6 +71,41 @@ export class ViewAccessService {
|
||||
return this.checkViewAccess(view, userWorkspaceId, workspaceId);
|
||||
}
|
||||
|
||||
async canUserCreateView(
|
||||
visibility: ViewVisibility,
|
||||
userWorkspaceId: string | undefined,
|
||||
workspaceId: string,
|
||||
): Promise<boolean> {
|
||||
// For WORKSPACE visibility views, user must have VIEWS permission
|
||||
if (visibility === ViewVisibility.WORKSPACE && isDefined(userWorkspaceId)) {
|
||||
const permissions =
|
||||
await this.permissionsService.getUserWorkspacePermissions({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const hasViewsPermission =
|
||||
permissions.permissionFlags[PermissionFlagType.VIEWS] ?? false;
|
||||
|
||||
if (!hasViewsPermission) {
|
||||
throw new ViewException(
|
||||
generateViewExceptionMessage(
|
||||
ViewExceptionMessageKey.VIEW_CREATE_PERMISSION_DENIED,
|
||||
),
|
||||
ViewExceptionCode.VIEW_CREATE_PERMISSION_DENIED,
|
||||
{
|
||||
userFriendlyMessage: generateViewUserFriendlyExceptionMessage(
|
||||
ViewExceptionMessageKey.VIEW_CREATE_PERMISSION_DENIED,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// For UNLISTED views or users without userWorkspaceId, allow creation
|
||||
return true;
|
||||
}
|
||||
|
||||
private async checkViewAccess(
|
||||
view: ViewEntity,
|
||||
userWorkspaceId: string | undefined,
|
||||
|
||||
+3
@@ -11,6 +11,7 @@ import { CreateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view
|
||||
import { CreateViewFilterGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-filter-group-permission.guard';
|
||||
import { CreateViewFilterPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-filter-permission.guard';
|
||||
import { CreateViewGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-group-permission.guard';
|
||||
import { CreateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard';
|
||||
import { CreateViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-sort-permission.guard';
|
||||
import { DeleteViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard';
|
||||
import { DeleteViewFilterGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-filter-group-permission.guard';
|
||||
@@ -55,6 +56,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
ViewService,
|
||||
ViewEntityLookupService,
|
||||
ViewAccessService,
|
||||
CreateViewPermissionGuard,
|
||||
UpdateViewPermissionGuard,
|
||||
DeleteViewPermissionGuard,
|
||||
DestroyViewPermissionGuard,
|
||||
@@ -83,6 +85,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
ViewService,
|
||||
ViewEntityLookupService,
|
||||
ViewAccessService,
|
||||
CreateViewPermissionGuard,
|
||||
UpdateViewPermissionGuard,
|
||||
DeleteViewPermissionGuard,
|
||||
DestroyViewPermissionGuard,
|
||||
|
||||
+3
@@ -15,6 +15,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-sort-permission.guard';
|
||||
import { DeleteViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-sort-permission.guard';
|
||||
@@ -39,6 +40,7 @@ export class ViewSortController {
|
||||
constructor(private readonly viewSortService: ViewSortService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('viewId') viewId?: string,
|
||||
@@ -51,6 +53,7 @@ export class ViewSortController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-sort-permission.guard';
|
||||
import { DeleteViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-sort-permission.guard';
|
||||
@@ -23,6 +24,7 @@ export class ViewSortResolver {
|
||||
constructor(private readonly viewSortService: ViewSortService) {}
|
||||
|
||||
@Query(() => [ViewSortDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewSorts(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('viewId', { type: () => String, nullable: true })
|
||||
@@ -36,6 +38,7 @@ export class ViewSortResolver {
|
||||
}
|
||||
|
||||
@Query(() => ViewSortDTO, { nullable: true })
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewSort(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+14
-1
@@ -21,8 +21,11 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { RequestLocale } from 'src/engine/decorators/locale/request-locale.decorator';
|
||||
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
|
||||
import { CreateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard';
|
||||
import { DeleteViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-permission.guard';
|
||||
import { UpdateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard';
|
||||
import { CreateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/create-view.input';
|
||||
@@ -53,6 +56,7 @@ export class ViewController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(CustomPermissionGuard)
|
||||
async findMany(
|
||||
@RequestLocale() locale: keyof typeof APP_LOCALES | undefined,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -71,6 +75,7 @@ export class ViewController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@RequestLocale() locale: keyof typeof APP_LOCALES | undefined,
|
||||
@@ -103,6 +108,7 @@ export class ViewController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(CreateViewPermissionGuard)
|
||||
async create(
|
||||
@Body() input: CreateViewInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -144,6 +150,7 @@ export class ViewController {
|
||||
@Body() input: UpdateViewInput,
|
||||
@RequestLocale() locale: keyof typeof APP_LOCALES | undefined,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@AuthUserWorkspaceId() userWorkspaceId: string | undefined,
|
||||
): Promise<ViewDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
@@ -160,9 +167,15 @@ export class ViewController {
|
||||
id,
|
||||
},
|
||||
workspaceId: workspace.id,
|
||||
userWorkspaceId,
|
||||
});
|
||||
} else {
|
||||
updatedView = await this.viewService.update(id, workspace.id, input);
|
||||
updatedView = await this.viewService.update(
|
||||
id,
|
||||
workspace.id,
|
||||
input,
|
||||
userWorkspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
const processedViews = await this.processViewsWithTemplates(
|
||||
|
||||
@@ -21,10 +21,9 @@ import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
|
||||
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
|
||||
@@ -34,6 +33,7 @@ import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view
|
||||
import { ViewFilterService } from 'src/engine/metadata-modules/view-filter/services/view-filter.service';
|
||||
import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
|
||||
import { ViewGroupService } from 'src/engine/metadata-modules/view-group/services/view-group.service';
|
||||
import { CreateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard';
|
||||
import { DeleteViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-permission.guard';
|
||||
import { DestroyViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/destroy-view-permission.guard';
|
||||
import { UpdateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard';
|
||||
@@ -44,13 +44,6 @@ import { UpdateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/up
|
||||
import { ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { ViewVisibility } from 'src/engine/metadata-modules/view/enums/view-visibility.enum';
|
||||
import {
|
||||
ViewException,
|
||||
ViewExceptionCode,
|
||||
ViewExceptionMessageKey,
|
||||
generateViewExceptionMessage,
|
||||
generateViewUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/view/exceptions/view.exception';
|
||||
import { ViewV2Service } from 'src/engine/metadata-modules/view/services/view-v2.service';
|
||||
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
|
||||
import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter';
|
||||
@@ -70,7 +63,6 @@ export class ViewResolver {
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly viewV2Service: ViewV2Service,
|
||||
private readonly userRoleService: UserRoleService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
) {}
|
||||
|
||||
@ResolveField(() => String)
|
||||
@@ -119,6 +111,7 @@ export class ViewResolver {
|
||||
}
|
||||
|
||||
@Query(() => [ViewDTO])
|
||||
@UseGuards(CustomPermissionGuard)
|
||||
async getCoreViews(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@AuthUserWorkspaceId() userWorkspaceId: string | undefined,
|
||||
@@ -137,6 +130,7 @@ export class ViewResolver {
|
||||
}
|
||||
|
||||
@Query(() => ViewDTO, { nullable: true })
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreView(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -152,7 +146,7 @@ export class ViewResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => ViewDTO)
|
||||
@UseGuards(CustomPermissionGuard)
|
||||
@UseGuards(CreateViewPermissionGuard)
|
||||
async createCoreView(
|
||||
@Args('input') input: CreateViewInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -160,28 +154,6 @@ export class ViewResolver {
|
||||
): Promise<ViewDTO> {
|
||||
const visibility = input.visibility ?? ViewVisibility.WORKSPACE;
|
||||
|
||||
if (visibility === ViewVisibility.WORKSPACE && isDefined(userWorkspaceId)) {
|
||||
const permissions =
|
||||
await this.permissionsService.getUserWorkspacePermissions({
|
||||
userWorkspaceId,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
if (!permissions.permissionFlags[PermissionFlagType.VIEWS]) {
|
||||
throw new ViewException(
|
||||
generateViewExceptionMessage(
|
||||
ViewExceptionMessageKey.VIEW_CREATE_PERMISSION_DENIED,
|
||||
),
|
||||
ViewExceptionCode.VIEW_CREATE_PERMISSION_DENIED,
|
||||
{
|
||||
userFriendlyMessage: generateViewUserFriendlyExceptionMessage(
|
||||
ViewExceptionMessageKey.VIEW_CREATE_PERMISSION_DENIED,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
input.visibility = visibility;
|
||||
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
@@ -213,6 +185,7 @@ export class ViewResolver {
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdateViewInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@AuthUserWorkspaceId() userWorkspaceId: string | undefined,
|
||||
): Promise<ViewDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
@@ -224,10 +197,11 @@ export class ViewResolver {
|
||||
return await this.viewV2Service.updateOne({
|
||||
updateViewInput: { ...input, id },
|
||||
workspaceId: workspace.id,
|
||||
userWorkspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
return this.viewService.update(id, workspace.id, input);
|
||||
return this.viewService.update(id, workspace.id, input, userWorkspaceId);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
|
||||
+87
@@ -382,6 +382,93 @@ describe('ViewService', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should re-allocate view to current user when changing from WORKSPACE to UNLISTED visibility', async () => {
|
||||
const id = 'view-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const userWorkspaceId = 'current-user-workspace-id';
|
||||
const workspaceView = {
|
||||
...mockView,
|
||||
visibility: ViewVisibility.WORKSPACE,
|
||||
createdByUserWorkspaceId: null,
|
||||
} as ViewEntity;
|
||||
const updateData = { visibility: ViewVisibility.UNLISTED };
|
||||
const expectedSaveData = {
|
||||
id,
|
||||
visibility: ViewVisibility.UNLISTED,
|
||||
createdByUserWorkspaceId: userWorkspaceId,
|
||||
};
|
||||
const updatedView = {
|
||||
...workspaceView,
|
||||
...expectedSaveData,
|
||||
};
|
||||
|
||||
jest.spyOn(viewService, 'findById').mockResolvedValue(workspaceView);
|
||||
jest.spyOn(viewRepository, 'save').mockResolvedValue(updatedView);
|
||||
|
||||
const result = await viewService.update(
|
||||
id,
|
||||
workspaceId,
|
||||
updateData,
|
||||
userWorkspaceId,
|
||||
);
|
||||
|
||||
expect(viewService.findById).toHaveBeenCalledWith(id, workspaceId);
|
||||
expect(viewRepository.save).toHaveBeenCalledWith(expectedSaveData);
|
||||
expect(result.createdByUserWorkspaceId).toBe(userWorkspaceId);
|
||||
});
|
||||
|
||||
it('should not change createdByUserWorkspaceId when visibility is not changing to UNLISTED', async () => {
|
||||
const id = 'view-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const userWorkspaceId = 'current-user-workspace-id';
|
||||
const updateData = { name: 'Updated Name' };
|
||||
const updatedView = { ...mockView, ...updateData };
|
||||
|
||||
jest.spyOn(viewService, 'findById').mockResolvedValue(mockView);
|
||||
jest.spyOn(viewRepository, 'save').mockResolvedValue(updatedView);
|
||||
|
||||
await viewService.update(id, workspaceId, updateData, userWorkspaceId);
|
||||
|
||||
expect(viewRepository.save).toHaveBeenCalledWith({
|
||||
id,
|
||||
...updateData,
|
||||
});
|
||||
expect(viewRepository.save).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
createdByUserWorkspaceId: userWorkspaceId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not change createdByUserWorkspaceId when view is already UNLISTED', async () => {
|
||||
const id = 'view-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const userWorkspaceId = 'current-user-workspace-id';
|
||||
const originalOwner = 'original-owner-workspace-id';
|
||||
const unlistedView = {
|
||||
...mockView,
|
||||
visibility: ViewVisibility.UNLISTED,
|
||||
createdByUserWorkspaceId: originalOwner,
|
||||
} as ViewEntity;
|
||||
const updateData = { visibility: ViewVisibility.UNLISTED };
|
||||
const updatedView = { ...unlistedView, ...updateData };
|
||||
|
||||
jest.spyOn(viewService, 'findById').mockResolvedValue(unlistedView);
|
||||
jest.spyOn(viewRepository, 'save').mockResolvedValue(updatedView);
|
||||
|
||||
await viewService.update(id, workspaceId, updateData, userWorkspaceId);
|
||||
|
||||
expect(viewRepository.save).toHaveBeenCalledWith({
|
||||
id,
|
||||
...updateData,
|
||||
});
|
||||
expect(viewRepository.save).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
createdByUserWorkspaceId: userWorkspaceId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
|
||||
@@ -100,9 +100,11 @@ export class ViewV2Service {
|
||||
async updateOne({
|
||||
updateViewInput,
|
||||
workspaceId,
|
||||
userWorkspaceId,
|
||||
}: {
|
||||
updateViewInput: UpdateViewInput;
|
||||
workspaceId: string;
|
||||
userWorkspaceId?: string;
|
||||
}): Promise<ViewDTO> {
|
||||
const {
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
@@ -121,6 +123,21 @@ export class ViewV2Service {
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
});
|
||||
|
||||
const existingFlatView = existingFlatViewMaps.byId[updateViewInput.id];
|
||||
|
||||
// If changing visibility from WORKSPACE to UNLISTED, ensure createdByUserWorkspaceId is set
|
||||
// This prevents the view from disappearing for the user making the change
|
||||
if (
|
||||
isDefined(existingFlatView) &&
|
||||
isDefined(updateViewInput.visibility) &&
|
||||
updateViewInput.visibility === 'UNLISTED' &&
|
||||
existingFlatView.visibility === 'WORKSPACE' &&
|
||||
isDefined(userWorkspaceId)
|
||||
) {
|
||||
// Re-allocate the view to the current user
|
||||
flatViewFromUpdateInput.createdByUserWorkspaceId = userWorkspaceId;
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
|
||||
@@ -193,6 +193,7 @@ export class ViewService {
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: Partial<ViewEntity>,
|
||||
userWorkspaceId?: string,
|
||||
): Promise<ViewEntity> {
|
||||
const existingView = await this.findById(id, workspaceId);
|
||||
|
||||
@@ -206,9 +207,23 @@ export class ViewService {
|
||||
);
|
||||
}
|
||||
|
||||
// If changing visibility from WORKSPACE to UNLISTED, ensure createdByUserWorkspaceId is set
|
||||
// This prevents the view from disappearing for the user making the change
|
||||
const dataToUpdate = { ...updateData };
|
||||
|
||||
if (
|
||||
isDefined(updateData.visibility) &&
|
||||
updateData.visibility === ViewVisibility.UNLISTED &&
|
||||
existingView.visibility === ViewVisibility.WORKSPACE &&
|
||||
isDefined(userWorkspaceId)
|
||||
) {
|
||||
// Re-allocate the view to the current user if it has no owner or a different owner
|
||||
dataToUpdate.createdByUserWorkspaceId = userWorkspaceId;
|
||||
}
|
||||
|
||||
const updatedView = await this.viewRepository.save({
|
||||
id,
|
||||
...updateData,
|
||||
...dataToUpdate,
|
||||
});
|
||||
|
||||
await this.flushGraphQLCache(workspaceId);
|
||||
|
||||
@@ -6,13 +6,14 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { OnDbEventDTO } from 'src/engine/subscriptions/dtos/on-db-event.dto';
|
||||
import { OnDbEventInput } from 'src/engine/subscriptions/dtos/on-db-event.input';
|
||||
|
||||
@Resolver()
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, NoPermissionGuard)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
|
||||
export class SubscriptionsResolver {
|
||||
|
||||
Reference in New Issue
Block a user