From 65dced14fff4799b0cdfa2ac7617c94546c77a3e Mon Sep 17 00:00:00 2001 From: eeeggg Date: Tue, 23 Dec 2025 02:06:47 -0600 Subject: [PATCH] fix: enable API key management of workspace views + fix permission bypass vulnerability (#16768) Fixes #16739 - Remove empty string coercion in createCoreView that caused PostgreSQL UUID errors for API keys - Add permission check allowing API keys with VIEWS permission to manage workspace views they created API keys with 'Manage Views' permission can now create, update, and delete workspace views via both GraphQL and REST APIs. --- .../create-view-field-permission.guard.ts | 1 + ...eate-view-filter-group-permission.guard.ts | 1 + .../create-view-filter-permission.guard.ts | 1 + .../create-view-group-permission.guard.ts | 1 + .../guards/create-view-permission.guard.ts | 1 + .../create-view-sort-permission.guard.ts | 1 + .../delete-view-field-permission.guard.ts | 1 + ...lete-view-filter-group-permission.guard.ts | 1 + .../delete-view-filter-permission.guard.ts | 1 + .../delete-view-group-permission.guard.ts | 1 + .../guards/delete-view-permission.guard.ts | 1 + .../delete-view-sort-permission.guard.ts | 1 + .../destroy-view-field-permission.guard.ts | 1 + ...troy-view-filter-group-permission.guard.ts | 1 + .../destroy-view-filter-permission.guard.ts | 1 + .../destroy-view-group-permission.guard.ts | 1 + .../guards/destroy-view-permission.guard.ts | 1 + .../destroy-view-sort-permission.guard.ts | 1 + .../update-view-field-permission.guard.ts | 1 + ...date-view-filter-group-permission.guard.ts | 1 + .../update-view-filter-permission.guard.ts | 1 + .../update-view-group-permission.guard.ts | 1 + .../guards/update-view-permission.guard.ts | 1 + .../update-view-sort-permission.guard.ts | 1 + .../services/view-access.service.ts | 63 +++++++++++++------ .../view/resolvers/view.resolver.ts | 2 +- 26 files changed, 69 insertions(+), 20 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard.ts index ad2bc62a83..b5cda6f4b0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard.ts @@ -40,6 +40,7 @@ export class CreateViewFieldPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-filter-group-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-filter-group-permission.guard.ts index a79da1a2f3..f57d6bc02c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-filter-group-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-filter-group-permission.guard.ts @@ -33,6 +33,7 @@ export class CreateViewFilterGroupPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-filter-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-filter-permission.guard.ts index a48699bb4d..a94cb91b3f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-filter-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-filter-permission.guard.ts @@ -33,6 +33,7 @@ export class CreateViewFilterPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-group-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-group-permission.guard.ts index 958b209d3b..2ef73d4acb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-group-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-group-permission.guard.ts @@ -40,6 +40,7 @@ export class CreateViewGroupPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard.ts index 5f1baf9050..0d267eddcf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard.ts @@ -34,6 +34,7 @@ export class CreateViewPermissionGuard implements CanActivate { visibility, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-sort-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-sort-permission.guard.ts index d15537b71e..33b4d5c7cb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-sort-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/create-view-sort-permission.guard.ts @@ -33,6 +33,7 @@ export class CreateViewSortPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard.ts index a00ee8cd0f..9d6d967928 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard.ts @@ -45,6 +45,7 @@ export class DeleteViewFieldPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-filter-group-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-filter-group-permission.guard.ts index 9f30a662d9..1a31c1d8cd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-filter-group-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-filter-group-permission.guard.ts @@ -45,6 +45,7 @@ export class DeleteViewFilterGroupPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-filter-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-filter-permission.guard.ts index 90ab35416e..82312a22c5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-filter-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-filter-permission.guard.ts @@ -45,6 +45,7 @@ export class DeleteViewFilterPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-group-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-group-permission.guard.ts index f8f4ae014e..d36398a279 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-group-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-group-permission.guard.ts @@ -45,6 +45,7 @@ export class DeleteViewGroupPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-permission.guard.ts index 39da34d675..a9324ce4b5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-permission.guard.ts @@ -33,6 +33,7 @@ export class DeleteViewPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-sort-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-sort-permission.guard.ts index 6c9fc51938..a69a7f59a4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-sort-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/delete-view-sort-permission.guard.ts @@ -45,6 +45,7 @@ export class DeleteViewSortPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-field-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-field-permission.guard.ts index 095e375656..93e6c5c64f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-field-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-field-permission.guard.ts @@ -45,6 +45,7 @@ export class DestroyViewFieldPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-filter-group-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-filter-group-permission.guard.ts index ec6b427484..c836641662 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-filter-group-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-filter-group-permission.guard.ts @@ -45,6 +45,7 @@ export class DestroyViewFilterGroupPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-filter-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-filter-permission.guard.ts index bc59c21861..ef88ae11f5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-filter-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-filter-permission.guard.ts @@ -45,6 +45,7 @@ export class DestroyViewFilterPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-group-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-group-permission.guard.ts index dea1be66f3..b1c83a2da1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-group-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-group-permission.guard.ts @@ -45,6 +45,7 @@ export class DestroyViewGroupPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-permission.guard.ts index 686328ff92..d671f858df 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-permission.guard.ts @@ -33,6 +33,7 @@ export class DestroyViewPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-sort-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-sort-permission.guard.ts index e127634f06..9e3717e38e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-sort-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/destroy-view-sort-permission.guard.ts @@ -45,6 +45,7 @@ export class DestroyViewSortPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-field-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-field-permission.guard.ts index c1b7573f7c..79e5e83c62 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-field-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-field-permission.guard.ts @@ -45,6 +45,7 @@ export class UpdateViewFieldPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-filter-group-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-filter-group-permission.guard.ts index 9e6406fb2a..7280e5ca9d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-filter-group-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-filter-group-permission.guard.ts @@ -45,6 +45,7 @@ export class UpdateViewFilterGroupPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-filter-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-filter-permission.guard.ts index 473c105acf..0f17eaccf5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-filter-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-filter-permission.guard.ts @@ -45,6 +45,7 @@ export class UpdateViewFilterPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-group-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-group-permission.guard.ts index f44c134afd..e2b7ceebe3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-group-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-group-permission.guard.ts @@ -45,6 +45,7 @@ export class UpdateViewGroupPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard.ts index ca6d8e32e2..81c803c42f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard.ts @@ -33,6 +33,7 @@ export class UpdateViewPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-sort-permission.guard.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-sort-permission.guard.ts index 800a42630a..f75526014c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-sort-permission.guard.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-sort-permission.guard.ts @@ -45,6 +45,7 @@ export class UpdateViewSortPermissionGuard implements CanActivate { viewId, request.userWorkspaceId, request.workspace.id, + request.apiKey?.id, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-permissions/services/view-access.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-permissions/services/view-access.service.ts index 16a98b721c..9cbf49cbd7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-permissions/services/view-access.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-permissions/services/view-access.service.ts @@ -26,6 +26,7 @@ export class ViewAccessService { viewId: string | null, userWorkspaceId: string | undefined, workspaceId: string, + apiKeyId?: string, ): Promise { // If viewId is null, the entity doesn't exist - allow the operation // so the service can handle the NOT_FOUND error properly @@ -43,13 +44,14 @@ export class ViewAccessService { return true; } - return this.checkViewAccess(view, userWorkspaceId, workspaceId); + return this.checkViewAccess(view, userWorkspaceId, workspaceId, apiKeyId); } async canUserModifyViewByChildEntity( viewId: string | null, userWorkspaceId: string | undefined, workspaceId: string, + apiKeyId?: string, ): Promise { // If viewId is null, the child entity doesn't exist // Allow through so the service can throw the proper entity-specific error @@ -68,24 +70,36 @@ export class ViewAccessService { return true; } - return this.checkViewAccess(view, userWorkspaceId, workspaceId); + return this.checkViewAccess(view, userWorkspaceId, workspaceId, apiKeyId); } async canUserCreateView( visibility: ViewVisibility, userWorkspaceId: string | undefined, workspaceId: string, + apiKeyId?: string, ): Promise { - // For WORKSPACE visibility views, user must have VIEWS permission - if (visibility === ViewVisibility.WORKSPACE && isDefined(userWorkspaceId)) { - const permissions = - await this.permissionsService.getUserWorkspacePermissions({ - userWorkspaceId, - workspaceId, - }); + // For WORKSPACE visibility views, check VIEWS permission + if (visibility === ViewVisibility.WORKSPACE) { + let hasViewsPermission = false; - const hasViewsPermission = - permissions.permissionFlags[PermissionFlagType.VIEWS] ?? false; + if (isDefined(userWorkspaceId)) { + const permissions = + await this.permissionsService.getUserWorkspacePermissions({ + userWorkspaceId, + workspaceId, + }); + + hasViewsPermission = + permissions.permissionFlags[PermissionFlagType.VIEWS] ?? false; + } else if (isDefined(apiKeyId)) { + hasViewsPermission = + await this.permissionsService.userHasWorkspaceSettingPermission({ + workspaceId, + apiKeyId, + setting: PermissionFlagType.VIEWS, + }); + } if (!hasViewsPermission) { throw new ViewException( @@ -102,7 +116,7 @@ export class ViewAccessService { } } - // For UNLISTED views or users without userWorkspaceId, allow creation + // For UNLISTED views, allow creation return true; } @@ -110,18 +124,29 @@ export class ViewAccessService { view: ViewEntity, userWorkspaceId: string | undefined, workspaceId: string, + apiKeyId?: string, ): Promise { - const permissions = isDefined(userWorkspaceId) - ? await this.permissionsService.getUserWorkspacePermissions({ + let hasViewsPermission = false; + + if (isDefined(userWorkspaceId)) { + const permissions = + await this.permissionsService.getUserWorkspacePermissions({ userWorkspaceId, workspaceId, - }) - : null; + }); - const hasViewsPermission = - permissions?.permissionFlags[PermissionFlagType.VIEWS] ?? false; + hasViewsPermission = + permissions.permissionFlags[PermissionFlagType.VIEWS] ?? false; + } else if (isDefined(apiKeyId)) { + hasViewsPermission = + await this.permissionsService.userHasWorkspaceSettingPermission({ + workspaceId, + apiKeyId, + setting: PermissionFlagType.VIEWS, + }); + } - // Users with VIEWS permission can manipulate all views + // Users/API keys with VIEWS permission can manipulate all views if (hasViewsPermission) { return true; } diff --git a/packages/twenty-server/src/engine/metadata-modules/view/resolvers/view.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/view/resolvers/view.resolver.ts index 6e21bb572e..b76efc4b10 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/resolvers/view.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/resolvers/view.resolver.ts @@ -153,7 +153,7 @@ export class ViewResolver { return await this.viewService.createOne({ createViewInput: input, workspaceId: workspace.id, - createdByUserWorkspaceId: userWorkspaceId ?? '', + createdByUserWorkspaceId: userWorkspaceId, }); }