2c596e7b1e
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
69 lines
2.1 KiB
TypeScript
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);
|
|
};
|