fix calendar events and messages post hooks (#12034)

Context
workspaceMemberId is not always present in AuthContext. Solution
implemented here is to fetch workspaceMemberId via userId.

QRQC coming ... 

Tested
- FindManyCalendarEvents / FindOneCalendarEvent
- FindManyMessages / FindOneMessage

closes https://github.com/twentyhq/twenty/issues/12027
This commit is contained in:
Etienne
2025-05-14 15:52:52 +02:00
committed by GitHub
parent 0c60fa9c23
commit 929f7876de
8 changed files with 263 additions and 49 deletions

View File

@ -1,9 +1,11 @@
import { isDefined } from 'twenty-shared/utils';
import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { ForbiddenError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { ApplyCalendarEventsVisibilityRestrictionsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service'; import { ApplyCalendarEventsVisibilityRestrictionsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity';
@ -23,13 +25,15 @@ export class CalendarEventFindManyPostQueryHook
_objectName: string, _objectName: string,
payload: CalendarEventWorkspaceEntity[], payload: CalendarEventWorkspaceEntity[],
): Promise<void> { ): Promise<void> {
if (!authContext.workspaceMemberId) { const { user, apiKey } = authContext;
throw new UserInputError('Workspace member id is required');
if (!isDefined(user) && !isDefined(apiKey)) {
throw new ForbiddenError('User is required');
} }
await this.applyCalendarEventsVisibilityRestrictionsService.applyCalendarEventsVisibilityRestrictions( await this.applyCalendarEventsVisibilityRestrictionsService.applyCalendarEventsVisibilityRestrictions(
authContext.workspaceMemberId,
payload, payload,
user?.id,
); );
} }
} }

View File

@ -1,9 +1,11 @@
import { isDefined } from 'twenty-shared/utils';
import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { ForbiddenError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { ApplyCalendarEventsVisibilityRestrictionsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service'; import { ApplyCalendarEventsVisibilityRestrictionsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity';
@ -23,13 +25,15 @@ export class CalendarEventFindOnePostQueryHook
_objectName: string, _objectName: string,
payload: CalendarEventWorkspaceEntity[], payload: CalendarEventWorkspaceEntity[],
): Promise<void> { ): Promise<void> {
if (!authContext.workspaceMemberId) { const { user, apiKey } = authContext;
throw new UserInputError('Workspace member id is required');
if (!isDefined(user) && !isDefined(apiKey)) {
throw new ForbiddenError('User is required');
} }
await this.applyCalendarEventsVisibilityRestrictionsService.applyCalendarEventsVisibilityRestrictions( await this.applyCalendarEventsVisibilityRestrictionsService.applyCalendarEventsVisibilityRestrictions(
authContext.workspaceMemberId,
payload, payload,
user?.id,
); );
} }
} }

View File

