Fixed API Query permissions in User Settings (#18680)

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.

---

<!-- Leave the line below if you'd like GitHub Copilot to generate a
summary from your commit -->
<!--
copilot:summary
-->
### <samp>🤖 Generated by Copilot at 9d9cc07</samp>

Added a `usePermission` hook and a `permissions` option for custom API
queries to implement role-based permissions in the admin settings app.
This commit is contained in:
Ronald Langeveld 2023-10-18 17:04:59 +07:00 committed by GitHub
parent 099db95278
commit a9ad08cf89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 32 additions and 13 deletions

View File

@ -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<CustomThemeSettingsResp
update: newData => newData
}
});
export function isCustomThemeSettingVisible(setting: CustomThemeSetting, settingsKeyValueObj: Record<string, string>) {
if (!setting.visibility) {
return true;
}
return nql(setting.visibility).queryJSON(settingsKeyValueObj);
}

View File

@ -20,7 +20,8 @@ const dataType = 'InvitesResponseType';
export const useBrowseInvites = createQuery<InvitesResponseType>({
dataType,
path: '/invites/'
path: '/invites/',
permissions: ['Owner', 'Administrator']
});
export const useAddInvite = createMutation<InvitesResponseType, {email: string, roleId: string}>({

View File

@ -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;

View File

@ -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,

View File

@ -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)));
};

View File

@ -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<ResponseData> {
dataType: string
path: string
defaultSearchParams?: Record<string, string>;
permissions?: string[];
returnData?: (originalData: unknown) => ResponseData;
}
@ -163,6 +165,7 @@ export const createQuery = <ResponseData>(options: QueryOptions<ResponseData>) =
const handleError = useHandleError();
const result = useQuery<ResponseData>({
enabled: options.permissions ? usePermission(options.permissions) : true,
queryKey: [options.dataType, url],
queryFn: () => fetchApi(url),
...query

View File

@ -0,0 +1,10 @@
import nql from '@tryghost/nql';
import {CustomThemeSetting} from '../api/customThemeSettings';
export function isCustomThemeSettingVisible(setting: CustomThemeSetting, settingsKeyValueObj: Record<string, string>) {
if (!setting.visibility) {
return true;
}
return nql(setting.visibility).queryJSON(settingsKeyValueObj);
}

View File

@ -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 () {