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:
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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([
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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([
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user