Permission checks on twentyORM global manager (#11477)
In this PR we are handling permissions when using twentyORMGlobalManager, and handling permissions for rest api and api key
This commit is contained in:
2
packages/twenty-server/@types/jest.d.ts
vendored
2
packages/twenty-server/@types/jest.d.ts
vendored
@ -9,6 +9,7 @@ declare module '@jest/types' {
|
|||||||
INVALID_ACCESS_TOKEN: string;
|
INVALID_ACCESS_TOKEN: string;
|
||||||
MEMBER_ACCESS_TOKEN: string;
|
MEMBER_ACCESS_TOKEN: string;
|
||||||
GUEST_ACCESS_TOKEN: string;
|
GUEST_ACCESS_TOKEN: string;
|
||||||
|
API_KEY_ACCESS_TOKEN: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,6 +21,7 @@ declare global {
|
|||||||
const INVALID_ACCESS_TOKEN: string;
|
const INVALID_ACCESS_TOKEN: string;
|
||||||
const MEMBER_ACCESS_TOKEN: string;
|
const MEMBER_ACCESS_TOKEN: string;
|
||||||
const GUEST_ACCESS_TOKEN: string;
|
const GUEST_ACCESS_TOKEN: string;
|
||||||
|
const API_KEY_ACCESS_TOKEN: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
@ -76,6 +76,8 @@ const jestConfig: JestConfigWithTsJest = {
|
|||||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC0zOTU3LTQ5MDgtOWMzNi0yOTI5YTIzZjgzNTciLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNzdkNS00Y2I2LWI2MGEtZjRhODM1YTg1ZDYxIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMzk1Ny00OTA4LTljMzYtMjkyOWEyM2Y4MzUzIiwiaWF0IjoxNzM5NDU5NTcwLCJleHAiOjMzMjk3MDU5NTcwfQ.Er7EEU4IP4YlGN79jCLR_6sUBqBfKx2M3G_qGiDpPRo',
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC0zOTU3LTQ5MDgtOWMzNi0yOTI5YTIzZjgzNTciLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNzdkNS00Y2I2LWI2MGEtZjRhODM1YTg1ZDYxIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMzk1Ny00OTA4LTljMzYtMjkyOWEyM2Y4MzUzIiwiaWF0IjoxNzM5NDU5NTcwLCJleHAiOjMzMjk3MDU5NTcwfQ.Er7EEU4IP4YlGN79jCLR_6sUBqBfKx2M3G_qGiDpPRo',
|
||||||
GUEST_ACCESS_TOKEN:
|
GUEST_ACCESS_TOKEN:
|
||||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC03MTY5LTQyY2YtYmM0Ny0xY2ZlZjE1MjY0YjgiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMTU1My00NWM2LWEwMjgtNWE5MDY0Y2NlMDdmIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtNzE2OS00MmNmLWJjNDctMWNmZWYxNTI2NGIxIiwiaWF0IjoxNzM5ODg4NDcwLCJleHAiOjMzMjk3NDg4NDcwfQ.0NEu-AWGv3l77rs-56Z5Gt0UTU7HDl6qUTHUcMWNrCc',
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC03MTY5LTQyY2YtYmM0Ny0xY2ZlZjE1MjY0YjgiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMTU1My00NWM2LWEwMjgtNWE5MDY0Y2NlMDdmIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtNzE2OS00MmNmLWJjNDctMWNmZWYxNTI2NGIxIiwiaWF0IjoxNzM5ODg4NDcwLCJleHAiOjMzMjk3NDg4NDcwfQ.0NEu-AWGv3l77rs-56Z5Gt0UTU7HDl6qUTHUcMWNrCc',
|
||||||
|
API_KEY_ACCESS_TOKEN:
|
||||||
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC0xYzI1LTRkMDItYmYyNS02YWVjY2Y3ZWE0MTkiLCJ0eXBlIjoiQVBJX0tFWSIsIndvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWMyNS00ZDAyLWJmMjUtNmFlY2NmN2VhNDE5IiwiaWF0IjoxNzQ0OTgzNzUwLCJleHAiOjQ4OTg1ODM2OTMsImp0aSI6IjIwMjAyMDIwLWY0MDEtNGQ4YS1hNzMxLTY0ZDAwN2MyN2JhZCJ9.4xkkwz_uu2xzs_V8hJSaM15fGziT5zS3vq2lM48OHr0',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
getDevSeedCompanyCustomFields,
|
getDevSeedCompanyCustomFields,
|
||||||
getDevSeedPeopleCustomFields,
|
getDevSeedPeopleCustomFields,
|
||||||
} from 'src/database/typeorm-seeds/metadata/fieldsMetadata';
|
} from 'src/database/typeorm-seeds/metadata/fieldsMetadata';
|
||||||
|
import { seedApiKey } from 'src/database/typeorm-seeds/workspace/api-key';
|
||||||
import { seedCalendarChannels } from 'src/database/typeorm-seeds/workspace/calendar-channel';
|
import { seedCalendarChannels } from 'src/database/typeorm-seeds/workspace/calendar-channel';
|
||||||
import { seedCalendarChannelEventAssociations } from 'src/database/typeorm-seeds/workspace/calendar-channel-event-association';
|
import { seedCalendarChannelEventAssociations } from 'src/database/typeorm-seeds/workspace/calendar-channel-event-association';
|
||||||
import { seedCalendarEventParticipants } from 'src/database/typeorm-seeds/workspace/calendar-event-participants';
|
import { seedCalendarEventParticipants } from 'src/database/typeorm-seeds/workspace/calendar-event-participants';
|
||||||
@ -184,6 +185,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (dataSourceMetadata.workspaceId === SEED_APPLE_WORKSPACE_ID) {
|
if (dataSourceMetadata.workspaceId === SEED_APPLE_WORKSPACE_ID) {
|
||||||
|
await seedApiKey(entityManager, dataSourceMetadata.schema);
|
||||||
await seedMessageThread(entityManager, dataSourceMetadata.schema);
|
await seedMessageThread(entityManager, dataSourceMetadata.schema);
|
||||||
await seedConnectedAccount(entityManager, dataSourceMetadata.schema);
|
await seedConnectedAccount(entityManager, dataSourceMetadata.schema);
|
||||||
|
|
||||||
|
|||||||
@ -88,7 +88,6 @@ export class AddTasksAssignedToMeViewCommand extends ActiveOrSuspendedWorkspaces
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'view',
|
'view',
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const existingView = await viewRepository.findOne({
|
const existingView = await viewRepository.findOne({
|
||||||
@ -126,7 +125,6 @@ export class AddTasksAssignedToMeViewCommand extends ActiveOrSuspendedWorkspaces
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFieldWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFieldWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'viewField',
|
'viewField',
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const viewFields = viewDefinition.fields.map((field) => ({
|
const viewFields = viewDefinition.fields.map((field) => ({
|
||||||
@ -145,7 +143,6 @@ export class AddTasksAssignedToMeViewCommand extends ActiveOrSuspendedWorkspaces
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFilterWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFilterWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'viewFilter',
|
'viewFilter',
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const viewFilters = viewDefinition.filters.map((filter) => ({
|
const viewFilters = viewDefinition.filters.map((filter) => ({
|
||||||
@ -202,7 +199,6 @@ export class AddTasksAssignedToMeViewCommand extends ActiveOrSuspendedWorkspaces
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewGroupWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewGroupWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'viewGroup',
|
'viewGroup',
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await viewGroupRepository.insert(viewGroups);
|
await viewGroupRepository.insert(viewGroups);
|
||||||
|
|||||||
@ -81,7 +81,9 @@ export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends Acti
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'view',
|
'view',
|
||||||
failOnMetadataCacheMiss,
|
{
|
||||||
|
shouldFailIfMetadataNotFound: failOnMetadataCacheMiss,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await viewRepository.update(
|
await viewRepository.update(
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { EntityManager } from 'typeorm';
|
||||||
|
|
||||||
|
const tableName = 'apiKey';
|
||||||
|
|
||||||
|
const API_KEY_ID = '20202020-f401-4d8a-a731-64d007c27bad';
|
||||||
|
|
||||||
|
export const seedApiKey = async (
|
||||||
|
entityManager: EntityManager,
|
||||||
|
schemaName: string,
|
||||||
|
) => {
|
||||||
|
await entityManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(`${schemaName}.${tableName}`, ['id', 'name', 'expiresAt'])
|
||||||
|
.orIgnore()
|
||||||
|
.values([
|
||||||
|
{
|
||||||
|
id: API_KEY_ID,
|
||||||
|
name: 'My api key',
|
||||||
|
expiresAt: new Date(
|
||||||
|
new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 100, // In 100 years
|
||||||
|
),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.execute();
|
||||||
|
};
|
||||||
@ -42,6 +42,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
@ -52,6 +53,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
limit: number;
|
limit: number;
|
||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
dataSource: WorkspaceDataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const processRelationTasks = Object.entries(relations).map(
|
const processRelationTasks = Object.entries(relations).map(
|
||||||
@ -67,6 +69,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
limit,
|
limit,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -85,6 +88,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
limit,
|
limit,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
@ -97,6 +101,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
limit: number;
|
limit: number;
|
||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
dataSource: WorkspaceDataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const sourceFieldMetadata =
|
const sourceFieldMetadata =
|
||||||
@ -129,6 +134,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
|
|
||||||
const targetObjectRepository = dataSource.getRepository(
|
const targetObjectRepository = dataSource.getRepository(
|
||||||
targetObjectMetadata.nameSingular,
|
targetObjectMetadata.nameSingular,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -199,6 +205,8 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
limit,
|
limit,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
@ -58,6 +59,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
dataSource: WorkspaceDataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
isNewRelationEnabled: boolean;
|
isNewRelationEnabled: boolean;
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (isNewRelationEnabled) {
|
if (isNewRelationEnabled) {
|
||||||
@ -71,6 +73,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
limit,
|
limit,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -89,6 +92,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -108,6 +112,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
@ -118,9 +123,10 @@ export class ProcessNestedRelationsHelper {
|
|||||||
nestedRelations: any;
|
nestedRelations: any;
|
||||||
aggregate: Record<string, AggregationField>;
|
aggregate: Record<string, AggregationField>;
|
||||||
limit: number;
|
limit: number;
|
||||||
authContext: any;
|
authContext: AuthContext;
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
isNewRelationEnabled: boolean;
|
isNewRelationEnabled: boolean;
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const relationFieldMetadata =
|
const relationFieldMetadata =
|
||||||
@ -148,6 +154,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -164,6 +171,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
@ -177,6 +185,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
dataSource: WorkspaceDataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
isNewRelationEnabled: boolean;
|
isNewRelationEnabled: boolean;
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { inverseRelationName, referenceObjectMetadata } =
|
const { inverseRelationName, referenceObjectMetadata } =
|
||||||
@ -188,6 +197,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
|
|
||||||
const relationRepository = dataSource.getRepository(
|
const relationRepository = dataSource.getRepository(
|
||||||
referenceObjectMetadata.nameSingular,
|
referenceObjectMetadata.nameSingular,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -248,6 +258,8 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,6 +276,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
@ -277,6 +290,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext: any;
|
authContext: any;
|
||||||
dataSource: WorkspaceDataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
isNewRelationEnabled: boolean;
|
isNewRelationEnabled: boolean;
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { referenceObjectMetadata } = this.getRelationMetadata({
|
const { referenceObjectMetadata } = this.getRelationMetadata({
|
||||||
@ -287,6 +301,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
|
|
||||||
const relationRepository = dataSource.getRepository(
|
const relationRepository = dataSource.getRepository(
|
||||||
referenceObjectMetadata.nameSingular,
|
referenceObjectMetadata.nameSingular,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -346,6 +361,8 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,7 @@ export type GraphqlQueryResolverExecutionArgs<Input extends ResolverArgs> = {
|
|||||||
repository: WorkspaceRepository<ObjectLiteral>;
|
repository: WorkspaceRepository<ObjectLiteral>;
|
||||||
graphqlQueryParser: GraphqlQueryParser;
|
graphqlQueryParser: GraphqlQueryParser;
|
||||||
graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult;
|
graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult;
|
||||||
|
isExecutedByApiKey: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -123,8 +124,12 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
workspaceId: authContext.workspace.id,
|
workspaceId: authContext.workspace.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const executedByApiKey = isDefined(authContext.apiKey);
|
||||||
|
const shouldBypassPermissionChecks = executedByApiKey;
|
||||||
|
|
||||||
const repository = dataSource.getRepository(
|
const repository = dataSource.getRepository(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -150,6 +155,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
repository,
|
repository,
|
||||||
graphqlQueryParser,
|
graphqlQueryParser,
|
||||||
graphqlQuerySelectedFieldsResult,
|
graphqlQuerySelectedFieldsResult,
|
||||||
|
isExecutedByApiKey: executedByApiKey,
|
||||||
roleId,
|
roleId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -53,12 +53,15 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const shouldBypassPermissionChecks = executionArgs.isExecutedByApiKey;
|
||||||
|
|
||||||
await this.processNestedRelationsIfNeeded(
|
await this.processNestedRelationsIfNeeded(
|
||||||
executionArgs,
|
executionArgs,
|
||||||
upsertedRecords,
|
upsertedRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
featureFlagsMap,
|
featureFlagsMap,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -329,6 +332,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
featureFlagsMap: Record<FeatureFlagKey, boolean>,
|
featureFlagsMap: Record<FeatureFlagKey, boolean>,
|
||||||
|
shouldBypassPermissionChecks: boolean,
|
||||||
roleId?: string,
|
roleId?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (!executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
@ -346,6 +350,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,7 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -160,6 +160,7 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -83,6 +83,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,7 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -113,6 +113,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -107,6 +107,7 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
roleId,
|
roleId,
|
||||||
|
shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,17 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
|
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
|
|
||||||
|
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||||
import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
|
import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
|
||||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
|
||||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
|
||||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
|
||||||
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 { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||||
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiCoreServiceV2 {
|
export class RestApiCoreServiceV2 {
|
||||||
@ -19,6 +20,7 @@ export class RestApiCoreServiceV2 {
|
|||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly recordInputTransformerService: RecordInputTransformerService,
|
private readonly recordInputTransformerService: RecordInputTransformerService,
|
||||||
protected readonly apiEventEmitterService: ApiEventEmitterService,
|
protected readonly apiEventEmitterService: ApiEventEmitterService,
|
||||||
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async delete(request: Request) {
|
async delete(request: Request) {
|
||||||
@ -137,7 +139,7 @@ export class RestApiCoreServiceV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getRepositoryAndMetadataOrFail(request: Request) {
|
private async getRepositoryAndMetadataOrFail(request: Request) {
|
||||||
const { workspace } = request;
|
const { workspace, apiKey, userWorkspaceId } = request;
|
||||||
const { object: parsedObject } = parseCorePath(request);
|
const { object: parsedObject } = parseCorePath(request);
|
||||||
|
|
||||||
const objectMetadata = await this.coreQueryBuilderFactory.getObjectMetadata(
|
const objectMetadata = await this.coreQueryBuilderFactory.getObjectMetadata(
|
||||||
@ -153,13 +155,25 @@ export class RestApiCoreServiceV2 {
|
|||||||
throw new BadRequestException('Workspace not found');
|
throw new BadRequestException('Workspace not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dataSource =
|
||||||
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspace.id);
|
||||||
|
|
||||||
const objectMetadataNameSingular =
|
const objectMetadataNameSingular =
|
||||||
objectMetadata.objectMetadataMapItem.nameSingular;
|
objectMetadata.objectMetadataMapItem.nameSingular;
|
||||||
const repository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ObjectRecord>(
|
const shouldBypassPermissionChecks = !!apiKey;
|
||||||
workspace.id,
|
|
||||||
objectMetadataNameSingular,
|
const roleId =
|
||||||
);
|
await this.workspacePermissionsCacheService.getRoleIdFromUserWorkspaceId({
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
userWorkspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const repository = dataSource.getRepository<ObjectRecord>(
|
||||||
|
objectMetadataNameSingular,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
roleId,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
objectMetadataNameSingular,
|
objectMetadataNameSingular,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { HttpModule } from '@nestjs/axios';
|
import { HttpModule } from '@nestjs/axios';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||||
import { RestApiCoreBatchController } from 'src/engine/api/rest/core/controllers/rest-api-core-batch.controller';
|
import { RestApiCoreBatchController } from 'src/engine/api/rest/core/controllers/rest-api-core-batch.controller';
|
||||||
import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller';
|
import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller';
|
||||||
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
||||||
@ -15,9 +16,9 @@ import { RestApiMetadataService } from 'src/engine/api/rest/metadata/rest-api-me
|
|||||||
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||||
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
||||||
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -28,6 +29,7 @@ import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-run
|
|||||||
HttpModule,
|
HttpModule,
|
||||||
TwentyORMModule,
|
TwentyORMModule,
|
||||||
RecordTransformerModule,
|
RecordTransformerModule,
|
||||||
|
WorkspacePermissionsCacheModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
RestApiMetadataController,
|
RestApiMetadataController,
|
||||||
|
|||||||
@ -72,7 +72,6 @@ export class AccessTokenService {
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'workspaceMember',
|
'workspaceMember',
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { Repository } from 'typeorm';
|
|||||||
|
|
||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
|
import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants';
|
||||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
@ -12,6 +13,7 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
|
|||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
@ -19,8 +21,6 @@ import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role
|
|||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
|
||||||
import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants';
|
|
||||||
|
|
||||||
describe('UserWorkspaceService', () => {
|
describe('UserWorkspaceService', () => {
|
||||||
let service: UserWorkspaceService;
|
let service: UserWorkspaceService;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
|||||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||||
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import {
|
import {
|
||||||
@ -32,7 +33,6 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.
|
|||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
import { assert } from 'src/utils/assert';
|
import { assert } from 'src/utils/assert';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
|
||||||
|
|
||||||
export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-s
|
|||||||
import { SearchVectorModule } from 'src/engine/metadata-modules/search-vector/search-vector.module';
|
import { SearchVectorModule } from 'src/engine/metadata-modules/search-vector/search-vector.module';
|
||||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
||||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||||
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||||
@ -59,6 +60,7 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
|
|||||||
IndexMetadataModule,
|
IndexMetadataModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
|
WorkspacePermissionsCacheModule,
|
||||||
],
|
],
|
||||||
services: [
|
services: [
|
||||||
ObjectMetadataService,
|
ObjectMetadataService,
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-
|
|||||||
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
|
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
|
||||||
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||||
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||||
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||||
@ -68,6 +69,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
private readonly objectMetadataMigrationService: ObjectMetadataMigrationService,
|
private readonly objectMetadataMigrationService: ObjectMetadataMigrationService,
|
||||||
private readonly objectMetadataRelatedRecordsService: ObjectMetadataRelatedRecordsService,
|
private readonly objectMetadataRelatedRecordsService: ObjectMetadataRelatedRecordsService,
|
||||||
private readonly indexMetadataService: IndexMetadataService,
|
private readonly indexMetadataService: IndexMetadataService,
|
||||||
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
private readonly featureFlagService: FeatureFlagService,
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
) {
|
) {
|
||||||
super(objectMetadataRepository);
|
super(objectMetadataRepository);
|
||||||
@ -236,6 +238,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
objectMetadataInput.workspaceId,
|
objectMetadataInput.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
||||||
|
workspaceId: objectMetadataInput.workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
return createdObjectMetadata;
|
return createdObjectMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,6 +447,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
return objectMetadata;
|
return objectMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
ObjectRecordsPermissions,
|
ObjectRecordsPermissions,
|
||||||
ObjectRecordsPermissionsByRoleId,
|
ObjectRecordsPermissionsByRoleId,
|
||||||
} from 'twenty-shared/types';
|
} from 'twenty-shared/types';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { In, Repository } from 'typeorm';
|
import { In, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
@ -51,19 +52,15 @@ export class WorkspacePermissionsCacheService {
|
|||||||
ignoreLock?: boolean;
|
ignoreLock?: boolean;
|
||||||
roleIds?: string[];
|
roleIds?: string[];
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const isPermissionsV2Enabled =
|
if (!ignoreLock) {
|
||||||
await this.featureFlagService.isFeatureEnabled(
|
const isAlreadyCaching =
|
||||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
await this.workspacePermissionsCacheStorageService.getRolesPermissionsOngoingCachingLock(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isAlreadyCaching =
|
if (isAlreadyCaching) {
|
||||||
await this.workspacePermissionsCacheStorageService.getRolesPermissionsOngoingCachingLock(
|
return;
|
||||||
workspaceId,
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (!ignoreLock && isAlreadyCaching) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.workspacePermissionsCacheStorageService.addRolesPermissionsOngoingCachingLock(
|
await this.workspacePermissionsCacheStorageService.addRolesPermissionsOngoingCachingLock(
|
||||||
@ -80,6 +77,12 @@ export class WorkspacePermissionsCacheService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPermissionsV2Enabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
const recomputedRolesPermissions =
|
const recomputedRolesPermissions =
|
||||||
await this.getObjectRecordPermissionsForRoles({
|
await this.getObjectRecordPermissionsForRoles({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -109,13 +112,15 @@ export class WorkspacePermissionsCacheService {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
ignoreLock?: boolean;
|
ignoreLock?: boolean;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const isAlreadyCaching =
|
if (!ignoreLock) {
|
||||||
await this.workspacePermissionsCacheStorageService.getUserWorkspaceRoleMapOngoingCachingLock(
|
const isAlreadyCaching =
|
||||||
workspaceId,
|
await this.workspacePermissionsCacheStorageService.getUserWorkspaceRoleMapOngoingCachingLock(
|
||||||
);
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
if (!ignoreLock && isAlreadyCaching) {
|
if (isAlreadyCaching) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.workspacePermissionsCacheStorageService.addUserWorkspaceRoleMapOngoingCachingLock(
|
await this.workspacePermissionsCacheStorageService.addUserWorkspaceRoleMapOngoingCachingLock(
|
||||||
@ -183,6 +188,24 @@ export class WorkspacePermissionsCacheService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRoleIdFromUserWorkspaceId({
|
||||||
|
workspaceId,
|
||||||
|
userWorkspaceId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
userWorkspaceId?: string;
|
||||||
|
}): Promise<string | undefined> {
|
||||||
|
if (!isDefined(userWorkspaceId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userWorkspaceRoleMap = await this.getUserWorkspaceRoleMapFromCache({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return userWorkspaceRoleMap[userWorkspaceId];
|
||||||
|
}
|
||||||
|
|
||||||
private async getObjectRecordPermissionsForRoles({
|
private async getObjectRecordPermissionsForRoles({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
isPermissionsV2Enabled,
|
isPermissionsV2Enabled,
|
||||||
|
|||||||
@ -41,10 +41,19 @@ export class WorkspaceDataSource extends DataSource {
|
|||||||
|
|
||||||
override getRepository<Entity extends ObjectLiteral>(
|
override getRepository<Entity extends ObjectLiteral>(
|
||||||
target: EntityTarget<Entity>,
|
target: EntityTarget<Entity>,
|
||||||
|
shouldBypassPermissionChecks = false,
|
||||||
roleId?: string,
|
roleId?: string,
|
||||||
): WorkspaceRepository<Entity> {
|
): WorkspaceRepository<Entity> {
|
||||||
|
if (shouldBypassPermissionChecks === true) {
|
||||||
|
return this.manager.getRepository(target, shouldBypassPermissionChecks);
|
||||||
|
}
|
||||||
|
|
||||||
if (roleId) {
|
if (roleId) {
|
||||||
return this.manager.getRepository(target, roleId);
|
return this.manager.getRepository(
|
||||||
|
target,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
roleId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.manager.getRepository(target);
|
return this.manager.getRepository(target);
|
||||||
@ -64,11 +73,11 @@ export class WorkspaceDataSource extends DataSource {
|
|||||||
this.permissionsPerRoleId = permissionsPerRoleId;
|
this.permissionsPerRoleId = permissionsPerRoleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFeatureFlagsMap(featureFlagMap: FeatureFlagMap) {
|
setFeatureFlagMap(featureFlagMap: FeatureFlagMap) {
|
||||||
this.featureFlagMap = featureFlagMap;
|
this.featureFlagMap = featureFlagMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFeatureFlagsMapVersion(featureFlagMapVersion: string) {
|
setFeatureFlagMapVersion(featureFlagMapVersion: string) {
|
||||||
this.featureFlagMapVersion = featureFlagMapVersion;
|
this.featureFlagMapVersion = featureFlagMapVersion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,10 +28,17 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
|
|
||||||
override getRepository<Entity extends ObjectLiteral>(
|
override getRepository<Entity extends ObjectLiteral>(
|
||||||
target: EntityTarget<Entity>,
|
target: EntityTarget<Entity>,
|
||||||
|
shouldBypassPermissionChecks = false,
|
||||||
roleId?: string,
|
roleId?: string,
|
||||||
): WorkspaceRepository<Entity> {
|
): WorkspaceRepository<Entity> {
|
||||||
const dataSource = this.connection as WorkspaceDataSource;
|
const dataSource = this.connection as WorkspaceDataSource;
|
||||||
const repositoryKey = `${dataSource.getMetadata(target).name}_${roleId ?? 'default'}${dataSource.rolesPermissionsVersion ? `_${dataSource.rolesPermissionsVersion}` : ''}${dataSource.featureFlagMapVersion ? `_${dataSource.featureFlagMapVersion}` : ''}`;
|
|
||||||
|
const repositoryKey = this.getRepositoryKey({
|
||||||
|
target,
|
||||||
|
dataSource,
|
||||||
|
roleId,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
});
|
||||||
const repoFromMap = this.repositories.get(repositoryKey);
|
const repoFromMap = this.repositories.get(repositoryKey);
|
||||||
|
|
||||||
if (repoFromMap) {
|
if (repoFromMap) {
|
||||||
@ -53,10 +60,36 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
dataSource.featureFlagMap,
|
dataSource.featureFlagMap,
|
||||||
this.queryRunner,
|
this.queryRunner,
|
||||||
objectPermissions,
|
objectPermissions,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.repositories.set(repositoryKey, newRepository);
|
this.repositories.set(repositoryKey, newRepository);
|
||||||
|
|
||||||
return newRepository;
|
return newRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getRepositoryKey({
|
||||||
|
target,
|
||||||
|
dataSource,
|
||||||
|
roleId,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
}: {
|
||||||
|
target: EntityTarget<any>;
|
||||||
|
dataSource: WorkspaceDataSource;
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
|
roleId?: string;
|
||||||
|
}) {
|
||||||
|
const repositoryPrefix = dataSource.getMetadata(target).name;
|
||||||
|
const roleIdSuffix = roleId ? `_${roleId}` : '';
|
||||||
|
const rolesPermissionsVersionSuffix = dataSource.rolesPermissionsVersion
|
||||||
|
? `_${dataSource.rolesPermissionsVersion}`
|
||||||
|
: '';
|
||||||
|
const featureFlagMapVersionSuffix = dataSource.featureFlagMapVersion
|
||||||
|
? `_${dataSource.featureFlagMapVersion}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return shouldBypassPermissionChecks
|
||||||
|
? `${repositoryPrefix}_bypass${featureFlagMapVersionSuffix}`
|
||||||
|
: `${repositoryPrefix}${roleIdSuffix}${rolesPermissionsVersionSuffix}${featureFlagMapVersionSuffix}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export class ScopedWorkspaceContextFactory {
|
|||||||
workspaceId: string | null;
|
workspaceId: string | null;
|
||||||
workspaceMetadataVersion: number | null;
|
workspaceMetadataVersion: number | null;
|
||||||
userWorkspaceId: string | null;
|
userWorkspaceId: string | null;
|
||||||
|
isExecutedByApiKey: boolean;
|
||||||
} {
|
} {
|
||||||
const workspaceId: string | undefined =
|
const workspaceId: string | undefined =
|
||||||
this.request?.['req']?.['workspaceId'] ||
|
this.request?.['req']?.['workspaceId'] ||
|
||||||
@ -24,6 +25,7 @@ export class ScopedWorkspaceContextFactory {
|
|||||||
workspaceId: workspaceId ?? null,
|
workspaceId: workspaceId ?? null,
|
||||||
workspaceMetadataVersion: workspaceMetadataVersion ?? null,
|
workspaceMetadataVersion: workspaceMetadataVersion ?? null,
|
||||||
userWorkspaceId: this.request?.['req']?.['userWorkspaceId'] ?? null,
|
userWorkspaceId: this.request?.['req']?.['userWorkspaceId'] ?? null,
|
||||||
|
isExecutedByApiKey: !!this.request?.['req']?.['apiKey'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -294,9 +294,9 @@ export class WorkspaceDatasourceFactory {
|
|||||||
currentVersion: workspaceDataSource.featureFlagMapVersion,
|
currentVersion: workspaceDataSource.featureFlagMapVersion,
|
||||||
newVersion: cachedFeatureFlagMapVersion,
|
newVersion: cachedFeatureFlagMapVersion,
|
||||||
newData: cachedFeatureFlagMap,
|
newData: cachedFeatureFlagMap,
|
||||||
setData: (data) => workspaceDataSource.setFeatureFlagsMap(data),
|
setData: (data) => workspaceDataSource.setFeatureFlagMap(data),
|
||||||
setVersion: (version) =>
|
setVersion: (version) =>
|
||||||
workspaceDataSource.setFeatureFlagsMapVersion(version),
|
workspaceDataSource.setFeatureFlagMapVersion(version),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
PermissionsExceptionMessage,
|
PermissionsExceptionMessage,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
|
|
||||||
const getTargetEntityAndOperationType = (expressionMap: QueryExpressionMap) => {
|
const getTargetEntityAndOperationType = (expressionMap: QueryExpressionMap) => {
|
||||||
const mainEntity = expressionMap.aliases[0].metadata.name;
|
const mainEntity = expressionMap.aliases[0].metadata.name;
|
||||||
@ -20,10 +21,26 @@ const getTargetEntityAndOperationType = (expressionMap: QueryExpressionMap) => {
|
|||||||
export const validateQueryIsPermittedOrThrow = (
|
export const validateQueryIsPermittedOrThrow = (
|
||||||
expressionMap: QueryExpressionMap,
|
expressionMap: QueryExpressionMap,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
|
shouldBypassPermissionChecks: boolean,
|
||||||
) => {
|
) => {
|
||||||
|
if (shouldBypassPermissionChecks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { mainEntity, operationType } =
|
const { mainEntity, operationType } =
|
||||||
getTargetEntityAndOperationType(expressionMap);
|
getTargetEntityAndOperationType(expressionMap);
|
||||||
|
|
||||||
|
const objectMetadataIdForEntity =
|
||||||
|
objectMetadataMaps.idByNameSingular[mainEntity];
|
||||||
|
|
||||||
|
const objectMetadataIsSystem =
|
||||||
|
objectMetadataMaps.byId[objectMetadataIdForEntity]?.isSystem === true;
|
||||||
|
|
||||||
|
if (objectMetadataIsSystem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const permissionsForEntity = objectRecordsPermissions[mainEntity];
|
const permissionsForEntity = objectRecordsPermissions[mainEntity];
|
||||||
|
|
||||||
switch (operationType) {
|
switch (operationType) {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
||||||
|
|
||||||
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
|
|
||||||
export class WorkspaceQueryBuilder<
|
export class WorkspaceQueryBuilder<
|
||||||
@ -9,9 +11,15 @@ export class WorkspaceQueryBuilder<
|
|||||||
constructor(
|
constructor(
|
||||||
queryBuilder: SelectQueryBuilder<T>,
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
|
internalContext: WorkspaceInternalContext,
|
||||||
|
shouldBypassPermissionChecks: boolean,
|
||||||
) {
|
) {
|
||||||
super(queryBuilder, objectRecordsPermissions);
|
super(
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
queryBuilder,
|
||||||
|
objectRecordsPermissions,
|
||||||
|
internalContext,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override clone(): this {
|
override clone(): this {
|
||||||
@ -20,6 +28,8 @@ export class WorkspaceQueryBuilder<
|
|||||||
return new WorkspaceQueryBuilder(
|
return new WorkspaceQueryBuilder(
|
||||||
clonedQueryBuilder,
|
clonedQueryBuilder,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
|
this.internalContext,
|
||||||
|
this.shouldBypassPermissionChecks,
|
||||||
) as this;
|
) as this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
|||||||
import { ObjectLiteral, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm';
|
import { ObjectLiteral, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm';
|
||||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||||
|
|
||||||
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||||
|
|
||||||
@ -9,12 +11,18 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
T extends ObjectLiteral,
|
T extends ObjectLiteral,
|
||||||
> extends SelectQueryBuilder<T> {
|
> extends SelectQueryBuilder<T> {
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions;
|
objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
|
internalContext: WorkspaceInternalContext;
|
||||||
constructor(
|
constructor(
|
||||||
queryBuilder: SelectQueryBuilder<T>,
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
|
internalContext: WorkspaceInternalContext,
|
||||||
|
shouldBypassPermissionChecks: boolean,
|
||||||
) {
|
) {
|
||||||
super(queryBuilder);
|
super(queryBuilder);
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
|
this.internalContext = internalContext;
|
||||||
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
}
|
}
|
||||||
|
|
||||||
override update(): WorkspaceUpdateQueryBuilder<T>;
|
override update(): WorkspaceUpdateQueryBuilder<T>;
|
||||||
@ -33,6 +41,8 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
return new WorkspaceUpdateQueryBuilder<T>(
|
return new WorkspaceUpdateQueryBuilder<T>(
|
||||||
updateQueryBuilder,
|
updateQueryBuilder,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
|
this.internalContext,
|
||||||
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +50,8 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
validateQueryIsPermittedOrThrow(
|
validateQueryIsPermittedOrThrow(
|
||||||
this.expressionMap,
|
this.expressionMap,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.execute();
|
return super.execute();
|
||||||
@ -49,6 +61,8 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
validateQueryIsPermittedOrThrow(
|
validateQueryIsPermittedOrThrow(
|
||||||
this.expressionMap,
|
this.expressionMap,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.getMany();
|
return super.getMany();
|
||||||
|
|||||||
@ -1,24 +1,34 @@
|
|||||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm';
|
import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm';
|
||||||
|
|
||||||
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||||
|
|
||||||
export class WorkspaceUpdateQueryBuilder<
|
export class WorkspaceUpdateQueryBuilder<
|
||||||
Entity extends ObjectLiteral,
|
Entity extends ObjectLiteral,
|
||||||
> extends UpdateQueryBuilder<Entity> {
|
> extends UpdateQueryBuilder<Entity> {
|
||||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
|
private shouldBypassPermissionChecks: boolean;
|
||||||
|
private internalContext: WorkspaceInternalContext;
|
||||||
constructor(
|
constructor(
|
||||||
queryBuilder: UpdateQueryBuilder<Entity>,
|
queryBuilder: UpdateQueryBuilder<Entity>,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
|
internalContext: WorkspaceInternalContext,
|
||||||
|
shouldBypassPermissionChecks: boolean,
|
||||||
) {
|
) {
|
||||||
super(queryBuilder);
|
super(queryBuilder);
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
|
this.internalContext = internalContext;
|
||||||
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
}
|
}
|
||||||
|
|
||||||
override execute(): Promise<UpdateResult> {
|
override execute(): Promise<UpdateResult> {
|
||||||
validateQueryIsPermittedOrThrow(
|
validateQueryIsPermittedOrThrow(
|
||||||
this.expressionMap,
|
this.expressionMap,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.execute();
|
return super.execute();
|
||||||
|
|||||||
@ -36,9 +36,9 @@ export class WorkspaceRepository<
|
|||||||
T extends ObjectLiteral,
|
T extends ObjectLiteral,
|
||||||
> extends Repository<T> {
|
> extends Repository<T> {
|
||||||
private readonly internalContext: WorkspaceInternalContext;
|
private readonly internalContext: WorkspaceInternalContext;
|
||||||
|
private shouldBypassPermissionChecks: boolean;
|
||||||
private featureFlagMap: FeatureFlagMap;
|
private featureFlagMap: FeatureFlagMap;
|
||||||
private objectRecordsPermissions?: ObjectRecordsPermissions;
|
private objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
target: EntityTarget<T>,
|
target: EntityTarget<T>,
|
||||||
@ -46,11 +46,13 @@ export class WorkspaceRepository<
|
|||||||
featureFlagMap: FeatureFlagMap,
|
featureFlagMap: FeatureFlagMap,
|
||||||
queryRunner?: QueryRunner,
|
queryRunner?: QueryRunner,
|
||||||
objectRecordsPermissions?: ObjectRecordsPermissions,
|
objectRecordsPermissions?: ObjectRecordsPermissions,
|
||||||
|
shouldBypassPermissionChecks = false,
|
||||||
) {
|
) {
|
||||||
super(target, manager, queryRunner);
|
super(target, manager, queryRunner);
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
this.featureFlagMap = featureFlagMap;
|
this.featureFlagMap = featureFlagMap;
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
}
|
}
|
||||||
|
|
||||||
override createQueryBuilder<U extends T>(
|
override createQueryBuilder<U extends T>(
|
||||||
@ -74,6 +76,8 @@ export class WorkspaceRepository<
|
|||||||
return new WorkspaceQueryBuilder(
|
return new WorkspaceQueryBuilder(
|
||||||
queryBuilder,
|
queryBuilder,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
|
this.internalContext,
|
||||||
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,19 +15,31 @@ export class TwentyORMGlobalManager {
|
|||||||
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
workspaceEntity: Type<T>,
|
workspaceEntity: Type<T>,
|
||||||
shouldFailIfMetadataNotFound?: boolean,
|
options?: {
|
||||||
|
shouldBypassPermissionChecks?: boolean;
|
||||||
|
shouldFailIfMetadataNotFound?: boolean;
|
||||||
|
},
|
||||||
): Promise<WorkspaceRepository<T>>;
|
): Promise<WorkspaceRepository<T>>;
|
||||||
|
|
||||||
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
objectMetadataName: string,
|
objectMetadataName: string,
|
||||||
shouldFailIfMetadataNotFound?: boolean,
|
options?: {
|
||||||
|
shouldBypassPermissionChecks?: boolean;
|
||||||
|
shouldFailIfMetadataNotFound?: boolean;
|
||||||
|
},
|
||||||
): Promise<WorkspaceRepository<T>>;
|
): Promise<WorkspaceRepository<T>>;
|
||||||
|
|
||||||
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
workspaceEntityOrobjectMetadataName: Type<T> | string,
|
workspaceEntityOrobjectMetadataName: Type<T> | string,
|
||||||
shouldFailIfMetadataNotFound = true,
|
options: {
|
||||||
|
shouldBypassPermissionChecks?: boolean;
|
||||||
|
shouldFailIfMetadataNotFound?: boolean;
|
||||||
|
} = {
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
shouldFailIfMetadataNotFound: true,
|
||||||
|
},
|
||||||
): Promise<WorkspaceRepository<T>> {
|
): Promise<WorkspaceRepository<T>> {
|
||||||
let objectMetadataName: string;
|
let objectMetadataName: string;
|
||||||
|
|
||||||
@ -42,10 +54,13 @@ export class TwentyORMGlobalManager {
|
|||||||
const workspaceDataSource = await this.workspaceDataSourceFactory.create(
|
const workspaceDataSource = await this.workspaceDataSourceFactory.create(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
null,
|
null,
|
||||||
shouldFailIfMetadataNotFound,
|
options.shouldFailIfMetadataNotFound,
|
||||||
);
|
);
|
||||||
|
|
||||||
const repository = workspaceDataSource.getRepository<T>(objectMetadataName);
|
const repository = workspaceDataSource.getRepository<T>(
|
||||||
|
objectMetadataName,
|
||||||
|
options.shouldBypassPermissionChecks,
|
||||||
|
);
|
||||||
|
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,8 +30,12 @@ export class TwentyORMManager {
|
|||||||
async getRepository<T extends ObjectLiteral>(
|
async getRepository<T extends ObjectLiteral>(
|
||||||
workspaceEntityOrobjectMetadataName: Type<T> | string,
|
workspaceEntityOrobjectMetadataName: Type<T> | string,
|
||||||
): Promise<WorkspaceRepository<T>> {
|
): Promise<WorkspaceRepository<T>> {
|
||||||
const { workspaceId, workspaceMetadataVersion, userWorkspaceId } =
|
const {
|
||||||
this.scopedWorkspaceContextFactory.create();
|
workspaceId,
|
||||||
|
workspaceMetadataVersion,
|
||||||
|
userWorkspaceId,
|
||||||
|
isExecutedByApiKey,
|
||||||
|
} = this.scopedWorkspaceContextFactory.create();
|
||||||
|
|
||||||
let objectMetadataName: string;
|
let objectMetadataName: string;
|
||||||
|
|
||||||
@ -65,7 +69,13 @@ export class TwentyORMManager {
|
|||||||
roleId = userWorkspaceRole?.roleId;
|
roleId = userWorkspaceRole?.roleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return workspaceDataSource.getRepository<T>(objectMetadataName, roleId);
|
const shouldBypassPermissionChecks = !!isExecutedByApiKey;
|
||||||
|
|
||||||
|
return workspaceDataSource.getRepository<T>(
|
||||||
|
objectMetadataName,
|
||||||
|
shouldBypassPermissionChecks,
|
||||||
|
roleId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDatasource() {
|
async getDatasource() {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { Any, Repository } from 'typeorm';
|
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||||
|
import { Any, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { Equal, Repository } from 'typeorm';
|
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||||
|
import { Equal, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
|
||||||
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
|
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
|
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
|
||||||
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
||||||
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';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ConnectedAccountListener {
|
export class ConnectedAccountListener {
|
||||||
|
|||||||
@ -55,6 +55,9 @@ export class CreateCompanyAndContactService {
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
PersonWorkspaceEntity,
|
PersonWorkspaceEntity,
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceMembers =
|
const workspaceMembers =
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import uniqBy from 'lodash.uniqby';
|
import uniqBy from 'lodash.uniqby';
|
||||||
import { DeepPartial, EntityManager, ILike } from 'typeorm';
|
|
||||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
|
||||||
import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants';
|
import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants';
|
||||||
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
|
import { DeepPartial, EntityManager, ILike } from 'typeorm';
|
||||||
|
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
@ -49,6 +49,9 @@ export class CreateCompanyService {
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
CompanyWorkspaceEntity,
|
CompanyWorkspaceEntity,
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Avoid creating duplicate companies
|
// Avoid creating duplicate companies
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
import { DeepPartial, EntityManager } from 'typeorm';
|
import { DeepPartial, EntityManager } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
|
||||||
|
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
@ -82,6 +82,9 @@ export class CreateContactService {
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
PersonWorkspaceEntity,
|
PersonWorkspaceEntity,
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const lastPersonPosition = await this.getLastPersonPosition(
|
const lastPersonPosition = await this.getLastPersonPosition(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { In, Repository } from 'typeorm';
|
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||||
|
import { In, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { Logger } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import snakeCase from 'lodash.snakecase';
|
import snakeCase from 'lodash.snakecase';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import {
|
|||||||
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 { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
@WorkspaceQueryHook(`view.deleteOne`)
|
@WorkspaceQueryHook(`view.deleteOne`)
|
||||||
export class ViewDeleteOnePreQueryHook implements WorkspaceQueryHookInstance {
|
export class ViewDeleteOnePreQueryHook implements WorkspaceQueryHookInstance {
|
||||||
constructor(
|
constructor(
|
||||||
@ -21,7 +20,6 @@ export class ViewDeleteOnePreQueryHook implements WorkspaceQueryHookInstance {
|
|||||||
payload: DeleteOneResolverArgs,
|
payload: DeleteOneResolverArgs,
|
||||||
): Promise<DeleteOneResolverArgs> {
|
): Promise<DeleteOneResolverArgs> {
|
||||||
const targettedViewId = payload.id;
|
const targettedViewId = payload.id;
|
||||||
|
|
||||||
const viewRepository =
|
const viewRepository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||||||
|
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import { z } from 'zod';
|
|
||||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
|
||||||
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
||||||
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
|
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
|
||||||
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
|
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
|
||||||
@ -16,8 +18,6 @@ import {
|
|||||||
WorkflowTriggerJob,
|
WorkflowTriggerJob,
|
||||||
WorkflowTriggerJobData,
|
WorkflowTriggerJobData,
|
||||||
} from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job';
|
} from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job';
|
||||||
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DatabaseEventTriggerListener {
|
export class DatabaseEventTriggerListener {
|
||||||
|
|||||||
@ -2,67 +2,219 @@ import { randomUUID } from 'node:crypto';
|
|||||||
|
|
||||||
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
|
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
|
||||||
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
|
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
|
||||||
|
import { findOneOperationFactory } from 'test/integration/graphql/utils/find-one-operation-factory.util';
|
||||||
|
import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util';
|
||||||
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
|
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
|
||||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||||
|
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
|
||||||
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
|
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
|
||||||
|
|
||||||
|
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
|
||||||
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||||
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
|
||||||
describe('updateOneObjectRecordsPermissions', () => {
|
describe('updateOneObjectRecordsPermissions', () => {
|
||||||
const personId = randomUUID();
|
describe('permissions V2 disabled', () => {
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const createPersonOperation = createOneOperationFactory({
|
|
||||||
objectMetadataSingularName: 'person',
|
|
||||||
gqlFields: PERSON_GQL_FIELDS,
|
|
||||||
data: {
|
|
||||||
id: personId,
|
|
||||||
jobTitle: 'Software Engineer',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await makeGraphqlAPIRequest(createPersonOperation);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw a permission error when user does not have permission (guest role)', async () => {
|
|
||||||
const personId = randomUUID();
|
const personId = randomUUID();
|
||||||
const graphqlOperation = updateOneOperationFactory({
|
|
||||||
objectMetadataSingularName: 'person',
|
beforeAll(async () => {
|
||||||
gqlFields: PERSON_GQL_FIELDS,
|
const createPersonOperation = createOneOperationFactory({
|
||||||
recordId: personId,
|
objectMetadataSingularName: 'person',
|
||||||
data: {
|
gqlFields: PERSON_GQL_FIELDS,
|
||||||
jobTitle: 'Senior Software Engineer',
|
data: {
|
||||||
},
|
id: personId,
|
||||||
|
jobTitle: 'Software Engineer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(createPersonOperation);
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
|
it('should throw a permission error when user does not have permission (guest role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'person',
|
||||||
|
gqlFields: PERSON_GQL_FIELDS,
|
||||||
|
recordId: personId,
|
||||||
|
data: {
|
||||||
|
jobTitle: 'Senior Software Engineer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.body.data).toStrictEqual({ updatePerson: null });
|
const response =
|
||||||
expect(response.body.errors).toBeDefined();
|
await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
|
||||||
expect(response.body.errors[0].message).toBe(
|
|
||||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
expect(response.body.data).toStrictEqual({ updatePerson: null });
|
||||||
);
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
|
expect(response.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update an object record when user has permission (admin role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'person',
|
||||||
|
gqlFields: PERSON_GQL_FIELDS,
|
||||||
|
recordId: personId,
|
||||||
|
data: {
|
||||||
|
jobTitle: 'Senior Software Engineer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.updatePerson).toBeDefined();
|
||||||
|
expect(response.body.data.updatePerson.id).toBe(personId);
|
||||||
|
expect(response.body.data.updatePerson.jobTitle).toBe(
|
||||||
|
'Senior Software Engineer',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update an object record when user has permission (admin role)', async () => {
|
describe('permissions V2 enabled', () => {
|
||||||
const graphqlOperation = updateOneOperationFactory({
|
const personId = randomUUID();
|
||||||
objectMetadataSingularName: 'person',
|
let allPetsViewId: string;
|
||||||
gqlFields: PERSON_GQL_FIELDS,
|
|
||||||
recordId: personId,
|
beforeAll(async () => {
|
||||||
data: {
|
const createPersonOperation = createOneOperationFactory({
|
||||||
jobTitle: 'Senior Software Engineer',
|
objectMetadataSingularName: 'person',
|
||||||
},
|
gqlFields: PERSON_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
id: personId,
|
||||||
|
jobTitle: 'Software Engineer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(createPersonOperation);
|
||||||
|
|
||||||
|
const enablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IsPermissionsV2Enabled',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(enablePermissionsQuery);
|
||||||
|
|
||||||
|
const findAllPetsViewOperation = findOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'view',
|
||||||
|
gqlFields: 'id',
|
||||||
|
filter: {
|
||||||
|
icon: {
|
||||||
|
eq: 'IconCat',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const findAllPetsViewResponse = await makeGraphqlAPIRequest(
|
||||||
|
findAllPetsViewOperation,
|
||||||
|
);
|
||||||
|
|
||||||
|
allPetsViewId = findAllPetsViewResponse.body.data.view.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
afterAll(async () => {
|
||||||
|
const disablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IsPermissionsV2Enabled',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
expect(response.body.data).toBeDefined();
|
await makeGraphqlAPIRequest(disablePermissionsQuery);
|
||||||
expect(response.body.data.updatePerson).toBeDefined();
|
|
||||||
expect(response.body.data.updatePerson.id).toBe(personId);
|
const updateViewOperation = updateOneOperationFactory({
|
||||||
expect(response.body.data.updatePerson.jobTitle).toBe(
|
objectMetadataSingularName: 'view',
|
||||||
'Senior Software Engineer',
|
gqlFields: 'id',
|
||||||
);
|
recordId: allPetsViewId,
|
||||||
|
data: {
|
||||||
|
icon: 'IconCat',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(updateViewOperation);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (guest role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'person',
|
||||||
|
gqlFields: PERSON_GQL_FIELDS,
|
||||||
|
recordId: personId,
|
||||||
|
data: {
|
||||||
|
jobTitle: 'Senior Software Engineer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({ updatePerson: null });
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow to update a system object record even without update permission (guest role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'view',
|
||||||
|
gqlFields: `
|
||||||
|
id
|
||||||
|
icon
|
||||||
|
`,
|
||||||
|
recordId: allPetsViewId,
|
||||||
|
data: {
|
||||||
|
icon: 'IconDog',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.updateView).toBeDefined();
|
||||||
|
expect(response.body.data.updateView.id).toBe(allPetsViewId);
|
||||||
|
expect(response.body.data.updateView.icon).toBe('IconDog');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update an object record when user has permission (admin role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'person',
|
||||||
|
gqlFields: PERSON_GQL_FIELDS,
|
||||||
|
recordId: personId,
|
||||||
|
data: {
|
||||||
|
jobTitle: 'Senior Software Engineer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.updatePerson).toBeDefined();
|
||||||
|
expect(response.body.data.updatePerson.id).toBe(personId);
|
||||||
|
expect(response.body.data.updatePerson.jobTitle).toBe(
|
||||||
|
'Senior Software Engineer',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update an object record when executed by api key', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'person',
|
||||||
|
gqlFields: PERSON_GQL_FIELDS,
|
||||||
|
recordId: personId,
|
||||||
|
data: {
|
||||||
|
jobTitle: 'Senior Software Engineer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.updatePerson).toBeDefined();
|
||||||
|
expect(response.body.data.updatePerson.id).toBe(personId);
|
||||||
|
expect(response.body.data.updatePerson.jobTitle).toBe(
|
||||||
|
'Senior Software Engineer',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { ASTNode, print } from 'graphql';
|
||||||
|
import request from 'supertest';
|
||||||
|
|
||||||
|
type GraphqlOperation = {
|
||||||
|
query: ASTNode;
|
||||||
|
variables?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeGraphqlAPIRequestWithApiKey = (
|
||||||
|
graphqlOperation: GraphqlOperation,
|
||||||
|
) => {
|
||||||
|
const client = request(`http://localhost:${APP_PORT}`);
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${API_KEY_ACCESS_TOKEN}`)
|
||||||
|
.send({
|
||||||
|
query: print(graphqlOperation.query),
|
||||||
|
variables: graphqlOperation.variables || {},
|
||||||
|
});
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user