Client:PortalSettings:OAuth: add generate and revoke developer token dialogs

This commit is contained in:
Timofey Boyko 2024-08-15 16:20:03 +03:00
parent b3dd681065
commit b89bea7123
6 changed files with 404 additions and 30 deletions

View File

@ -18,6 +18,9 @@ export interface OAuthProps {
previewDialogVisible?: boolean;
disableDialogVisible?: boolean;
deleteDialogVisible?: boolean;
generateDeveloperTokenDialogVisible?: boolean;
revokeDeveloperTokenDialogVisible?: boolean;
isInit: boolean;
setIsInit: (value: boolean) => void;
}

View File

@ -18,6 +18,8 @@ import DisableDialog from "./sub-components/DisableDialog";
import DeleteDialog from "./sub-components/DeleteDialog";
import OAuthEmptyScreen from "./sub-components/EmptyScreen";
import List from "./sub-components/List";
import GenerateDeveloperTokenDialog from "./sub-components/GenerateDeveloperTokenDialog";
import RevokeDeveloperTokenDialog from "./sub-components/RevokeDeveloperTokenDialog";
const MIN_LOADER_TIME = 500;
@ -35,6 +37,8 @@ const OAuth = ({
setIsInit,
disableDialogVisible,
deleteDialogVisible,
generateDeveloperTokenDialogVisible,
revokeDeveloperTokenDialogVisible,
}: OAuthProps) => {
const { t } = useTranslation(["OAuth"]);
@ -102,6 +106,8 @@ const OAuth = ({
{disableDialogVisible && <DisableDialog />}
{previewDialogVisible && <PreviewDialog visible={previewDialogVisible} />}
{deleteDialogVisible && <DeleteDialog />}
{generateDeveloperTokenDialogVisible && <GenerateDeveloperTokenDialog />}
{revokeDeveloperTokenDialogVisible && <RevokeDeveloperTokenDialog />}
</OAuthContainer>
);
};
@ -128,6 +134,8 @@ export default inject(
setIsInit,
disableDialogVisible,
deleteDialogVisible,
generateDeveloperTokenDialogVisible,
revokeDeveloperTokenDialogVisible,
} = oauthStore;
return {
viewAs,
@ -143,6 +151,8 @@ export default inject(
setIsInit,
disableDialogVisible,
deleteDialogVisible,
generateDeveloperTokenDialogVisible,
revokeDeveloperTokenDialogVisible,
};
},
)(observer(OAuth));

View File

@ -1,26 +0,0 @@
import React from "react";
import { inject, observer } from "mobx-react";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
const GenerateDeveloperTokenDialog = () => {
return <div></div>;
};
export default inject(({ oauthStore }: { oauthStore: OAuthStoreProps }) => {
const {
setInfoDialogVisible,
bufferSelection,
scopeList,
getContextMenuItems,
} = oauthStore;
return {
setInfoDialogVisible,
client: bufferSelection,
scopeList,
getContextMenuItems,
};
})(observer(GenerateDeveloperTokenDialog));

View File

