Files
twenty/packages/twenty-server/src/engine/guards/settings-permission.guard.ts
T
Marie 2c596e7b1e [Apps] App misc - fixes + settings permissions for apps + uploadFile (#17167)
In this PR

- handle settings permission check for applications. Until then this was
unhandled and applications could not perform actions requiring settings
permissions, even if they were granted them

- fix ties to attachment, noteTarget etc: 
When an object had their fields synchronized in the app, the system
fields created as a side-effect of the object creation (relations to
noteTarget, attachment, taskTarget, favorites, timelineActivities -
created with `isCustom: true`, not sure that is correct btw) were then
deleted because they are not declared in the app, and identified as
deletable because of `isCustom: true`.
Updating the logic to exclude system fields from the logic that detects
fields to delete.
I think this outline the confusion we have around isCustom, isSystem
etc.

- introduce uploadFile util in generated twenty client as it cannot be
handled by the client's query / mutation. I had to use this for my
invoicing app
2026-01-18 18:41:48 +00:00

69 lines
2.1 KiB
TypeScript

import {
type CanActivate,
type ExecutionContext,
Injectable,
mixin,
type Type,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { msg } from '@lingui/core/macro';
import { type PermissionFlagType } from 'twenty-shared/constants';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import {
PermissionsException,
PermissionsExceptionCode,
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
export const SettingsPermissionGuard = (
requiredPermission: PermissionFlagType,
): Type<CanActivate> => {
@Injectable()
class SettingsPermissionMixin implements CanActivate {
constructor(private readonly permissionsService: PermissionsService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const workspaceId = ctx.getContext().req.workspace.id;
const userWorkspaceId = ctx.getContext().req.userWorkspaceId;
const workspaceActivationStatus =
ctx.getContext().req.workspace.activationStatus;
if (
[
WorkspaceActivationStatus.PENDING_CREATION,
WorkspaceActivationStatus.ONGOING_CREATION,
].includes(workspaceActivationStatus)
) {
return true;
}
const hasPermission =
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId,
setting: requiredPermission,
workspaceId,
apiKeyId: ctx.getContext().req.apiKey?.id,
applicationId: ctx.getContext().req.application?.id,
});
if (hasPermission === true) {
return true;
}
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSION_DENIED,
PermissionsExceptionCode.PERMISSION_DENIED,
{
userFriendlyMessage: msg`You do not have permission to access this feature. Please contact your workspace administrator for access.`,
},
);
}
}
return mixin(SettingsPermissionMixin);
};