refactor(auth): add workspaces selection (#12098)

This commit is contained in:
Antoine Moreaux
2025-06-13 16:17:35 +02:00
committed by GitHub
parent 836e2f792c
commit b1af98f93d
162 changed files with 3542 additions and 1340 deletions

View File

@ -37,6 +37,7 @@ import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
export type GraphqlQueryResolverExecutionArgs<Input extends ResolverArgs> = {
args: Input;
@ -83,11 +84,15 @@ export abstract class GraphqlQueryBaseResolverService<
try {
const { authContext, objectMetadataItemWithFieldMaps } = options;
const workspace = authContext.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
await this.validate(args, options);
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: authContext.workspace.id,
workspaceId: workspace.id,
shouldFailIfMetadataNotFound: false,
});
@ -124,7 +129,7 @@ export abstract class GraphqlQueryBaseResolverService<
const roleId = await this.userRoleService.getRoleIdForUserWorkspace({
userWorkspaceId: authContext.userWorkspaceId,
workspaceId: authContext.workspace.id,
workspaceId: workspace.id,
});
const executedByApiKey = isDefined(authContext.apiKey);
@ -169,7 +174,7 @@ export abstract class GraphqlQueryBaseResolverService<
const resultWithGetters = await this.queryResultGettersFactory.create(
results,
objectMetadataItemWithFieldMaps,
authContext.workspace.id,
workspace.id,
options.objectMetadataMaps,
);
@ -191,6 +196,10 @@ export abstract class GraphqlQueryBaseResolverService<
) {
const { authContext, objectMetadataItemWithFieldMaps } = options;
const workspace = authContext.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
if (
Object.keys(OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS).includes(
objectMetadataItemWithFieldMaps.nameSingular,
@ -206,7 +215,7 @@ export abstract class GraphqlQueryBaseResolverService<
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId: authContext.userWorkspaceId,
setting: permissionRequired,
workspaceId: authContext.workspace.id,
workspaceId: workspace.id,
isExecutedByApiKey: isDefined(authContext.apiKey),
});
@ -231,11 +240,15 @@ export abstract class GraphqlQueryBaseResolverService<
const requiredPermission =
this.getRequiredPermissionForMethod(operationName);
const workspace = options.authContext.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
const userHasPermission =
await this.permissionsService.userHasObjectRecordsPermission({
userWorkspaceId: options.authContext.userWorkspaceId,
requiredPermission,
workspaceId: options.authContext.workspace.id,
workspaceId: workspace.id,
isExecutedByApiKey: isDefined(options.authContext.apiKey),
objectMetadataId,
});

View File

@ -7,6 +7,7 @@ import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
@Injectable()
export class ApiEventEmitterService {
@ -33,7 +34,7 @@ export class ApiEventEmitterService {
after: record,
},
})),
workspaceId: authContext.workspace.id,
workspaceId: authContext.workspace?.id,
});
}
@ -50,6 +51,10 @@ export class ApiEventEmitterService {
authContext: AuthContext;
objectMetadataItem: ObjectMetadataInterface;
}): void {
const workspace = authContext.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
const mappedExistingRecords = existingRecords.reduce(
(acc, { id, ...record }) => ({
...acc,
@ -84,7 +89,7 @@ export class ApiEventEmitterService {
},
};
}),
workspaceId: authContext.workspace.id,
workspaceId: workspace.id,
});
}
@ -97,6 +102,10 @@ export class ApiEventEmitterService {
authContext: AuthContext;
objectMetadataItem: ObjectMetadataInterface;
}): void {
const workspace = authContext.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: objectMetadataItem.nameSingular,
action: DatabaseEventAction.DELETED,
@ -111,7 +120,7 @@ export class ApiEventEmitterService {
},
};
}),
workspaceId: authContext.workspace.id,
workspaceId: workspace.id,
});
}
@ -124,6 +133,10 @@ export class ApiEventEmitterService {
authContext: AuthContext;
objectMetadataItem: ObjectMetadataInterface;
}): void {
const workspace = authContext.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: objectMetadataItem.nameSingular,
action: DatabaseEventAction.RESTORED,
@ -138,7 +151,7 @@ export class ApiEventEmitterService {
},
};
}),
workspaceId: authContext.workspace.id,
workspaceId: workspace.id,
});
}
@ -151,6 +164,10 @@ export class ApiEventEmitterService {
authContext: AuthContext;
objectMetadataItem: ObjectMetadataInterface;
}): void {
const workspace = authContext.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: objectMetadataItem.nameSingular,
action: DatabaseEventAction.DESTROYED,
@ -165,7 +182,7 @@ export class ApiEventEmitterService {
},
};
}),
workspaceId: authContext.workspace.id,
workspaceId: workspace.id,
});
}
}

