From 5c2b96a81231e092ddf678b03ca4d7aff0a2efeb Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Sat, 1 Apr 2023 23:00:25 +0000 Subject: [PATCH] feat(dropbox): support connections --- .../src/apps/dropbox/assets/favicon.svg | 3 + .../apps/dropbox/auth/generate-auth-url.ts | 22 ++++ .../backend/src/apps/dropbox/auth/index.ts | 48 +++++++++ .../apps/dropbox/auth/is-still-verified.ts | 9 ++ .../src/apps/dropbox/auth/refresh-token.ts | 38 +++++++ .../apps/dropbox/auth/verify-credentials.ts | 102 ++++++++++++++++++ .../apps/dropbox/common/add-auth-header.ts | 13 +++ .../dropbox/common/get-current-account.ts | 8 ++ .../backend/src/apps/dropbox/common/scopes.ts | 8 ++ packages/backend/src/apps/dropbox/index.d.ts | 0 packages/backend/src/apps/dropbox/index.ts | 16 +++ 11 files changed, 267 insertions(+) create mode 100644 packages/backend/src/apps/dropbox/assets/favicon.svg create mode 100644 packages/backend/src/apps/dropbox/auth/generate-auth-url.ts create mode 100644 packages/backend/src/apps/dropbox/auth/index.ts create mode 100644 packages/backend/src/apps/dropbox/auth/is-still-verified.ts create mode 100644 packages/backend/src/apps/dropbox/auth/refresh-token.ts create mode 100644 packages/backend/src/apps/dropbox/auth/verify-credentials.ts create mode 100644 packages/backend/src/apps/dropbox/common/add-auth-header.ts create mode 100644 packages/backend/src/apps/dropbox/common/get-current-account.ts create mode 100644 packages/backend/src/apps/dropbox/common/scopes.ts create mode 100644 packages/backend/src/apps/dropbox/index.d.ts create mode 100644 packages/backend/src/apps/dropbox/index.ts diff --git a/packages/backend/src/apps/dropbox/assets/favicon.svg b/packages/backend/src/apps/dropbox/assets/favicon.svg new file mode 100644 index 00000000..59f38626 --- /dev/null +++ b/packages/backend/src/apps/dropbox/assets/favicon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/backend/src/apps/dropbox/auth/generate-auth-url.ts b/packages/backend/src/apps/dropbox/auth/generate-auth-url.ts new file mode 100644 index 00000000..d663c25c --- /dev/null +++ b/packages/backend/src/apps/dropbox/auth/generate-auth-url.ts @@ -0,0 +1,22 @@ +import { URLSearchParams } from 'url'; +import { IField, IGlobalVariable } from '@automatisch/types'; +import scopes from '../common/scopes'; + +export default async function generateAuthUrl($: IGlobalVariable) { + const oauthRedirectUrlField = $.app.auth.fields.find( + (field: IField) => field.key == 'oAuthRedirectUrl' + ); + const callbackUrl = oauthRedirectUrlField.value as string; + + const searchParams = new URLSearchParams({ + client_id: $.auth.data.clientId as string, + redirect_uri: callbackUrl, + response_type: 'code', + scope: scopes.join(' '), + token_access_type: 'offline', + }); + + const url = `${$.app.baseUrl}/oauth2/authorize?${searchParams.toString()}`; + + await $.auth.set({ url }); +} diff --git a/packages/backend/src/apps/dropbox/auth/index.ts b/packages/backend/src/apps/dropbox/auth/index.ts new file mode 100644 index 00000000..4331302b --- /dev/null +++ b/packages/backend/src/apps/dropbox/auth/index.ts @@ -0,0 +1,48 @@ +import generateAuthUrl from './generate-auth-url'; +import verifyCredentials from './verify-credentials'; +import isStillVerified from './is-still-verified'; +import refreshToken from './refresh-token'; + +export default { + fields: [ + { + key: 'oAuthRedirectUrl', + label: 'OAuth Redirect URL', + type: 'string' as const, + required: true, + readOnly: true, + value: '{WEB_APP_URL}/app/dropbox/connections/add', + placeholder: null, + description: + 'When asked to input an OAuth callback or redirect URL in Dropbox OAuth, enter the URL above.', + clickToCopy: true, + }, + { + key: 'clientId', + label: 'App Key', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: null, + clickToCopy: false, + }, + { + key: 'clientSecret', + label: 'App Secret', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: null, + clickToCopy: false, + }, + ], + + generateAuthUrl, + verifyCredentials, + isStillVerified, + refreshToken, +}; diff --git a/packages/backend/src/apps/dropbox/auth/is-still-verified.ts b/packages/backend/src/apps/dropbox/auth/is-still-verified.ts new file mode 100644 index 00000000..bf70f874 --- /dev/null +++ b/packages/backend/src/apps/dropbox/auth/is-still-verified.ts @@ -0,0 +1,9 @@ +import { IGlobalVariable } from '@automatisch/types'; +import getCurrentAccount from '../common/get-current-account'; + +const isStillVerified = async ($: IGlobalVariable) => { + const account = await getCurrentAccount($); + return !!account; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/dropbox/auth/refresh-token.ts b/packages/backend/src/apps/dropbox/auth/refresh-token.ts new file mode 100644 index 00000000..5cd2e226 --- /dev/null +++ b/packages/backend/src/apps/dropbox/auth/refresh-token.ts @@ -0,0 +1,38 @@ +import { Buffer } from 'node:buffer'; +import { IGlobalVariable } from '@automatisch/types'; + +const refreshToken = async ($: IGlobalVariable) => { + const params = { + grant_type: 'refresh_token', + refresh_token: $.auth.data.refreshToken as string, + }; + + const basicAuthToken = Buffer + .from(`${$.auth.data.clientId}:${$.auth.data.clientSecret}`) + .toString('base64'); + + const { data } = await $.http.post( + 'oauth2/token', + null, + { + params, + headers: { + Authorization: `Basic ${basicAuthToken}` + } + } + ); + + const { + access_token: accessToken, + expires_in: expiresIn, + token_type: tokenType, + } = data; + + await $.auth.set({ + accessToken, + expiresIn, + tokenType, + }); +}; + +export default refreshToken; diff --git a/packages/backend/src/apps/dropbox/auth/verify-credentials.ts b/packages/backend/src/apps/dropbox/auth/verify-credentials.ts new file mode 100644 index 00000000..f56b25d2 --- /dev/null +++ b/packages/backend/src/apps/dropbox/auth/verify-credentials.ts @@ -0,0 +1,102 @@ +import { IGlobalVariable, IField } from '@automatisch/types'; +import getCurrentAccount from '../common/get-current-account'; + +type TAccount = { + account_id: string, + name: { + given_name: string, + surname: string, + familiar_name: string, + display_name: string, + abbreviated_name: string, + }, + email: string, + email_verified: boolean, + disabled: boolean, + country: string, + locale: string, + referral_link: string, + is_paired: boolean, + account_type: { + ".tag": string, + }, + root_info: { + ".tag": string, + root_namespace_id: string, + home_namespace_id: string, + }, +} + +const verifyCredentials = async ($: IGlobalVariable) => { + const oauthRedirectUrlField = $.app.auth.fields.find( + (field: IField) => field.key == 'oAuthRedirectUrl' + ); + const redirectUrl = oauthRedirectUrlField.value as string; + const params = { + client_id: $.auth.data.clientId as string, + redirect_uri: redirectUrl, + client_secret: $.auth.data.clientSecret as string, + code: $.auth.data.code as string, + grant_type: 'authorization_code', + } + const { data: verifiedCredentials } = await $.http.post( + '/oauth2/token', + null, + { params } + ); + + const { + access_token: accessToken, + refresh_token: refreshToken, + expires_in: expiresIn, + scope: scope, + token_type: tokenType, + account_id: accountId, + team_id: teamId, + id_token: idToken, + uid, + } = verifiedCredentials; + + await $.auth.set({ + accessToken, + refreshToken, + expiresIn, + scope, + tokenType, + accountId, + teamId, + idToken, + uid + }); + + const account = await getCurrentAccount($) as TAccount; + + await $.auth.set({ + accountId: account.account_id, + name: { + givenName: account.name.given_name, + surname: account.name.surname, + familiarName: account.name.familiar_name, + displayName: account.name.display_name, + abbreviatedName: account.name.abbreviated_name, + }, + email: account.email, + emailVerified: account.email_verified, + disabled: account.disabled, + country: account.country, + locale: account.locale, + referralLink: account.referral_link, + isPaired: account.is_paired, + accountType: { + ".tag": account.account_type['.tag'], + }, + rootInfo: { + ".tag": account.root_info['.tag'], + rootNamespaceId: account.root_info.root_namespace_id, + homeNamespaceId: account.root_info.home_namespace_id, + }, + screenName: `${account.name.display_name} - ${account.email}`, + }); +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/dropbox/common/add-auth-header.ts b/packages/backend/src/apps/dropbox/common/add-auth-header.ts new file mode 100644 index 00000000..984d0b3a --- /dev/null +++ b/packages/backend/src/apps/dropbox/common/add-auth-header.ts @@ -0,0 +1,13 @@ +import { TBeforeRequest } from '@automatisch/types'; + +const addAuthHeader: TBeforeRequest = ($, requestConfig) => { + requestConfig.headers['Content-Type'] = 'application/json'; + + if (!requestConfig.headers.Authorization && $.auth.data?.accessToken) { + requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`; + } + + return requestConfig; +}; + +export default addAuthHeader; diff --git a/packages/backend/src/apps/dropbox/common/get-current-account.ts b/packages/backend/src/apps/dropbox/common/get-current-account.ts new file mode 100644 index 00000000..6764e781 --- /dev/null +++ b/packages/backend/src/apps/dropbox/common/get-current-account.ts @@ -0,0 +1,8 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +const getCurrentAccount = async ($: IGlobalVariable): Promise => { + const response = await $.http.post('/2/users/get_current_account', null); + return response.data; +}; + +export default getCurrentAccount; diff --git a/packages/backend/src/apps/dropbox/common/scopes.ts b/packages/backend/src/apps/dropbox/common/scopes.ts new file mode 100644 index 00000000..b257d7cb --- /dev/null +++ b/packages/backend/src/apps/dropbox/common/scopes.ts @@ -0,0 +1,8 @@ +const scopes = [ + 'account_info.read', + 'files.metadata.read', + 'files.content.write', + 'files.content.read', +]; + +export default scopes; diff --git a/packages/backend/src/apps/dropbox/index.d.ts b/packages/backend/src/apps/dropbox/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/backend/src/apps/dropbox/index.ts b/packages/backend/src/apps/dropbox/index.ts new file mode 100644 index 00000000..334b70f9 --- /dev/null +++ b/packages/backend/src/apps/dropbox/index.ts @@ -0,0 +1,16 @@ +import defineApp from '../../helpers/define-app'; +import addAuthHeader from './common/add-auth-header'; +import auth from './auth'; + +export default defineApp({ + name: 'Dropbox', + key: 'dropbox', + iconUrl: '{BASE_URL}/apps/dropbox/assets/favicon.svg', + authDocUrl: 'https://automatisch.io/docs/apps/dropbox/connection', + supportsConnections: true, + baseUrl: 'https://dropbox.com', + apiBaseUrl: 'https://api.dropboxapi.com', + primaryColor: '0061ff', + beforeRequest: [addAuthHeader], + auth, +});