Client:PortalSettings:OAuth2: add reset client secret dialog

This commit is contained in:
Timofey Boyko 2023-12-08 15:37:51 +03:00
parent 84bc76263f
commit edc3636ba0
7 changed files with 220 additions and 76 deletions

View File

@ -18,7 +18,7 @@
"ClientHelpButton": "Credentials for using OAth 2.0 as your Authentication type.<br/> <strong>Note</strong>: Any enterprise admin who knows the app's client ID will be able to retrieve information about the app including app name, authentication type, app scopes and redirect URI.",
"CodeVerifier": "Code verifier",
"DisableApplication": "Disable application",
"DisableApplicationDescription": "If you disable this application, all active consents will be revoked. If necessary, you can later enable the disabled application.<br/> <strong>Note</strong> that all users will again be required to complete the consent screen.",
"DisableApplicationDescription": "If you disable this application, all active consents and authorization will be disabled. If necessary, you can later enable the disabled application.",
"EditApp": "Edit application",
"EnterDescription": "Enter description",
"ErrorName": "Minimal name length:",
@ -43,7 +43,7 @@
"RegisterNewApp": "Register a new application",
"Reset": "Reset",
"ResetHeader": "Reset client secret",
"ResetDescription": "If you reset client secret, all active consents will be revoked. For apply next consent need use new client secret.<br/> <strong>Note</strong> that all users will again be required to complete the consent screen.",
"ResetDescription": "If you reset client secret, all active consents and authorization will be revoked. For apply next consent need use new client secret. Note that all users will again be required to complete the consent screen.",
"Revoke": "Revoke",
"RevokeConsent": "Revoke consent",
"RevokeConsentDescription": "Once you revoke the consent to use the ONLYOFFICE DocSpace auth data in the service {{name}}, ONLYOFFICE DocSpace will automatically stop logging into {{name}}. Your account in {{name}} will not be deleted.",

View File

