mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console): regenerate signing key (#3866)
This commit is contained in:
parent
9423b273b6
commit
85fb03651c
4 changed files with 111 additions and 1 deletions
3
packages/console/src/assets/images/redo.svg
Normal file
3
packages/console/src/assets/images/redo.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.5 7.8335C14.1 7.8335 13.8333 8.10016 13.8333 8.50016C13.8333 10.4335 12.8333 12.1668 11.1667 13.1002C8.63334 14.5668 5.36668 13.7002 3.90001 11.1668C2.43334 8.6335 3.30001 5.36683 5.83334 3.90016C8.03334 2.6335 10.7 3.10016 12.3667 4.8335H10.7667C10.3667 4.8335 10.1 5.10016 10.1 5.50016C10.1 5.90016 10.3667 6.16683 10.7667 6.16683H13.7667C14.1667 6.16683 14.4333 5.90016 14.4333 5.50016V2.50016C14.4333 2.10016 14.1667 1.8335 13.7667 1.8335C13.3667 1.8335 13.1 2.10016 13.1 2.50016V3.70016C11.8333 2.50016 10.2333 1.8335 8.50001 1.8335C4.83334 1.8335 1.83334 4.8335 1.83334 8.50016C1.83334 12.1668 4.83334 15.1668 8.50001 15.1668C12.1667 15.1668 15.1667 12.1668 15.1667 8.50016C15.1667 8.10016 14.9 7.8335 14.5 7.8335Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 860 B |
|
@ -0,0 +1,9 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.regenerateButton {
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
|
||||
.signingKeyField {
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import { type Hook } from '@logto/schemas';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Redo from '@/assets/images/redo.svg';
|
||||
import Button from '@/components/Button';
|
||||
import ConfirmModal from '@/components/ConfirmModal';
|
||||
import CopyToClipboard from '@/components/CopyToClipboard';
|
||||
import DynamicT from '@/components/DynamicT';
|
||||
import FormField from '@/components/FormField';
|
||||
import useApi from '@/hooks/use-api';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
hookId: string;
|
||||
signingKey: string;
|
||||
onSigningKeyUpdated: (signingKey: string) => void;
|
||||
};
|
||||
|
||||
function SigningKeyField({ hookId, signingKey, onSigningKeyUpdated }: Props) {
|
||||
const { t } = useTranslation(undefined);
|
||||
const api = useApi();
|
||||
const [isRegenerateFormOpen, setIsRegenerateFormOpen] = useState(false);
|
||||
const [isRegenerating, setIsRegenerating] = useState(false);
|
||||
|
||||
const regenerateSigningKey = useCallback(
|
||||
async (silent = false) => {
|
||||
if (isRegenerating) {
|
||||
return;
|
||||
}
|
||||
setIsRegenerating(true);
|
||||
try {
|
||||
const { signingKey } = await api.patch(`api/hooks/${hookId}/signing-key`).json<Hook>();
|
||||
if (!silent) {
|
||||
toast.success(t('admin_console.webhook_details.settings.regenerated'));
|
||||
}
|
||||
setIsRegenerateFormOpen(false);
|
||||
onSigningKeyUpdated(signingKey);
|
||||
} finally {
|
||||
setIsRegenerating(false);
|
||||
}
|
||||
},
|
||||
[api, hookId, isRegenerating, onSigningKeyUpdated, t]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!signingKey) {
|
||||
void regenerateSigningKey(true);
|
||||
}
|
||||
}, [regenerateSigningKey, signingKey]);
|
||||
|
||||
return (
|
||||
<FormField
|
||||
title="webhook_details.settings.signing_key"
|
||||
tip={<DynamicT forKey="webhook_details.settings.signing_key_tip" />}
|
||||
>
|
||||
<CopyToClipboard
|
||||
hasVisibilityToggle
|
||||
value={signingKey}
|
||||
variant="border"
|
||||
className={styles.signingKeyField}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<Redo />}
|
||||
title="webhook_details.settings.regenerate"
|
||||
className={styles.regenerateButton}
|
||||
onClick={() => {
|
||||
setIsRegenerateFormOpen(true);
|
||||
}}
|
||||
/>
|
||||
<ConfirmModal
|
||||
isOpen={isRegenerateFormOpen}
|
||||
isLoading={isRegenerating}
|
||||
confirmButtonText="webhook_details.settings.regenerate"
|
||||
title="webhook_details.settings.regenerate_key_title"
|
||||
onCancel={async () => {
|
||||
setIsRegenerateFormOpen(false);
|
||||
}}
|
||||
onConfirm={async () => regenerateSigningKey()}
|
||||
>
|
||||
<DynamicT forKey="webhook_details.settings.regenerate_key_reminder" />
|
||||
</ConfirmModal>
|
||||
</FormField>
|
||||
);
|
||||
}
|
||||
|
||||
export default SigningKeyField;
|
|
@ -1,4 +1,5 @@
|
|||
import { type Hook } from '@logto/schemas';
|
||||
import { useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -14,11 +15,12 @@ import { type WebhookDetailsFormType, type WebhookDetailsOutletContext } from '.
|
|||
import { webhookDetailsParser } from '../utils';
|
||||
|
||||
import CustomHeaderField from './components/CustomHeaderField';
|
||||
import SigningKeyField from './components/SigningKeyField';
|
||||
|
||||
function WebhookSettings() {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { hook, isDeleting, onHookUpdated } = useOutletContext<WebhookDetailsOutletContext>();
|
||||
|
||||
const [signingKey, setSigningKey] = useState(hook.signingKey);
|
||||
const webhookFormData = webhookDetailsParser.toLocalForm(hook);
|
||||
const formMethods = useForm<WebhookDetailsFormType>({
|
||||
defaultValues: webhookFormData,
|
||||
|
@ -54,6 +56,11 @@ function WebhookSettings() {
|
|||
>
|
||||
<FormProvider {...formMethods}>
|
||||
<BasicWebhookForm />
|
||||
<SigningKeyField
|
||||
hookId={hook.id}
|
||||
signingKey={signingKey}
|
||||
onSigningKeyUpdated={setSigningKey}
|
||||
/>
|
||||
<CustomHeaderField />
|
||||
</FormProvider>
|
||||
</FormCard>
|
||||
|
|
Loading…
Reference in a new issue