[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'];
|
uploadImage: Scalars['String'];
|
||||||
uploadProfilePicture: Scalars['String'];
|
uploadProfilePicture: Scalars['String'];
|
||||||
uploadWorkspaceLogo: Scalars['String'];
|
uploadWorkspaceLogo: Scalars['String'];
|
||||||
|
upsertOneObjectPermission: ObjectPermission;
|
||||||
|
upsertOneSettingPermission: SettingPermission;
|
||||||
userLookupAdminPanel: UserLookup;
|
userLookupAdminPanel: UserLookup;
|
||||||
validateApprovedAccessDomain: ApprovedAccessDomain;
|
validateApprovedAccessDomain: ApprovedAccessDomain;
|
||||||
};
|
};
|
||||||
@ -1164,6 +1166,16 @@ export type MutationUploadWorkspaceLogoArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationUpsertOneObjectPermissionArgs = {
|
||||||
|
upsertObjectPermissionInput: UpsertObjectPermissionInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationUpsertOneSettingPermissionArgs = {
|
||||||
|
upsertSettingPermissionInput: UpsertSettingPermissionInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationUserLookupAdminPanelArgs = {
|
export type MutationUserLookupAdminPanelArgs = {
|
||||||
userIdentifier: Scalars['String'];
|
userIdentifier: Scalars['String'];
|
||||||
};
|
};
|
||||||
@ -1255,6 +1267,17 @@ export type ObjectIndexMetadatasConnection = {
|
|||||||
pageInfo: PageInfo;
|
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 = {
|
export type ObjectRecordFilterInput = {
|
||||||
and?: InputMaybe<Array<ObjectRecordFilterInput>>;
|
and?: InputMaybe<Array<ObjectRecordFilterInput>>;
|
||||||
createdAt?: InputMaybe<DateFilter>;
|
createdAt?: InputMaybe<DateFilter>;
|
||||||
@ -1715,7 +1738,15 @@ export enum ServerlessFunctionSyncStatus {
|
|||||||
READY = 'READY'
|
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',
|
ADMIN_PANEL = 'ADMIN_PANEL',
|
||||||
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
||||||
DATA_MODEL = 'DATA_MODEL',
|
DATA_MODEL = 'DATA_MODEL',
|
||||||
@ -1988,6 +2019,21 @@ export type UpdateWorkspaceInput = {
|
|||||||
subdomain?: InputMaybe<Scalars['String']>;
|
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 = {
|
export type User = {
|
||||||
__typename?: 'User';
|
__typename?: 'User';
|
||||||
analyticsTinybirdJwts?: Maybe<AnalyticsTinybirdJwtMap>;
|
analyticsTinybirdJwts?: Maybe<AnalyticsTinybirdJwtMap>;
|
||||||
@ -2062,7 +2108,7 @@ export type UserWorkspace = {
|
|||||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>;
|
objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>;
|
||||||
settingsPermissions?: Maybe<Array<SettingsPermissions>>;
|
settingsPermissions?: Maybe<Array<SettingPermissionType>>;
|
||||||
updatedAt: Scalars['DateTime'];
|
updatedAt: Scalars['DateTime'];
|
||||||
user: User;
|
user: User;
|
||||||
userId: Scalars['String'];
|
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 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; }>;
|
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 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<{
|
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
workflowVersionId: Scalars['String'];
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { SettingsProtectedRouteWrapper } from '@/settings/components/SettingsPro
|
|||||||
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
|
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
import { SettingsPermissions } from '~/generated/graphql';
|
import { SettingPermissionType } from '~/generated/graphql';
|
||||||
|
|
||||||
const SettingsApiKeys = lazy(() =>
|
const SettingsApiKeys = lazy(() =>
|
||||||
import('~/pages/settings/developers/api-keys/SettingsApiKeys').then(
|
import('~/pages/settings/developers/api-keys/SettingsApiKeys').then(
|
||||||
@ -334,7 +334,7 @@ export const SettingsRoutes = ({
|
|||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<SettingsProtectedRouteWrapper
|
<SettingsProtectedRouteWrapper
|
||||||
settingsPermission={SettingsPermissions.WORKSPACE}
|
settingsPermission={SettingPermissionType.WORKSPACE}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -345,7 +345,7 @@ export const SettingsRoutes = ({
|
|||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<SettingsProtectedRouteWrapper
|
<SettingsProtectedRouteWrapper
|
||||||
settingsPermission={SettingsPermissions.WORKSPACE_MEMBERS}
|
settingsPermission={SettingPermissionType.WORKSPACE_MEMBERS}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -357,7 +357,7 @@ export const SettingsRoutes = ({
|
|||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<SettingsProtectedRouteWrapper
|
<SettingsProtectedRouteWrapper
|
||||||
settingsPermission={SettingsPermissions.DATA_MODEL}
|
settingsPermission={SettingPermissionType.DATA_MODEL}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -387,7 +387,7 @@ export const SettingsRoutes = ({
|
|||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<SettingsProtectedRouteWrapper
|
<SettingsProtectedRouteWrapper
|
||||||
settingsPermission={SettingsPermissions.ROLES}
|
settingsPermission={SettingPermissionType.ROLES}
|
||||||
requiredFeatureFlag={FeatureFlagKey.IsPermissionsEnabled}
|
requiredFeatureFlag={FeatureFlagKey.IsPermissionsEnabled}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -398,7 +398,7 @@ export const SettingsRoutes = ({
|
|||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<SettingsProtectedRouteWrapper
|
<SettingsProtectedRouteWrapper
|
||||||
settingsPermission={SettingsPermissions.API_KEYS_AND_WEBHOOKS}
|
settingsPermission={SettingPermissionType.API_KEYS_AND_WEBHOOKS}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -465,7 +465,7 @@ export const SettingsRoutes = ({
|
|||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<SettingsProtectedRouteWrapper
|
<SettingsProtectedRouteWrapper
|
||||||
settingsPermission={SettingsPermissions.SECURITY}
|
settingsPermission={SettingPermissionType.SECURITY}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -496,7 +496,7 @@ export const SettingsRoutes = ({
|
|||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<SettingsProtectedRouteWrapper
|
<SettingsProtectedRouteWrapper
|
||||||
settingsPermission={SettingsPermissions.WORKSPACE}
|
settingsPermission={SettingPermissionType.WORKSPACE}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -12,10 +12,10 @@ import { ViewType } from '@/views/types/ViewType';
|
|||||||
import { useCallback, useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useSetRecoilState } from 'recoil';
|
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 { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { IconEyeOff, IconSettings } from 'twenty-ui';
|
||||||
|
import { SettingPermissionType } from '~/generated/graphql';
|
||||||
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
|
|
||||||
type UseRecordGroupActionsParams = {
|
type UseRecordGroupActionsParams = {
|
||||||
viewType: ViewType;
|
viewType: ViewType;
|
||||||
@ -71,7 +71,7 @@ export const useRecordGroupActions = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const hasAccessToDataModelSettings = useHasSettingsPermission(
|
const hasAccessToDataModelSettings = useHasSettingsPermission(
|
||||||
SettingsPermissions.DATA_MODEL,
|
SettingPermissionType.DATA_MODEL,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordGroupActions: RecordGroupAction[] = [];
|
const recordGroupActions: RecordGroupAction[] = [];
|
||||||
|
|||||||
@ -3,12 +3,12 @@ import { SettingsPath } from '@/types/SettingsPath';
|
|||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Navigate, Outlet } from 'react-router-dom';
|
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';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
type SettingsProtectedRouteWrapperProps = {
|
type SettingsProtectedRouteWrapperProps = {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
settingsPermission?: SettingsPermissions;
|
settingsPermission?: SettingPermissionType;
|
||||||
requiredFeatureFlag?: FeatureFlagKey;
|
requiredFeatureFlag?: FeatureFlagKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
Billing,
|
Billing,
|
||||||
FeatureFlagKey,
|
FeatureFlagKey,
|
||||||
OnboardingStatus,
|
OnboardingStatus,
|
||||||
SettingsPermissions,
|
SettingPermissionType,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
@ -61,12 +61,12 @@ jest.mock('@/workspace/hooks/useFeatureFlagsMap', () => ({
|
|||||||
describe('useSettingsNavigationItems', () => {
|
describe('useSettingsNavigationItems', () => {
|
||||||
it('should hide workspace settings when no permissions', () => {
|
it('should hide workspace settings when no permissions', () => {
|
||||||
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({
|
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({
|
||||||
[SettingsPermissions.WORKSPACE]: false,
|
[SettingPermissionType.WORKSPACE]: false,
|
||||||
[SettingsPermissions.WORKSPACE_MEMBERS]: false,
|
[SettingPermissionType.WORKSPACE_MEMBERS]: false,
|
||||||
[SettingsPermissions.DATA_MODEL]: false,
|
[SettingPermissionType.DATA_MODEL]: false,
|
||||||
[SettingsPermissions.API_KEYS_AND_WEBHOOKS]: false,
|
[SettingPermissionType.API_KEYS_AND_WEBHOOKS]: false,
|
||||||
[SettingsPermissions.ROLES]: false,
|
[SettingPermissionType.ROLES]: false,
|
||||||
[SettingsPermissions.SECURITY]: false,
|
[SettingPermissionType.SECURITY]: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { result } = renderHook(() => useSettingsNavigationItems(), {
|
const { result } = renderHook(() => useSettingsNavigationItems(), {
|
||||||
@ -82,12 +82,12 @@ describe('useSettingsNavigationItems', () => {
|
|||||||
|
|
||||||
it('should show workspace settings when has permissions', () => {
|
it('should show workspace settings when has permissions', () => {
|
||||||
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({
|
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({
|
||||||
[SettingsPermissions.WORKSPACE]: true,
|
[SettingPermissionType.WORKSPACE]: true,
|
||||||
[SettingsPermissions.WORKSPACE_MEMBERS]: true,
|
[SettingPermissionType.WORKSPACE_MEMBERS]: true,
|
||||||
[SettingsPermissions.DATA_MODEL]: true,
|
[SettingPermissionType.DATA_MODEL]: true,
|
||||||
[SettingsPermissions.API_KEYS_AND_WEBHOOKS]: true,
|
[SettingPermissionType.API_KEYS_AND_WEBHOOKS]: true,
|
||||||
[SettingsPermissions.ROLES]: true,
|
[SettingPermissionType.ROLES]: true,
|
||||||
[SettingsPermissions.SECURITY]: true,
|
[SettingPermissionType.SECURITY]: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { result } = renderHook(() => useSettingsNavigationItems(), {
|
const { result } = renderHook(() => useSettingsNavigationItems(), {
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import {
|
|||||||
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
import { SettingsPermissions } from '~/generated/graphql';
|
|
||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { billingState } from '@/client-config/states/billingState';
|
import { billingState } from '@/client-config/states/billingState';
|
||||||
@ -32,6 +31,7 @@ import { NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation
|
|||||||
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
|
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { SettingPermissionType } from '~/generated/graphql';
|
||||||
|
|
||||||
export type SettingsNavigationSection = {
|
export type SettingsNavigationSection = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -108,13 +108,13 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
|||||||
label: t`General`,
|
label: t`General`,
|
||||||
path: SettingsPath.Workspace,
|
path: SettingsPath.Workspace,
|
||||||
Icon: IconSettings,
|
Icon: IconSettings,
|
||||||
isHidden: !permissionMap[SettingsPermissions.WORKSPACE],
|
isHidden: !permissionMap[SettingPermissionType.WORKSPACE],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Members`,
|
label: t`Members`,
|
||||||
path: SettingsPath.WorkspaceMembersPage,
|
path: SettingsPath.WorkspaceMembersPage,
|
||||||
Icon: IconUsers,
|
Icon: IconUsers,
|
||||||
isHidden: !permissionMap[SettingsPermissions.WORKSPACE_MEMBERS],
|
isHidden: !permissionMap[SettingPermissionType.WORKSPACE_MEMBERS],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Roles`,
|
label: t`Roles`,
|
||||||
@ -122,33 +122,34 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
|||||||
Icon: IconLock,
|
Icon: IconLock,
|
||||||
isHidden:
|
isHidden:
|
||||||
!featureFlags[FeatureFlagKey.IsPermissionsEnabled] ||
|
!featureFlags[FeatureFlagKey.IsPermissionsEnabled] ||
|
||||||
!permissionMap[SettingsPermissions.ROLES],
|
!permissionMap[SettingPermissionType.ROLES],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Billing`,
|
label: t`Billing`,
|
||||||
path: SettingsPath.Billing,
|
path: SettingsPath.Billing,
|
||||||
Icon: IconCurrencyDollar,
|
Icon: IconCurrencyDollar,
|
||||||
isHidden:
|
isHidden:
|
||||||
!isBillingEnabled || !permissionMap[SettingsPermissions.WORKSPACE],
|
!isBillingEnabled ||
|
||||||
|
!permissionMap[SettingPermissionType.WORKSPACE],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Data model`,
|
label: t`Data model`,
|
||||||
path: SettingsPath.Objects,
|
path: SettingsPath.Objects,
|
||||||
Icon: IconHierarchy2,
|
Icon: IconHierarchy2,
|
||||||
isHidden: !permissionMap[SettingsPermissions.DATA_MODEL],
|
isHidden: !permissionMap[SettingPermissionType.DATA_MODEL],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Integrations`,
|
label: t`Integrations`,
|
||||||
path: SettingsPath.Integrations,
|
path: SettingsPath.Integrations,
|
||||||
Icon: IconApps,
|
Icon: IconApps,
|
||||||
isHidden: !permissionMap[SettingsPermissions.API_KEYS_AND_WEBHOOKS],
|
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Security`,
|
label: t`Security`,
|
||||||
path: SettingsPath.Security,
|
path: SettingsPath.Security,
|
||||||
Icon: IconKey,
|
Icon: IconKey,
|
||||||
isAdvanced: true,
|
isAdvanced: true,
|
||||||
isHidden: !permissionMap[SettingsPermissions.SECURITY],
|
isHidden: !permissionMap[SettingPermissionType.SECURITY],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -161,14 +162,14 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
|||||||
path: SettingsPath.APIs,
|
path: SettingsPath.APIs,
|
||||||
Icon: IconApi,
|
Icon: IconApi,
|
||||||
isAdvanced: true,
|
isAdvanced: true,
|
||||||
isHidden: !permissionMap[SettingsPermissions.API_KEYS_AND_WEBHOOKS],
|
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Webhooks`,
|
label: t`Webhooks`,
|
||||||
path: SettingsPath.Webhooks,
|
path: SettingsPath.Webhooks,
|
||||||
Icon: IconWebhook,
|
Icon: IconWebhook,
|
||||||
isAdvanced: true,
|
isAdvanced: true,
|
||||||
isHidden: !permissionMap[SettingsPermissions.API_KEYS_AND_WEBHOOKS],
|
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Functions`,
|
label: t`Functions`,
|
||||||
@ -194,7 +195,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
|||||||
Icon: IconFlask,
|
Icon: IconFlask,
|
||||||
isHidden:
|
isHidden:
|
||||||
!labPublicFeatureFlags.length ||
|
!labPublicFeatureFlags.length ||
|
||||||
!permissionMap[SettingsPermissions.WORKSPACE],
|
!permissionMap[SettingPermissionType.WORKSPACE],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Releases`,
|
label: t`Releases`,
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { SettingsPermissions } from '~/generated/graphql';
|
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||||
|
import { SettingPermissionType } from '~/generated/graphql';
|
||||||
|
|
||||||
export const useHasSettingsPermission = (
|
export const useHasSettingsPermission = (
|
||||||
settingsPermission?: SettingsPermissions,
|
settingsPermission?: SettingPermissionType,
|
||||||
) => {
|
) => {
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
|
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
|
||||||
@ -15,19 +15,18 @@ export const useHasSettingsPermission = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
settingsPermission === SettingsPermissions.WORKSPACE &&
|
settingsPermission === SettingPermissionType.WORKSPACE &&
|
||||||
currentWorkspace?.activationStatus ===
|
currentWorkspace?.activationStatus ===
|
||||||
WorkspaceActivationStatus.PENDING_CREATION
|
WorkspaceActivationStatus.PENDING_CREATION
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUserWorkspaceSettingsPermissions =
|
const currentUserWorkspaceSetting = currentUserWorkspace?.settingsPermissions;
|
||||||
currentUserWorkspace?.settingsPermissions;
|
|
||||||
|
|
||||||
if (!currentUserWorkspaceSettingsPermissions) {
|
if (!currentUserWorkspaceSetting) {
|
||||||
return false;
|
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 { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
import { SettingsPermissions } from '~/generated/graphql';
|
import { SettingPermissionType } from '~/generated/graphql';
|
||||||
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
||||||
|
|
||||||
export const useSettingsPermissionMap = (): Record<
|
export const useSettingsPermissionMap = (): Record<
|
||||||
SettingsPermissions,
|
SettingPermissionType,
|
||||||
boolean
|
boolean
|
||||||
> => {
|
> => {
|
||||||
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
|
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
|
||||||
@ -19,7 +19,7 @@ export const useSettingsPermissionMap = (): Record<
|
|||||||
currentUserWorkspace?.settingsPermissions;
|
currentUserWorkspace?.settingsPermissions;
|
||||||
|
|
||||||
const initialPermissions = buildRecordFromKeysWithSameValue(
|
const initialPermissions = buildRecordFromKeysWithSameValue(
|
||||||
Object.values(SettingsPermissions),
|
Object.values(SettingPermissionType),
|
||||||
!isPermissionEnabled,
|
!isPermissionEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { Role } from '~/generated-metadata/graphql';
|
import { Role } from '~/generated-metadata/graphql';
|
||||||
import { SettingsPermissions } from '~/generated/graphql';
|
import { SettingPermissionType } from '~/generated/graphql';
|
||||||
import { RolePermissionsObjectsTableRow } from './RolePermissionsObjectsTableRow';
|
import { RolePermissionsObjectsTableRow } from './RolePermissionsObjectsTableRow';
|
||||||
|
|
||||||
const StyledRolePermissionsContainer = styled.div`
|
const StyledRolePermissionsContainer = styled.div`
|
||||||
@ -81,49 +81,49 @@ export const RolePermissions = ({ role }: RolePermissionsProps) => {
|
|||||||
|
|
||||||
const settingsPermissionsConfig: RolePermissionsSettingPermission[] = [
|
const settingsPermissionsConfig: RolePermissionsSettingPermission[] = [
|
||||||
{
|
{
|
||||||
key: SettingsPermissions.API_KEYS_AND_WEBHOOKS,
|
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||||
name: 'API Keys & Webhooks',
|
name: 'API Keys & Webhooks',
|
||||||
description: 'Manage API keys and webhooks',
|
description: 'Manage API keys and webhooks',
|
||||||
value: role.canUpdateAllSettings,
|
value: role.canUpdateAllSettings,
|
||||||
Icon: IconCode,
|
Icon: IconCode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingsPermissions.WORKSPACE,
|
key: SettingPermissionType.WORKSPACE,
|
||||||
name: 'Workspace',
|
name: 'Workspace',
|
||||||
description: 'Set global workspace preferences',
|
description: 'Set global workspace preferences',
|
||||||
value: role.canUpdateAllSettings,
|
value: role.canUpdateAllSettings,
|
||||||
Icon: IconSettings,
|
Icon: IconSettings,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingsPermissions.WORKSPACE_MEMBERS,
|
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
||||||
name: 'Users',
|
name: 'Users',
|
||||||
description: 'Add or remove users',
|
description: 'Add or remove users',
|
||||||
value: role.canUpdateAllSettings,
|
value: role.canUpdateAllSettings,
|
||||||
Icon: IconUsers,
|
Icon: IconUsers,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingsPermissions.ROLES,
|
key: SettingPermissionType.ROLES,
|
||||||
name: 'Roles',
|
name: 'Roles',
|
||||||
description: 'Define user roles and access levels',
|
description: 'Define user roles and access levels',
|
||||||
value: role.canUpdateAllSettings,
|
value: role.canUpdateAllSettings,
|
||||||
Icon: IconLockOpen,
|
Icon: IconLockOpen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingsPermissions.DATA_MODEL,
|
key: SettingPermissionType.DATA_MODEL,
|
||||||
name: 'Data Model',
|
name: 'Data Model',
|
||||||
description: 'Edit CRM data structure and fields',
|
description: 'Edit CRM data structure and fields',
|
||||||
value: role.canUpdateAllSettings,
|
value: role.canUpdateAllSettings,
|
||||||
Icon: IconHierarchy,
|
Icon: IconHierarchy,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingsPermissions.ADMIN_PANEL,
|
key: SettingPermissionType.ADMIN_PANEL,
|
||||||
name: 'Admin Panel',
|
name: 'Admin Panel',
|
||||||
description: 'Admin settings and system tools',
|
description: 'Admin settings and system tools',
|
||||||
value: role.canUpdateAllSettings,
|
value: role.canUpdateAllSettings,
|
||||||
Icon: IconServer,
|
Icon: IconServer,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingsPermissions.SECURITY,
|
key: SettingPermissionType.SECURITY,
|
||||||
name: 'Security',
|
name: 'Security',
|
||||||
description: 'Manage security policies',
|
description: 'Manage security policies',
|
||||||
value: role.canUpdateAllSettings,
|
value: role.canUpdateAllSettings,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
|||||||
import {
|
import {
|
||||||
FeatureFlagKey,
|
FeatureFlagKey,
|
||||||
OnboardingStatus,
|
OnboardingStatus,
|
||||||
SettingsPermissions,
|
SettingPermissionType,
|
||||||
SubscriptionInterval,
|
SubscriptionInterval,
|
||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
User,
|
User,
|
||||||
@ -131,7 +131,7 @@ export const mockedUserData: MockedUser = {
|
|||||||
workspaceMember: mockedWorkspaceMemberData,
|
workspaceMember: mockedWorkspaceMemberData,
|
||||||
currentWorkspace: mockCurrentWorkspace,
|
currentWorkspace: mockCurrentWorkspace,
|
||||||
currentUserWorkspace: {
|
currentUserWorkspace: {
|
||||||
settingsPermissions: [SettingsPermissions.WORKSPACE_MEMBERS],
|
settingsPermissions: [SettingPermissionType.WORKSPACE_MEMBERS],
|
||||||
},
|
},
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
workspaces: [{ workspace: mockCurrentWorkspace }],
|
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 = {
|
export const SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS = {
|
||||||
apiKey: SettingsPermissions.API_KEYS_AND_WEBHOOKS,
|
apiKey: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||||
webhook: SettingsPermissions.API_KEYS_AND_WEBHOOKS,
|
webhook: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import graphqlFields from 'graphql-fields';
|
import graphqlFields from 'graphql-fields';
|
||||||
import { DataSource, ObjectLiteral } from 'typeorm';
|
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
|
||||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
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 { 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';
|
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 { 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 { 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 { 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 {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -190,7 +190,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const permissionRequired: SettingsPermissions =
|
const permissionRequired: SettingPermissionType =
|
||||||
SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS[
|
SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS[
|
||||||
objectMetadataItemWithFieldMaps.nameSingular
|
objectMetadataItemWithFieldMaps.nameSingular
|
||||||
];
|
];
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import omit from 'lodash.omit';
|
import omit from 'lodash.omit';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
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 { ApiKeyTokenInput } from 'src/engine/core-modules/auth/dto/api-key-token.input';
|
||||||
import { AppTokenInput } from 'src/engine/core-modules/auth/dto/app-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 { 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 { SignUpOutput } from 'src/engine/core-modules/auth/dto/sign-up.output';
|
||||||
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
|
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 { 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 { 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';
|
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 { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-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 { 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 { GetAuthTokensFromLoginTokenInput } from './dto/get-auth-tokens-from-login-token.input';
|
||||||
import { GetLoginTokenFromCredentialsInput } from './dto/get-login-token-from-credentials.input';
|
import { GetLoginTokenFromCredentialsInput } from './dto/get-login-token-from-credentials.input';
|
||||||
@ -367,7 +367,7 @@ export class AuthResolver {
|
|||||||
|
|
||||||
@UseGuards(
|
@UseGuards(
|
||||||
WorkspaceAuthGuard,
|
WorkspaceAuthGuard,
|
||||||
SettingsPermissionsGuard(SettingsPermissions.API_KEYS_AND_WEBHOOKS),
|
SettingsPermissionsGuard(SettingPermissionType.API_KEYS_AND_WEBHOOKS),
|
||||||
)
|
)
|
||||||
@Mutation(() => ApiKeyToken)
|
@Mutation(() => ApiKeyToken)
|
||||||
async generateApiKeyToken(
|
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 { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-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 {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -52,7 +52,7 @@ export class BillingResolver {
|
|||||||
@Query(() => BillingSessionOutput)
|
@Query(() => BillingSessionOutput)
|
||||||
@UseGuards(
|
@UseGuards(
|
||||||
WorkspaceAuthGuard,
|
WorkspaceAuthGuard,
|
||||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE),
|
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||||
)
|
)
|
||||||
async billingPortalSession(
|
async billingPortalSession(
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@ -115,7 +115,7 @@ export class BillingResolver {
|
|||||||
@Mutation(() => BillingUpdateOutput)
|
@Mutation(() => BillingUpdateOutput)
|
||||||
@UseGuards(
|
@UseGuards(
|
||||||
WorkspaceAuthGuard,
|
WorkspaceAuthGuard,
|
||||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE),
|
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||||
)
|
)
|
||||||
async updateBillingSubscription(@AuthWorkspace() workspace: Workspace) {
|
async updateBillingSubscription(@AuthWorkspace() workspace: Workspace) {
|
||||||
await this.billingSubscriptionService.applyBillingSubscription(workspace);
|
await this.billingSubscriptionService.applyBillingSubscription(workspace);
|
||||||
@ -161,7 +161,7 @@ export class BillingResolver {
|
|||||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
_setting: SettingsPermissions.WORKSPACE,
|
_setting: SettingPermissionType.WORKSPACE,
|
||||||
isExecutedByApiKey,
|
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 { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-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 { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
|
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.WORKSPACE))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.WORKSPACE))
|
||||||
export class LabResolver {
|
export class LabResolver {
|
||||||
constructor(private featureFlagService: FeatureFlagService) {}
|
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 { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-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 { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.SECURITY))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.SECURITY))
|
||||||
export class SSOResolver {
|
export class SSOResolver {
|
||||||
constructor(private readonly sSOService: SSOService) {}
|
constructor(private readonly sSOService: SSOService) {}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||||
|
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
@ -14,16 +15,15 @@ import {
|
|||||||
Unique,
|
Unique,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
|
||||||
|
|
||||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
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 { TwoFactorMethod } from 'src/engine/core-modules/two-factor-method/two-factor-method.entity';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.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, {
|
registerEnumType(SettingPermissionType, {
|
||||||
name: 'SettingsPermissions',
|
name: 'SettingPermissionType',
|
||||||
});
|
});
|
||||||
|
|
||||||
registerEnumType(PermissionsOnAllObjectRecords, {
|
registerEnumType(PermissionsOnAllObjectRecords, {
|
||||||
@ -78,8 +78,8 @@ export class UserWorkspace {
|
|||||||
)
|
)
|
||||||
twoFactorMethods: Relation<TwoFactorMethod[]>;
|
twoFactorMethods: Relation<TwoFactorMethod[]>;
|
||||||
|
|
||||||
@Field(() => [SettingsPermissions], { nullable: true })
|
@Field(() => [SettingPermissionType], { nullable: true })
|
||||||
settingsPermissions?: SettingsPermissions[];
|
settingsPermissions?: SettingPermissionType[];
|
||||||
|
|
||||||
@Field(() => [PermissionsOnAllObjectRecords], { nullable: true })
|
@Field(() => [PermissionsOnAllObjectRecords], { nullable: true })
|
||||||
objectRecordsPermissions?: PermissionsOnAllObjectRecords[];
|
objectRecordsPermissions?: PermissionsOnAllObjectRecords[];
|
||||||
|
|||||||
@ -13,8 +13,8 @@ import crypto from 'crypto';
|
|||||||
|
|
||||||
import { GraphQLJSONObject } from 'graphql-type-json';
|
import { GraphQLJSONObject } from 'graphql-type-json';
|
||||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||||
import { In, Repository } from 'typeorm';
|
|
||||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||||
|
import { In, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
|
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
|
||||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.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 { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-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 { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
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 { 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 { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||||
@ -122,8 +122,8 @@ export class UserResolver {
|
|||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const grantedSettingsPermissions: SettingsPermissions[] = (
|
const grantedSettingsPermissions: SettingPermissionType[] = (
|
||||||
Object.keys(settingsPermissions) as SettingsPermissions[]
|
Object.keys(settingsPermissions) as SettingPermissionType[]
|
||||||
).filter((feature) => settingsPermissions[feature] === true);
|
).filter((feature) => settingsPermissions[feature] === true);
|
||||||
|
|
||||||
const grantedObjectRecordsPermissions = (
|
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 { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-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 { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
|
|
||||||
import { SendInvitationsInput } from './dtos/send-invitations.input';
|
import { SendInvitationsInput } from './dtos/send-invitations.input';
|
||||||
|
|
||||||
@UseGuards(
|
@UseGuards(
|
||||||
WorkspaceAuthGuard,
|
WorkspaceAuthGuard,
|
||||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE_MEMBERS),
|
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE_MEMBERS),
|
||||||
)
|
)
|
||||||
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||||
@Resolver()
|
@Resolver()
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
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';
|
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||||
@ -34,7 +34,7 @@ import {
|
|||||||
WorkspaceExceptionCode,
|
WorkspaceExceptionCode,
|
||||||
} from 'src/engine/core-modules/workspace/workspace.exception';
|
} from 'src/engine/core-modules/workspace/workspace.exception';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
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 {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -442,7 +442,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
const userHasPermission =
|
const userHasPermission =
|
||||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
_setting: SettingsPermissions.SECURITY,
|
_setting: SettingPermissionType.SECURITY,
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
isExecutedByApiKey: isDefined(apiKey),
|
isExecutedByApiKey: isDefined(apiKey),
|
||||||
});
|
});
|
||||||
@ -481,7 +481,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
_setting: SettingsPermissions.WORKSPACE,
|
_setting: SettingPermissionType.WORKSPACE,
|
||||||
isExecutedByApiKey: isDefined(apiKey),
|
isExecutedByApiKey: isDefined(apiKey),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
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 { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-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 { 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 { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||||
@ -130,7 +130,7 @@ export class WorkspaceResolver {
|
|||||||
@Mutation(() => String)
|
@Mutation(() => String)
|
||||||
@UseGuards(
|
@UseGuards(
|
||||||
WorkspaceAuthGuard,
|
WorkspaceAuthGuard,
|
||||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE),
|
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||||
)
|
)
|
||||||
async uploadWorkspaceLogo(
|
async uploadWorkspaceLogo(
|
||||||
@AuthWorkspace() { id }: Workspace,
|
@AuthWorkspace() { id }: Workspace,
|
||||||
@ -174,7 +174,7 @@ export class WorkspaceResolver {
|
|||||||
@Mutation(() => Workspace)
|
@Mutation(() => Workspace)
|
||||||
@UseGuards(
|
@UseGuards(
|
||||||
WorkspaceAuthGuard,
|
WorkspaceAuthGuard,
|
||||||
SettingsPermissionsGuard(SettingsPermissions.WORKSPACE),
|
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||||
)
|
)
|
||||||
async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) {
|
async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) {
|
||||||
return this.workspaceService.deleteWorkspace(id);
|
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 { 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 { 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 {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -20,7 +20,7 @@ import {
|
|||||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
|
|
||||||
export const SettingsPermissionsGuard = (
|
export const SettingsPermissionsGuard = (
|
||||||
requiredPermission: SettingsPermissions,
|
requiredPermission: SettingPermissionType,
|
||||||
): Type<CanActivate> => {
|
): Type<CanActivate> => {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class SettingsPermissionsMixin implements CanActivate {
|
class SettingsPermissionsMixin implements CanActivate {
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import {
|
|||||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
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 { 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 { 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 { 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';
|
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 })
|
@ResolveField(() => String, { nullable: true })
|
||||||
async icon(
|
async icon(
|
||||||
@Parent() fieldMetadata: FieldMetadataDTO,
|
@Parent() fieldMetadata: FieldMetadataDTO,
|
||||||
@ -86,7 +87,7 @@ export class FieldMetadataResolver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||||
@Mutation(() => FieldMetadataDTO)
|
@Mutation(() => FieldMetadataDTO)
|
||||||
async createOneField(
|
async createOneField(
|
||||||
@Args('input') input: CreateOneFieldMetadataInput,
|
@Args('input') input: CreateOneFieldMetadataInput,
|
||||||
@ -102,7 +103,7 @@ export class FieldMetadataResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||||
@Mutation(() => FieldMetadataDTO)
|
@Mutation(() => FieldMetadataDTO)
|
||||||
async updateOneField(
|
async updateOneField(
|
||||||
@Args('input') input: UpdateOneFieldMetadataInput,
|
@Args('input') input: UpdateOneFieldMetadataInput,
|
||||||
@ -123,7 +124,7 @@ export class FieldMetadataResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||||
@Mutation(() => FieldMetadataDTO)
|
@Mutation(() => FieldMetadataDTO)
|
||||||
async deleteOneField(
|
async deleteOneField(
|
||||||
@Args('input') input: DeleteOneFieldInput,
|
@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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-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 { 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';
|
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
|
|
||||||
@Entity('objectMetadata')
|
@Entity('objectMetadata')
|
||||||
@ -142,9 +142,12 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => ObjectPermissionsEntity,
|
() => ObjectPermissionEntity,
|
||||||
(objectPermissions: ObjectPermissionsEntity) =>
|
(objectPermission: ObjectPermissionEntity) =>
|
||||||
objectPermissions.objectMetadata,
|
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 { 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 { 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 { 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 { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
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';
|
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
@ -78,7 +78,9 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
|
|||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
many: { disabled: true },
|
many: { disabled: true },
|
||||||
guards: [SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL)],
|
guards: [
|
||||||
|
SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
update: { disabled: true },
|
update: { disabled: true },
|
||||||
delete: { 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 { 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 { 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 { 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';
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
|
|
||||||
@UseGuards(WorkspaceAuthGuard)
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
@ -72,6 +72,7 @@ export class ObjectMetadataResolver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||||
@ResolveField(() => String, { nullable: true })
|
@ResolveField(() => String, { nullable: true })
|
||||||
async icon(
|
async icon(
|
||||||
@Parent() objectMetadata: ObjectMetadataDTO,
|
@Parent() objectMetadata: ObjectMetadataDTO,
|
||||||
@ -84,7 +85,7 @@ export class ObjectMetadataResolver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||||
@Mutation(() => ObjectMetadataDTO)
|
@Mutation(() => ObjectMetadataDTO)
|
||||||
async deleteOneObject(
|
async deleteOneObject(
|
||||||
@Args('input') input: DeleteOneObjectInput,
|
@Args('input') input: DeleteOneObjectInput,
|
||||||
@ -100,7 +101,7 @@ export class ObjectMetadataResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||||
@Mutation(() => ObjectMetadataDTO)
|
@Mutation(() => ObjectMetadataDTO)
|
||||||
async updateOneObject(
|
async updateOneObject(
|
||||||
@Args('input') input: UpdateOneObjectInput,
|
@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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
|
||||||
@Entity('objectPermissions')
|
@Entity('objectPermission')
|
||||||
@Unique('IndexOnObjectPermissionsUnique', ['objectMetadataId', 'roleId'])
|
@Unique('IndexOnObjectPermissionUnique', ['objectMetadataId', 'roleId'])
|
||||||
export class ObjectPermissionsEntity {
|
export class ObjectPermissionEntity {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
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',
|
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
||||||
WORKSPACE = 'WORKSPACE',
|
WORKSPACE = 'WORKSPACE',
|
||||||
WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS',
|
WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS',
|
||||||
@ -25,6 +25,9 @@ export enum PermissionsExceptionCode {
|
|||||||
PERMISSIONS_V2_NOT_ENABLED = 'PERMISSIONS_V2_NOT_ENABLED',
|
PERMISSIONS_V2_NOT_ENABLED = 'PERMISSIONS_V2_NOT_ENABLED',
|
||||||
ROLE_LABEL_ALREADY_EXISTS = 'ROLE_LABEL_ALREADY_EXISTS',
|
ROLE_LABEL_ALREADY_EXISTS = 'ROLE_LABEL_ALREADY_EXISTS',
|
||||||
DEFAULT_ROLE_NOT_FOUND = 'DEFAULT_ROLE_NOT_FOUND',
|
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 {
|
export enum PermissionsExceptionMessage {
|
||||||
@ -45,4 +48,7 @@ export enum PermissionsExceptionMessage {
|
|||||||
PERMISSIONS_V2_NOT_ENABLED = 'Permissions V2 is not enabled',
|
PERMISSIONS_V2_NOT_ENABLED = 'Permissions V2 is not enabled',
|
||||||
ROLE_LABEL_ALREADY_EXISTS = 'A role with this label already exists',
|
ROLE_LABEL_ALREADY_EXISTS = 'A role with this label already exists',
|
||||||
DEFAULT_ROLE_NOT_FOUND = 'Default role not found',
|
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 { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthException,
|
AuthException,
|
||||||
AuthExceptionCode,
|
AuthExceptionCode,
|
||||||
} from 'src/engine/core-modules/auth/auth.exception';
|
} from 'src/engine/core-modules/auth/auth.exception';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
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 {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -31,7 +31,7 @@ export class PermissionsService {
|
|||||||
userWorkspaceId: string;
|
userWorkspaceId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
settingsPermissions: Record<SettingsPermissions, boolean>;
|
settingsPermissions: Record<SettingPermissionType, boolean>;
|
||||||
objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>;
|
objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>;
|
||||||
}> {
|
}> {
|
||||||
const [roleOfUserWorkspace] = await this.userRoleService
|
const [roleOfUserWorkspace] = await this.userRoleService
|
||||||
@ -47,12 +47,12 @@ export class PermissionsService {
|
|||||||
hasPermissionOnSettingFeature = true;
|
hasPermissionOnSettingFeature = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsPermissionsMap = Object.keys(SettingsPermissions).reduce(
|
const settingsPermissionsMap = Object.keys(SettingPermissionType).reduce(
|
||||||
(acc, feature) => ({
|
(acc, feature) => ({
|
||||||
...acc,
|
...acc,
|
||||||
[feature]: hasPermissionOnSettingFeature,
|
[feature]: hasPermissionOnSettingFeature,
|
||||||
}),
|
}),
|
||||||
{} as Record<SettingsPermissions, boolean>,
|
{} as Record<SettingPermissionType, boolean>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectRecordsPermissionsMap: Record<
|
const objectRecordsPermissionsMap: Record<
|
||||||
@ -83,7 +83,7 @@ export class PermissionsService {
|
|||||||
}: {
|
}: {
|
||||||
userWorkspaceId?: string;
|
userWorkspaceId?: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
_setting: SettingsPermissions;
|
_setting: SettingPermissionType;
|
||||||
isExecutedByApiKey: boolean;
|
isExecutedByApiKey: boolean;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
if (isExecutedByApiKey) {
|
if (isExecutedByApiKey) {
|
||||||
|
|||||||
@ -19,11 +19,14 @@ export const permissionGraphqlApiExceptionHandler = (
|
|||||||
case PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER:
|
case PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER:
|
||||||
case PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED:
|
case PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED:
|
||||||
case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS:
|
case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS:
|
||||||
|
case PermissionsExceptionCode.ROLE_NOT_EDITABLE:
|
||||||
throw new ForbiddenError(error.message);
|
throw new ForbiddenError(error.message);
|
||||||
case PermissionsExceptionCode.INVALID_ARG:
|
case PermissionsExceptionCode.INVALID_ARG:
|
||||||
|
case PermissionsExceptionCode.INVALID_SETTING:
|
||||||
throw new UserInputError(error.message);
|
throw new UserInputError(error.message);
|
||||||
case PermissionsExceptionCode.ROLE_NOT_FOUND:
|
case PermissionsExceptionCode.ROLE_NOT_FOUND:
|
||||||
case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
||||||
|
case PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||||
throw new NotFoundError(error.message);
|
throw new NotFoundError(error.message);
|
||||||
case PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND:
|
case PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND:
|
||||||
default:
|
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 { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||||
import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-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 { 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 { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
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';
|
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,
|
pagingStrategy: PagingStrategies.CURSOR,
|
||||||
create: {
|
create: {
|
||||||
many: { disabled: true },
|
many: { disabled: true },
|
||||||
guards: [SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL)],
|
guards: [
|
||||||
|
SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
update: { disabled: true },
|
update: { disabled: true },
|
||||||
delete: { 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 { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-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 { 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 { 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';
|
import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto';
|
||||||
@ -20,7 +20,7 @@ export class RelationMetadataResolver {
|
|||||||
private readonly relationMetadataService: RelationMetadataService,
|
private readonly relationMetadataService: RelationMetadataService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.DATA_MODEL))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL))
|
||||||
@Mutation(() => RelationMetadataDTO)
|
@Mutation(() => RelationMetadataDTO)
|
||||||
async deleteOneRelation(
|
async deleteOneRelation(
|
||||||
@Args('input') input: DeleteOneRelationInput,
|
@Args('input') input: DeleteOneRelationInput,
|
||||||
|
|||||||
@ -9,9 +9,9 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} 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 { 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')
|
@Entity('role')
|
||||||
@Unique('IndexOnRoleUnique', ['label', 'workspaceId'])
|
@Unique('IndexOnRoleUnique', ['label', 'workspaceId'])
|
||||||
@ -62,15 +62,14 @@ export class RoleEntity {
|
|||||||
userWorkspaceRoles: Relation<UserWorkspaceRoleEntity[]>;
|
userWorkspaceRoles: Relation<UserWorkspaceRoleEntity[]>;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => ObjectPermissionsEntity,
|
() => ObjectPermissionEntity,
|
||||||
(objectPermissions: ObjectPermissionsEntity) => objectPermissions.role,
|
(objectPermission: ObjectPermissionEntity) => objectPermission.role,
|
||||||
)
|
)
|
||||||
objectPermissions: Relation<ObjectPermissionsEntity[]>;
|
objectPermissions: Relation<ObjectPermissionEntity[]>;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => SettingsPermissionsEntity,
|
() => SettingPermissionEntity,
|
||||||
(settingsPermissions: SettingsPermissionsEntity) =>
|
(settingPermission: SettingPermissionEntity) => settingPermission.role,
|
||||||
settingsPermissions.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 { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
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 { 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 { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { RoleResolver } from 'src/engine/metadata-modules/role/role.resolver';
|
import { RoleResolver } from 'src/engine/metadata-modules/role/role.resolver';
|
||||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
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';
|
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@ -19,6 +21,8 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
|
|||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
UserWorkspaceModule,
|
UserWorkspaceModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
|
ObjectPermissionModule,
|
||||||
|
SettingPermissionModule,
|
||||||
],
|
],
|
||||||
providers: [RoleService, RoleResolver],
|
providers: [RoleService, RoleResolver],
|
||||||
exports: [RoleService],
|
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 { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
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 {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
PermissionsExceptionMessage,
|
PermissionsExceptionMessage,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
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 { 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 { 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 { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
@Resolver(() => RoleDTO)
|
@Resolver(() => RoleDTO)
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.ROLES))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.ROLES))
|
||||||
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||||
export class RoleResolver {
|
export class RoleResolver {
|
||||||
constructor(
|
constructor(
|
||||||
@ -39,6 +45,8 @@ export class RoleResolver {
|
|||||||
private readonly roleService: RoleService,
|
private readonly roleService: RoleService,
|
||||||
private readonly userWorkspaceService: UserWorkspaceService,
|
private readonly userWorkspaceService: UserWorkspaceService,
|
||||||
private readonly featureFlagService: FeatureFlagService,
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
private readonly objectPermissionService: ObjectPermissionService,
|
||||||
|
private readonly settingPermissionService: SettingPermissionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => [RoleDTO])
|
@Query(() => [RoleDTO])
|
||||||
@ -101,18 +109,7 @@ export class RoleResolver {
|
|||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Args('createRoleInput') createRoleInput: CreateRoleInput,
|
@Args('createRoleInput') createRoleInput: CreateRoleInput,
|
||||||
): Promise<RoleDTO> {
|
): Promise<RoleDTO> {
|
||||||
const isPermissionsV2Enabled =
|
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||||
await this.featureFlagService.isFeatureEnabled(
|
|
||||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
|
||||||
workspace.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isPermissionsV2Enabled) {
|
|
||||||
throw new PermissionsException(
|
|
||||||
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
|
|
||||||
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.roleService.createRole({
|
return this.roleService.createRole({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
@ -125,18 +122,11 @@ export class RoleResolver {
|
|||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Args('updateRoleInput') updateRoleInput: UpdateRoleInput,
|
@Args('updateRoleInput') updateRoleInput: UpdateRoleInput,
|
||||||
): Promise<RoleDTO> {
|
): Promise<RoleDTO> {
|
||||||
const isPermissionsV2Enabled =
|
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||||
await this.featureFlagService.isFeatureEnabled(
|
await this.validateRoleIsEditableOrThrow({
|
||||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
roleId: updateRoleInput.id,
|
||||||
workspace.id,
|
workspaceId: workspace.id,
|
||||||
);
|
});
|
||||||
|
|
||||||
if (!isPermissionsV2Enabled) {
|
|
||||||
throw new PermissionsException(
|
|
||||||
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
|
|
||||||
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.roleService.updateRole({
|
return this.roleService.updateRole({
|
||||||
input: updateRoleInput,
|
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])
|
@ResolveField('workspaceMembers', () => [WorkspaceMember])
|
||||||
async getWorkspaceMembersAssignedToRole(
|
async getWorkspaceMembersAssignedToRole(
|
||||||
@Parent() role: RoleDTO,
|
@Parent() role: RoleDTO,
|
||||||
@ -154,4 +180,36 @@ export class RoleResolver {
|
|||||||
workspace.id,
|
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 { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
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 { 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';
|
import { MEMBER_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/member-role-label.constants';
|
||||||
@ -10,11 +10,11 @@ import {
|
|||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
PermissionsExceptionMessage,
|
PermissionsExceptionMessage,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} 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 {
|
import {
|
||||||
UpdateRoleInput,
|
UpdateRoleInput,
|
||||||
UpdateRolePayload,
|
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 { 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';
|
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,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} 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';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
|
||||||
@Entity('settingsPermissions')
|
@Entity('settingPermission')
|
||||||
@Unique('IndexOnSettingsPermissionsUnique', ['setting', 'roleId'])
|
@Unique('IndexOnSettingPermissionUnique', ['setting', 'roleId'])
|
||||||
export class SettingsPermissionsEntity {
|
export class SettingPermissionEntity {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@Column({ nullable: false, type: 'uuid' })
|
@Column({ nullable: false, type: 'uuid' })
|
||||||
roleId: string;
|
roleId: string;
|
||||||
|
|
||||||
@ManyToOne(() => RoleEntity, (role) => role.settingsPermissions, {
|
@ManyToOne(() => RoleEntity, (role) => role.settingPermissions, {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
})
|
})
|
||||||
@JoinColumn({ name: 'roleId' })
|
@JoinColumn({ name: 'roleId' })
|
||||||
role: Relation<RoleEntity>;
|
role: Relation<RoleEntity>;
|
||||||
|
|
||||||
@Column({ nullable: false, type: 'varchar' })
|
@Column({ nullable: false, type: 'varchar' })
|
||||||
setting: SettingsPermissions;
|
setting: SettingPermissionType;
|
||||||
|
|
||||||
@Column({ nullable: true, type: 'boolean' })
|
@Column({ nullable: true, type: 'boolean' })
|
||||||
canUpdateSetting?: 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 { 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 { 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 {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -65,7 +65,7 @@ export class WorkspaceMemberPreQueryHookService {
|
|||||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
_setting: SettingsPermissions.WORKSPACE_MEMBERS,
|
_setting: SettingPermissionType.WORKSPACE_MEMBERS,
|
||||||
isExecutedByApiKey: isDefined(apiKey),
|
isExecutedByApiKey: isDefined(apiKey),
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -1,15 +1,41 @@
|
|||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
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 { 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 { 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 { 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 { 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';
|
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
|
||||||
const client = request(`http://localhost:${APP_PORT}`);
|
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', () => {
|
describe('roles permissions', () => {
|
||||||
|
let adminRoleId: string;
|
||||||
|
let guestRoleId: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const enablePermissionsQuery = updateFeatureFlagFactory(
|
const enablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
SEED_APPLE_WORKSPACE_ID,
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
@ -17,7 +43,38 @@ describe('roles permissions', () => {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const enablePermissionsV2Query = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IsPermissionsV2Enabled',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
await makeGraphqlAPIRequest(enablePermissionsQuery);
|
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 () => {
|
afterAll(async () => {
|
||||||
@ -27,7 +84,14 @@ describe('roles permissions', () => {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const disablePermissionsV2Query = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IsPermissionsV2Enabled',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
await makeGraphqlAPIRequest(disablePermissionsQuery);
|
await makeGraphqlAPIRequest(disablePermissionsQuery);
|
||||||
|
await makeGraphqlAPIRequest(disablePermissionsV2Query);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getRoles', () => {
|
describe('getRoles', () => {
|
||||||
@ -116,19 +180,7 @@ describe('roles permissions', () => {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
await client
|
await assertPermissionDeniedForMemberWithMemberRole({ query });
|
||||||
.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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -144,19 +196,7 @@ describe('roles permissions', () => {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
await client
|
await assertPermissionDeniedForMemberWithMemberRole({ query });
|
||||||
.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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw a permission error when tries to update their own role (admin role)', async () => {
|
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