From a9ad08cf89f7ea4ba899d79b1abee13d1a56f1e0 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Wed, 18 Oct 2023 17:04:59 +0700 Subject: [PATCH] Fixed API Query permissions in User Settings (#18680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refs https://www.notion.so/ghost/Cannot-fetch-invites-error-shown-for-authors-edc00af822d844e7add114fd834fc8fc - the problem is that certain users don't have permissions to make certain API calls. - This adds a new hook that validates the current user against permissions before a query can be made. --- ### 🤖 Generated by Copilot at 9d9cc07 Added a `usePermission` hook and a `permissions` option for custom API queries to implement role-based permissions in the admin settings app. --- apps/admin-x-settings/src/api/customThemeSettings.ts | 9 --------- apps/admin-x-settings/src/api/invites.ts | 3 ++- .../settings/site/designAndBranding/ThemePreview.tsx | 3 ++- .../settings/site/designAndBranding/ThemeSettings.tsx | 3 ++- apps/admin-x-settings/src/hooks/usePermissions.ts | 11 +++++++++++ apps/admin-x-settings/src/utils/api/hooks.ts | 3 +++ .../src/utils/isCustomThemeSettingsVisible.ts | 10 ++++++++++ .../test/unit/api/customThemeSettings.ts | 3 ++- 8 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 apps/admin-x-settings/src/hooks/usePermissions.ts create mode 100644 apps/admin-x-settings/src/utils/isCustomThemeSettingsVisible.ts diff --git a/apps/admin-x-settings/src/api/customThemeSettings.ts b/apps/admin-x-settings/src/api/customThemeSettings.ts index 5503995ec9..2c59fab67e 100644 --- a/apps/admin-x-settings/src/api/customThemeSettings.ts +++ b/apps/admin-x-settings/src/api/customThemeSettings.ts @@ -1,4 +1,3 @@ -import nql from '@tryghost/nql'; import {Setting} from './settings'; import {createMutation, createQuery} from '../utils/api/hooks'; @@ -49,11 +48,3 @@ export const useEditCustomThemeSettings = createMutation newData } }); - -export function isCustomThemeSettingVisible(setting: CustomThemeSetting, settingsKeyValueObj: Record) { - if (!setting.visibility) { - return true; - } - - return nql(setting.visibility).queryJSON(settingsKeyValueObj); -} diff --git a/apps/admin-x-settings/src/api/invites.ts b/apps/admin-x-settings/src/api/invites.ts index 47a84c2b64..4dcf17a8dc 100644 --- a/apps/admin-x-settings/src/api/invites.ts +++ b/apps/admin-x-settings/src/api/invites.ts @@ -20,7 +20,8 @@ const dataType = 'InvitesResponseType'; export const useBrowseInvites = createQuery({ dataType, - path: '/invites/' + path: '/invites/', + permissions: ['Owner', 'Administrator'] }); export const useAddInvite = createMutation({ diff --git a/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemePreview.tsx b/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemePreview.tsx index 4f85f00a83..d747d33760 100644 --- a/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemePreview.tsx +++ b/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemePreview.tsx @@ -1,6 +1,7 @@ import IframeBuffering from '../../../../utils/IframeBuffering'; import React, {useCallback} from 'react'; -import {CustomThemeSetting, hiddenCustomThemeSettingValue, isCustomThemeSettingVisible} from '../../../../api/customThemeSettings'; +import {CustomThemeSetting, hiddenCustomThemeSettingValue} from '../../../../api/customThemeSettings'; +import {isCustomThemeSettingVisible} from '../../../../utils/isCustomThemeSettingsVisible'; type BrandSettings = { description: string; diff --git a/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemeSettings.tsx b/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemeSettings.tsx index f5bfbbf099..e317061fab 100644 --- a/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemeSettings.tsx +++ b/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemeSettings.tsx @@ -8,9 +8,10 @@ import SettingGroupContent from '../../../../admin-x-ds/settings/SettingGroupCon import TextField from '../../../../admin-x-ds/global/form/TextField'; import Toggle from '../../../../admin-x-ds/global/form/Toggle'; import useHandleError from '../../../../utils/api/handleError'; -import {CustomThemeSetting, isCustomThemeSettingVisible} from '../../../../api/customThemeSettings'; +import {CustomThemeSetting} from '../../../../api/customThemeSettings'; import {getImageUrl, useUploadImage} from '../../../../api/images'; import {humanizeSettingKey} from '../../../../api/settings'; +import {isCustomThemeSettingVisible} from '../../../../utils/isCustomThemeSettingsVisible'; const ThemeSetting: React.FC<{ setting: CustomThemeSetting, diff --git a/apps/admin-x-settings/src/hooks/usePermissions.ts b/apps/admin-x-settings/src/hooks/usePermissions.ts new file mode 100644 index 0000000000..ef1e33815c --- /dev/null +++ b/apps/admin-x-settings/src/hooks/usePermissions.ts @@ -0,0 +1,11 @@ +import {UserRoleType} from '../api/roles'; +import {useGlobalData} from '../components/providers/GlobalDataProvider'; + +export const usePermission = (userRoles:string[]) => { + const {currentUser} = useGlobalData(); + const currentUserRoles = currentUser?.roles.map(role => role.name); + if (!currentUserRoles) { + return false; + } + return userRoles.some((role => currentUserRoles.includes(role as UserRoleType))); +}; diff --git a/apps/admin-x-settings/src/utils/api/hooks.ts b/apps/admin-x-settings/src/utils/api/hooks.ts index 65635773ae..a5b02b8994 100644 --- a/apps/admin-x-settings/src/utils/api/hooks.ts +++ b/apps/admin-x-settings/src/utils/api/hooks.ts @@ -6,6 +6,7 @@ import {UseInfiniteQueryOptions, UseQueryOptions, useInfiniteQuery, useMutation, import {getGhostPaths} from '../helpers'; import {useCallback, useEffect, useMemo} from 'react'; import {usePage, usePagination} from '../../hooks/usePagination'; +import {usePermission} from '../../hooks/usePermissions'; import {useSentryDSN, useServices} from '../../components/providers/ServiceProvider'; export interface Meta { @@ -149,6 +150,7 @@ interface QueryOptions { dataType: string path: string defaultSearchParams?: Record; + permissions?: string[]; returnData?: (originalData: unknown) => ResponseData; } @@ -163,6 +165,7 @@ export const createQuery = (options: QueryOptions) = const handleError = useHandleError(); const result = useQuery({ + enabled: options.permissions ? usePermission(options.permissions) : true, queryKey: [options.dataType, url], queryFn: () => fetchApi(url), ...query diff --git a/apps/admin-x-settings/src/utils/isCustomThemeSettingsVisible.ts b/apps/admin-x-settings/src/utils/isCustomThemeSettingsVisible.ts new file mode 100644 index 0000000000..717f7cf170 --- /dev/null +++ b/apps/admin-x-settings/src/utils/isCustomThemeSettingsVisible.ts @@ -0,0 +1,10 @@ +import nql from '@tryghost/nql'; +import {CustomThemeSetting} from '../api/customThemeSettings'; + +export function isCustomThemeSettingVisible(setting: CustomThemeSetting, settingsKeyValueObj: Record) { + if (!setting.visibility) { + return true; + } + + return nql(setting.visibility).queryJSON(settingsKeyValueObj); +} diff --git a/apps/admin-x-settings/test/unit/api/customThemeSettings.ts b/apps/admin-x-settings/test/unit/api/customThemeSettings.ts index 29c417e984..abcb21c654 100644 --- a/apps/admin-x-settings/test/unit/api/customThemeSettings.ts +++ b/apps/admin-x-settings/test/unit/api/customThemeSettings.ts @@ -1,5 +1,6 @@ import * as assert from 'assert/strict'; -import {CustomThemeSetting, isCustomThemeSettingVisible} from '../../../src/api/customThemeSettings'; +import {CustomThemeSetting} from '../../../src/api/customThemeSettings'; +import {isCustomThemeSettingVisible} from '../../../src/utils/isCustomThemeSettingsVisible'; describe('isCustomThemeSettingVisible', function () { it('returns whether or not a custom theme setting is visible', function () {