[permissions V2] Upsert object and setting permissions (#11119)
Closes https://github.com/twentyhq/core-team-issues/issues/639
This commit is contained in:
@ -865,6 +865,8 @@ export type Mutation = {
|
||||
uploadImage: Scalars['String'];
|
||||
uploadProfilePicture: Scalars['String'];
|
||||
uploadWorkspaceLogo: Scalars['String'];
|
||||
upsertOneObjectPermission: ObjectPermission;
|
||||
upsertOneSettingPermission: SettingPermission;
|
||||
userLookupAdminPanel: UserLookup;
|
||||
validateApprovedAccessDomain: ApprovedAccessDomain;
|
||||
};
|
||||
@ -1164,6 +1166,16 @@ export type MutationUploadWorkspaceLogoArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpsertOneObjectPermissionArgs = {
|
||||
upsertObjectPermissionInput: UpsertObjectPermissionInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpsertOneSettingPermissionArgs = {
|
||||
upsertSettingPermissionInput: UpsertSettingPermissionInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUserLookupAdminPanelArgs = {
|
||||
userIdentifier: Scalars['String'];
|
||||
};
|
||||
@ -1255,6 +1267,17 @@ export type ObjectIndexMetadatasConnection = {
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type ObjectPermission = {
|
||||
__typename?: 'ObjectPermission';
|
||||
canDestroyObjectRecords?: Maybe<Scalars['Boolean']>;
|
||||
canReadObjectRecords?: Maybe<Scalars['Boolean']>;
|
||||
canSoftDeleteObjectRecords?: Maybe<Scalars['Boolean']>;
|
||||
canUpdateObjectRecords?: Maybe<Scalars['Boolean']>;
|
||||
id: Scalars['String'];
|
||||
objectMetadataId: Scalars['String'];
|
||||
roleId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ObjectRecordFilterInput = {
|
||||
and?: InputMaybe<Array<ObjectRecordFilterInput>>;
|
||||
createdAt?: InputMaybe<DateFilter>;
|
||||
@ -1715,7 +1738,15 @@ export enum ServerlessFunctionSyncStatus {
|
||||
READY = 'READY'
|
||||
}
|
||||
|
||||
export enum SettingsPermissions {
|
||||
export type SettingPermission = {
|
||||
__typename?: 'SettingPermission';
|
||||
canUpdateSetting?: Maybe<Scalars['Boolean']>;
|
||||
id: Scalars['String'];
|
||||
roleId: Scalars['String'];
|
||||
setting: SettingPermissionType;
|
||||
};
|
||||
|
||||
export enum SettingPermissionType {
|
||||
ADMIN_PANEL = 'ADMIN_PANEL',
|
||||
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
||||
DATA_MODEL = 'DATA_MODEL',
|
||||
@ -1988,6 +2019,21 @@ export type UpdateWorkspaceInput = {
|
||||
subdomain?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpsertObjectPermissionInput = {
|
||||
canDestroyObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canReadObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canSoftDeleteObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canUpdateObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
objectMetadataId: Scalars['String'];
|
||||
roleId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type UpsertSettingPermissionInput = {
|
||||
canUpdateSetting?: InputMaybe<Scalars['Boolean']>;
|
||||
roleId: Scalars['String'];
|
||||
setting: SettingPermissionType;
|
||||
};
|
||||
|
||||
export type User = {
|
||||
__typename?: 'User';
|
||||
analyticsTinybirdJwts?: Maybe<AnalyticsTinybirdJwtMap>;
|
||||
@ -2062,7 +2108,7 @@ export type UserWorkspace = {
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['UUID'];
|
||||
objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>;
|
||||
settingsPermissions?: Maybe<Array<SettingsPermissions>>;
|
||||
settingsPermissions?: Maybe<Array<SettingPermissionType>>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
user: User;
|
||||
userId: Scalars['String'];
|
||||
@ -2607,7 +2653,7 @@ export type GetSsoIdentityProvidersQueryVariables = Exact<{ [key: string]: never
|
||||
|
||||
export type GetSsoIdentityProvidersQuery = { __typename?: 'Query', getSSOIdentityProviders: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
||||
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsPermissions> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||
|
||||
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@ -2624,7 +2670,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsPermissions> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||
|
||||
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||
workflowVersionId: Scalars['String'];
|
||||
|
||||
@ -5,7 +5,7 @@ import { SettingsProtectedRouteWrapper } from '@/settings/components/SettingsPro
|
||||
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { SettingsPermissions } from '~/generated/graphql';
|
||||
import { SettingPermissionType } from '~/generated/graphql';
|
||||
|
||||
const SettingsApiKeys = lazy(() =>
|
||||
import('~/pages/settings/developers/api-keys/SettingsApiKeys').then(
|
||||
@ -334,7 +334,7 @@ export const SettingsRoutes = ({
|
||||
<Route
|
||||
element={
|
||||
<SettingsProtectedRouteWrapper
|
||||
settingsPermission={SettingsPermissions.WORKSPACE}
|
||||
settingsPermission={SettingPermissionType.WORKSPACE}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@ -345,7 +345,7 @@ export const SettingsRoutes = ({
|
||||
<Route
|
||||
element={
|
||||
<SettingsProtectedRouteWrapper
|
||||
settingsPermission={SettingsPermissions.WORKSPACE_MEMBERS}
|
||||
settingsPermission={SettingPermissionType.WORKSPACE_MEMBERS}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@ -357,7 +357,7 @@ export const SettingsRoutes = ({
|
||||
<Route
|
||||
element={
|
||||
<SettingsProtectedRouteWrapper
|
||||
settingsPermission={SettingsPermissions.DATA_MODEL}
|
||||
settingsPermission={SettingPermissionType.DATA_MODEL}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@ -387,7 +387,7 @@ export const SettingsRoutes = ({
|
||||
<Route
|
||||
element={
|
||||
<SettingsProtectedRouteWrapper
|
||||
settingsPermission={SettingsPermissions.ROLES}
|
||||
settingsPermission={SettingPermissionType.ROLES}
|
||||
requiredFeatureFlag={FeatureFlagKey.IsPermissionsEnabled}
|
||||
/>
|
||||
}
|
||||
@ -398,7 +398,7 @@ export const SettingsRoutes = ({
|
||||
<Route
|
||||
element={
|
||||
<SettingsProtectedRouteWrapper
|
||||
settingsPermission={SettingsPermissions.API_KEYS_AND_WEBHOOKS}
|
||||
settingsPermission={SettingPermissionType.API_KEYS_AND_WEBHOOKS}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@ -465,7 +465,7 @@ export const SettingsRoutes = ({
|
||||
<Route
|
||||
element={
|
||||
<SettingsProtectedRouteWrapper
|
||||
settingsPermission={SettingsPermissions.SECURITY}
|
||||
settingsPermission={SettingPermissionType.SECURITY}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@ -496,7 +496,7 @@ export const SettingsRoutes = ({
|
||||
<Route
|
||||
element={
|
||||
<SettingsProtectedRouteWrapper
|
||||
settingsPermission={SettingsPermissions.WORKSPACE}
|
||||
settingsPermission={SettingPermissionType.WORKSPACE}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
@ -12,10 +12,10 @@ import { ViewType } from '@/views/types/ViewType';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { IconEyeOff, IconSettings } from 'twenty-ui';
|
||||
import { SettingsPermissions } from '~/generated/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconEyeOff, IconSettings } from 'twenty-ui';
|
||||
import { SettingPermissionType } from '~/generated/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
type UseRecordGroupActionsParams = {
|
||||
viewType: ViewType;
|
||||
@ -71,7 +71,7 @@ export const useRecordGroupActions = ({
|
||||
]);
|
||||
|
||||
const hasAccessToDataModelSettings = useHasSettingsPermission(
|
||||
SettingsPermissions.DATA_MODEL,
|
||||
SettingPermissionType.DATA_MODEL,
|
||||
);
|
||||
|
||||
const recordGroupActions: RecordGroupAction[] = [];
|
||||
|
||||
@ -3,12 +3,12 @@ import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { ReactNode } from 'react';
|
||||
import { Navigate, Outlet } from 'react-router-dom';
|
||||
import { FeatureFlagKey, SettingsPermissions } from '~/generated/graphql';
|
||||
import { FeatureFlagKey, SettingPermissionType } from '~/generated/graphql';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
type SettingsProtectedRouteWrapperProps = {
|
||||
children?: ReactNode;
|
||||
settingsPermission?: SettingsPermissions;
|
||||
settingsPermission?: SettingPermissionType;
|
||||
requiredFeatureFlag?: FeatureFlagKey;
|
||||
};
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
Billing,
|
||||
FeatureFlagKey,
|
||||
OnboardingStatus,
|
||||
SettingsPermissions,
|
||||
SettingPermissionType,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
@ -61,12 +61,12 @@ jest.mock('@/workspace/hooks/useFeatureFlagsMap', () => ({
|
||||
describe('useSettingsNavigationItems', () => {
|
||||
it('should hide workspace settings when no permissions', () => {
|
||||
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({
|
||||
[SettingsPermissions.WORKSPACE]: false,
|
||||
[SettingsPermissions.WORKSPACE_MEMBERS]: false,
|
||||
[SettingsPermissions.DATA_MODEL]: false,
|
||||
[SettingsPermissions.API_KEYS_AND_WEBHOOKS]: false,
|
||||
[SettingsPermissions.ROLES]: false,
|
||||
[SettingsPermissions.SECURITY]: false,
|
||||
[SettingPermissionType.WORKSPACE]: false,
|
||||
[SettingPermissionType.WORKSPACE_MEMBERS]: false,
|
||||
[SettingPermissionType.DATA_MODEL]: false,
|
||||
[SettingPermissionType.API_KEYS_AND_WEBHOOKS]: false,
|
||||
[SettingPermissionType.ROLES]: false,
|
||||
[SettingPermissionType.SECURITY]: false,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSettingsNavigationItems(), {
|
||||
@ -82,12 +82,12 @@ describe('useSettingsNavigationItems', () => {
|
||||
|
||||
it('should show workspace settings when has permissions', () => {
|
||||
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({
|
||||
[SettingsPermissions.WORKSPACE]: true,
|
||||
[SettingsPermissions.WORKSPACE_MEMBERS]: true,
|
||||
[SettingsPermissions.DATA_MODEL]: true,
|
||||
[SettingsPermissions.API_KEYS_AND_WEBHOOKS]: true,
|
||||
[SettingsPermissions.ROLES]: true,
|
||||
[SettingsPermissions.SECURITY]: true,
|
||||
[SettingPermissionType.WORKSPACE]: true,
|
||||
[SettingPermissionType.WORKSPACE_MEMBERS]: true,
|
||||
[SettingPermissionType.DATA_MODEL]: true,
|
||||
[SettingPermissionType.API_KEYS_AND_WEBHOOKS]: true,
|
||||
[SettingPermissionType.ROLES]: true,
|
||||
[SettingPermissionType.SECURITY]: true,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSettingsNavigationItems(), {
|
||||
|
||||
@ -22,7 +22,6 @@ import {
|
||||
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { SettingsPermissions } from '~/generated/graphql';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
@ -32,6 +31,7 @@ import { NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation
|
||||
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingPermissionType } from '~/generated/graphql';
|
||||
|
||||
export type SettingsNavigationSection = {
|
||||
label: string;
|
||||
@ -108,13 +108,13 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
label: t`General`,
|
||||
path: SettingsPath.Workspace,
|
||||
Icon: IconSettings,
|
||||
isHidden: !permissionMap[SettingsPermissions.WORKSPACE],
|
||||
isHidden: !permissionMap[SettingPermissionType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Members`,
|
||||
path: SettingsPath.WorkspaceMembersPage,
|
||||
Icon: IconUsers,
|
||||
isHidden: !permissionMap[SettingsPermissions.WORKSPACE_MEMBERS],
|
||||
isHidden: !permissionMap[SettingPermissionType.WORKSPACE_MEMBERS],
|
||||
},
|
||||
{
|
||||
label: t`Roles`,
|
||||
@ -122,33 +122,34 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
Icon: IconLock,
|
||||
isHidden:
|
||||
!featureFlags[FeatureFlagKey.IsPermissionsEnabled] ||
|
||||
!permissionMap[SettingsPermissions.ROLES],
|
||||
!permissionMap[SettingPermissionType.ROLES],
|
||||
},
|
||||
{
|
||||
label: t`Billing`,
|
||||
path: SettingsPath.Billing,
|
||||
Icon: IconCurrencyDollar,
|
||||
isHidden:
|
||||
!isBillingEnabled || !permissionMap[SettingsPermissions.WORKSPACE],
|
||||
!isBillingEnabled ||
|
||||
!permissionMap[SettingPermissionType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Data model`,
|
||||
path: SettingsPath.Objects,
|
||||
Icon: IconHierarchy2,
|
||||
isHidden: !permissionMap[SettingsPermissions.DATA_MODEL],
|
||||
isHidden: !permissionMap[SettingPermissionType.DATA_MODEL],
|
||||
},
|
||||
{
|
||||
label: t`Integrations`,
|
||||
path: SettingsPath.Integrations,
|
||||
Icon: IconApps,
|
||||
isHidden: !permissionMap[SettingsPermissions.API_KEYS_AND_WEBHOOKS],
|
||||
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
|
||||
},
|
||||
{
|
||||
label: t`Security`,
|
||||
path: SettingsPath.Security,
|
||||
Icon: IconKey,
|
||||
isAdvanced: true,
|
||||
isHidden: !permissionMap[SettingsPermissions.SECURITY],
|
||||
isHidden: !permissionMap[SettingPermissionType.SECURITY],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -161,14 +162,14 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
path: SettingsPath.APIs,
|
||||
Icon: IconApi,
|
||||
isAdvanced: true,
|
||||
isHidden: !permissionMap[SettingsPermissions.API_KEYS_AND_WEBHOOKS],
|
||||
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
|
||||
},
|
||||
{
|
||||
label: t`Webhooks`,
|
||||
path: SettingsPath.Webhooks,
|
||||
Icon: IconWebhook,
|
||||
isAdvanced: true,
|
||||
isHidden: !permissionMap[SettingsPermissions.API_KEYS_AND_WEBHOOKS],
|
||||
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
|
||||
},
|
||||
{
|
||||
label: t`Functions`,
|
||||
@ -194,7 +195,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
Icon: IconFlask,
|
||||
isHidden:
|
||||
!labPublicFeatureFlags.length ||
|
||||
!permissionMap[SettingsPermissions.WORKSPACE],
|
||||
!permissionMap[SettingPermissionType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Releases`,
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsPermissions } from '~/generated/graphql';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { SettingPermissionType } from '~/generated/graphql';
|
||||
|
||||
export const useHasSettingsPermission = (
|
||||
settingsPermission?: SettingsPermissions,
|
||||
settingsPermission?: SettingPermissionType,
|
||||
) => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
|
||||
@ -15,19 +15,18 @@ export const useHasSettingsPermission = (
|
||||
}
|
||||
|
||||
if (
|
||||
settingsPermission === SettingsPermissions.WORKSPACE &&
|
||||
settingsPermission === SettingPermissionType.WORKSPACE &&
|
||||
currentWorkspace?.activationStatus ===
|
||||
WorkspaceActivationStatus.PENDING_CREATION
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const currentUserWorkspaceSettingsPermissions =
|
||||
currentUserWorkspace?.settingsPermissions;
|
||||
const currentUserWorkspaceSetting = currentUserWorkspace?.settingsPermissions;
|
||||
|
||||
if (!currentUserWorkspaceSettingsPermissions) {
|
||||
if (!currentUserWorkspaceSetting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentUserWorkspaceSettingsPermissions.includes(settingsPermission);
|
||||
return currentUserWorkspaceSetting.includes(settingsPermission);
|
||||
};
|
||||
|
||||
@ -2,11 +2,11 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceSta
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { SettingsPermissions } from '~/generated/graphql';
|
||||
import { SettingPermissionType } from '~/generated/graphql';
|
||||
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
||||
|
||||
export const useSettingsPermissionMap = (): Record<
|
||||
SettingsPermissions,
|
||||
SettingPermissionType,
|
||||
boolean
|
||||
> => {
|
||||
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
|
||||
@ -19,7 +19,7 @@ export const useSettingsPermissionMap = (): Record<
|
||||
currentUserWorkspace?.settingsPermissions;
|
||||
|
||||
const initialPermissions = buildRecordFromKeysWithSameValue(
|
||||
Object.values(SettingsPermissions),
|
||||
Object.values(SettingPermissionType),
|
||||
!isPermissionEnabled,
|
||||
);
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ import {
|
||||
Section,
|
||||
} from 'twenty-ui';
|
||||
import { Role } from '~/generated-metadata/graphql';
|
||||
import { SettingsPermissions } from '~/generated/graphql';
|
||||
import { SettingPermissionType } from '~/generated/graphql';
|
||||
import { RolePermissionsObjectsTableRow } from './RolePermissionsObjectsTableRow';
|
||||
|
||||
const StyledRolePermissionsContainer = styled.div`
|
||||
@ -81,49 +81,49 @@ export const RolePermissions = ({ role }: RolePermissionsProps) => {
|
||||
|
||||
const settingsPermissionsConfig: RolePermissionsSettingPermission[] = [
|
||||
{
|
||||
key: SettingsPermissions.API_KEYS_AND_WEBHOOKS,
|
||||
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||
name: 'API Keys & Webhooks',
|
||||
description: 'Manage API keys and webhooks',
|
||||
value: role.canUpdateAllSettings,
|
||||
Icon: IconCode,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.WORKSPACE,
|
||||
key: SettingPermissionType.WORKSPACE,
|
||||
name: 'Workspace',
|
||||
description: 'Set global workspace preferences',
|
||||
value: role.canUpdateAllSettings,
|
||||
Icon: IconSettings,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.WORKSPACE_MEMBERS,
|
||||
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
||||
name: 'Users',
|
||||
description: 'Add or remove users',
|
||||
value: role.canUpdateAllSettings,
|
||||
Icon: IconUsers,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.ROLES,
|
||||
key: SettingPermissionType.ROLES,
|
||||
name: 'Roles',
|
||||
description: 'Define user roles and access levels',
|
||||
value: role.canUpdateAllSettings,
|
||||
Icon: IconLockOpen,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.DATA_MODEL,
|
||||
key: SettingPermissionType.DATA_MODEL,
|
||||
name: 'Data Model',
|
||||
description: 'Edit CRM data structure and fields',
|
||||
value: role.canUpdateAllSettings,
|
||||
Icon: IconHierarchy,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.ADMIN_PANEL,
|
||||
key: SettingPermissionType.ADMIN_PANEL,
|
||||
name: 'Admin Panel',
|
||||
description: 'Admin settings and system tools',
|
||||
value: role.canUpdateAllSettings,
|
||||
Icon: IconServer,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.SECURITY,
|
||||
key: SettingPermissionType.SECURITY,
|
||||
name: 'Security',
|
||||
description: 'Manage security policies',
|
||||
value: role.canUpdateAllSettings,
|
||||
|
||||
@ -3,7 +3,7 @@ import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
OnboardingStatus,
|
||||
SettingsPermissions,
|
||||
SettingPermissionType,
|
||||
SubscriptionInterval,
|
||||
SubscriptionStatus,
|
||||
User,
|
||||
@ -131,7 +131,7 @@ export const mockedUserData: MockedUser = {
|
||||
workspaceMember: mockedWorkspaceMemberData,
|
||||
currentWorkspace: mockCurrentWorkspace,
|
||||
currentUserWorkspace: {
|
||||
settingsPermissions: [SettingsPermissions.WORKSPACE_MEMBERS],
|
||||
settingsPermissions: [SettingPermissionType.WORKSPACE_MEMBERS],
|
||||
},
|
||||
locale: 'en',
|
||||
workspaces: [{ workspace: mockCurrentWorkspace }],
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RenamePermissionTables1742488572894 implements MigrationInterface {
|
||||
name = 'RenamePermissionTables1742488572894';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "metadata"."settingsPermissions"`);
|
||||
await queryRunner.query(`DROP TABLE "metadata"."objectPermissions"`);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "metadata"."objectPermission" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "roleId" uuid NOT NULL, "objectMetadataId" uuid NOT NULL, "canReadObjectRecords" boolean, "canUpdateObjectRecords" boolean, "canSoftDeleteObjectRecords" boolean, "canDestroyObjectRecords" boolean, "workspaceId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "IndexOnObjectPermissionUnique" UNIQUE ("objectMetadataId", "roleId"), CONSTRAINT "PK_23a4033c1aa380d0d1431731add" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "metadata"."settingPermission" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "roleId" uuid NOT NULL, "setting" character varying NOT NULL, "canUpdateSetting" boolean, "workspaceId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "IndexOnSettingPermissionUnique" UNIQUE ("setting", "roleId"), CONSTRAINT "PK_8c144a021030d7e3326835a04c8" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."objectPermission" ADD CONSTRAINT "FK_826052747c82e59f0a006204256" FOREIGN KEY ("roleId") REFERENCES "metadata"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."objectPermission" ADD CONSTRAINT "FK_efbcf3528718de2b5c45c0a8a83" FOREIGN KEY ("objectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."settingPermission" ADD CONSTRAINT "FK_b327aadd9fd189f33d2c5237833" FOREIGN KEY ("roleId") REFERENCES "metadata"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."settingPermission" DROP CONSTRAINT "FK_b327aadd9fd189f33d2c5237833"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."objectPermission" DROP CONSTRAINT "FK_efbcf3528718de2b5c45c0a8a83"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."objectPermission" DROP CONSTRAINT "FK_826052747c82e59f0a006204256"`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "metadata"."settingPermission"`);
|
||||
await queryRunner.query(`DROP TABLE "metadata"."objectPermission"`);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
|
||||
export const SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS = {
|
||||
apiKey: SettingsPermissions.API_KEYS_AND_WEBHOOKS,
|
||||
webhook: SettingsPermissions.API_KEYS_AND_WEBHOOKS,
|
||||
apiKey: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||
webhook: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||
} as const;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import graphqlFields from 'graphql-fields';
|
||||
import { DataSource, ObjectLiteral } from 'typeorm';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
@ -27,7 +27,7 @@ import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-quer
|
||||
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -190,7 +190,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
)
|
||||
) {
|
||||
const permissionRequired: SettingsPermissions =
|
||||
const permissionRequired: SettingPermissionType =
|
||||
SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS[
|
||||
objectMetadataItemWithFieldMaps.nameSingular
|
||||
];
|
||||
|
||||
@ -3,8 +3,8 @@ import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import omit from 'lodash.omit';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ApiKeyTokenInput } from 'src/engine/core-modules/auth/dto/api-key-token.input';
|
||||
import { AppTokenInput } from 'src/engine/core-modules/auth/dto/app-token.input';
|
||||
@ -29,6 +29,7 @@ import { GetAuthorizationUrlForSSOOutput } from 'src/engine/core-modules/auth/dt
|
||||
import { GetLoginTokenFromEmailVerificationTokenInput } from 'src/engine/core-modules/auth/dto/get-login-token-from-email-verification-token.input';
|
||||
import { SignUpOutput } from 'src/engine/core-modules/auth/dto/sign-up.output';
|
||||
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { EmailVerificationTokenService } from 'src/engine/core-modules/auth/token/services/email-verification-token.service';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { RenewTokenService } from 'src/engine/core-modules/auth/token/services/renew-token.service';
|
||||
@ -49,9 +50,8 @@ import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
|
||||
import { GetAuthTokensFromLoginTokenInput } from './dto/get-auth-tokens-from-login-token.input';
|
||||
import { GetLoginTokenFromCredentialsInput } from './dto/get-login-token-from-credentials.input';
|
||||
@ -367,7 +367,7 @@ export class AuthResolver {
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingsPermissions.API_KEYS_AND_WEBHOOKS),
|
||||
SettingsPermissionsGuard(SettingPermissionType.API_KEYS_AND_WEBHOOKS),
|
||||
)
|
||||
@Mutation(() => ApiKeyToken)
|
||||
async generateApiKeyToken(
|
||||
|
||||
@ -28,7 +28,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -52,7 +52,7 @@ export class BillingResolver {
|
||||
@Query(() => BillingSessionOutput)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE),
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
)
|
||||
async billingPortalSession(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@ -115,7 +115,7 @@ export class BillingResolver {
|
||||
@Mutation(() => BillingUpdateOutput)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE),
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
)
|
||||
async updateBillingSubscription(@AuthWorkspace() workspace: Workspace) {
|
||||
await this.billingSubscriptionService.applyBillingSubscription(workspace);
|
||||
@ -161,7 +161,7 @@ export class BillingResolver {
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
_setting: SettingsPermissions.WORKSPACE,
|
||||
_setting: SettingPermissionType.WORKSPACE,
|
||||
isExecutedByApiKey,
|
||||
});
|
||||
|
||||
|
||||
@ -11,12 +11,12 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
|
||||
@Resolver()
|
||||
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.WORKSPACE))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.WORKSPACE))
|
||||
export class LabResolver {
|
||||
constructor(private featureFlagService: FeatureFlagService) {}
|
||||
|
||||
|
||||
@ -20,12 +20,12 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
|
||||
@Resolver()
|
||||
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.SECURITY))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.SECURITY))
|
||||
export class SSOResolver {
|
||||
constructor(private readonly sSOService: SSOService) {}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
@ -14,16 +15,15 @@ import {
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { TwoFactorMethod } from 'src/engine/core-modules/two-factor-method/two-factor-method.entity';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
|
||||
registerEnumType(SettingsPermissions, {
|
||||
name: 'SettingsPermissions',
|
||||
registerEnumType(SettingPermissionType, {
|
||||
name: 'SettingPermissionType',
|
||||
});
|
||||
|
||||
registerEnumType(PermissionsOnAllObjectRecords, {
|
||||
@ -78,8 +78,8 @@ export class UserWorkspace {
|
||||
)
|
||||
twoFactorMethods: Relation<TwoFactorMethod[]>;
|
||||
|
||||
@Field(() => [SettingsPermissions], { nullable: true })
|
||||
settingsPermissions?: SettingsPermissions[];
|
||||
@Field(() => [SettingPermissionType], { nullable: true })
|
||||
settingsPermissions?: SettingPermissionType[];
|
||||
|
||||
@Field(() => [PermissionsOnAllObjectRecords], { nullable: true })
|
||||
objectRecordsPermissions?: PermissionsOnAllObjectRecords[];
|
||||
|
||||
@ -13,8 +13,8 @@ import crypto from 'crypto';
|
||||
|
||||
import { GraphQLJSONObject } from 'graphql-type-json';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
@ -48,7 +48,7 @@ import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
@ -122,8 +122,8 @@ export class UserResolver {
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
const grantedSettingsPermissions: SettingsPermissions[] = (
|
||||
Object.keys(settingsPermissions) as SettingsPermissions[]
|
||||
const grantedSettingsPermissions: SettingPermissionType[] = (
|
||||
Object.keys(settingsPermissions) as SettingPermissionType[]
|
||||
).filter((feature) => settingsPermissions[feature] === true);
|
||||
|
||||
const grantedObjectRecordsPermissions = (
|
||||
|
||||
@ -12,14 +12,14 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
|
||||
import { SendInvitationsInput } from './dtos/send-invitations.input';
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE_MEMBERS),
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE_MEMBERS),
|
||||
)
|
||||
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||
@Resolver()
|
||||
|
||||
@ -4,9 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import assert from 'assert';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
@ -34,7 +34,7 @@ import {
|
||||
WorkspaceExceptionCode,
|
||||
} from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -442,7 +442,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
const userHasPermission =
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
_setting: SettingsPermissions.SECURITY,
|
||||
_setting: SettingPermissionType.SECURITY,
|
||||
workspaceId: workspaceId,
|
||||
isExecutedByApiKey: isDefined(apiKey),
|
||||
});
|
||||
@ -481,7 +481,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
_setting: SettingsPermissions.WORKSPACE,
|
||||
_setting: SettingPermissionType.WORKSPACE,
|
||||
isExecutedByApiKey: isDefined(apiKey),
|
||||
});
|
||||
|
||||
|
||||
@ -12,8 +12,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import assert from 'assert';
|
||||
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
import { Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
@ -47,7 +47,7 @@ import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
@ -130,7 +130,7 @@ export class WorkspaceResolver {
|
||||
@Mutation(() => String)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE),
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
)
|
||||
async uploadWorkspaceLogo(
|
||||
@AuthWorkspace() { id }: Workspace,
|
||||
@ -174,7 +174,7 @@ export class WorkspaceResolver {
|
||||
@Mutation(() => Workspace)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE),
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
)
|
||||
async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) {
|
||||
return this.workspaceService.deleteWorkspace(id);
|
||||
|
||||
@ -11,7 +11,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -20,7 +20,7 @@ import {
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
|
||||
export const SettingsPermissionsGuard = (
|
||||
requiredPermission: SettingsPermissions,
|
||||
requiredPermission: SettingPermissionType,
|
||||
): Type<CanActivate> => {
|
||||
@Injectable()
|
||||
class SettingsPermissionsMixin implements CanActivate {
|
||||
|
||||
@ -36,7 +36,7 @@ import {
|
||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||
import { BeforeUpdateOneField } from 'src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook';
|
||||
import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
|
||||
@ -74,6 +74,7 @@ export class FieldMetadataResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||
@ResolveField(() => String, { nullable: true })
|
||||
async icon(
|
||||
@Parent() fieldMetadata: FieldMetadataDTO,
|
||||
@ -86,7 +87,7 @@ export class FieldMetadataResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||
@Mutation(() => FieldMetadataDTO)
|
||||
async createOneField(
|
||||
@Args('input') input: CreateOneFieldMetadataInput,
|
||||
@ -102,7 +103,7 @@ export class FieldMetadataResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||
@Mutation(() => FieldMetadataDTO)
|
||||
async updateOneField(
|
||||
@Args('input') input: UpdateOneFieldMetadataInput,
|
||||
@ -123,7 +124,7 @@ export class FieldMetadataResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||
@Mutation(() => FieldMetadataDTO)
|
||||
async deleteOneField(
|
||||
@Args('input') input: DeleteOneFieldInput,
|
||||
|
||||
@ -17,7 +17,7 @@ import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-s
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { ObjectStandardOverridesDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-standard-overrides.dto';
|
||||
import { ObjectPermissionsEntity } from 'src/engine/metadata-modules/object-permissions/object-permissions.entity';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
|
||||
@Entity('objectMetadata')
|
||||
@ -142,9 +142,12 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
|
||||
updatedAt: Date;
|
||||
|
||||
@OneToMany(
|
||||
() => ObjectPermissionsEntity,
|
||||
(objectPermissions: ObjectPermissionsEntity) =>
|
||||
objectPermissions.objectMetadata,
|
||||
() => ObjectPermissionEntity,
|
||||
(objectPermission: ObjectPermissionEntity) =>
|
||||
objectPermission.objectMetadata,
|
||||
{
|
||||
cascade: true,
|
||||
},
|
||||
)
|
||||
objectPermissions: Relation<ObjectPermissionsEntity[]>;
|
||||
objectPermissions: Relation<ObjectPermissionEntity[]>;
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metad
|
||||
import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service';
|
||||
import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service';
|
||||
import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
@ -78,7 +78,9 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
guards: [SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL)],
|
||||
guards: [
|
||||
SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL),
|
||||
],
|
||||
},
|
||||
update: { disabled: true },
|
||||
delete: { disabled: true },
|
||||
|
||||
@ -24,7 +24,7 @@ import {
|
||||
import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@ -72,6 +72,7 @@ export class ObjectMetadataResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||
@ResolveField(() => String, { nullable: true })
|
||||
async icon(
|
||||
@Parent() objectMetadata: ObjectMetadataDTO,
|
||||
@ -84,7 +85,7 @@ export class ObjectMetadataResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||
@Mutation(() => ObjectMetadataDTO)
|
||||
async deleteOneObject(
|
||||
@Args('input') input: DeleteOneObjectInput,
|
||||
@ -100,7 +101,7 @@ export class ObjectMetadataResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||
@Mutation(() => ObjectMetadataDTO)
|
||||
async updateOneObject(
|
||||
@Args('input') input: UpdateOneObjectInput,
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType('ObjectPermission')
|
||||
export class ObjectPermissionDTO {
|
||||
@Field({ nullable: false })
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
roleId: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
objectMetadataId: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
canReadObjectRecords?: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
canUpdateObjectRecords?: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
canSoftDeleteObjectRecords?: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
canDestroyObjectRecords?: boolean;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class UpsertObjectPermissionInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
roleId: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
objectMetadataId: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canReadObjectRecords?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canUpdateObjectRecords?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canSoftDeleteObjectRecords?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canDestroyObjectRecords?: boolean;
|
||||
}
|
||||
@ -13,9 +13,9 @@ import {
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
@Entity('objectPermissions')
|
||||
@Unique('IndexOnObjectPermissionsUnique', ['objectMetadataId', 'roleId'])
|
||||
export class ObjectPermissionsEntity {
|
||||
@Entity('objectPermission')
|
||||
@Unique('IndexOnObjectPermissionUnique', ['objectMetadataId', 'roleId'])
|
||||
export class ObjectPermissionEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature(
|
||||
[ObjectPermissionEntity, RoleEntity, ObjectMetadataEntity],
|
||||
'metadata',
|
||||
),
|
||||
],
|
||||
providers: [ObjectPermissionService],
|
||||
exports: [ObjectPermissionService],
|
||||
})
|
||||
export class ObjectPermissionModule {}
|
||||
@ -0,0 +1,115 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { UpsertObjectPermissionInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permission-input';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
PermissionsExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
export class ObjectPermissionService {
|
||||
constructor(
|
||||
@InjectRepository(ObjectPermissionEntity, 'metadata')
|
||||
private readonly objectPermissionRepository: Repository<ObjectPermissionEntity>,
|
||||
@InjectRepository(RoleEntity, 'metadata')
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
public async upsertObjectPermission({
|
||||
workspaceId,
|
||||
input,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
input: UpsertObjectPermissionInput;
|
||||
}): Promise<ObjectPermissionEntity | null> {
|
||||
try {
|
||||
const result = await this.objectPermissionRepository.upsert(
|
||||
{
|
||||
workspaceId,
|
||||
...input,
|
||||
},
|
||||
{
|
||||
conflictPaths: ['objectMetadataId', 'roleId'],
|
||||
},
|
||||
);
|
||||
|
||||
const objectPermissionId = result.generatedMaps?.[0]?.id;
|
||||
|
||||
if (!isDefined(objectPermissionId)) {
|
||||
throw new Error('Failed to upsert object permission');
|
||||
}
|
||||
|
||||
return this.objectPermissionRepository.findOne({
|
||||
where: {
|
||||
id: objectPermissionId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
await this.handleForeignKeyError({
|
||||
error,
|
||||
roleId: input.roleId,
|
||||
workspaceId,
|
||||
objectMetadataId: input.objectMetadataId,
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleForeignKeyError({
|
||||
error,
|
||||
roleId,
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
}: {
|
||||
error: Error;
|
||||
roleId: string;
|
||||
workspaceId: string;
|
||||
objectMetadataId: string;
|
||||
}) {
|
||||
if (error.message.includes('violates foreign key constraint')) {
|
||||
const role = await this.getRole(roleId, workspaceId);
|
||||
|
||||
if (!isDefined(role)) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.ROLE_NOT_FOUND,
|
||||
PermissionsExceptionCode.ROLE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
workspaceId,
|
||||
id: objectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.OBJECT_METADATA_NOT_FOUND,
|
||||
PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getRole(
|
||||
roleId: string,
|
||||
workspaceId: string,
|
||||
): Promise<RoleEntity | null> {
|
||||
return this.roleRepository.findOne({
|
||||
where: {
|
||||
id: roleId,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
export enum SettingsPermissions {
|
||||
export enum SettingPermissionType {
|
||||
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
||||
WORKSPACE = 'WORKSPACE',
|
||||
WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS',
|
||||
@ -25,6 +25,9 @@ export enum PermissionsExceptionCode {
|
||||
PERMISSIONS_V2_NOT_ENABLED = 'PERMISSIONS_V2_NOT_ENABLED',
|
||||
ROLE_LABEL_ALREADY_EXISTS = 'ROLE_LABEL_ALREADY_EXISTS',
|
||||
DEFAULT_ROLE_NOT_FOUND = 'DEFAULT_ROLE_NOT_FOUND',
|
||||
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
|
||||
INVALID_SETTING = 'INVALID_SETTING',
|
||||
ROLE_NOT_EDITABLE = 'ROLE_NOT_EDITABLE',
|
||||
}
|
||||
|
||||
export enum PermissionsExceptionMessage {
|
||||
@ -45,4 +48,7 @@ export enum PermissionsExceptionMessage {
|
||||
PERMISSIONS_V2_NOT_ENABLED = 'Permissions V2 is not enabled',
|
||||
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',
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -31,7 +31,7 @@ export class PermissionsService {
|
||||
userWorkspaceId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<{
|
||||
settingsPermissions: Record<SettingsPermissions, boolean>;
|
||||
settingsPermissions: Record<SettingPermissionType, boolean>;
|
||||
objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>;
|
||||
}> {
|
||||
const [roleOfUserWorkspace] = await this.userRoleService
|
||||
@ -47,12 +47,12 @@ export class PermissionsService {
|
||||
hasPermissionOnSettingFeature = true;
|
||||
}
|
||||
|
||||
const settingsPermissionsMap = Object.keys(SettingsPermissions).reduce(
|
||||
const settingsPermissionsMap = Object.keys(SettingPermissionType).reduce(
|
||||
(acc, feature) => ({
|
||||
...acc,
|
||||
[feature]: hasPermissionOnSettingFeature,
|
||||
}),
|
||||
{} as Record<SettingsPermissions, boolean>,
|
||||
{} as Record<SettingPermissionType, boolean>,
|
||||
);
|
||||
|
||||
const objectRecordsPermissionsMap: Record<
|
||||
@ -83,7 +83,7 @@ export class PermissionsService {
|
||||
}: {
|
||||
userWorkspaceId?: string;
|
||||
workspaceId: string;
|
||||
_setting: SettingsPermissions;
|
||||
_setting: SettingPermissionType;
|
||||
isExecutedByApiKey: boolean;
|
||||
}): Promise<boolean> {
|
||||
if (isExecutedByApiKey) {
|
||||
|
||||
@ -19,11 +19,14 @@ export const permissionGraphqlApiExceptionHandler = (
|
||||
case PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER:
|
||||
case PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED:
|
||||
case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS:
|
||||
case PermissionsExceptionCode.ROLE_NOT_EDITABLE:
|
||||
throw new ForbiddenError(error.message);
|
||||
case PermissionsExceptionCode.INVALID_ARG:
|
||||
case PermissionsExceptionCode.INVALID_SETTING:
|
||||
throw new UserInputError(error.message);
|
||||
case PermissionsExceptionCode.ROLE_NOT_FOUND:
|
||||
case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
||||
case PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
case PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND:
|
||||
default:
|
||||
|
||||
@ -13,7 +13,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
|
||||
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||
import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { RelationMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/relation-metadata/interceptors/relation-metadata-graphql-api-exception.interceptor';
|
||||
@ -57,7 +57,9 @@ import { RelationMetadataDTO } from './dtos/relation-metadata.dto';
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
guards: [SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL)],
|
||||
guards: [
|
||||
SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL),
|
||||
],
|
||||
},
|
||||
update: { disabled: true },
|
||||
delete: { disabled: true },
|
||||
|
||||
@ -5,7 +5,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { DeleteOneRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/delete-relation.input';
|
||||
import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto';
|
||||
@ -20,7 +20,7 @@ export class RelationMetadataResolver {
|
||||
private readonly relationMetadataService: RelationMetadataService,
|
||||
) {}
|
||||
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||
@Mutation(() => RelationMetadataDTO)
|
||||
async deleteOneRelation(
|
||||
@Args('input') input: DeleteOneRelationInput,
|
||||
|
||||
@ -9,9 +9,9 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { ObjectPermissionsEntity } from 'src/engine/metadata-modules/object-permissions/object-permissions.entity';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||
import { SettingsPermissionsEntity } from 'src/engine/metadata-modules/settings-permissions/settings-permissions.entity';
|
||||
import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity';
|
||||
|
||||
@Entity('role')
|
||||
@Unique('IndexOnRoleUnique', ['label', 'workspaceId'])
|
||||
@ -62,15 +62,14 @@ export class RoleEntity {
|
||||
userWorkspaceRoles: Relation<UserWorkspaceRoleEntity[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => ObjectPermissionsEntity,
|
||||
(objectPermissions: ObjectPermissionsEntity) => objectPermissions.role,
|
||||
() => ObjectPermissionEntity,
|
||||
(objectPermission: ObjectPermissionEntity) => objectPermission.role,
|
||||
)
|
||||
objectPermissions: Relation<ObjectPermissionsEntity[]>;
|
||||
objectPermissions: Relation<ObjectPermissionEntity[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => SettingsPermissionsEntity,
|
||||
(settingsPermissions: SettingsPermissionsEntity) =>
|
||||
settingsPermissions.role,
|
||||
() => SettingPermissionEntity,
|
||||
(settingPermission: SettingPermissionEntity) => settingPermission.role,
|
||||
)
|
||||
settingsPermissions: Relation<SettingsPermissionsEntity[]>;
|
||||
settingPermissions: Relation<SettingPermissionEntity[]>;
|
||||
}
|
||||
|
||||
@ -4,11 +4,13 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { RoleResolver } from 'src/engine/metadata-modules/role/role.resolver';
|
||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||
import { SettingPermissionModule } from 'src/engine/metadata-modules/setting-permission/setting-permission.module';
|
||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||
|
||||
@Module({
|
||||
@ -19,6 +21,8 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
|
||||
PermissionsModule,
|
||||
UserWorkspaceModule,
|
||||
FeatureFlagModule,
|
||||
ObjectPermissionModule,
|
||||
SettingPermissionModule,
|
||||
],
|
||||
providers: [RoleService, RoleResolver],
|
||||
exports: [RoleService],
|
||||
|
||||
@ -16,22 +16,28 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { UpsertObjectPermissionInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permission-input';
|
||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
PermissionsExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/createRoleInput.dto';
|
||||
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/create-role-input.dto';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { UpdateRoleInput } from 'src/engine/metadata-modules/role/dtos/updateRoleInput.dto';
|
||||
import { UpdateRoleInput } from 'src/engine/metadata-modules/role/dtos/update-role-input.dto';
|
||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto';
|
||||
import { UpsertSettingPermissionInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input';
|
||||
import { SettingPermissionService } from 'src/engine/metadata-modules/setting-permission/setting-permission.service';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Resolver(() => RoleDTO)
|
||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.ROLES))
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.ROLES))
|
||||
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||
export class RoleResolver {
|
||||
constructor(
|
||||
@ -39,6 +45,8 @@ export class RoleResolver {
|
||||
private readonly roleService: RoleService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly objectPermissionService: ObjectPermissionService,
|
||||
private readonly settingPermissionService: SettingPermissionService,
|
||||
) {}
|
||||
|
||||
@Query(() => [RoleDTO])
|
||||
@ -101,18 +109,7 @@ export class RoleResolver {
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('createRoleInput') createRoleInput: CreateRoleInput,
|
||||
): Promise<RoleDTO> {
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
|
||||
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
|
||||
);
|
||||
}
|
||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||
|
||||
return this.roleService.createRole({
|
||||
workspaceId: workspace.id,
|
||||
@ -125,18 +122,11 @@ export class RoleResolver {
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('updateRoleInput') updateRoleInput: UpdateRoleInput,
|
||||
): Promise<RoleDTO> {
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
|
||||
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
|
||||
);
|
||||
}
|
||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||
await this.validateRoleIsEditableOrThrow({
|
||||
roleId: updateRoleInput.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return this.roleService.updateRole({
|
||||
input: updateRoleInput,
|
||||
@ -144,6 +134,42 @@ export class RoleResolver {
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => ObjectPermissionDTO)
|
||||
async upsertOneObjectPermission(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('upsertObjectPermissionInput')
|
||||
upsertObjectPermissionInput: UpsertObjectPermissionInput,
|
||||
) {
|
||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||
await this.validateRoleIsEditableOrThrow({
|
||||
roleId: upsertObjectPermissionInput.roleId,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return this.objectPermissionService.upsertObjectPermission({
|
||||
workspaceId: workspace.id,
|
||||
input: upsertObjectPermissionInput,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => SettingPermissionDTO)
|
||||
async upsertOneSettingPermission(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('upsertSettingPermissionInput')
|
||||
upsertSettingPermissionInput: UpsertSettingPermissionInput,
|
||||
) {
|
||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||
await this.validateRoleIsEditableOrThrow({
|
||||
roleId: upsertSettingPermissionInput.roleId,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return this.settingPermissionService.upsertSettingPermission({
|
||||
workspaceId: workspace.id,
|
||||
input: upsertSettingPermissionInput,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField('workspaceMembers', () => [WorkspaceMember])
|
||||
async getWorkspaceMembersAssignedToRole(
|
||||
@Parent() role: RoleDTO,
|
||||
@ -154,4 +180,36 @@ export class RoleResolver {
|
||||
workspace.id,
|
||||
);
|
||||
}
|
||||
|
||||
private async validatePermissionsV2EnabledOrThrow(workspace: Workspace) {
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
|
||||
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async validateRoleIsEditableOrThrow({
|
||||
roleId,
|
||||
workspaceId,
|
||||
}: {
|
||||
roleId: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const role = await this.roleService.getRoleById(roleId, workspaceId);
|
||||
|
||||
if (!role?.isEditable) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
|
||||
PermissionsExceptionCode.ROLE_NOT_EDITABLE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
|
||||
import { MEMBER_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/member-role-label.constants';
|
||||
@ -10,11 +10,11 @@ import {
|
||||
PermissionsExceptionCode,
|
||||
PermissionsExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/createRoleInput.dto';
|
||||
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/create-role-input.dto';
|
||||
import {
|
||||
UpdateRoleInput,
|
||||
UpdateRolePayload,
|
||||
} from 'src/engine/metadata-modules/role/dtos/updateRoleInput.dto';
|
||||
} from 'src/engine/metadata-modules/role/dtos/update-role-input.dto';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { isArgDefinedIfProvidedOrThrow } from 'src/engine/metadata-modules/utils/is-arg-defined-if-provided-or-throw.util';
|
||||
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
|
||||
@ObjectType('SettingPermission')
|
||||
export class SettingPermissionDTO {
|
||||
@Field({ nullable: false })
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
roleId: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
setting: SettingPermissionType;
|
||||
|
||||
@Field({ nullable: true })
|
||||
canUpdateSetting?: boolean;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
|
||||
@InputType()
|
||||
export class UpsertSettingPermissionInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
roleId: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field({ nullable: false })
|
||||
setting: SettingPermissionType;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canUpdateSetting?: boolean;
|
||||
}
|
||||
@ -10,26 +10,26 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
@Entity('settingsPermissions')
|
||||
@Unique('IndexOnSettingsPermissionsUnique', ['setting', 'roleId'])
|
||||
export class SettingsPermissionsEntity {
|
||||
@Entity('settingPermission')
|
||||
@Unique('IndexOnSettingPermissionUnique', ['setting', 'roleId'])
|
||||
export class SettingPermissionEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
roleId: string;
|
||||
|
||||
@ManyToOne(() => RoleEntity, (role) => role.settingsPermissions, {
|
||||
@ManyToOne(() => RoleEntity, (role) => role.settingPermissions, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'roleId' })
|
||||
role: Relation<RoleEntity>;
|
||||
|
||||
@Column({ nullable: false, type: 'varchar' })
|
||||
setting: SettingsPermissions;
|
||||
setting: SettingPermissionType;
|
||||
|
||||
@Column({ nullable: true, type: 'boolean' })
|
||||
canUpdateSetting?: boolean;
|
||||
@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity';
|
||||
import { SettingPermissionService } from 'src/engine/metadata-modules/setting-permission/setting-permission.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([SettingPermissionEntity, RoleEntity], 'metadata'),
|
||||
],
|
||||
providers: [SettingPermissionService],
|
||||
exports: [SettingPermissionService],
|
||||
})
|
||||
export class SettingPermissionModule {}
|
||||
@ -0,0 +1,79 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
PermissionsExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { UpsertSettingPermissionInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input';
|
||||
import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity';
|
||||
|
||||
export class SettingPermissionService {
|
||||
constructor(
|
||||
@InjectRepository(SettingPermissionEntity, 'metadata')
|
||||
private readonly settingPermissionRepository: Repository<SettingPermissionEntity>,
|
||||
@InjectRepository(RoleEntity, 'metadata')
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
) {}
|
||||
|
||||
public async upsertSettingPermission({
|
||||
workspaceId,
|
||||
input,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
input: UpsertSettingPermissionInput;
|
||||
}): Promise<SettingPermissionEntity | null | undefined> {
|
||||
if (!Object.values(SettingPermissionType).includes(input.setting)) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.INVALID_SETTING,
|
||||
PermissionsExceptionCode.INVALID_SETTING,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.settingPermissionRepository.upsert(
|
||||
{
|
||||
workspaceId,
|
||||
...input,
|
||||
},
|
||||
{
|
||||
conflictPaths: ['setting', 'roleId'],
|
||||
},
|
||||
);
|
||||
|
||||
const settingPermissionId = result.generatedMaps?.[0]?.id;
|
||||
|
||||
if (!isDefined(settingPermissionId)) {
|
||||
throw new Error('Failed to upsert setting permission');
|
||||
}
|
||||
|
||||
return this.settingPermissionRepository.findOne({
|
||||
where: {
|
||||
id: settingPermissionId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes('violates foreign key constraint')) {
|
||||
const role = await this.roleRepository.findOne({
|
||||
where: {
|
||||
id: input.roleId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(role)) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.ROLE_NOT_FOUND,
|
||||
PermissionsExceptionCode.ROLE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -65,7 +65,7 @@ export class WorkspaceMemberPreQueryHookService {
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
_setting: SettingsPermissions.WORKSPACE_MEMBERS,
|
||||
_setting: SettingPermissionType.WORKSPACE_MEMBERS,
|
||||
isExecutedByApiKey: isDefined(apiKey),
|
||||
})
|
||||
) {
|
||||
|
||||
@ -1,15 +1,41 @@
|
||||
import request from 'supertest';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
|
||||
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
|
||||
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
|
||||
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
|
||||
const client = request(`http://localhost:${APP_PORT}`);
|
||||
|
||||
async function assertPermissionDeniedForMemberWithMemberRole({
|
||||
query,
|
||||
}: {
|
||||
query: { query: string };
|
||||
}) {
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||
.send(query)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.data).toBeNull();
|
||||
expect(res.body.errors).toBeDefined();
|
||||
expect(res.body.errors[0].message).toBe(
|
||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||
);
|
||||
expect(res.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
|
||||
});
|
||||
}
|
||||
|
||||
describe('roles permissions', () => {
|
||||
let adminRoleId: string;
|
||||
let guestRoleId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const enablePermissionsQuery = updateFeatureFlagFactory(
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
@ -17,7 +43,38 @@ describe('roles permissions', () => {
|
||||
true,
|
||||
);
|
||||
|
||||
const enablePermissionsV2Query = updateFeatureFlagFactory(
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
'IsPermissionsV2Enabled',
|
||||
true,
|
||||
);
|
||||
|
||||
await makeGraphqlAPIRequest(enablePermissionsQuery);
|
||||
await makeGraphqlAPIRequest(enablePermissionsV2Query);
|
||||
|
||||
const query = {
|
||||
query: `
|
||||
query GetRoles {
|
||||
getRoles {
|
||||
label
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
const resp = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||
.send(query);
|
||||
|
||||
adminRoleId = resp.body.data.getRoles.find(
|
||||
(role) => role.label === 'Admin',
|
||||
).id;
|
||||
|
||||
guestRoleId = resp.body.data.getRoles.find(
|
||||
(role) => role.label === 'Guest',
|
||||
).id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -27,7 +84,14 @@ describe('roles permissions', () => {
|
||||
false,
|
||||
);
|
||||
|
||||
const disablePermissionsV2Query = updateFeatureFlagFactory(
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
'IsPermissionsV2Enabled',
|
||||
false,
|
||||
);
|
||||
|
||||
await makeGraphqlAPIRequest(disablePermissionsQuery);
|
||||
await makeGraphqlAPIRequest(disablePermissionsV2Query);
|
||||
});
|
||||
|
||||
describe('getRoles', () => {
|
||||
@ -116,19 +180,7 @@ describe('roles permissions', () => {
|
||||
`,
|
||||
};
|
||||
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||
.send(query)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.data).toBeNull();
|
||||
expect(res.body.errors).toBeDefined();
|
||||
expect(res.body.errors[0].message).toBe(
|
||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||
);
|
||||
expect(res.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
|
||||
});
|
||||
await assertPermissionDeniedForMemberWithMemberRole({ query });
|
||||
});
|
||||
});
|
||||
|
||||
@ -144,19 +196,7 @@ describe('roles permissions', () => {
|
||||
`,
|
||||
};
|
||||
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||
.send(query)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.data).toBeNull();
|
||||
expect(res.body.errors).toBeDefined();
|
||||
expect(res.body.errors[0].message).toBe(
|
||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||
);
|
||||
expect(res.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
|
||||
});
|
||||
await assertPermissionDeniedForMemberWithMemberRole({ query });
|
||||
});
|
||||
|
||||
it('should throw a permission error when tries to update their own role (admin role)', async () => {
|
||||
@ -260,4 +300,244 @@ describe('roles permissions', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createRole', () => {
|
||||
it('should throw a permission error when user does not have permission to create roles (member role)', async () => {
|
||||
const query = {
|
||||
query: `
|
||||
mutation CreateOneRole {
|
||||
createOneRole(createRoleInput: {label: "test-role"}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
await assertPermissionDeniedForMemberWithMemberRole({ query });
|
||||
});
|
||||
|
||||
// TODO - to uncomment after deleteOneRole has been implemented
|
||||
// it('should create a role when user has permission to create a role (admin role)', async () => {
|
||||
// const query = {
|
||||
// query: `
|
||||
// mutation CreateOneRole {
|
||||
// createOneRole(createRoleInput: {label: "Test role"}) {
|
||||
// id
|
||||
// }
|
||||
// }
|
||||
// `,
|
||||
// };
|
||||
|
||||
// await client
|
||||
// .post('/graphql')
|
||||
// .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||
// .send(query)
|
||||
// .expect(200)
|
||||
// .expect((res) => {
|
||||
// expect(res.body.data).toBeDefined();
|
||||
// expect(res.body.errors).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
describe('updateRole', () => {
|
||||
// let createdEditableRoleId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
// TODO - to uncomment after deleteOneRole has been implemented
|
||||
// const query = {
|
||||
// query: `
|
||||
// mutation CreateOneRole {
|
||||
// createOneRole(createRoleInput: {label: "Test role 2"}) {
|
||||
// id
|
||||
// }
|
||||
// }
|
||||
// `,
|
||||
// };
|
||||
// await client
|
||||
// .post('/graphql')
|
||||
// .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||
// .send(query)
|
||||
// .then((res) => {
|
||||
// createdEditableRoleId = res.body.data.createOneRole.id;
|
||||
// });
|
||||
});
|
||||
|
||||
describe('updateRole', () => {
|
||||
it('should throw a permission error when user does not have permission to update roles (member role)', async () => {
|
||||
const query = {
|
||||
query: `
|
||||
mutation UpdateOneRole {
|
||||
updateOneRole(updateRoleInput: {id: "test-role-id", update: {label: "new-role-label"}}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
await assertPermissionDeniedForMemberWithMemberRole({ query });
|
||||
});
|
||||
|
||||
it('should throw an error when role is not editable', async () => {
|
||||
const query = {
|
||||
query: `
|
||||
mutation UpdateOneRole {
|
||||
updateOneRole(updateRoleInput: {id: "${adminRoleId}", update: {label: "new-role-label"}}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||
.send(query)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.data).toBeNull();
|
||||
expect(res.body.errors).toBeDefined();
|
||||
expect(res.body.errors[0].message).toBe(
|
||||
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
|
||||
);
|
||||
expect(res.body.errors[0].extensions.code).toBe(
|
||||
ErrorCode.FORBIDDEN,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('upsertObjectPermission', () => {
|
||||
let listingObjectId = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
});
|
||||
|
||||
const upsertObjectPermissionMutation = ({
|
||||
objectMetadataId,
|
||||
roleId,
|
||||
}: {
|
||||
objectMetadataId: string;
|
||||
roleId: string;
|
||||
}) => `
|
||||
mutation UpsertObjectPermissions {
|
||||
upsertOneObjectPermission(upsertObjectPermissionInput: {objectMetadataId: "${objectMetadataId}", roleId: "${roleId}", canUpdateObjectRecords: true}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
it('should throw a permission error when user does not have permission to upsert object permission (member role)', async () => {
|
||||
const query = {
|
||||
query: upsertObjectPermissionMutation({
|
||||
objectMetadataId: listingObjectId,
|
||||
roleId: guestRoleId,
|
||||
}),
|
||||
};
|
||||
|
||||
await assertPermissionDeniedForMemberWithMemberRole({ query });
|
||||
});
|
||||
|
||||
it('should throw an error when role is not editable', async () => {
|
||||
const query = {
|
||||
query: upsertObjectPermissionMutation({
|
||||
objectMetadataId: listingObjectId,
|
||||
roleId: adminRoleId,
|
||||
}),
|
||||
};
|
||||
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||
.send(query)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.data).toBeNull();
|
||||
expect(res.body.errors).toBeDefined();
|
||||
expect(res.body.errors[0].message).toBe(
|
||||
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
|
||||
);
|
||||
expect(res.body.errors[0].extensions.code).toBe(
|
||||
ErrorCode.FORBIDDEN,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO - to uncomment after deleteOneRole has been implemented
|
||||
// it('should upsert a setting permission when user has permission to create a setting permission', async () => {
|
||||
// const query = {
|
||||
// query: upsertObjectPermissionMutation({
|
||||
// objectMetadataId: listingObjectId,
|
||||
// roleId: createdEditableRoleId,
|
||||
// }),
|
||||
// };
|
||||
|
||||
// await client
|
||||
// .post('/graphql')
|
||||
// .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||
// .send(query)
|
||||
// .expect(200)
|
||||
// .expect((res) => {
|
||||
// expect(res.body.data).toBeDefined();
|
||||
// expect(res.body.errors).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
describe('upsertSettingPermission', () => {
|
||||
const upsertSettingPermissionMutation = ({
|
||||
roleId,
|
||||
}: {
|
||||
roleId: string;
|
||||
}) => `
|
||||
mutation UpsertSettingPermissions {
|
||||
upsertOneSettingPermission(upsertSettingPermissionInput: {roleId: "${roleId}", setting: ${SettingPermissionType.DATA_MODEL}}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
it('should throw a permission error when user does not have permission to upsert object permission (member role)', async () => {
|
||||
const query = {
|
||||
query: upsertSettingPermissionMutation({
|
||||
roleId: guestRoleId,
|
||||
}),
|
||||
};
|
||||
|
||||
await assertPermissionDeniedForMemberWithMemberRole({ query });
|
||||
});
|
||||
|
||||
it('should throw an error when role is not editable', async () => {
|
||||
const query = {
|
||||
query: upsertSettingPermissionMutation({
|
||||
roleId: adminRoleId,
|
||||
}),
|
||||
};
|
||||
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||
.send(query)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.data).toBeNull();
|
||||
expect(res.body.errors).toBeDefined();
|
||||
expect(res.body.errors[0].message).toBe(
|
||||
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
|
||||
);
|
||||
expect(res.body.errors[0].extensions.code).toBe(
|
||||
ErrorCode.FORBIDDEN,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user