mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
Added Staff Token copy and generation to Admin X (#18123)
refs https://www.notion.so/ghost/Staff-access-token-is-missing-from-user-detail-screen-0336ea3e586c4b88ad7dae266a95429c?pvs=4 - Added API routes to retrieve and regenrate Staff Token keys. - Wired up UI --- <!-- Leave the line below if you'd like GitHub Copilot to generate a summary from your commit --> <!-- copilot:summary --> ### <samp>🤖 Generated by Copilot at 6ae12a5</samp> This pull request introduces a new feature for staff access tokens, which enable users to access the Ghost Admin API securely and conveniently. It adds a new `staffToken.ts` module for the API logic and a new `StaffToken` component for the UI. It also integrates the feature into the user profile and settings in the `general` page.
This commit is contained in:
parent
7a49183bc8
commit
430671d12a
31
apps/admin-x-settings/src/api/staffToken.ts
Normal file
31
apps/admin-x-settings/src/api/staffToken.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {Meta, createMutation, createPaginatedQuery} from '../utils/apiRequests';
|
||||
|
||||
export type staffToken = {
|
||||
id: string;
|
||||
created_at: string;
|
||||
integration_id: string | null;
|
||||
last_seen_at: string | null;
|
||||
last_seen_version: string | null;
|
||||
role_id: string;
|
||||
secret: string;
|
||||
type: string;
|
||||
updated_at: string;
|
||||
user_id: string;
|
||||
};
|
||||
|
||||
export interface StaffTokenResponseType {
|
||||
meta?: Meta
|
||||
apiKey: staffToken
|
||||
}
|
||||
|
||||
const dataType = 'StaffTokenResponseType';
|
||||
|
||||
export const getStaffToken = createPaginatedQuery<StaffTokenResponseType>({
|
||||
dataType,
|
||||
path: '/users/me/token'
|
||||
});
|
||||
|
||||
export const genStaffToken = createMutation<StaffTokenResponseType, []>({
|
||||
path: () => '/users/me/token',
|
||||
method: 'PUT'
|
||||
});
|
@ -22,6 +22,7 @@ import validator from 'validator';
|
||||
import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
|
||||
import {RoutingModalProps} from '../../providers/RoutingProvider';
|
||||
import {User, isAdminUser, isOwnerUser, useDeleteUser, useEditUser, useMakeOwner, useUpdatePassword} from '../../../api/users';
|
||||
import {genStaffToken, getStaffToken} from '../../../api/staffToken';
|
||||
import {getImageUrl, useUploadImage} from '../../../api/images';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {showToast} from '../../../admin-x-ds/global/Toast';
|
||||
@ -433,6 +434,58 @@ const Password: React.FC<UserDetailProps> = ({user}) => {
|
||||
);
|
||||
};
|
||||
|
||||
const StaffToken: React.FC<UserDetailProps> = () => {
|
||||
const {data: {apiKey} = {}} = getStaffToken();
|
||||
const [token, setToken] = useState('');
|
||||
const {mutateAsync: newApiKey} = genStaffToken();
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(token);
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setToken(apiKey?.secret || '');
|
||||
} , [apiKey]);
|
||||
|
||||
const genConfirmation = () => {
|
||||
NiceModal.show(ConfirmationModal, {
|
||||
title: 'Regenerate your Staff Access Token',
|
||||
prompt: 'You can regenerate your Staff Access Token any time, but any scripts or applications using it will need to be updated.',
|
||||
okLabel: 'Regenerate your Staff Access Token',
|
||||
okColor: 'red',
|
||||
onOk: async (modal) => {
|
||||
const newAPI = await newApiKey([]);
|
||||
setToken(newAPI?.apiKey?.secret || '');
|
||||
modal?.remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<SettingGroup
|
||||
border={false}
|
||||
customHeader={<CustomHeader>Staff access token</CustomHeader>}
|
||||
title='Staff access token'
|
||||
>
|
||||
<TextField
|
||||
rightPlaceholder={
|
||||
<div className='flex'>
|
||||
<Button className='mt-2' color='white' label='Regenerate' size='sm' onClick={genConfirmation} />
|
||||
<Button className='mt-2' color={copied ? 'green' : 'white'} label={copied ? 'Copied' : 'Copy'} size='sm' onClick={copyToClipboard} />
|
||||
</div>
|
||||
}
|
||||
title="Staff access token"
|
||||
type="text"
|
||||
value={token}
|
||||
/>
|
||||
</SettingGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const UserMenuTrigger = () => (
|
||||
<button className='flex h-8 cursor-pointer items-center justify-center rounded bg-[rgba(0,0,0,0.75)] px-3 opacity-80 hover:opacity-100' type='button'>
|
||||
<span className='sr-only'>Actions</span>
|
||||
@ -793,6 +846,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
||||
<Details errors={errors} setUserData={setUserData} user={userData} validators={validators} />
|
||||
<EmailNotifications setUserData={setUserData} user={userData} />
|
||||
<Password user={userData} />
|
||||
<StaffToken user={userData} />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
Loading…
Reference in New Issue
Block a user