@ -48,6 +48,10 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
find: jest.fn(), find: jest.fn(),
}; };
const mockWorkspaceMemberRepository = {
findOneByOrFail: jest.fn(),
};
const mockTwentyORMManager = { const mockTwentyORMManager = {
getRepository: jest.fn().mockImplementation((name) => { getRepository: jest.fn().mockImplementation((name) => {
if (name === 'calendarChannelEventAssociation') { if (name === 'calendarChannelEventAssociation') {
@ -56,6 +60,9 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
if (name === 'connectedAccount') { if (name === 'connectedAccount') {
return mockConnectedAccountRepository; return mockConnectedAccountRepository;
} }
if (name === 'workspaceMember') {
return mockWorkspaceMemberRepository;
}
}), }),
}; };
@ -83,6 +90,10 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
createMockCalendarEvent('1', 'Test Event', 'Test Description'), createMockCalendarEvent('1', 'Test Event', 'Test Description'),
]; ];
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-id',
});
mockCalendarEventAssociationRepository.find.mockResolvedValue([ mockCalendarEventAssociationRepository.find.mockResolvedValue([
{ {
calendarEventId: '1', calendarEventId: '1',
@ -94,8 +105,8 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
]); ]);
const result = await service.applyCalendarEventsVisibilityRestrictions( const result = await service.applyCalendarEventsVisibilityRestrictions(
'workspace-member-id',
calendarEvents, calendarEvents,
'user-id',
); );
expect(result).toEqual(calendarEvents); expect(result).toEqual(calendarEvents);
@ -124,11 +135,15 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
}, },
]); ]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-id',
});
mockConnectedAccountRepository.find.mockResolvedValue([]); mockConnectedAccountRepository.find.mockResolvedValue([]);
const result = await service.applyCalendarEventsVisibilityRestrictions( const result = await service.applyCalendarEventsVisibilityRestrictions(
'workspace-member-id',
calendarEvents, calendarEvents,
'user-id',
); );
expect(result).toEqual([ expect(result).toEqual([
@ -155,11 +170,15 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
}, },
]); ]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-account-owner-id',
});
mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]); mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]);
const result = await service.applyCalendarEventsVisibilityRestrictions( const result = await service.applyCalendarEventsVisibilityRestrictions(
'workspace-member-id',
calendarEvents, calendarEvents,
'user-id',
); );
expect(result).toEqual(calendarEvents); expect(result).toEqual(calendarEvents);
@ -186,11 +205,15 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
}, },
]); ]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-not-account-owner-id',
});
mockConnectedAccountRepository.find.mockResolvedValue([]); mockConnectedAccountRepository.find.mockResolvedValue([]);
const result = await service.applyCalendarEventsVisibilityRestrictions( const result = await service.applyCalendarEventsVisibilityRestrictions(
'workspace-member-id',
calendarEvents, calendarEvents,
'user-id',
); );
expect(result).toEqual([]); expect(result).toEqual([]);
@ -203,6 +226,10 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
createMockCalendarEvent('3', 'Event 3', 'Description 3'), createMockCalendarEvent('3', 'Event 3', 'Description 3'),
]; ];
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-id',
});
mockCalendarEventAssociationRepository.find.mockResolvedValue([ mockCalendarEventAssociationRepository.find.mockResolvedValue([
{ {
calendarEventId: '1', calendarEventId: '1',
@ -232,8 +259,63 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
.mockResolvedValueOnce([{ id: '1' }]); // request for calendar event 2 .mockResolvedValueOnce([{ id: '1' }]); // request for calendar event 2
const result = await service.applyCalendarEventsVisibilityRestrictions( const result = await service.applyCalendarEventsVisibilityRestrictions(
'workspace-member-id',
calendarEvents, calendarEvents,
'user-id',
);
expect(result).toEqual([
calendarEvents[0],
calendarEvents[1],
{
...calendarEvents[2],
title: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED,
description: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED,
},
]);
});
it('should return all calendar events with the right visibility when userId is undefined (api key request)', async () => {
const calendarEvents = [
createMockCalendarEvent('1', 'Event 1', 'Description 1'),
createMockCalendarEvent('2', 'Event 2', 'Description 2'),
createMockCalendarEvent('3', 'Event 3', 'Description 3'),
];
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-id',
});
mockCalendarEventAssociationRepository.find.mockResolvedValue([
{
calendarEventId: '1',
calendarChannel: {
id: '1',
visibility: CalendarChannelVisibility.SHARE_EVERYTHING,
},
},
{
calendarEventId: '2',
calendarChannel: {
id: '2',
visibility: CalendarChannelVisibility.METADATA,
},
},
{
calendarEventId: '3',
calendarChannel: {
id: '3',
visibility: CalendarChannelVisibility.METADATA,
},
},
]);
mockConnectedAccountRepository.find
.mockResolvedValueOnce([]) // request for calendar event 3
.mockResolvedValueOnce([{ id: '1' }]); // request for calendar event 2
const result = await service.applyCalendarEventsVisibilityRestrictions(
calendarEvents,
undefined,
); );
expect(result).toEqual([ expect(result).toEqual([

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import groupBy from 'lodash.groupby'; import groupBy from 'lodash.groupby';
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
@ -9,14 +10,15 @@ import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/cale
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Injectable() @Injectable()
export class ApplyCalendarEventsVisibilityRestrictionsService { export class ApplyCalendarEventsVisibilityRestrictionsService {
constructor(private readonly twentyORMManager: TwentyORMManager) {} constructor(private readonly twentyORMManager: TwentyORMManager) {}
public async applyCalendarEventsVisibilityRestrictions( public async applyCalendarEventsVisibilityRestrictions(
workspaceMemberId: string,
calendarEvents: CalendarEventWorkspaceEntity[], calendarEvents: CalendarEventWorkspaceEntity[],
userId?: string, // undefined when request is made with api key
) { ) {
const calendarChannelEventAssociationRepository = const calendarChannelEventAssociationRepository =
await this.twentyORMManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>( await this.twentyORMManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
@ -36,6 +38,11 @@ export class ApplyCalendarEventsVisibilityRestrictionsService {
'connectedAccount', 'connectedAccount',
); );
const workspaceMemberRepository =
await this.twentyORMManager.getRepository<WorkspaceMemberWorkspaceEntity>(
'workspaceMember',
);
for (let i = calendarEvents.length - 1; i >= 0; i--) { for (let i = calendarEvents.length - 1; i >= 0; i--) {
const calendarChannelCalendarEventAssociations = const calendarChannelCalendarEventAssociations =
calendarChannelCalendarEventsAssociations.filter( calendarChannelCalendarEventsAssociations.filter(
@ -59,18 +66,26 @@ export class ApplyCalendarEventsVisibilityRestrictionsService {
continue; continue;
} }
const connectedAccounts = await connectedAccountRepository.find({ if (isDefined(userId)) {
select: ['id'], const workspaceMember = await workspaceMemberRepository.findOneByOrFail(
where: { {
calendarChannels: { userId: userId,
id: In(calendarChannels.map((channel) => channel.id)),
}, },
accountOwnerId: workspaceMemberId, );
},
});
if (connectedAccounts.length > 0) { const connectedAccounts = await connectedAccountRepository.find({
continue; select: ['id'],
where: {
calendarChannels: {
id: In(calendarChannels.map((channel) => channel.id)),
},
accountOwnerId: workspaceMember.id,
},
});
if (connectedAccounts.length > 0) {
continue;
}
} }
if ( if (

View File

@ -38,6 +38,10 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
find: jest.fn(), find: jest.fn(),
}; };
const mockWorkspaceMemberRepository = {
findOneByOrFail: jest.fn(),
};
const mockTwentyORMManager = { const mockTwentyORMManager = {
getRepository: jest.fn().mockImplementation((name) => { getRepository: jest.fn().mockImplementation((name) => {
if (name === 'messageChannelMessageAssociation') { if (name === 'messageChannelMessageAssociation') {
@ -46,6 +50,9 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
if (name === 'connectedAccount') { if (name === 'connectedAccount') {
return mockConnectedAccountRepository; return mockConnectedAccountRepository;
} }
if (name === 'workspaceMember') {
return mockWorkspaceMemberRepository;
}
}), }),
}; };
@ -83,8 +90,8 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
]); ]);
const result = await service.applyMessagesVisibilityRestrictions( const result = await service.applyMessagesVisibilityRestrictions(
'workspace-member-id',
messages, messages,
'user-id',
); );
expect(result).toEqual(messages); expect(result).toEqual(messages);
@ -114,9 +121,13 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
mockConnectedAccountRepository.find.mockResolvedValue([]); mockConnectedAccountRepository.find.mockResolvedValue([]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-id',
});
const result = await service.applyMessagesVisibilityRestrictions( const result = await service.applyMessagesVisibilityRestrictions(
'workspace-member-id',
messages, messages,
'user-id',
); );
expect(result).toEqual([ expect(result).toEqual([
@ -144,9 +155,13 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
mockConnectedAccountRepository.find.mockResolvedValue([]); mockConnectedAccountRepository.find.mockResolvedValue([]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-id',
});
const result = await service.applyMessagesVisibilityRestrictions( const result = await service.applyMessagesVisibilityRestrictions(
'workspace-member-id',
messages, messages,
'user-id',
); );
expect(result).toEqual([ expect(result).toEqual([
@ -173,11 +188,15 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
}, },
]); ]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-account-owner-id',
});
mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]); mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]);
const result = await service.applyMessagesVisibilityRestrictions( const result = await service.applyMessagesVisibilityRestrictions(
'workspace-member-id',
messages, messages,
'user-id',
); );
expect(result).toEqual(messages); expect(result).toEqual(messages);
@ -203,11 +222,15 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
}, },
]); ]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-not-account-owner-id',
});
mockConnectedAccountRepository.find.mockResolvedValue([]); mockConnectedAccountRepository.find.mockResolvedValue([]);
const result = await service.applyMessagesVisibilityRestrictions( const result = await service.applyMessagesVisibilityRestrictions(
'workspace-member-id',
messages, messages,
'user-id',
); );
expect(result).toEqual([]); expect(result).toEqual([]);
@ -244,13 +267,75 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
}, },
]); ]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-id',
});
mockConnectedAccountRepository.find mockConnectedAccountRepository.find
.mockResolvedValueOnce([]) // request for message 3 .mockResolvedValueOnce([]) // request for message 3
.mockResolvedValueOnce([]); // request for message 2 .mockResolvedValueOnce([]); // request for message 2
const result = await service.applyMessagesVisibilityRestrictions( const result = await service.applyMessagesVisibilityRestrictions(
'workspace-member-id',
messages, messages,
'user-id',
);
expect(result).toEqual([
messages[0],
{
...messages[1],
text: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED,
},
{
...messages[2],
subject: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED,
text: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED,
},
]);
});
it('should return all messages with the right visibility when userId is undefined (api key request)', async () => {
const messages = [
createMockMessage('1', 'Subject 1', 'Message 1'),
createMockMessage('2', 'Subject 2', 'Message 2'),
createMockMessage('3', 'Subject 3', 'Message 3'),
];
mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([
{
messageId: '1',
messageChannel: {
id: '1',
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
},
},
{
messageId: '2',
messageChannel: {
id: '2',
visibility: MessageChannelVisibility.SUBJECT,
},
},
{
messageId: '3',
messageChannel: {
id: '3',
visibility: MessageChannelVisibility.METADATA,
},
},
]);
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
id: 'workspace-member-id',
});
mockConnectedAccountRepository.find
.mockResolvedValueOnce([]) // request for message 3
.mockResolvedValueOnce([]); // request for message 2
const result = await service.applyMessagesVisibilityRestrictions(
messages,
undefined,
); );
expect(result).toEqual([ expect(result).toEqual([

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import groupBy from 'lodash.groupby'; import groupBy from 'lodash.groupby';
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
@ -10,14 +11,15 @@ import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/s
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Injectable() @Injectable()
export class ApplyMessagesVisibilityRestrictionsService { export class ApplyMessagesVisibilityRestrictionsService {
constructor(private readonly twentyORMManager: TwentyORMManager) {} constructor(private readonly twentyORMManager: TwentyORMManager) {}
public async applyMessagesVisibilityRestrictions( public async applyMessagesVisibilityRestrictions(
workspaceMemberId: string,
messages: MessageWorkspaceEntity[], messages: MessageWorkspaceEntity[],
userId?: string, // undefined when request is made with api key
) { ) {
const messageChannelMessageAssociationRepository = const messageChannelMessageAssociationRepository =
await this.twentyORMManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>( await this.twentyORMManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
@ -37,6 +39,11 @@ export class ApplyMessagesVisibilityRestrictionsService {
'connectedAccount', 'connectedAccount',
); );
const workspaceMemberRepository =
await this.twentyORMManager.getRepository<WorkspaceMemberWorkspaceEntity>(
'workspaceMember',
);
for (let i = messages.length - 1; i >= 0; i--) { for (let i = messages.length - 1; i >= 0; i--) {
const messageChannelMessageAssociations = const messageChannelMessageAssociations =
messageChannelMessagesAssociations.filter( messageChannelMessagesAssociations.filter(
@ -66,19 +73,28 @@ export class ApplyMessagesVisibilityRestrictionsService {
continue; continue;
} }
const connectedAccounts = await connectedAccountRepository.find({ if (isDefined(userId)) {
select: ['id'], const workspaceMember = await workspaceMemberRepository.findOneByOrFail(
where: { {
messageChannels: { userId,
id: In(messageChannels.map((channel) => channel.id)),
}, },
accountOwnerId: workspaceMemberId, );
},
});
if (connectedAccounts.length > 0) { const connectedAccounts = await connectedAccountRepository.find({
continue; select: ['id'],
where: {
messageChannels: {
id: In(messageChannels.map((channel) => channel.id)),
},
accountOwnerId: workspaceMember.id,
},
});
if (connectedAccounts.length > 0) {
continue;
}
} }
if (messageChannelsGroupByVisibility[MessageChannelVisibility.SUBJECT]) { if (messageChannelsGroupByVisibility[MessageChannelVisibility.SUBJECT]) {
messages[i].text = FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED; messages[i].text = FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED;
continue; continue;

View File

@ -1,9 +1,11 @@
import { isDefined } from 'twenty-shared/utils';
import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { ForbiddenError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service'; import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service';
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
@ -23,13 +25,15 @@ export class MessageFindManyPostQueryHook
_objectName: string, _objectName: string,
payload: MessageWorkspaceEntity[], payload: MessageWorkspaceEntity[],
): Promise<void> { ): Promise<void> {
if (!authContext.workspaceMemberId) { const { user, apiKey } = authContext;
throw new UserInputError('Workspace member id is required');
if (!isDefined(user) && !isDefined(apiKey)) {
throw new ForbiddenError('User is required');
} }
await this.applyMessagesVisibilityRestrictionsService.applyMessagesVisibilityRestrictions( await this.applyMessagesVisibilityRestrictionsService.applyMessagesVisibilityRestrictions(
authContext.workspaceMemberId,
payload, payload,
user?.id,
); );
} }
} }

View File

@ -1,9 +1,11 @@
import { isDefined } from 'twenty-shared/utils';
import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { ForbiddenError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service'; import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service';
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
@ -23,13 +25,15 @@ export class MessageFindOnePostQueryHook
_objectName: string, _objectName: string,
payload: MessageWorkspaceEntity[], payload: MessageWorkspaceEntity[],
): Promise<void> { ): Promise<void> {
if (!authContext.workspaceMemberId) { const { user, apiKey } = authContext;
throw new UserInputError('Workspace member id is required');
if (!isDefined(user) && !isDefined(apiKey)) {
throw new ForbiddenError('User is required');
} }
await this.applyMessagesVisibilityRestrictionsService.applyMessagesVisibilityRestrictions( await this.applyMessagesVisibilityRestrictionsService.applyMessagesVisibilityRestrictions(
authContext.workspaceMemberId,
payload, payload,
user?.id,
); );
} }
} }