diff --git a/packages/twenty-server/@types/jest.d.ts b/packages/twenty-server/@types/jest.d.ts
index 523142b5b..2b06c7722 100644
--- a/packages/twenty-server/@types/jest.d.ts
+++ b/packages/twenty-server/@types/jest.d.ts
@@ -7,6 +7,7 @@ declare module '@jest/types' {
ADMIN_ACCESS_TOKEN: string;
EXPIRED_ACCESS_TOKEN: string;
MEMBER_ACCESS_TOKEN: string;
+ GUEST_ACCESS_TOKEN: string;
}
}
}
@@ -16,6 +17,7 @@ declare global {
const ADMIN_ACCESS_TOKEN: string;
const EXPIRED_ACCESS_TOKEN: string;
const MEMBER_ACCESS_TOKEN: string;
+ const GUEST_ACCESS_TOKEN: string;
}
export {};
diff --git a/packages/twenty-server/jest-integration.config.ts b/packages/twenty-server/jest-integration.config.ts
index 0288407ae..f532f9ab1 100644
--- a/packages/twenty-server/jest-integration.config.ts
+++ b/packages/twenty-server/jest-integration.config.ts
@@ -64,6 +64,8 @@ const jestConfig: JestConfigWithTsJest = {
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwiaWF0IjoxNzM4MzIzODc5LCJleHAiOjE3MzgzMjU2Nzl9.m73hHVpnw5uGNGrSuKxn6XtKEUK3Wqkp4HsQdYfZiHo',
MEMBER_ACCESS_TOKEN:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC0zOTU3LTQ5MDgtOWMzNi0yOTI5YTIzZjgzNTciLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNzdkNS00Y2I2LWI2MGEtZjRhODM1YTg1ZDYxIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMzk1Ny00OTA4LTljMzYtMjkyOWEyM2Y4MzUzIiwiaWF0IjoxNzM5NDU5NTcwLCJleHAiOjMzMjk3MDU5NTcwfQ.Er7EEU4IP4YlGN79jCLR_6sUBqBfKx2M3G_qGiDpPRo',
+ GUEST_ACCESS_TOKEN:
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC03MTY5LTQyY2YtYmM0Ny0xY2ZlZjE1MjY0YjgiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMTU1My00NWM2LWEwMjgtNWE5MDY0Y2NlMDdmIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtNzE2OS00MmNmLWJjNDctMWNmZWYxNTI2NGIxIiwiaWF0IjoxNzM5ODg4NDcwLCJleHAiOjMzMjk3NDg4NDcwfQ.0NEu-AWGv3l77rs-56Z5Gt0UTU7HDl6qUTHUcMWNrCc',
},
};
diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts
index 37f7d340c..7af89bc7d 100644
--- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts
+++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts
@@ -1,7 +1,12 @@
import { Inject, Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
-import { capitalize, SettingsFeatures } from 'twenty-shared';
+import {
+ capitalize,
+ isObjectRecordUnderObjectRecordsPermissions,
+ PermissionsOnAllObjectRecords,
+ SettingsFeatures,
+} from 'twenty-shared';
import { DataSource, ObjectLiteral } from 'typeorm';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
@@ -23,6 +28,7 @@ import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-quer
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import {
AuthException,
AuthExceptionCode,
@@ -92,7 +98,20 @@ export abstract class GraphqlQueryBaseResolverService<
featureFlagsMap[FeatureFlagKey.IsPermissionsEnabled] &&
objectMetadataItemWithFieldMaps.isSystem === true
) {
- await this.validateSystemObjectPermissions(options);
+ await this.validateSystemObjectPermissionsOrThrow(options);
+ }
+
+ if (
+ featureFlagsMap[FeatureFlagKey.IsPermissionsEnabled] &&
+ isObjectRecordUnderObjectRecordsPermissions({
+ isCustom: objectMetadataItemWithFieldMaps.isCustom,
+ nameSingular: objectMetadataItemWithFieldMaps.nameSingular,
+ })
+ ) {
+ await this.validateCustomObjectPermissionsOrThrow({
+ operationName,
+ options,
+ });
}
const hookedArgs =
@@ -170,7 +189,7 @@ export abstract class GraphqlQueryBaseResolverService<
}
}
- private async validateSystemObjectPermissions(
+ private async validateSystemObjectPermissionsOrThrow(
options: WorkspaceQueryRunnerOptions,
) {
const { authContext, objectMetadataItemWithFieldMaps } = options;
@@ -210,6 +229,70 @@ export abstract class GraphqlQueryBaseResolverService<
}
}
+ private async validateCustomObjectPermissionsOrThrow({
+ operationName,
+ options,
+ }: {
+ operationName: WorkspaceResolverBuilderMethodNames;
+ options: WorkspaceQueryRunnerOptions;
+ }) {
+ if (!options.authContext.apiKey) {
+ if (!options.authContext.userWorkspaceId) {
+ throw new AuthException(
+ 'Missing userWorkspaceId in authContext',
+ AuthExceptionCode.USER_WORKSPACE_NOT_FOUND,
+ );
+ }
+
+ const requiredPermission =
+ this.getRequiredPermissionForMethod(operationName);
+
+ const userHasPermission =
+ await this.permissionsService.userHasObjectRecordsPermission({
+ userWorkspaceId: options.authContext.userWorkspaceId,
+ requiredPermission,
+ workspaceId: options.authContext.workspace.id,
+ });
+
+ if (!userHasPermission) {
+ throw new PermissionsException(
+ PermissionsExceptionMessage.PERMISSION_DENIED,
+ PermissionsExceptionCode.PERMISSION_DENIED,
+ );
+ }
+ }
+ }
+
+ private getRequiredPermissionForMethod(
+ operationName: WorkspaceResolverBuilderMethodNames,
+ ) {
+ switch (operationName) {
+ case RESOLVER_METHOD_NAMES.FIND_MANY:
+ case RESOLVER_METHOD_NAMES.FIND_ONE:
+ case RESOLVER_METHOD_NAMES.FIND_DUPLICATES:
+ case RESOLVER_METHOD_NAMES.SEARCH:
+ return PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS;
+ case RESOLVER_METHOD_NAMES.CREATE_MANY:
+ case RESOLVER_METHOD_NAMES.CREATE_ONE:
+ case RESOLVER_METHOD_NAMES.UPDATE_MANY:
+ case RESOLVER_METHOD_NAMES.UPDATE_ONE:
+ return PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS;
+ case RESOLVER_METHOD_NAMES.DELETE_MANY:
+ case RESOLVER_METHOD_NAMES.DELETE_ONE:
+ case RESOLVER_METHOD_NAMES.RESTORE_MANY:
+ case RESOLVER_METHOD_NAMES.RESTORE_ONE:
+ return PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS;
+ case RESOLVER_METHOD_NAMES.DESTROY_MANY:
+ case RESOLVER_METHOD_NAMES.DESTROY_ONE:
+ return PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS;
+ default:
+ throw new PermissionsException(
+ PermissionsExceptionMessage.UNKNOWN_OPERATION_NAME,
+ PermissionsExceptionCode.UNKNOWN_OPERATION_NAME,
+ );
+ }
+ }
+
protected abstract resolve(
executionArgs: GraphqlQueryResolverExecutionArgs,
featureFlagsMap: Record,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util.ts
new file mode 100644
index 000000000..773f61ff7
--- /dev/null
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util.ts
@@ -0,0 +1,31 @@
+import {
+ GraphqlQueryRunnerException,
+ GraphqlQueryRunnerExceptionCode,
+} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
+import {
+ InternalServerError,
+ NotFoundError,
+ UserInputError,
+} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
+
+export const graphqlQueryRunnerExceptionHandler = (
+ error: GraphqlQueryRunnerException,
+) => {
+ switch (error.code) {
+ case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST:
+ case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST:
+ case GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND:
+ case GraphqlQueryRunnerExceptionCode.MAX_DEPTH_REACHED:
+ case GraphqlQueryRunnerExceptionCode.INVALID_CURSOR:
+ case GraphqlQueryRunnerExceptionCode.INVALID_DIRECTION:
+ case GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR:
+ case GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT:
+ case GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND:
+ case GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
+ throw new UserInputError(error.message);
+ case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND:
+ throw new NotFoundError(error.message);
+ default:
+ throw new InternalServerError(error.message);
+ }
+};
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util.ts
new file mode 100644
index 000000000..2c2254b64
--- /dev/null
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util.ts
@@ -0,0 +1,51 @@
+import { isDefined } from 'twenty-shared';
+import { QueryFailedError } from 'typeorm';
+
+import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
+
+import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
+
+export const handleDuplicateKeyError = (
+ error: QueryFailedError,
+ context: WorkspaceQueryRunnerOptions,
+) => {
+ const indexNameMatch = error.message.match(/"([^"]+)"/);
+
+ if (indexNameMatch) {
+ const indexName = indexNameMatch[1];
+
+ const deletedAtFieldMetadata =
+ context.objectMetadataItemWithFieldMaps.fieldsByName['deletedAt'];
+
+ const affectedColumns =
+ context.objectMetadataItemWithFieldMaps.indexMetadatas
+ .find((index) => index.name === indexName)
+ ?.indexFieldMetadatas?.filter(
+ (field) => field.fieldMetadataId !== deletedAtFieldMetadata?.id,
+ )
+ .map((indexField) => {
+ const fieldMetadata =
+ context.objectMetadataItemWithFieldMaps.fieldsById[
+ indexField.fieldMetadataId
+ ];
+
+ return fieldMetadata?.label;
+ });
+
+ if (!isDefined(affectedColumns)) {
+ throw new UserInputError(`A duplicate entry was detected`);
+ }
+
+ const columnNames = affectedColumns.join(', ');
+
+ if (affectedColumns?.length === 1) {
+ throw new UserInputError(
+ `Duplicate ${columnNames}. Please set a unique one.`,
+ );
+ }
+
+ throw new UserInputError(
+ `A duplicate entry was detected. The combination of ${columnNames} must be unique.`,
+ );
+ }
+};
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util.ts
new file mode 100644
index 000000000..69b9b3fa1
--- /dev/null
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util.ts
@@ -0,0 +1,32 @@
+import {
+ WorkspaceQueryRunnerException,
+ WorkspaceQueryRunnerExceptionCode,
+} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
+import {
+ ForbiddenError,
+ InternalServerError,
+ NotFoundError,
+ TimeoutError,
+ UserInputError,
+} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
+
+export const workspaceExceptionHandler = (
+ error: WorkspaceQueryRunnerException,
+) => {
+ switch (error.code) {
+ case WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND:
+ throw new NotFoundError(error.message);
+ case WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
+ throw new UserInputError(error.message);
+ case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT:
+ case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT:
+ case WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED:
+ case WorkspaceQueryRunnerExceptionCode.NO_ROWS_AFFECTED:
+ throw new ForbiddenError(error.message);
+ case WorkspaceQueryRunnerExceptionCode.QUERY_TIMEOUT:
+ throw new TimeoutError(error.message);
+ case WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR:
+ default:
+ throw new InternalServerError(error.message);
+ }
+};
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts
index cd0002d43..a7636a29d 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts
@@ -1,114 +1,35 @@
import { QueryFailedError } from 'typeorm';
-import { isDefined } from 'twenty-shared';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
-import {
- GraphqlQueryRunnerException,
- GraphqlQueryRunnerExceptionCode,
-} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
-import {
- WorkspaceQueryRunnerException,
- WorkspaceQueryRunnerExceptionCode,
-} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
-import {
- ForbiddenError,
- InternalServerError,
- NotFoundError,
- TimeoutError,
- UserInputError,
-} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
+import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
+import { graphqlQueryRunnerExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util';
+import { handleDuplicateKeyError } from 'src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util';
+import { workspaceExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util';
+import { WorkspaceQueryRunnerException } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
+import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
+import { permissionGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util';
export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
error: Error,
context: WorkspaceQueryRunnerOptions,
) => {
- if (error instanceof QueryFailedError) {
- if (
- error.message.includes('duplicate key value violates unique constraint')
- ) {
- const indexNameMatch = error.message.match(/"([^"]+)"/);
-
- if (indexNameMatch) {
- const indexName = indexNameMatch[1];
-
- const deletedAtFieldMetadata =
- context.objectMetadataItemWithFieldMaps.fieldsByName['deletedAt'];
-
- const affectedColumns =
- context.objectMetadataItemWithFieldMaps.indexMetadatas
- .find((index) => index.name === indexName)
- ?.indexFieldMetadatas?.filter(
- (field) => field.fieldMetadataId !== deletedAtFieldMetadata?.id,
- )
- .map((indexField) => {
- const fieldMetadata =
- context.objectMetadataItemWithFieldMaps.fieldsById[
- indexField.fieldMetadataId
- ];
-
- return fieldMetadata?.label;
- });
-
- if (!isDefined(affectedColumns)) {
- throw new UserInputError(`A duplicate entry was detected`);
- }
-
- const columnNames = affectedColumns.join(', ');
-
- if (affectedColumns?.length === 1) {
- throw new UserInputError(
- `Duplicate ${columnNames}. Please set a unique one.`,
- );
- }
-
- throw new UserInputError(
- `A duplicate entry was detected. The combination of ${columnNames} must be unique.`,
- );
+ switch (true) {
+ case error instanceof QueryFailedError: {
+ if (
+ error.message.includes('duplicate key value violates unique constraint')
+ ) {
+ return handleDuplicateKeyError(error, context);
}
+ throw error;
}
-
- throw error;
+ case error instanceof PermissionsException:
+ return permissionGraphqlApiExceptionHandler(error);
+ case error instanceof WorkspaceQueryRunnerException:
+ return workspaceExceptionHandler(error);
+ case error instanceof GraphqlQueryRunnerException:
+ return graphqlQueryRunnerExceptionHandler(error);
+ default:
+ throw error;
}
-
- if (error instanceof WorkspaceQueryRunnerException) {
- switch (error.code) {
- case WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND:
- throw new NotFoundError(error.message);
- case WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
- throw new UserInputError(error.message);
- case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT:
- case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT:
- case WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED:
- case WorkspaceQueryRunnerExceptionCode.NO_ROWS_AFFECTED:
- throw new ForbiddenError(error.message);
- case WorkspaceQueryRunnerExceptionCode.QUERY_TIMEOUT:
- throw new TimeoutError(error.message);
- case WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR:
- default:
- throw new InternalServerError(error.message);
- }
- }
-
- if (error instanceof GraphqlQueryRunnerException) {
- switch (error.code) {
- case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST:
- case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST:
- case GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND:
- case GraphqlQueryRunnerExceptionCode.MAX_DEPTH_REACHED:
- case GraphqlQueryRunnerExceptionCode.INVALID_CURSOR:
- case GraphqlQueryRunnerExceptionCode.INVALID_DIRECTION:
- case GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR:
- case GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT:
- case GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND:
- case GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
- throw new UserInputError(error.message);
- case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND:
- throw new NotFoundError(error.message);
- default:
- throw new InternalServerError(error.message);
- }
- }
-
- throw error;
};
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names.ts
new file mode 100644
index 000000000..15fde4c0a
--- /dev/null
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names.ts
@@ -0,0 +1,16 @@
+export const RESOLVER_METHOD_NAMES = {
+ FIND_MANY: 'findMany',
+ FIND_ONE: 'findOne',
+ FIND_DUPLICATES: 'findDuplicates',
+ SEARCH: 'search',
+ CREATE_MANY: 'createMany',
+ CREATE_ONE: 'createOne',
+ UPDATE_MANY: 'updateMany',
+ UPDATE_ONE: 'updateOne',
+ DELETE_MANY: 'deleteMany',
+ DELETE_ONE: 'deleteOne',
+ RESTORE_MANY: 'restoreMany',
+ RESTORE_ONE: 'restoreOne',
+ DESTROY_MANY: 'destroyMany',
+ DESTROY_ONE: 'destroyOne',
+} as const;
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts
index 6b4211820..147e2866a 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class CreateManyResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'createMany' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.CREATE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryCreateManyResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts
index 64bbe6875..18d21b9e6 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryCreateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class CreateOneResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'createOne' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.CREATE_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryCreateOneResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts
index fab10575a..534f3cc4d 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryDeleteManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class DeleteManyResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'deleteMany' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.DELETE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryDeleteManyResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts
index 2b14aad99..67cec20eb 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts
@@ -9,13 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryDeleteOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class DeleteOneResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'deleteOne' as const;
-
+ public static methodName = RESOLVER_METHOD_NAMES.DELETE_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryDeleteOneResolverService,
) {}
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts
index f4f92e248..c6a07b36d 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class DestroyManyResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'destroyMany' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.DESTROY_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryDestroyManyResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts
index 4f60eb9b8..25a508d7e 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class DestroyOneResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'destroyOne' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.DESTROY_ONE;
constructor(
private readonly graphQLQueryRunnerService: GraphqlQueryDestroyOneResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts
index 0ad5cb69a..21576100c 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class FindDuplicatesResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'findDuplicates' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.FIND_DUPLICATES;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryFindDuplicatesResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts
index 08b17ddc1..c6d0662da 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class FindManyResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'findMany' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.FIND_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryFindManyResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts
index dc2302adc..d7983a67d 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class FindOneResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'findOne' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.FIND_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryFindOneResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts
index 06213a00d..e9385b311 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class RestoreManyResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'restoreMany' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.RESTORE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRestoreManyResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts
index e9a0f2d7c..25f986f9d 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class RestoreOneResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'restoreOne' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.RESTORE_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRestoreOneResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts
index 05f7d6431..60a7eb2b8 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class SearchResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'search' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.SEARCH;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQuerySearchResolverService,
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts
index 3830c3f22..efbeeee65 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts
@@ -9,13 +9,12 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service';
-
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class UpdateManyResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'updateMany' as const;
-
+ public static methodName = RESOLVER_METHOD_NAMES.UPDATE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryUpdateManyResolverService,
) {}
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts
index cc4f2d984..401f070c2 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts
@@ -9,12 +9,13 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service';
+import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class UpdateOneResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
- public static methodName = 'updateOne' as const;
+ public static methodName = RESOLVER_METHOD_NAMES.UPDATE_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryUpdateOneResolverService,
diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts
index 9dacb79c9..2535b8c36 100644
--- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts
@@ -16,6 +16,8 @@ export enum PermissionsExceptionCode {
WORKSPACE_MEMBER_NOT_FOUND = 'WORKSPACE_MEMBER_NOT_FOUND',
ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
CANNOT_UNASSIGN_LAST_ADMIN = 'CANNOT_UNASSIGN_LAST_ADMIN',
+ UNKNOWN_OPERATION_NAME = 'UNKNOWN_OPERATION_NAME',
+ UNKNOWN_REQUIRED_PERMISSION = 'UNKNOWN_REQUIRED_PERMISSION',
}
export enum PermissionsExceptionMessage {
@@ -28,4 +30,6 @@ export enum PermissionsExceptionMessage {
WORKSPACE_MEMBER_NOT_FOUND = 'Workspace member not found',
ROLE_NOT_FOUND = 'Role not found',
CANNOT_UNASSIGN_LAST_ADMIN = 'Cannot unassign last admin',
+ UNKNOWN_OPERATION_NAME = 'Unknown operation name, cannot determine required permission',
+ UNKNOWN_REQUIRED_PERMISSION = 'Unknown required permission',
}
diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts
index c5d9a143b..cb508a70c 100644
--- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts
@@ -3,6 +3,12 @@ import { Injectable } from '@nestjs/common';
import { PermissionsOnAllObjectRecords, SettingsFeatures } from 'twenty-shared';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
+import {
+ PermissionsException,
+ PermissionsExceptionCode,
+ PermissionsExceptionMessage,
+} from 'src/engine/metadata-modules/permissions/permissions.exception';
+import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
@Injectable()
@@ -86,7 +92,49 @@ export class PermissionsService {
return false;
}
+ public async userHasObjectRecordsPermission({
+ userWorkspaceId,
+ workspaceId,
+ requiredPermission,
+ }: {
+ userWorkspaceId: string;
+ workspaceId: string;
+ requiredPermission: PermissionsOnAllObjectRecords;
+ }): Promise {
+ const [roleOfUserWorkspace] = await this.userRoleService
+ .getRolesByUserWorkspaces({
+ userWorkspaceIds: [userWorkspaceId],
+ workspaceId,
+ })
+ .then((roles) => roles?.get(userWorkspaceId) ?? []);
+
+ const roleColumn =
+ this.getRoleColumnForRequiredPermission(requiredPermission);
+
+ return roleOfUserWorkspace?.[roleColumn] === true;
+ }
+
public async isPermissionsEnabled(): Promise {
return this.environmentService.get('PERMISSIONS_ENABLED') === true;
}
+
+ private getRoleColumnForRequiredPermission(
+ requiredPermission: PermissionsOnAllObjectRecords,
+ ): keyof RoleEntity {
+ switch (requiredPermission) {
+ case PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS:
+ return 'canReadAllObjectRecords';
+ case PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS:
+ return 'canUpdateAllObjectRecords';
+ case PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS:
+ return 'canSoftDeleteAllObjectRecords';
+ case PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS:
+ return 'canDestroyAllObjectRecords';
+ default:
+ throw new PermissionsException(
+ PermissionsExceptionMessage.UNKNOWN_REQUIRED_PERMISSION,
+ PermissionsExceptionCode.UNKNOWN_REQUIRED_PERMISSION,
+ );
+ }
+ }
}
diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts
new file mode 100644
index 000000000..60614a396
--- /dev/null
+++ b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts
@@ -0,0 +1,24 @@
+import {
+ ForbiddenError,
+ InternalServerError,
+ NotFoundError,
+} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
+import {
+ PermissionsException,
+ PermissionsExceptionCode,
+} from 'src/engine/metadata-modules/permissions/permissions.exception';
+
+export const permissionGraphqlApiExceptionHandler = (
+ error: PermissionsException,
+) => {
+ switch (error.code) {
+ case PermissionsExceptionCode.PERMISSION_DENIED:
+ case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN:
+ throw new ForbiddenError(error.message);
+ case PermissionsExceptionCode.ROLE_NOT_FOUND:
+ case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND:
+ throw new NotFoundError(error.message);
+ default:
+ throw new InternalServerError(error.message);
+ }
+};
diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts
index dbecb22ba..17e5d4121 100644
--- a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts
@@ -1,27 +1,11 @@
import { Catch, ExceptionFilter } from '@nestjs/common';
-import {
- ForbiddenError,
- InternalServerError,
- NotFoundError,
-} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
-import {
- PermissionsException,
- PermissionsExceptionCode,
-} from 'src/engine/metadata-modules/permissions/permissions.exception';
+import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
+import { permissionGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util';
@Catch(PermissionsException)
export class PermissionsGraphqlApiExceptionFilter implements ExceptionFilter {
catch(exception: PermissionsException) {
- switch (exception.code) {
- case PermissionsExceptionCode.PERMISSION_DENIED:
- case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN:
- throw new ForbiddenError(exception.message);
- case PermissionsExceptionCode.ROLE_NOT_FOUND:
- case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND:
- throw new NotFoundError(exception.message);
- default:
- throw new InternalServerError(exception.message);
- }
+ return permissionGraphqlApiExceptionHandler(exception);
}
}
diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts
index d161681ec..0c1adcf54 100644
--- a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts
@@ -56,4 +56,23 @@ export class RoleService {
workspaceId,
});
}
+
+ // Only used for dev seeding and testing
+ public async createGuestRole({
+ workspaceId,
+ }: {
+ workspaceId: string;
+ }): Promise {
+ return this.roleRepository.save({
+ label: 'Guest',
+ description: 'Guest role',
+ canUpdateAllSettings: false,
+ canReadAllObjectRecords: true,
+ canUpdateAllObjectRecords: false,
+ canSoftDeleteAllObjectRecords: false,
+ canDestroyAllObjectRecords: false,
+ isEditable: false,
+ workspaceId,
+ });
+ }
}
diff --git a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts
index 2547b4fe9..ebc6a6939 100644
--- a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts
@@ -109,7 +109,7 @@ export class UserRoleService {
}: {
userWorkspaceIds: string[];
workspaceId: string;
- }): Promise