@ -52,9 +52,13 @@ export interface ClientFormProps {
client: IClientReqDTO
) => Promise<IClientReqDTO>;
regenerateSecret?: (clientId: string) => Promise<string>;
resetDialogVisible?: boolean;
setResetDialogVisible?: (value: boolean) => void;
currentDeviceType?: DeviceUnionType;
setClientSecretProps?: (value: string) => void;
clientSecretProps?: string;
}
export interface ClientStore {

View File

@ -16,7 +16,7 @@ interface ClientBlockProps {
idValue: string;
secretValue: string;
onResetClick: () => Promise<void>;
onResetClick: () => void;
}
const ClientBlock = ({

View File

@ -25,7 +25,7 @@ interface InputGroupProps {
helpButtonText?: string;
buttonLabel?: string;
onButtonClick?: () => Promise<void>;
onButtonClick?: () => void;
withCopy?: boolean;
onCopyClick?: (name: string) => void;
@ -66,15 +66,15 @@ const InputGroup = ({
}: InputGroupProps) => {
const [isRequestRunning, setIsRequestRunning] = React.useState(false);
const onButtonClickAction = React.useCallback(async () => {
const onButtonClickAction = async () => {
setIsRequestRunning(true);
await onButtonClick?.();
onButtonClick?.();
setTimeout(() => {
setIsRequestRunning(false);
}, 300);
}, [onButtonClick]);
});
};
return (
<StyledInputGroup>

View File

@ -20,6 +20,7 @@ import { StyledContainer } from "./ClientForm.styled";
import { ClientFormProps, ClientStore } from "./ClientForm.types";
import ClientFormLoader from "./Loader";
import ResetDialog from "../ResetDialog";
export function isValidUrl(url: string) {
try {
@ -43,7 +44,11 @@ const ClientForm = ({
saveClient,
updateClient,
regenerateSecret,
setResetDialogVisible,
resetDialogVisible,
setClientSecretProps,
clientSecretProps,
currentDeviceType,
}: ClientFormProps) => {
@ -83,6 +88,13 @@ const ClientForm = ({
const isEdit = !!id;
React.useEffect(() => {
if (clientSecretProps) {
setClientSecret(clientSecretProps);
setClientSecretProps?.("");
}
}, [clientSecretProps, setClientSecretProps]);
const onSaveClick = async () => {
try {
if (!id) {
@ -104,11 +116,11 @@ const ClientForm = ({
};
const onResetClick = React.useCallback(async () => {
if (!regenerateSecret) return;
const newSecret = await regenerateSecret(clientId);
if (!setResetDialogVisible) return;
setResetDialogVisible(true);
setClientSecret(newSecret);
}, [clientId, regenerateSecret]);
// setClientSecret(newSecret);
}, [clientId, setResetDialogVisible]);
const onChangeForm = (name: string, value: string | boolean) => {
setForm((val) => {
@ -374,70 +386,73 @@ const ClientForm = ({
const isValid = compareAndValidate();
return (
<StyledContainer>
{isLoading ? (
<ClientFormLoader
isEdit={isEdit}
currentDeviceType={currentDeviceType}
/>
) : (
<>
<BasicBlock
t={t}
nameValue={form.name}
websiteUrlValue={form.website_url}
descriptionValue={form.description}
logoValue={form.logo}
allowPkce={form.allow_pkce}
changeValue={onChangeForm}
<>
<StyledContainer>
{isLoading ? (
<ClientFormLoader
isEdit={isEdit}
errorFields={errorFields}
onBlur={onBlur}
currentDeviceType={currentDeviceType}
/>
{isEdit && (
<ClientBlock
) : (
<>
<BasicBlock
t={t}
idValue={clientId}
secretValue={clientSecret}
onResetClick={onResetClick}
nameValue={form.name}
websiteUrlValue={form.website_url}
descriptionValue={form.description}
logoValue={form.logo}
allowPkce={form.allow_pkce}
changeValue={onChangeForm}
isEdit={isEdit}
errorFields={errorFields}
onBlur={onBlur}
/>
)}
<OAuthBlock
t={t}
redirectUrisValue={form.redirect_uris}
allowedOriginsValue={form.allowed_origins}
changeValue={onChangeForm}
isEdit={isEdit}
/>
<ScopesBlock
t={t}
scopes={scopeList || []}
selectedScopes={form.scopes}
onAddScope={onChangeForm}
isEdit={isEdit}
/>
<SupportBlock
t={t}
policyUrlValue={form.policy_url}
termsUrlValue={form.terms_url}
changeValue={onChangeForm}
isEdit={isEdit}
errorFields={errorFields}
onBlur={onBlur}
/>
<ButtonsBlock
saveLabel={t("Common:SaveButton")}
cancelLabel={t("Common:CancelButton")}
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
isRequestRunning={isRequestRunning}
saveButtonDisabled={!isValid}
cancelButtonDisabled={isRequestRunning}
currentDeviceType={currentDeviceType || ""}
/>
</>
)}
</StyledContainer>
{isEdit && (
<ClientBlock
t={t}
idValue={clientId}
secretValue={clientSecret}
onResetClick={onResetClick}
/>
)}
<OAuthBlock
t={t}
redirectUrisValue={form.redirect_uris}
allowedOriginsValue={form.allowed_origins}
changeValue={onChangeForm}
isEdit={isEdit}
/>
<ScopesBlock
t={t}
scopes={scopeList || []}
selectedScopes={form.scopes}
onAddScope={onChangeForm}
isEdit={isEdit}
/>
<SupportBlock
t={t}
policyUrlValue={form.policy_url}
termsUrlValue={form.terms_url}
changeValue={onChangeForm}
isEdit={isEdit}
errorFields={errorFields}
onBlur={onBlur}
/>
<ButtonsBlock
saveLabel={t("Common:SaveButton")}
cancelLabel={t("Common:CancelButton")}
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
isRequestRunning={isRequestRunning}
saveButtonDisabled={!isValid}
cancelButtonDisabled={isRequestRunning}
currentDeviceType={currentDeviceType || ""}
/>
</>
)}
</StyledContainer>
{resetDialogVisible && <ResetDialog />}
</>
);
};
@ -453,7 +468,11 @@ export default inject(
saveClient,
updateClient,
regenerateSecret,
setResetDialogVisible,
resetDialogVisible,
setClientSecret,
clientSecret,
} = oauthStore;
const { currentDeviceType } = auth.settingsStore;
@ -467,8 +486,11 @@ export default inject(
saveClient,
updateClient,
regenerateSecret,
setResetDialogVisible,
currentDeviceType,
resetDialogVisible,
setClientSecretProps: setClientSecret,
clientSecretProps: clientSecret,
};
if (id) {

View File

@ -0,0 +1,94 @@
import React from "react";
import { useParams } from "react-router-dom";
import { inject, observer } from "mobx-react";
import { useTranslation, Trans } from "react-i18next";
// @ts-ignore
import ModalDialog from "@docspace/components/modal-dialog";
// @ts-ignore
import Button from "@docspace/components/button";
// @ts-ignore
import toastr from "@docspace/components/toast/toastr";
// @ts-ignore
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
interface DisableClientDialog {
isVisible?: boolean;
onClose?: () => void;
onReset?: (id: string) => Promise<void>;
}
const DisableClientDialog = (props: DisableClientDialog) => {
const { id } = useParams();
const { t, ready } = useTranslation(["OAuth", "Common"]);
const { isVisible, onClose, onReset } = props;
const [isRequestRunning, setIsRequestRunning] = React.useState(false);
const onResetClick = async () => {
try {
setIsRequestRunning(true);
if (id) await onReset?.(id);
setIsRequestRunning(true);
onClose?.();
} catch (error) {
toastr.error(error);
onClose?.();
}
};
return (
<ModalDialog
isLoading={!ready}
visible={isVisible}
onClose={onClose}
displayType="modal"
>
<ModalDialog.Header>{t("ResetHeader")}</ModalDialog.Header>
<ModalDialog.Body>
<Trans t={t} i18nKey="ResetDescription" ns="OAuth" />
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
// @ts-ignore
className="delete-button"
key="DeletePortalBtn"
label={t("Common:OkButton")}
size="normal"
scale
primary={true}
isLoading={isRequestRunning}
onClick={onResetClick}
/>
<Button
// @ts-ignore
className="cancel-button"
key="CancelDeleteBtn"
label={t("Common:CancelButton")}
size="normal"
scale
isDisabled={isRequestRunning}
onClick={onClose}
/>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default inject(({ oauthStore }: { oauthStore: OAuthStoreProps }) => {
const { setResetDialogVisible, regenerateSecret, resetDialogVisible } =
oauthStore;
const onClose = () => {
setResetDialogVisible(false);
};
const onReset = async (id: string) => {
await regenerateSecret(id);
};
return { isVisible: resetDialogVisible, onClose, onReset };
})(observer(DisableClientDialog));

View File

@ -60,9 +60,15 @@ export interface OAuthStoreProps {
resetDialogVisible: boolean;
setResetDialogVisible: (value: boolean) => void;
deleteDialogVisible: boolean;
setDeleteDialogVisible: (value: boolean) => void;
clientsIsLoading: boolean;
setClientsIsLoading: (value: boolean) => void;
clientSecret: string;
setClientSecret: (value: string) => void;
editClient: (clientId: string) => void;
clients: IClientProps[];
@ -133,6 +139,8 @@ class OAuthStore implements OAuthStoreProps {
infoDialogVisible: boolean = false;
previewDialogVisible: boolean = false;
disableDialogVisible: boolean = false;
deleteDialogVisible: boolean = false;
resetDialogVisible: boolean = false;
selection: string[] = [];
@ -146,6 +154,8 @@ class OAuthStore implements OAuthStoreProps {
clientsIsLoading: boolean = true;
clientSecret: string = "";
consents: IClientProps[] = [];
isInit: boolean = false;
@ -181,6 +191,18 @@ class OAuthStore implements OAuthStoreProps {
this.disableDialogVisible = value;
};
setDeleteDialogVisible = (value: boolean) => {
this.deleteDialogVisible = value;
};
setResetDialogVisible = (value: boolean) => {
this.resetDialogVisible = value;
};
setClientSecret = (value: string) => {
this.clientSecret = value;
};
setSelection = (clientId: string) => {
if (!clientId) {
this.selection = [];
@ -366,6 +388,8 @@ class OAuthStore implements OAuthStoreProps {
try {
const { client_secret } = await regenerateSecret(clientId);
this.setClientSecret(client_secret);
return client_secret;
} catch (e) {
toastr.error(e);