Add definePermissionFlag for app-defined permission flags (#20887)
## Context
Adds the SDK plumbing for apps to declare custom permission flags and
the server-side manifest pipeline to persist them.
```typescript
import { definePermissionFlag } from 'twenty-sdk/define';
export const MANAGE_INVOICES_PERMISSION_FLAG_UNIVERSAL_IDENTIFIER = '…';
export default definePermissionFlag({
universalIdentifier: MANAGE_INVOICES_PERMISSION_FLAG_UNIVERSAL_IDENTIFIER,
key: 'MANAGE_INVOICES',
label: 'Manage Invoices',
description: 'Create, edit, and delete invoices',
icon: 'IconReceipt',
});
```
```typescript
import { defineApplicationRole, SystemPermissionFlag } from 'twenty-sdk/define';
import { MANAGE_INVOICES_PERMISSION_FLAG_UNIVERSAL_IDENTIFIER } from './permission-flags/manage-invoices';
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: `${APP_DISPLAY_NAME} default function role`,
// ...
permissionFlagUniversalIdentifiers: [
SystemPermissionFlag.UPLOAD_FILE,
MANAGE_INVOICES_PERMISSION_FLAG_UNIVERSAL_IDENTIFIER,
],
});
```
The flag can then be referenced by UUID in a role's
permissionFlagUniversalIdentifiers. On sync, the catalog row lands in
core.permissionFlag and the link in core.rolePermissionFlag.
## Not in this PR
- Runtime permission checks.
PermissionsService.getUserWorkspacePermissions still builds its result
from Object.values(PermissionFlagType), so custom flags are stored but
not yet enforced, code asking "does this role have MANAGE_INVOICES?"
won't get a meaningful answer. Widening PermissionsService and
UserWorkspacePermissions.permissionFlags to support arbitrary flag keys
is the next PR.
- PermissionFlag from apps can only define "tool" permissions and not
"settings" as a permissionType, this parameter is not mutable. This is
because "settings" are for settings page (until we might decide to
separate both type of permissions into 2 different entities) and apps
can't declare settings page or interact with them so this parameter
would be unnecessary.
This commit is contained in:
+1
@@ -71,6 +71,7 @@ export class ApplicationManifestMigrationService {
|
||||
fields: [],
|
||||
logicFunctions: [preInstallLogicFunctionManifest],
|
||||
frontComponents: [],
|
||||
permissionFlags: [],
|
||||
roles: [],
|
||||
skills: [],
|
||||
agents: [],
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { type PermissionFlagManifest } from 'twenty-shared/application';
|
||||
|
||||
import { type UniversalFlatPermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag.type';
|
||||
|
||||
export const fromPermissionFlagManifestToUniversalFlatPermissionFlag = ({
|
||||
permissionFlagManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
permissionFlagManifest: PermissionFlagManifest;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): UniversalFlatPermissionFlag => {
|
||||
return {
|
||||
universalIdentifier: permissionFlagManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
key: permissionFlagManifest.key,
|
||||
label: permissionFlagManifest.label,
|
||||
description: permissionFlagManifest.description ?? null,
|
||||
icon: permissionFlagManifest.icon ?? null,
|
||||
permissionType: 'tool',
|
||||
rolePermissionFlagUniversalIdentifiers: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
+14
@@ -19,6 +19,7 @@ import { fromObjectPermissionManifestToUniversalFlatObjectPermission } from 'src
|
||||
import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-manifest-to-universal-flat-page-layout.util';
|
||||
import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util';
|
||||
import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util';
|
||||
import { fromPermissionFlagManifestToUniversalFlatPermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-manifest-to-universal-flat-permission-flag.util';
|
||||
import { fromPermissionFlagToUniversalFlatRolePermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-role-permission-flag.util';
|
||||
import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util';
|
||||
import { fromSkillManifestToUniversalFlatSkill } from 'src/engine/core-modules/application/application-manifest/converters/from-skill-manifest-to-universal-flat-skill.util';
|
||||
@@ -246,6 +247,19 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
|
||||
});
|
||||
}
|
||||
|
||||
for (const permissionFlagManifest of manifest.permissionFlags ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromPermissionFlagManifestToUniversalFlatPermissionFlag({
|
||||
permissionFlagManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatPermissionFlagMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const roleManifest of manifest.roles) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromRoleManifestToUniversalFlatRole({
|
||||
|
||||
+1
@@ -18,6 +18,7 @@ const buildMinimalManifest = (
|
||||
fields: [],
|
||||
logicFunctions: [],
|
||||
frontComponents: [],
|
||||
permissionFlags: [],
|
||||
roles: [],
|
||||
skills: [],
|
||||
agents: [],
|
||||
|
||||
Reference in New Issue
Block a user