View File

@ -23,6 +23,7 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
type ArgPositionBackfillInput = {
argIndex?: number;
@ -171,7 +172,10 @@ export class QueryRunnerArgsFactory {
return Promise.resolve({});
}
const workspaceId = options.authContext.workspace.id;
const workspace = options.authContext.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
let isFieldPositionPresent = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -192,7 +196,7 @@ export class QueryRunnerArgsFactory {
const newValue = await this.recordPositionService.buildRecordPosition(
{
value,
workspaceId,
workspaceId: workspace.id,
objectMetadata: {
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
nameSingular:
@ -234,7 +238,7 @@ export class QueryRunnerArgsFactory {
'position',
await this.recordPositionService.buildRecordPosition({
value: 'first',
workspaceId,
workspaceId: workspace.id,
objectMetadata: {
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
nameSingular:

View File

@ -22,6 +22,7 @@ import { WorkspaceQueryHookKey } from 'src/engine/api/graphql/workspace-query-ru
import { WorkspaceQueryHookStorage } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/storage/workspace-query-hook.storage';
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
import { WorkspaceQueryHookMetadataAccessor } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook-metadata.accessor';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
@Injectable()
export class WorkspaceQueryHookExplorer implements OnModuleInit {
@ -89,6 +90,10 @@ export class WorkspaceQueryHookExplorer implements OnModuleInit {
): Promise<ReturnType<WorkspacePreQueryHookInstance['execute']>> {
const methodName = 'execute';
const workspace = executeParams?.[0].workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
if (isRequestScoped) {
const contextId = createContextId();
@ -96,7 +101,7 @@ export class WorkspaceQueryHookExplorer implements OnModuleInit {
this.moduleRef.registerRequestByContextId(
{
req: {
workspaceId: executeParams?.[0].workspace.id,
workspaceId: workspace.id,
},
},
contextId,
@ -152,6 +157,10 @@ export class WorkspaceQueryHookExplorer implements OnModuleInit {
): Promise<ReturnType<WorkspacePostQueryHookInstance['execute']>> {
const methodName = 'execute';
const workspace = executeParams?.[0].workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
const transformedPayload = this.transformPayload(executeParams[2]);
if (isRequestScoped) {
@ -161,7 +170,7 @@ export class WorkspaceQueryHookExplorer implements OnModuleInit {
this.moduleRef.registerRequestByContextId(
{
req: {
workspaceId: executeParams?.[0].workspace.id,
workspaceId: workspace.id,
userWorkspaceId: executeParams?.[0].userWorkspaceId,
apiKey: executeParams?.[0].apiKey,
workspaceMemberId: executeParams?.[0].workspaceMemberId,

View File

@ -18,6 +18,7 @@ import { getObjectMetadataMapItemByNamePlural } from 'src/engine/metadata-module
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
@Injectable()
export class CoreQueryBuilderFactory {
@ -42,6 +43,8 @@ export class CoreQueryBuilderFactory {
const { workspace } =
await this.accessTokenService.validateTokenByRequest(request);
workspaceValidator.assertIsDefinedOrThrow(workspace);
const currentCacheVersion =
await this.workspaceCacheStorageService.getMetadataVersion(workspace.id);