From 29a295f897db426aab5a33832506cdf3895117b3 Mon Sep 17 00:00:00 2001 From: Timofey Boyko Date: Tue, 13 Aug 2024 10:47:29 +0300 Subject: [PATCH 1/8] Client:Pages:OAuth2: fix header --- .../src/pages/PortalSettings/Layout/index.js | 2 +- .../OAuth/OAuthSectionHeader/index.tsx | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/client/src/pages/PortalSettings/Layout/index.js b/packages/client/src/pages/PortalSettings/Layout/index.js index eb188a7ed7..0c4019b59c 100644 --- a/packages/client/src/pages/PortalSettings/Layout/index.js +++ b/packages/client/src/pages/PortalSettings/Layout/index.js @@ -112,7 +112,7 @@ const Layout = ({ ) : currentPath === oauthCreatePath || currentPath === oauthEditPath ? ( - + ) : ( )} diff --git a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/OAuthSectionHeader/index.tsx b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/OAuthSectionHeader/index.tsx index e6ff21e183..a1fa9dafd8 100644 --- a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/OAuthSectionHeader/index.tsx +++ b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/OAuthSectionHeader/index.tsx @@ -28,17 +28,15 @@ const OAuthSectionHeader = ({ isEdit }: { isEdit: boolean }) => {
-
- + - {isEdit ? t("EditApp") : t("NewApp")} -
+ {isEdit ? t("EditApp") : t("NewApp")}
From 94d19ffdb8db2d96429ff30435fd64072f1a2689 Mon Sep 17 00:00:00 2001 From: Timofey Boyko Date: Tue, 13 Aug 2024 11:17:59 +0300 Subject: [PATCH 2/8] Shared:Themes:Dark: fix disabled input color text --- packages/shared/themes/dark.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/themes/dark.ts b/packages/shared/themes/dark.ts index 2a7fe92f3c..1b0eb05898 100644 --- a/packages/shared/themes/dark.ts +++ b/packages/shared/themes/dark.ts @@ -663,7 +663,7 @@ const Dark: TTheme = { input: { color: white, - disableColor: grayDarkStrong, + disableColor: grayDarkText, backgroundColor: black, disableBackgroundColor: grayDarkStrong, From 57ac4a729fbe555beec0cc22b92a7698844bd7ed Mon Sep 17 00:00:00 2001 From: Timofey Boyko Date: Tue, 13 Aug 2024 11:30:43 +0300 Subject: [PATCH 3/8] Client:PortalSettings:OAuth: fix select group required color --- .../sub-components/ClientForm/components/SelectGroup.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/ClientForm/components/SelectGroup.tsx b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/ClientForm/components/SelectGroup.tsx index d34a42fde6..25c15de91a 100644 --- a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/ClientForm/components/SelectGroup.tsx +++ b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/ClientForm/components/SelectGroup.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Text } from "@docspace/shared/components/text"; import { SelectorAddButton } from "@docspace/shared/components/selector-add-button"; +import { globalColors } from "@docspace/shared/themes"; import { StyledInputGroup } from "../ClientForm.styled"; @@ -55,7 +56,8 @@ const SelectGroup = ({ color="" textAlign="" > - {label} * + {label}{" "} + *
From 56d9c6e774e55bfe0a07d274764038575e6e9c45 Mon Sep 17 00:00:00 2001 From: Timofey Boyko Date: Wed, 14 Aug 2024 10:03:39 +0300 Subject: [PATCH 4/8] Client:OAuth2: add generateDeveloperToken to store --- .../GenerateDevelopTokenDialog.tsx | 26 +++++++++++++++ packages/client/src/store/OAuthStore.ts | 33 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDevelopTokenDialog.tsx diff --git a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDevelopTokenDialog.tsx b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDevelopTokenDialog.tsx new file mode 100644 index 0000000000..5492b9e903 --- /dev/null +++ b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDevelopTokenDialog.tsx @@ -0,0 +1,26 @@ +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
; +}; + +export default inject(({ oauthStore }: { oauthStore: OAuthStoreProps }) => { + const { + setInfoDialogVisible, + bufferSelection, + scopeList, + getContextMenuItems, + } = oauthStore; + + return { + setInfoDialogVisible, + client: bufferSelection, + scopeList, + getContextMenuItems, + }; +})(observer(GenerateDeveloperTokenDialog)); diff --git a/packages/client/src/store/OAuthStore.ts b/packages/client/src/store/OAuthStore.ts index 2b7c356ddf..9c3895976d 100644 --- a/packages/client/src/store/OAuthStore.ts +++ b/packages/client/src/store/OAuthStore.ts @@ -58,6 +58,9 @@ export interface OAuthStoreProps { resetDialogVisible: boolean; setResetDialogVisible: (value: boolean) => void; + generateDeveloperTokenDialogVisible: boolean; + setGenerateDeveloperTokenDialogVisible: (value: boolean) => void; + deleteDialogVisible: boolean; setDeleteDialogVisible: (value: boolean) => void; @@ -157,6 +160,8 @@ class OAuthStore implements OAuthStoreProps { resetDialogVisible: boolean = false; + generateDeveloperTokenDialogVisible: boolean = false; + selection: string[] = []; bufferSelection: IClientProps | null = null; @@ -216,6 +221,10 @@ class OAuthStore implements OAuthStoreProps { this.resetDialogVisible = value; }; + setGenerateDeveloperTokenDialogVisible = (value: boolean) => { + this.generateDeveloperTokenDialogVisible = value; + }; + setClientSecret = (value: string) => { this.clientSecret = value; }; @@ -526,6 +535,7 @@ class OAuthStore implements OAuthStoreProps { this.setInfoDialogVisible(true); this.setDisableDialogVisible(false); this.setDeleteDialogVisible(false); + this.setGenerateDeveloperTokenDialogVisible(false); }; const onRevoke = () => { @@ -535,6 +545,7 @@ class OAuthStore implements OAuthStoreProps { this.setRevokeDialogVisible(true); this.setDisableDialogVisible(false); this.setDeleteDialogVisible(false); + this.setGenerateDeveloperTokenDialogVisible(false); }; const onDisable = () => { @@ -544,6 +555,17 @@ class OAuthStore implements OAuthStoreProps { this.setRevokeDialogVisible(false); this.setDisableDialogVisible(true); this.setDeleteDialogVisible(false); + this.setGenerateDeveloperTokenDialogVisible(false); + }; + + const onGenerateDevelopToken = () => { + this.setBufferSelection(clientId); + this.setPreviewDialogVisible(false); + this.setInfoDialogVisible(false); + this.setRevokeDialogVisible(false); + this.setDisableDialogVisible(false); + this.setDeleteDialogVisible(false); + this.setGenerateDeveloperTokenDialogVisible(true); }; const openOption = { @@ -598,6 +620,7 @@ class OAuthStore implements OAuthStoreProps { this.setRevokeDialogVisible(false); this.setDisableDialogVisible(false); this.setDeleteDialogVisible(true); + this.setGenerateDeveloperTokenDialogVisible(false); }; const onShowPreview = () => { @@ -607,6 +630,7 @@ class OAuthStore implements OAuthStoreProps { this.setRevokeDialogVisible(false); this.setDisableDialogVisible(false); this.setDeleteDialogVisible(false); + this.setGenerateDeveloperTokenDialogVisible(false); }; const onEnable = async (status: boolean) => { @@ -615,6 +639,7 @@ class OAuthStore implements OAuthStoreProps { this.setRevokeDialogVisible(false); this.setDisableDialogVisible(false); this.setDeleteDialogVisible(false); + this.setGenerateDeveloperTokenDialogVisible(false); if (isGroupContext) { try { @@ -673,7 +698,15 @@ class OAuthStore implements OAuthStoreProps { onClick: onDisable, }; + const generateDevelopTokenOption = { + key: "generate-token", + icon: EnableReactSvgUrl, + label: "Generate developer token", + onClick: onGenerateDevelopToken, + }; + const contextOptions = [ + { ...generateDevelopTokenOption }, { key: "Separator dropdownItem", isSeparator: true, From ada7d8c960e4c11a6b6848dc0c2f11d84ce33e18 Mon Sep 17 00:00:00 2001 From: Timofey Boyko Date: Thu, 15 Aug 2024 16:18:59 +0300 Subject: [PATCH 5/8] Shared:Utils:Axios: add generic for API types --- packages/shared/api/client.ts | 6 +++--- packages/shared/utils/axiosClient.ts | 8 +++++--- packages/shared/utils/oauth/types.ts | 7 +++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/shared/api/client.ts b/packages/shared/api/client.ts index a0745847fb..59fc2d29a4 100644 --- a/packages/shared/api/client.ts +++ b/packages/shared/api/client.ts @@ -33,12 +33,12 @@ export const initSSR = (headers: Record) => { client.initSSR(headers); }; -export const request = ( +export const request = ( options: TReqOption & AxiosRequestConfig, skipRedirect = false, isOAuth = false, -) => { - return client.request(options, skipRedirect, isOAuth); +): Promise | undefined => { + return client.request(options, skipRedirect, isOAuth); }; export const setWithCredentialsStatus = (state: boolean) => { diff --git a/packages/shared/utils/axiosClient.ts b/packages/shared/utils/axiosClient.ts index c64fa9295b..81ba95615b 100644 --- a/packages/shared/utils/axiosClient.ts +++ b/packages/shared/utils/axiosClient.ts @@ -182,11 +182,11 @@ class AxiosClient { } }; - request = ( + request = ( options: TReqOption & AxiosRequestConfig, skipRedirect = false, isOAuth = false, - ) => { + ): Promise | undefined => { const onSuccess = (response: TRes) => { const error = this.getResponseError(response); @@ -295,7 +295,9 @@ class AxiosClient { return Promise.reject(error); }; - return this.client?.(options).then(onSuccess).catch(onError); + return this.client?.(options).then(onSuccess).catch(onError) as + | Promise + | undefined; }; } diff --git a/packages/shared/utils/oauth/types.ts b/packages/shared/utils/oauth/types.ts index 5c332d8840..d269b28910 100644 --- a/packages/shared/utils/oauth/types.ts +++ b/packages/shared/utils/oauth/types.ts @@ -150,3 +150,10 @@ export type IClientListProps = List; export type IClientListDTO = List; export type TConsentList = List; + +export type TGenerateDeveloperToken = { + access_token: string; + expires_in: number; + scope: string; + token_type: string; +}; From b3dd68106506e9fdf9b74e817fe8627cf6972f60 Mon Sep 17 00:00:00 2001 From: Timofey Boyko Date: Thu, 15 Aug 2024 16:19:27 +0300 Subject: [PATCH 6/8] Shared:API:OAuth2: add revoke and generate developer token methods --- packages/shared/api/oauth/index.ts | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/shared/api/oauth/index.ts b/packages/shared/api/oauth/index.ts index 784cfc54c1..5c3e6f79b2 100644 --- a/packages/shared/api/oauth/index.ts +++ b/packages/shared/api/oauth/index.ts @@ -11,6 +11,7 @@ import { IClientReqDTO, TConsentData, TConsentList, + TGenerateDeveloperToken, } from "../../utils/oauth/types"; export const getClient = async (clientId: string): Promise => { @@ -237,3 +238,38 @@ export const onOAuthCancel = (clientId: string, clientState: string) => { true, ); }; + +export const generateDevelopToken = ( + client_id: string, + client_secret: string, + scopes: string[], +): Promise | undefined => { + const params = new URLSearchParams(); + params.append("grant_type", "personal_access_token"); + params.append("client_id", client_id); + params.append("client_secret", client_secret); + params.append("scope", scopes.join(" ")); + + return request( + { method: "post", url: "/oauth2/token", data: params }, + false, + true, + ); +}; + +export const revokeDeveloperToken = ( + token: string, + client_id: string, + client_secret: string, +) => { + const params = new URLSearchParams(); + params.append("token", token); + params.append("client_id", client_id); + params.append("client_secret", client_secret); + + return request( + { method: "post", url: "/oauth2/revoke", data: params }, + false, + true, + ); +}; From b89bea71238673f1d4928a890b8c1a31320a1a7c Mon Sep 17 00:00:00 2001 From: Timofey Boyko Date: Thu, 15 Aug 2024 16:20:03 +0300 Subject: [PATCH 7/8] Client:PortalSettings:OAuth: add generate and revoke developer token dialogs --- .../developer-tools/OAuth/OAuth.types.ts | 3 + .../developer-tools/OAuth/index.tsx | 10 + .../GenerateDevelopTokenDialog.tsx | 26 --- .../GenerateDeveloperTokenDialog.tsx | 210 ++++++++++++++++++ .../RevokeDeveloperTokenDialog.tsx | 141 ++++++++++++ packages/client/src/store/OAuthStore.ts | 44 +++- 6 files changed, 404 insertions(+), 30 deletions(-) delete mode 100644 packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDevelopTokenDialog.tsx create mode 100644 packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDeveloperTokenDialog.tsx create mode 100644 packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/RevokeDeveloperTokenDialog.tsx diff --git a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/OAuth.types.ts b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/OAuth.types.ts index f2aac319aa..3e8e2e2e3b 100644 --- a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/OAuth.types.ts +++ b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/OAuth.types.ts @@ -18,6 +18,9 @@ export interface OAuthProps { previewDialogVisible?: boolean; disableDialogVisible?: boolean; deleteDialogVisible?: boolean; + generateDeveloperTokenDialogVisible?: boolean; + revokeDeveloperTokenDialogVisible?: boolean; + isInit: boolean; setIsInit: (value: boolean) => void; } diff --git a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/index.tsx b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/index.tsx index 52f8f78692..a754a8b734 100644 --- a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/index.tsx +++ b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/index.tsx @@ -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 && } {previewDialogVisible && } {deleteDialogVisible && } + {generateDeveloperTokenDialogVisible && } + {revokeDeveloperTokenDialogVisible && } ); }; @@ -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)); diff --git a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDevelopTokenDialog.tsx b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDevelopTokenDialog.tsx deleted file mode 100644 index 5492b9e903..0000000000 --- a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDevelopTokenDialog.tsx +++ /dev/null @@ -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
; -}; - -export default inject(({ oauthStore }: { oauthStore: OAuthStoreProps }) => { - const { - setInfoDialogVisible, - bufferSelection, - scopeList, - getContextMenuItems, - } = oauthStore; - - return { - setInfoDialogVisible, - client: bufferSelection, - scopeList, - getContextMenuItems, - }; -})(observer(GenerateDeveloperTokenDialog)); diff --git a/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDeveloperTokenDialog.tsx b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDeveloperTokenDialog.tsx new file mode 100644 index 0000000000..470973e9be --- /dev/null +++ b/packages/client/src/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/GenerateDeveloperTokenDialog.tsx @@ -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 ( + + Generate developer token + + + + 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. + + + For scoped apps, the token will have the same scope as the app. + + {token ? ( + <> + + This access token can be used to access your account ({email}) + via the API. Don`t share your access token with anyone. + + + + Created: {dates.created} +
+ Expires: {dates.expires}{" "} +
+ + ) : null} +
+
+ +