From 0975b06a9b0e618af2ad98bbd25b4fa96a9d14f8 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:57:37 +0100 Subject: [PATCH] [demo] Allow workspace to work with a non-system workspaceMember object (#15547) For demo purposes, we want to work with workspaceMember has a non-system object, allowing it to be displayed on the product, to be added custom fields etc. It is something we may want to do later anyway. This PR adds a bypassPermissionChecks on workspaceMember repository calls. This does not provide more permissions than before for other workspaces: workspaceMember being a system object, permissions are already bypassed for this one. (Note that actions such inviting a new workspaceMember, updating a workspaceMember are protected by a system permission checked in the relevant places). This work does **not** allow any workspace to switch their workspaceMember object to non-system, the switch is still manual. --- .../created-by-from-auth-context.service.ts | 3 ++- .../approved-access-domain.resolver.ts | 1 + .../auth/services/google-apis.service.ts | 1 + .../token/services/access-token.service.ts | 1 + .../user-workspace.service.spec.ts | 4 +++- .../user-workspace/user-workspace.service.ts | 2 ++ .../user/services/user.service.spec.ts | 18 ++++++++++-------- .../core-modules/user/services/user.service.ts | 6 +++++- .../resolvers/workflow-trigger.resolver.ts | 1 + .../workspace-invitation.resolver.ts | 2 ++ .../listeners/connected-account.listener.ts | 1 + .../match-participant.service.ts | 1 + 12 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/actor/services/created-by-from-auth-context.service.ts b/packages/twenty-server/src/engine/core-modules/actor/services/created-by-from-auth-context.service.ts index 523b8d48c7..8302b01324 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/services/created-by-from-auth-context.service.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/services/created-by-from-auth-context.service.ts @@ -7,13 +7,13 @@ import { Repository } from 'typeorm'; import { buildCreatedByFromApiKey } from 'src/engine/core-modules/actor/utils/build-created-by-from-api-key.util'; import { buildCreatedByFromFullNameMetadata } from 'src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; -import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type CreateInput = Record; @@ -119,6 +119,7 @@ export class CreatedByFromAuthContextService { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspace.id, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOneOrFail({ diff --git a/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.resolver.ts b/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.resolver.ts index 410cc98ccf..fdc272a062 100644 --- a/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.resolver.ts @@ -40,6 +40,7 @@ export class ApprovedAccessDomainResolver { await this.twentyORMGlobalManager.getRepositoryForWorkspace( currentWorkspace.id, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOneOrFail({ diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts index d5845a5004..5c8d7e8be8 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts @@ -173,6 +173,7 @@ export class GoogleAPIsService { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOneOrFail( diff --git a/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.ts b/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.ts index e706e4f159..bf044876bd 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.ts @@ -82,6 +82,7 @@ export class AccessTokenService { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOne({ diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts index 92ac914566..d5683e58f7 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts @@ -885,7 +885,9 @@ describe('UserWorkspaceService', () => { expect( twentyORMGlobalManager.getRepositoryForWorkspace, - ).toHaveBeenCalledWith(workspaceId, 'workspaceMember'); + ).toHaveBeenCalledWith(workspaceId, 'workspaceMember', { + shouldBypassPermissionChecks: true, + }); expect(workspaceMemberRepository.findOne).toHaveBeenCalledWith({ where: { id: workspaceMemberId, diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts index e6241466f5..4bc422f8f8 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts @@ -86,6 +86,7 @@ export class UserWorkspaceService extends TypeOrmQueryService( workspaceId, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const userWorkspace = await this.userWorkspaceRepository.findOneOrFail({ @@ -314,6 +315,7 @@ export class UserWorkspaceService extends TypeOrmQueryService( workspaceId, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOne({ diff --git a/packages/twenty-server/src/engine/core-modules/user/services/user.service.spec.ts b/packages/twenty-server/src/engine/core-modules/user/services/user.service.spec.ts index 220c9ff222..44ded3f2e5 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/user.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/user.service.spec.ts @@ -1,22 +1,22 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { type Repository } from 'typeorm'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; +import { type Repository } from 'typeorm'; -import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; +import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; import { UserService } from 'src/engine/core-modules/user/services/user.service'; import { UserEntity } from 'src/engine/core-modules/user/user.entity'; import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; -import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; -import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; +import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { PermissionsException, PermissionsExceptionCode, } from 'src/engine/metadata-modules/permissions/permissions.exception'; -import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; +import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; describe('UserService', () => { let service: UserService; @@ -107,7 +107,9 @@ describe('UserService', () => { expect( twentyORMGlobalManager.getRepositoryForWorkspace, - ).toHaveBeenCalledWith('w1', 'workspaceMember'); + ).toHaveBeenCalledWith('w1', 'workspaceMember', { + shouldBypassPermissionChecks: true, + }); expect(mockWorkspaceMemberRepo.findOne).toHaveBeenCalledWith({ where: { userId: 'u1' }, }); diff --git a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts index a6231fb817..6acb145414 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts @@ -4,9 +4,9 @@ import assert from 'assert'; import { msg } from '@lingui/core/macro'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; +import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; import { IsNull, Not, Repository } from 'typeorm'; -import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; import { AuthException, @@ -46,6 +46,7 @@ export class UserService extends TypeOrmQueryService { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspace.id, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); return await workspaceMemberRepository.findOne({ @@ -64,6 +65,7 @@ export class UserService extends TypeOrmQueryService { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspace.id, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); return await workspaceMemberRepository.find({ withDeleted: withDeleted }); @@ -78,6 +80,7 @@ export class UserService extends TypeOrmQueryService { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspace.id, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); return await workspaceMemberRepository.find({ @@ -104,6 +107,7 @@ export class UserService extends TypeOrmQueryService { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMembers = await workspaceMemberRepository.find(); diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts index 9dbc7ff18b..05aa762ead 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts @@ -71,6 +71,7 @@ export class WorkflowTriggerResolver { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspace.id, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOneOrFail({ diff --git a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts index 7ed5492929..dcd9cb570e 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts @@ -60,6 +60,7 @@ export class WorkspaceInvitationResolver { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspace.id, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOneOrFail({ @@ -100,6 +101,7 @@ export class WorkspaceInvitationResolver { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspace.id, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOneOrFail({ diff --git a/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts b/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts index 4db3e1e28c..5571f2ada3 100644 --- a/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts +++ b/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts @@ -29,6 +29,7 @@ export class ConnectedAccountListener { await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const workspaceMember = await workspaceMemberRepository.findOneOrFail({ where: { id: workspaceMemberId }, diff --git a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts index 603a7c31cf..8737ef50ba 100644 --- a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts +++ b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts @@ -110,6 +110,7 @@ export class MatchParticipantService< await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); const chunkSize = 200;