Files
twenty/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts
T
bb464b2ffb Forbid other app role extension (#19783)
# Introduction
Even though this would not possible through API at the moment, from
neither API metadata or manifest ( as manifest `permissionsFlag`
declarations etc are done from within a declared role )
Prevent any app to create permissions entities over another app role
from the validation engine itself

## `isEditable`
We might wanna deprecate this column at some point from the entity it
self as now the grain would rather be `what app owns that role ?`

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-04-17 12:00:37 +00:00

199 lines
11 KiB
TypeScript

import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { assertUnreachable } from 'twenty-shared/utils';
import { CustomException } from 'src/utils/custom-exception';
export enum PermissionsExceptionCode {
PERMISSION_DENIED = 'PERMISSION_DENIED',
ADMIN_ROLE_NOT_FOUND = 'ADMIN_ROLE_NOT_FOUND',
USER_WORKSPACE_NOT_FOUND = 'USER_WORKSPACE_NOT_FOUND',
WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH = 'WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH',
TOO_MANY_ADMIN_CANDIDATES = 'TOO_MANY_ADMIN_CANDIDATES',
USER_WORKSPACE_ALREADY_HAS_ROLE = 'USER_WORKSPACE_ALREADY_HAS_ROLE',
WORKSPACE_MEMBER_NOT_FOUND = 'WORKSPACE_MEMBER_NOT_FOUND',
ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
CANNOT_UNASSIGN_LAST_ADMIN = 'CANNOT_UNASSIGN_LAST_ADMIN',
CANNOT_DELETE_LAST_ADMIN_USER = 'CANNOT_DELETE_LAST_ADMIN_USER',
UNKNOWN_OPERATION_NAME = 'UNKNOWN_OPERATION_NAME_PERMISSIONS',
UNKNOWN_REQUIRED_PERMISSION = 'UNKNOWN_REQUIRED_PERMISSION',
CANNOT_UPDATE_SELF_ROLE = 'CANNOT_UPDATE_SELF_ROLE',
NO_ROLE_FOUND_FOR_USER_WORKSPACE = 'NO_ROLE_FOUND_FOR_USER_WORKSPACE',
API_KEY_ROLE_NOT_FOUND = 'API_KEY_ROLE_NOT_FOUND',
NO_AUTHENTICATION_CONTEXT = 'NO_AUTHENTICATION_CONTEXT',
INVALID_ARG = 'INVALID_ARG_PERMISSIONS',
ROLE_LABEL_ALREADY_EXISTS = 'ROLE_LABEL_ALREADY_EXISTS',
DEFAULT_ROLE_NOT_FOUND = 'DEFAULT_ROLE_NOT_FOUND',
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND_PERMISSIONS',
INVALID_SETTING = 'INVALID_SETTING_PERMISSIONS',
ROLE_NOT_EDITABLE = 'ROLE_NOT_EDITABLE',
DEFAULT_ROLE_CANNOT_BE_DELETED = 'DEFAULT_ROLE_CANNOT_BE_DELETED',
NO_PERMISSIONS_FOUND_IN_DATASOURCE = 'NO_PERMISSIONS_FOUND_IN_DATASOURCE',
CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT = 'CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT',
CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT = 'CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT',
METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED',
RAW_SQL_NOT_ALLOWED = 'RAW_SQL_NOT_ALLOWED',
CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT = 'CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT',
CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION = 'CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION',
FIELD_METADATA_NOT_FOUND = 'FIELD_METADATA_NOT_FOUND',
ONLY_FIELD_RESTRICTION_ALLOWED = 'ONLY_FIELD_RESTRICTION_ALLOWED',
FIELD_RESTRICTION_ONLY_ALLOWED_ON_READABLE_OBJECT = 'FIELD_RESTRICTION_ONLY_ALLOWED_ON_READABLE_OBJECT',
FIELD_RESTRICTION_ON_UPDATE_ONLY_ALLOWED_ON_UPDATABLE_OBJECT = 'FIELD_RESTRICTION_ON_UPDATE_ONLY_ALLOWED_ON_UPDATABLE_OBJECT',
UPSERT_FIELD_PERMISSION_FAILED = 'UPSERT_FIELD_PERMISSION_FAILED',
PERMISSION_NOT_FOUND = 'PERMISSION_NOT_FOUND',
OBJECT_PERMISSION_NOT_FOUND = 'OBJECT_PERMISSION_NOT_FOUND',
FIELD_PERMISSION_NOT_FOUND = 'FIELD_PERMISSION_NOT_FOUND',
EMPTY_FIELD_PERMISSION_NOT_ALLOWED = 'EMPTY_FIELD_PERMISSION_NOT_ALLOWED',
JOIN_COLUMN_NAME_REQUIRED = 'JOIN_COLUMN_NAME_REQUIRED',
COMPOSITE_TYPE_NOT_FOUND = 'COMPOSITE_TYPE_NOT_FOUND',
ROLE_MUST_HAVE_AT_LEAST_ONE_TARGET = 'ROLE_MUST_HAVE_AT_LEAST_ONE_TARGET',
ROLE_CANNOT_BE_ASSIGNED_TO_USERS = 'ROLE_CANNOT_BE_ASSIGNED_TO_USERS',
APPLICATION_ROLE_NOT_FOUND = 'APPLICATION_ROLE_NOT_FOUND',
ROLE_BELONGS_TO_ANOTHER_APPLICATION = 'ROLE_BELONGS_TO_ANOTHER_APPLICATION',
}
const getPermissionsExceptionUserFriendlyMessage = (
code: PermissionsExceptionCode,
) => {
switch (code) {
case PermissionsExceptionCode.PERMISSION_DENIED:
return msg`You do not have permission to perform this action.`;
case PermissionsExceptionCode.ADMIN_ROLE_NOT_FOUND:
return msg`Admin role not found.`;
case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND:
return msg`User workspace not found.`;
case PermissionsExceptionCode.WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH:
return msg`Workspace ID and role mismatch.`;
case PermissionsExceptionCode.TOO_MANY_ADMIN_CANDIDATES:
return msg`Too many admin candidates found.`;
case PermissionsExceptionCode.USER_WORKSPACE_ALREADY_HAS_ROLE:
return msg`User already has a role assigned.`;
case PermissionsExceptionCode.WORKSPACE_MEMBER_NOT_FOUND:
return msg`Workspace member not found.`;
case PermissionsExceptionCode.ROLE_NOT_FOUND:
return msg`Role not found.`;
case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN:
return msg`Cannot remove the last admin from the workspace.`;
case PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER:
return msg`Cannot delete the last admin user.`;
case PermissionsExceptionCode.UNKNOWN_OPERATION_NAME:
return msg`Unknown operation.`;
case PermissionsExceptionCode.UNKNOWN_REQUIRED_PERMISSION:
return msg`Unknown permission required.`;
case PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE:
return msg`You cannot update your own role.`;
case PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE:
return msg`No role found for this user in the workspace.`;
case PermissionsExceptionCode.API_KEY_ROLE_NOT_FOUND:
return msg`API key role not found.`;
case PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT:
return msg`Authentication is required.`;
case PermissionsExceptionCode.INVALID_ARG:
return msg`Invalid argument provided.`;
case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS:
return msg`A role with this label already exists.`;
case PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND:
return msg`Default role not found.`;
case PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND:
return msg`Object metadata not found.`;
case PermissionsExceptionCode.INVALID_SETTING:
return msg`Invalid permission setting.`;
case PermissionsExceptionCode.ROLE_NOT_EDITABLE:
return msg`This role cannot be edited.`;
case PermissionsExceptionCode.DEFAULT_ROLE_CANNOT_BE_DELETED:
return msg`The default role cannot be deleted.`;
case PermissionsExceptionCode.NO_PERMISSIONS_FOUND_IN_DATASOURCE:
return msg`No permissions found in datasource.`;
case PermissionsExceptionCode.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT:
return msg`Cannot add permissions on system objects.`;
case PermissionsExceptionCode.CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT:
return msg`Cannot add field permissions on system objects.`;
case PermissionsExceptionCode.METHOD_NOT_ALLOWED:
return msg`This method is not allowed.`;
case PermissionsExceptionCode.RAW_SQL_NOT_ALLOWED:
return msg`Raw SQL queries are not allowed.`;
case PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT:
return msg`Cannot give write permission on non-readable objects.`;
case PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION:
return msg`Cannot give write permission without read permission.`;
case PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND:
return msg`Field metadata not found.`;
case PermissionsExceptionCode.ONLY_FIELD_RESTRICTION_ALLOWED:
return msg`Only field restrictions are allowed.`;
case PermissionsExceptionCode.FIELD_RESTRICTION_ONLY_ALLOWED_ON_READABLE_OBJECT:
return msg`Field restrictions only apply to readable objects.`;
case PermissionsExceptionCode.FIELD_RESTRICTION_ON_UPDATE_ONLY_ALLOWED_ON_UPDATABLE_OBJECT:
return msg`Update field restrictions only apply to updatable objects.`;
case PermissionsExceptionCode.UPSERT_FIELD_PERMISSION_FAILED:
return msg`Failed to update field permission.`;
case PermissionsExceptionCode.PERMISSION_NOT_FOUND:
return msg`Permission not found.`;
case PermissionsExceptionCode.OBJECT_PERMISSION_NOT_FOUND:
return msg`Object permission not found.`;
case PermissionsExceptionCode.FIELD_PERMISSION_NOT_FOUND:
return msg`Field permission not found.`;
case PermissionsExceptionCode.EMPTY_FIELD_PERMISSION_NOT_ALLOWED:
return msg`Empty field permissions are not allowed.`;
case PermissionsExceptionCode.JOIN_COLUMN_NAME_REQUIRED:
return msg`Join column name is required.`;
case PermissionsExceptionCode.COMPOSITE_TYPE_NOT_FOUND:
return msg`Composite type not found.`;
case PermissionsExceptionCode.ROLE_MUST_HAVE_AT_LEAST_ONE_TARGET:
return msg`Role must have at least one target.`;
case PermissionsExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_USERS:
return msg`This role cannot be assigned to users.`;
case PermissionsExceptionCode.APPLICATION_ROLE_NOT_FOUND:
return msg`No role assigned to the application.`;
case PermissionsExceptionCode.ROLE_BELONGS_TO_ANOTHER_APPLICATION:
return msg`Cannot modify permissions on a role owned by another application.`;
default:
assertUnreachable(code);
}
};
export class PermissionsException extends CustomException<PermissionsExceptionCode> {
constructor(
message: string,
code: PermissionsExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? getPermissionsExceptionUserFriendlyMessage(code),
});
}
}
export enum PermissionsExceptionMessage {
PERMISSION_DENIED = 'Entity performing the request does not have permission',
USER_WORKSPACE_NOT_FOUND = 'User workspace not found',
ROLE_NOT_FOUND = 'Role not found',
CANNOT_UNASSIGN_LAST_ADMIN = 'Cannot unassign admin role from last admin of the workspace',
CANNOT_DELETE_LAST_ADMIN_USER = 'Cannot delete account: user is the unique admin of a workspace',
UNKNOWN_OPERATION_NAME = 'Unknown operation name, cannot determine required permission',
CANNOT_UPDATE_SELF_ROLE = 'Cannot update self role',
NO_ROLE_FOUND_FOR_USER_WORKSPACE = 'No role found for userWorkspace',
API_KEY_ROLE_NOT_FOUND = 'API key has no role assigned',
NO_AUTHENTICATION_CONTEXT = 'No valid authentication context found',
ROLE_LABEL_ALREADY_EXISTS = 'A role with this label already exists',
DEFAULT_ROLE_NOT_FOUND = 'Default role not found',
OBJECT_METADATA_NOT_FOUND = 'Object metadata not found',
INVALID_SETTING = 'Invalid permission setting (unknown value)',
ROLE_NOT_EDITABLE = 'Role is not editable',
DEFAULT_ROLE_CANNOT_BE_DELETED = 'Default role cannot be deleted',
CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT = 'Cannot add object permission on system object',
CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT = 'Cannot add field permission on system object',
CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT = 'Cannot give update permission to non-readable object',
CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION = 'Cannot give writing permission without reading permission',
FIELD_METADATA_NOT_FOUND = 'Field metadata not found',
ONLY_FIELD_RESTRICTION_ALLOWED = 'Field permission can only introduce a restriction',
FIELD_RESTRICTION_ONLY_ALLOWED_ON_READABLE_OBJECT = 'Field restriction only makes sense on readable object',
FIELD_RESTRICTION_ON_UPDATE_ONLY_ALLOWED_ON_UPDATABLE_OBJECT = 'Field restriction on update only makes sense on updatable object',
OBJECT_PERMISSION_NOT_FOUND = 'Object permission not found',
FIELD_PERMISSION_NOT_FOUND = 'Field permission not found',
EMPTY_FIELD_PERMISSION_NOT_ALLOWED = 'Empty field permission not allowed',
ROLE_MUST_HAVE_AT_LEAST_ONE_TARGET = 'Role must be assignable to at least one target type',
ROLE_CANNOT_BE_ASSIGNED_TO_USERS = 'Role cannot be assigned to users',
APPLICATION_ROLE_NOT_FOUND = 'Application role not found',
}