@ -0,0 +1,210 @@
import React from "react";
import { inject, observer } from "mobx-react";
import styled, { useTheme } from "styled-components";
import { i18n } from "i18next";
import { useTranslation } from "react-i18next";
import copy from "copy-to-clipboard";
import moment from "moment-timezone";
import api from "@docspace/shared/api";
import { IClientProps } from "@docspace/shared/utils/oauth/types";
import {
ModalDialog,
ModalDialogType,
} from "@docspace/shared/components/modal-dialog";
import { Button, ButtonSize } from "@docspace/shared/components/button";
import { Text } from "@docspace/shared/components/text";
import { toastr } from "@docspace/shared/components/toast";
import { TData } from "@docspace/shared/components/toast/Toast.type";
import { InputBlock } from "@docspace/shared/components/input-block";
import { InputSize, InputType } from "@docspace/shared/components/text-input";
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import { UserStore } from "@docspace/shared/store/UserStore";
import { globalColors } from "@docspace/shared/themes";
const StyledContainer = styled.div`
p {
margin-bottom: 16px;
}
.dates {
margin-top: 16px;
margin-bottom: 0;
}
`;
type GenerateDeveloperTokenDialogProps = {
client?: IClientProps;
email?: string;
setGenerateDeveloperTokenDialogVisible?: (value: boolean) => void;
};
const getDate = (date: Date, i18nArg: i18n) => {
return moment(date)
.locale(i18nArg.language)
.tz(window.timezone)
.format("MMM D, YYYY, h:mm:ss A");
};
const GenerateDeveloperTokenDialog = ({
client,
email,
setGenerateDeveloperTokenDialogVisible,
}: GenerateDeveloperTokenDialogProps) => {
const { i18n: i18nParam } = useTranslation(["OAuth", "Common"]);
const theme = useTheme();
const [token, setToken] = React.useState("");
const [dates, setDates] = React.useState({
created: getDate(new Date(), i18nParam),
expires: getDate(new Date(), i18nParam),
});
const [requestRunning, setRequestRunning] = React.useState(false);
const onGenerate = async () => {
if (token || !client || requestRunning) return;
try {
const { clientId, clientSecret, scopes } = client;
setRequestRunning(true);
const data = await api.oauth.generateDevelopToken(
clientId,
clientSecret,
scopes,
);
setRequestRunning(false);
if (!data) return;
const { access_token: accessToken, expires_in: expiresIn } = data;
const created = new Date();
// convert sec to ms
const expires = new Date(created.getTime() + expiresIn * 1000);
if (accessToken) {
setToken(accessToken);
setDates({
created: getDate(created, i18nParam),
expires: getDate(expires, i18nParam),
});
toastr.success("Copied");
}
} catch (e) {
toastr.error(e as TData);
}
};
const onCopyClick = async () => {
copy(token);
toastr.success("Copied");
};
const onClose = () => {
if (requestRunning) return;
setGenerateDeveloperTokenDialogVisible?.(false);
};
return (
<ModalDialog
visible
onClose={onClose}
displayType={ModalDialogType.modal}
autoMaxHeight
scale
>
<ModalDialog.Header>Generate developer token</ModalDialog.Header>
<ModalDialog.Body>
<StyledContainer>
<Text>
By generating an developer access token, you will be able to make
API calls for your own account without going through the
authorization flow. To obtain access tokens for other users, use the
standard OAuth flow.
</Text>
<Text>
For scoped apps, the token will have the same scope as the app.
</Text>
{token ? (
<>
<Text
color={
theme.isBase
? globalColors.lightErrorStatus
: globalColors.darkErrorStatus
}
>
This access token can be used to access your account ({email})
via the API. Don`t share your access token with anyone.
</Text>
<InputBlock
value={token}
scale
isReadOnly
isDisabled
size={InputSize.base}
iconName={CopyReactSvgUrl}
onIconClick={onCopyClick}
type={InputType.text}
/>
<Text className="dates">
Created: {dates.created}
<br />
Expires: {dates.expires}{" "}
</Text>
</>
) : null}
</StyledContainer>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
label="Generate developer token"
primary
scale
onClick={onGenerate}
isDisabled={!!token}
isLoading={requestRunning}
size={ButtonSize.small}
/>
<Button
label="Cancel"
scale
onClick={onClose}
size={ButtonSize.small}
isDisabled={requestRunning}
/>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default inject(
({
oauthStore,
userStore,
}: {
oauthStore: OAuthStoreProps;
userStore: UserStore;
}) => {
const { setGenerateDeveloperTokenDialogVisible, bufferSelection } =
oauthStore;
const { user } = userStore;
return {
setGenerateDeveloperTokenDialogVisible,
client: bufferSelection,
email: user?.email,
};
},
)(observer(GenerateDeveloperTokenDialog));

View File

@ -0,0 +1,141 @@
import React from "react";
import { inject, observer } from "mobx-react";
import styled from "styled-components";
import api from "@docspace/shared/api";
import { IClientProps } from "@docspace/shared/utils/oauth/types";
import {
ModalDialog,
ModalDialogType,
} from "@docspace/shared/components/modal-dialog";
import { Button, ButtonSize } from "@docspace/shared/components/button";
import { Text } from "@docspace/shared/components/text";
import { toastr } from "@docspace/shared/components/toast";
import { TData } from "@docspace/shared/components/toast/Toast.type";
import { InputBlock } from "@docspace/shared/components/input-block";
import { InputSize, InputType } from "@docspace/shared/components/text-input";
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import { UserStore } from "@docspace/shared/store/UserStore";
const StyledContainer = styled.div`
p {
margin-bottom: 16px;
}
`;
type GenerateDeveloperTokenDialogProps = {
client?: IClientProps;
setRevokeDeveloperTokenDialogVisible?: (value: boolean) => void;
};
const GenerateDeveloperTokenDialog = ({
client,
setRevokeDeveloperTokenDialogVisible,
}: GenerateDeveloperTokenDialogProps) => {
// const {} = useTranslation(["OAuth", "Common"]);
const [token, setToken] = React.useState("");
const [requestRunning, setRequestRunning] = React.useState(false);
const onRevoke = async () => {
if (!token || !client || requestRunning) return;
try {
const { clientId, clientSecret } = client;
setRequestRunning(true);
await api.oauth.revokeDeveloperToken(token, clientId, clientSecret);
setRequestRunning(false);
setToken("");
setRevokeDeveloperTokenDialogVisible?.(false);
toastr.success("Revoked");
} catch (e) {
toastr.error(e as TData);
}
};
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setToken(value);
};
const onClose = () => {
if (requestRunning) return;
setRevokeDeveloperTokenDialogVisible?.(false);
};
return (
<ModalDialog
visible
onClose={onClose}
displayType={ModalDialogType.modal}
autoMaxHeight
scale
>
<ModalDialog.Header>Revoke developer token</ModalDialog.Header>
<ModalDialog.Body>
<StyledContainer>
<Text>Warning text</Text>
<InputBlock
value={token}
scale
placeholder="Enter developer token"
type={InputType.text}
size={InputSize.base}
onChange={onChange}
/>
</StyledContainer>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
label="Revoke"
primary
scale
onClick={onRevoke}
isDisabled={!token}
isLoading={requestRunning}
size={ButtonSize.small}
/>
<Button
label="Cancel"
scale
onClick={onClose}
size={ButtonSize.small}
isDisabled={requestRunning}
/>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default inject(
({
oauthStore,
userStore,
}: {
oauthStore: OAuthStoreProps;
userStore: UserStore;
}) => {
const { setRevokeDeveloperTokenDialogVisible, bufferSelection } =
oauthStore;
const { user } = userStore;
return {
setRevokeDeveloperTokenDialogVisible,
client: bufferSelection,
email: user?.email,
};
},
)(observer(GenerateDeveloperTokenDialog));

View File

@ -61,6 +61,9 @@ export interface OAuthStoreProps {
generateDeveloperTokenDialogVisible: boolean;
setGenerateDeveloperTokenDialogVisible: (value: boolean) => void;
revokeDeveloperTokenDialogVisible: boolean;
setRevokeDeveloperTokenDialogVisible: (value: boolean) => void;
deleteDialogVisible: boolean;
setDeleteDialogVisible: (value: boolean) => void;
@ -162,6 +165,8 @@ class OAuthStore implements OAuthStoreProps {
generateDeveloperTokenDialogVisible: boolean = false;
revokeDeveloperTokenDialogVisible: boolean = false;
selection: string[] = [];
bufferSelection: IClientProps | null = null;
@ -225,6 +230,10 @@ class OAuthStore implements OAuthStoreProps {
this.generateDeveloperTokenDialogVisible = value;
};
setRevokeDeveloperTokenDialogVisible = (value: boolean) => {
this.revokeDeveloperTokenDialogVisible = value;
};
setClientSecret = (value: string) => {
this.clientSecret = value;
};
@ -536,6 +545,7 @@ class OAuthStore implements OAuthStoreProps {
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onRevoke = () => {
@ -546,6 +556,7 @@ class OAuthStore implements OAuthStoreProps {
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onDisable = () => {
@ -556,9 +567,10 @@ class OAuthStore implements OAuthStoreProps {
this.setDisableDialogVisible(true);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onGenerateDevelopToken = () => {
const onGenerateDeveloperToken = () => {
this.setBufferSelection(clientId);
this.setPreviewDialogVisible(false);
this.setInfoDialogVisible(false);
@ -566,6 +578,18 @@ class OAuthStore implements OAuthStoreProps {
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(true);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onRevokeDeveloperToken = () => {
this.setBufferSelection(clientId);
this.setPreviewDialogVisible(false);
this.setInfoDialogVisible(false);
this.setRevokeDialogVisible(false);
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(true);
};
const openOption = {
@ -621,6 +645,7 @@ class OAuthStore implements OAuthStoreProps {
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(true);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onShowPreview = () => {
@ -631,6 +656,7 @@ class OAuthStore implements OAuthStoreProps {
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onEnable = async (status: boolean) => {
@ -640,6 +666,7 @@ class OAuthStore implements OAuthStoreProps {
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
if (isGroupContext) {
try {
@ -698,15 +725,21 @@ class OAuthStore implements OAuthStoreProps {
onClick: onDisable,
};
const generateDevelopTokenOption = {
const generateDeveloperTokenOption = {
key: "generate-token",
icon: EnableReactSvgUrl,
label: "Generate developer token",
onClick: onGenerateDevelopToken,
onClick: onGenerateDeveloperToken,
};
const revokeDeveloperTokenOption = {
key: "revoke-token",
icon: EnableReactSvgUrl,
label: "Revoke developer token",
onClick: onRevokeDeveloperToken,
};
const contextOptions = [
{ ...generateDevelopTokenOption },
{
key: "Separator dropdownItem",
isSeparator: true,
@ -741,6 +774,9 @@ class OAuthStore implements OAuthStoreProps {
contextOptions.unshift(enableOption);
}
contextOptions.unshift(revokeDeveloperTokenOption);
contextOptions.unshift(generateDeveloperTokenOption);
if (!isInfo) contextOptions.unshift(infoOption);
contextOptions.unshift(authButtonOption);
contextOptions.unshift(editOption);