diff --git a/app/client/package.json b/app/client/package.json index add895688c86..f4b971e8fe2d 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -106,6 +106,7 @@ "@uppy/url": "^1.5.16", "@uppy/utils": "^6.0.2", "@uppy/webcam": "^1.8.4", + "@useparagon/connect": "^1.0.18", "@welldone-software/why-did-you-render": "^4.2.5", "acorn": "8.10.0", "acorn-walk": "8.2.0", diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab.tsx index e5880bd50320..851dd22f0b61 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab.tsx @@ -176,7 +176,11 @@ const QueryResponseTab = (props: Props) => { // Pass the error to be shown in the error tab error = actionResponse.readableError ? getErrorAsString(actionResponse.readableError) - : getErrorAsString(actionResponse.body); + : getErrorAsString( + (actionResponse.body as any)?.message + ? (actionResponse.body as any).message + : actionResponse.body, + ); } else if (isString(actionResponse.body)) { //reset error. error = ""; @@ -278,7 +282,10 @@ const QueryResponseTab = (props: Props) => { ) : ( actionResponse.body && ( -
{actionResponse.body}
+
+ {(actionResponse.body as any).message || + actionResponse.body} +
) ))} > { return Api.delete(`${WorkspaceApi.workspacesURL}/${workspaceId}`); } + static async fetchWorkspaceToken( + request: FetchWorkspaceRequest, + ): Promise> { + return Api.get( + WorkspaceApi.workspacesURL + "/" + request.workspaceId + "/token", + ); + } } export default WorkspaceApi; diff --git a/app/client/src/ce/constants/workspaceConstants.ts b/app/client/src/ce/constants/workspaceConstants.ts index e7e8321ee11c..a9f715127fa2 100644 --- a/app/client/src/ce/constants/workspaceConstants.ts +++ b/app/client/src/ce/constants/workspaceConstants.ts @@ -5,7 +5,7 @@ export interface WorkspaceRole { isDefault?: boolean; } -export interface Workspace { +export interface Workspace extends WorkspaceToken { id: string; name: string; website?: string; @@ -37,6 +37,12 @@ export interface WorkspaceUser { roles: WorkspaceUserRoles[]; } +export interface WorkspaceToken { + workspaceId?: string; + token?: string; + projectId?: string; +} + export enum ENTITY_TYPE { WORKSPACE = "Workspace", APPLICATION = "Application", diff --git a/app/client/src/ce/sagas/WorkspaceSagas.ts b/app/client/src/ce/sagas/WorkspaceSagas.ts index 5670661b597c..36e8bfca84b7 100644 --- a/app/client/src/ce/sagas/WorkspaceSagas.ts +++ b/app/client/src/ce/sagas/WorkspaceSagas.ts @@ -25,6 +25,7 @@ import type { FetchAllRolesRequest, SaveWorkspaceLogo, FetchWorkspacesResponse, + FetchWorkspaceTokenResponse, } from "ee/api/WorkspaceApi"; import WorkspaceApi from "ee/api/WorkspaceApi"; import type { ApiResponse } from "api/ApiResponses"; @@ -118,10 +119,18 @@ export function* fetchWorkspaceSaga( const isValidResponse: boolean = yield request.skipValidation || validateResponse(response); + const responseToken: FetchWorkspaceTokenResponse = yield call( + WorkspaceApi.fetchWorkspaceToken, + request, + ); + if (isValidResponse && response) { yield put({ type: ReduxActionTypes.FETCH_WORKSPACE_SUCCESS, - payload: response.data || {}, + payload: { + ...(response.data || {}), + ...(responseToken.data || {}), + }, }); } } catch (error) { diff --git a/app/client/src/ce/utils/autocomplete/EntityDefinitions.ts b/app/client/src/ce/utils/autocomplete/EntityDefinitions.ts index a0a59b4f9cfd..1038fe649402 100644 --- a/app/client/src/ce/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/ce/utils/autocomplete/EntityDefinitions.ts @@ -87,6 +87,15 @@ export const entityDefinitions = { "https://docs.appsmith.com/reference/appsmith-framework/context-object#geolocationclearwatch", }, }, + datasource: { + "!doc": "Object to enable running custom datasources", + "!url": "", + request: { + "!type": + "fn(requestType: string, apiPath: string, options: object) -> +Promise|void", + "!url": "", + }, + }, ...eeAppsmithAutocompleteDefs(generatedTypeDef), }; } diff --git a/app/client/src/components/editorComponents/ActionCreator/constants.ts b/app/client/src/components/editorComponents/ActionCreator/constants.ts index 859e75217316..d4792f6d22f3 100644 --- a/app/client/src/components/editorComponents/ActionCreator/constants.ts +++ b/app/client/src/components/editorComponents/ActionCreator/constants.ts @@ -20,6 +20,7 @@ export enum APPSMITH_NAMESPACED_FUNCTIONS { getGeolocation = "appsmith.geolocation.getCurrentPosition", watchGeolocation = "appsmith.geolocation.watchPosition", stopWatchGeolocation = "appsmith.geolocation.clearWatch", + datasourceRequest = "appsmith.datasource.request", } export enum APPSMITH_INTEGRATIONS { diff --git a/app/client/src/pages/Editor/APIEditor/index.tsx b/app/client/src/pages/Editor/APIEditor/index.tsx index 568fca9c0199..67edf8f37925 100644 --- a/app/client/src/pages/Editor/APIEditor/index.tsx +++ b/app/client/src/pages/Editor/APIEditor/index.tsx @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from "react-redux"; import type { RouteComponentProps } from "react-router"; import { + getDatasource, getIsActionConverting, getPageList, getPluginSettingConfigs, @@ -39,6 +40,8 @@ import { ENTITY_ICON_SIZE, EntityIcon } from "../Explorer/ExplorerIcons"; import { getIDEViewMode } from "selectors/ideSelectors"; import { EditorViewMode } from "ee/entities/IDE/constants"; import { AppPluginActionEditor } from "pages/Editor/AppPluginActionEditor"; +import { getCurrentEnvironmentId } from "ce/selectors/environmentSelectors"; +import QueryEditor from "pages/Editor/QueryEditor"; type ApiEditorWrapperProps = RouteComponentProps; @@ -60,6 +63,23 @@ function ApiEditorWrapper(props: ApiEditorWrapperProps) { const apiName = action?.name || ""; const pluginId = get(action, "pluginId", ""); const datasourceId = action?.datasource.id || ""; + if (datasourceId) { + const currentEnvironment = useSelector(getCurrentEnvironmentId); + const datasource = useSelector((state) => + getDatasource(state, datasourceId), + ); + const datasourceConfigurationProps = + datasource?.datasourceStorages?.[currentEnvironment] + ?.datasourceConfiguration?.properties; + if ( + datasourceConfigurationProps && + datasourceConfigurationProps.some( + ({ key }) => key === "integrationId" || key === "integrationType", + ) + ) { + return ; + } + } const plugins = useSelector(getPlugins); const pages = useSelector(getPageList); const pageName = getPageName(pages, basePageId); diff --git a/app/client/src/pages/Editor/DataSourceEditor/DSFormHeader.tsx b/app/client/src/pages/Editor/DataSourceEditor/DSFormHeader.tsx index 34561ee1f529..5551e537fc2a 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/DSFormHeader.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/DSFormHeader.tsx @@ -28,6 +28,7 @@ import type { PluginType } from "entities/Action"; import { useEditorType } from "ee/hooks"; import { useHistory } from "react-router"; import { useHeaderActions } from "ee/hooks/datasourceEditorHooks"; +import { paragon } from "@useparagon/connect"; export const ActionWrapper = styled.div` display: flex; @@ -172,6 +173,32 @@ export const DSFormHeader = (props: DSFormHeaderProps) => { showReconnectButton, }); + const onClickConnect = () => { + const datasourceConfigurationProps = (datasource as Datasource) + ?.datasourceStorages?.[currentEnv]?.datasourceConfiguration?.properties; + + const integrationId = + datasourceConfigurationProps?.find(({ key }) => key === "integrationId") + ?.value || ""; + const integrationType = + datasourceConfigurationProps?.find(({ key }) => key === "integrationType") + ?.value || ""; + + if (!integrationId || !integrationType) return; + + paragon.connect(integrationType, { + // selectedCredentialId: integrationId, + // allowMultipleCredentials: true, + accountType: "default", + onSuccess: (event, user) => { + console.log(event); + }, + onError: (err) => { + console.log(err); + }, + }); + }; + return (
@@ -232,6 +259,20 @@ export const DSFormHeader = (props: DSFormHeaderProps) => { {headerActions && headerActions.generatePageButton ? headerActions.generatePageButton : null} + )}
diff --git a/app/client/src/pages/Editor/IntegrationEditor/CreateNewDatasourceTab.tsx b/app/client/src/pages/Editor/IntegrationEditor/CreateNewDatasourceTab.tsx index 8bbdc69257f4..e744acc2d4b0 100644 --- a/app/client/src/pages/Editor/IntegrationEditor/CreateNewDatasourceTab.tsx +++ b/app/client/src/pages/Editor/IntegrationEditor/CreateNewDatasourceTab.tsx @@ -10,19 +10,20 @@ import { getHasCreateDatasourcePermission } from "ee/utils/BusinessFeatures/perm import { getDatasources, getMockDatasources, + getPlugins, } from "ee/selectors/entitiesSelector"; import { getCurrentApplicationId, getCurrentPageId, } from "selectors/editorSelectors"; -import { connect } from "react-redux"; +import { connect, useDispatch, useSelector } from "react-redux"; import type { Datasource, MockDatasource } from "entities/Datasource"; import scrollIntoView from "scroll-into-view-if-needed"; import { Text } from "@appsmith/ads"; import MockDataSources from "./MockDataSources"; -import NewApiScreen from "./NewApi"; +import NewApiScreen, { ApiCard, CardContentWrapper } from "./NewApi"; import NewQueryScreen from "./NewQuery"; -import { isAirgapped } from "ee/utils/airgapHelpers"; +import { getAssetUrl, isAirgapped } from "ee/utils/airgapHelpers"; import { showDebuggerFlag } from "selectors/debuggerSelectors"; import { createMessage, @@ -40,6 +41,14 @@ import { useParentEntityInfo } from "ee/hooks/datasourceEditorHooks"; import AIDataSources from "./AIDataSources"; import Debugger from "../DataSourceEditor/Debugger"; import { isPluginActionCreating } from "PluginActionEditor/store"; +import { paragon } from "@useparagon/connect"; +import AnalyticsUtil from "ee/utils/AnalyticsUtil"; +import { useParagonIntegrations } from "utils/paragonHooks"; +import { DATASOURCE_NAME_DEFAULT_PREFIX } from "constants/Datasource"; +import { ReduxActionTypes } from "ce/constants/ReduxActionConstants"; +import { PluginPackageName } from "entities/Action"; +import type { Plugin } from "api/PluginApi"; +import { getNextEntityName } from "utils/AppsmithUtils"; const NewIntegrationsContainer = styled.div` ${thinScrollbar}; @@ -174,6 +183,88 @@ function CreateNewDatasource({ ); } +function ParagonIntegrations() { + const { integrations } = useParagonIntegrations(); + const dispatch = useDispatch(); + const dsList: Datasource[] = useSelector(getDatasources); + const plugins: Plugin[] = useSelector(getPlugins); + const apiPlugin = plugins.find( + (plugin) => plugin.packageName === PluginPackageName.REST_API, + ); + const datasourceName = getNextEntityName( + DATASOURCE_NAME_DEFAULT_PREFIX, + dsList.map((el: Datasource) => el.name), + ); + const handleOnClick = (type: string) => { + paragon.installIntegration(type, { + allowMultipleCredentials: true, + accountType: "default", + onSuccess: (event, user) => { + console.log(event); + dispatch({ + type: ReduxActionTypes.CREATE_DATASOURCE_FROM_FORM_INIT, + payload: { + type: "API", + pluginId: apiPlugin!.id, + datasourceStorages: { + unused_env: { + environmentId: "unused_env", + isValid: false, + datasourceConfiguration: { + url: "https://proxy.useparagon.com", + properties: Object.keys(event).reduce((acc, key) => { + // @ts-expect-error + acc.push({ + key: key as string, + // @ts-expect-error + value: event[key] as any, + }); + return acc; + }, []), + }, + toastMessage: "EMPTY_TOAST_MESSAGE", + }, + }, + name: datasourceName, + }, + }); + }, + onError: (err) => { + console.log(err); + }, + }); + }; + + return ( + <> + {integrations.map((integration) => ( + { + AnalyticsUtil.logEvent("CREATE_DATA_SOURCE_CLICK", { + pluginName: integration.name, + pluginPackageName: integration.type, + }); + handleOnClick(integration.type); + }} + > + + {integration.name} +

{integration.name}

+
+
+ ))} + + ); +} + function CreateNewSaasIntegration({ active, isCreating, @@ -210,7 +301,9 @@ function CreateNewSaasIntegration({ pageId={pageId} showSaasAPIs showUnsupportedPluginDialog={showUnsupportedPluginDialog} - /> + > + + ) : null; diff --git a/app/client/src/pages/Editor/IntegrationEditor/NewApi.tsx b/app/client/src/pages/Editor/IntegrationEditor/NewApi.tsx index 4364bad281e3..743dd16a6e0d 100644 --- a/app/client/src/pages/Editor/IntegrationEditor/NewApi.tsx +++ b/app/client/src/pages/Editor/IntegrationEditor/NewApi.tsx @@ -142,6 +142,7 @@ interface ApiHomeScreenProps { apiType: string, ) => void; isOnboardingScreen?: boolean; + children?: JSX.Element; } type Props = ApiHomeScreenProps; @@ -328,6 +329,7 @@ function NewApiScreen(props: Props) { ))} + {props.children} ); diff --git a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx index d82a9640211c..d055fb051229 100644 --- a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx +++ b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx @@ -19,7 +19,7 @@ import type { AppState } from "ee/reducers"; import { thinScrollbar } from "constants/DefaultTheme"; import type { ActionResponse } from "api/ActionAPI"; import type { Plugin } from "api/PluginApi"; -import type { UIComponentTypes } from "api/PluginApi"; +import { UIComponentTypes } from "api/PluginApi"; import { EDITOR_TABS, SQL_DATASOURCES } from "constants/QueryEditorConstants"; import type { FormEvalOutput } from "reducers/evaluationReducers/formEvaluationReducer"; import { @@ -39,7 +39,15 @@ import RunHistory from "ee/components/RunHistory"; import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; import { getHasExecuteActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; -import { getPluginNameFromId } from "ee/selectors/entitiesSelector"; +import { + getDatasource, + getPluginNameFromId, +} from "ee/selectors/entitiesSelector"; +import { getCurrentEnvironmentId } from "ee/selectors/environmentSelectors"; +import { + useParagonIntegrationsWithWorklows, +} from "utils/paragonHooks"; +import { klona } from "klona"; const QueryFormContainer = styled.form` flex: 1; @@ -143,6 +151,108 @@ const StyledNotificationWrapper = styled.div` var(--ads-v2-spaces-7); `; +const PARAGON_EDITOR_JSON = [ + { + controlType: "DOUBLE_COLUMN_ZONE", + identifier: "FIND-Z1", + children: [ + { + label: "Method", + configProperty: "actionConfiguration.httpMethod", + controlType: "DROP_DOWN", + initialValue: "GET", + options: [ + { + label: "GET", + value: "GET", + }, + { + label: "POST", + value: "POST", + }, + { + label: "PUT", + value: "PUT", + }, + { + label: "DELETE", + value: "DELETE", + }, + ], + }, + { + label: "Action Type", + configProperty: "actionConfiguration.formData.actionType", + controlType: "DROP_DOWN", + options: [ + { + label: "Workflow", + value: "workflow", + }, + { + label: "Api Path", + value: "api", + }, + ], + initialValue: "workflow", + }, + ], + }, + { + controlType: "SECTION_V2", + identifier: "SECTION-ONE", + children: [ + { + controlType: "SINGLE_COLUMN_ZONE", + identifier: "API_PATH", + children: [ + { + label: "Api Path", + configProperty: "actionConfiguration.formData.apiPath", + controlType: "QUERY_DYNAMIC_INPUT_TEXT", + evaluationSubstitutionType: "TEMPLATE", + placeholderText: "v1/example", + }, + ], + }, + ], + }, + { + controlType: "SECTION_V3", + identifier: "SECTION-TWO", + children: [ + { + controlType: "SINGLE_COLUMN_ZONE", + identifier: "SO-Z2", + children: [ + { + label: "Query Params", + configProperty: "actionConfiguration.queryParameters", + controlType: "KEYVALUE_ARRAY", + }, + ], + }, + ], + }, + { + controlType: "SECTION_V4", + identifier: "SECTION-THREE", + children: [ + { + controlType: "SINGLE_COLUMN_ZONE", + identifier: "SO-Z3", + children: [ + { + label: "Body", + configProperty: "actionConfiguration.body", + controlType: "QUERY_DYNAMIC_TEXT", + }, + ], + }, + ], + }, +]; + interface QueryFormProps { onDeleteClick: () => void; onRunClick: () => void; @@ -190,7 +300,6 @@ export function EditorJSONtoForm(props: Props) { actionResponse, dataSources, documentationLink, - editorConfig, formName, handleSubmit, isRunning, @@ -199,9 +308,10 @@ export function EditorJSONtoForm(props: Props) { plugin, runErrorMessage, settingConfig, - uiComponent, } = props; + let { editorConfig, uiComponent } = props; + const { actionRightPaneAdditionSections, notification } = useContext(QueryEditorContext); @@ -273,6 +383,47 @@ export function EditorJSONtoForm(props: Props) { (!actionBody && SQL_DATASOURCES.includes(currentActionPluginName)) || !isExecutePermitted; + const formData = props.formData; + + // Paragon Integration + const datasource = useSelector((state) => + getDatasource(state, props.datasourceId), + ); + const currentEnvironment = useSelector(getCurrentEnvironmentId); + const datasourceConfigurationProps = + datasource?.datasourceStorages?.[currentEnvironment] + ?.datasourceConfiguration?.properties; + + const integrationId = + datasourceConfigurationProps?.find(({ key }) => key === "integrationId") + ?.value || ""; + + const integrationsWithWorkflows = useParagonIntegrationsWithWorklows(); + const integrationData = integrationsWithWorkflows[integrationId]; + if (integrationId && integrationData) { + const paragonEditorJson = klona(PARAGON_EDITOR_JSON); + paragonEditorJson[0].children.push({ + controlType: "SINGLE_COLUMN_ZONE", + identifier: "WORKFLOW", + children: [ + { + label: "Workflow", + configProperty: "actionConfiguration.formData.workflowId", + controlType: "DROP_DOWN", + initialValue: "", + options: integrationData.workflows.map((i) => ({ + label: i.description, + value: i.id, + })), + }, + ], + } as any); + editorConfig = paragonEditorJson; + uiComponent = UIComponentTypes.UQIDbEditorForm; + } + + if (integrationId && !integrationData) return null; + // when switching between different redux forms, make sure this redux form has been initialized before rendering anything. // the initialized prop below comes from redux-form. if (!props.initialized) { @@ -326,7 +477,7 @@ export function EditorJSONtoForm(props: Props) { > ; + datasource: Record; } const initialState: AppDataState = { @@ -59,6 +60,7 @@ const initialState: AppDataState = { currentPosition: {}, }, workflows: {}, + datasource: {}, }; const appReducer = createReducer(initialState, { diff --git a/app/client/src/utils/paragonHooks.tsx b/app/client/src/utils/paragonHooks.tsx new file mode 100644 index 000000000000..16a44c517842 --- /dev/null +++ b/app/client/src/utils/paragonHooks.tsx @@ -0,0 +1,71 @@ +import { paragon, type AuthenticatedConnectUser } from "@useparagon/connect"; +import type { + IConnectIntegration, + IIntegrationMetadata, +} from "@useparagon/connect/dist/src/entities/integration.interface"; +import { getCurrentAppWorkspace } from "ee/selectors/selectedWorkspaceSelectors"; +import { keyBy } from "lodash"; +import { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; + +export function useParagonUser() { + const [paragonUser, setParagonUser] = useState(); + const currentWorkspace = useSelector(getCurrentAppWorkspace); + + useEffect(() => { + paragon.configureGlobal({ + host: "https://useparagon.com", + }); + if (currentWorkspace.token && currentWorkspace.projectId) { + paragon + .authenticate(currentWorkspace.projectId, currentWorkspace.token) + .then(() => { + setParagonUser(paragon.getUser() as AuthenticatedConnectUser); + }); + } + }, [currentWorkspace.token]); + + return { + paragonUser, + }; +} + +export function useParagonIntegrations() { + const [integrations, setIntegrations] = useState([]); + const { paragonUser } = useParagonUser(); + useEffect(() => { + setIntegrations(paragon.getIntegrationMetadata()); + }, [paragonUser]); + + return { + integrations, + }; +} + +export function useParagonIntegrationsWithWorklows() { + const currentWorkspace = useSelector(getCurrentAppWorkspace); + const [integrations, setIntegrations] = useState< + Record + >({}); + + const getIntegrations = async () => { + const data = await fetch( + `https://api.useparagon.com/projects/${currentWorkspace.projectId}/sdk/integrations`, + { + headers: { + Authorization: `Bearer ${currentWorkspace.token}`, + }, + }, + ).then((res) => res.json()); + + setIntegrations(keyBy(data || [], "id")); + }; + + useEffect(() => { + if (currentWorkspace.token && currentWorkspace.projectId) { + getIntegrations(); + } + }, [currentWorkspace.token]); + + return integrations; +} diff --git a/app/client/src/workers/Evaluation/fns/datasourceFns.ts b/app/client/src/workers/Evaluation/fns/datasourceFns.ts new file mode 100644 index 000000000000..54dd5ad51eb6 --- /dev/null +++ b/app/client/src/workers/Evaluation/fns/datasourceFns.ts @@ -0,0 +1,26 @@ +const generateJWT = () => { + return "JWT token"; +}; + +export async function datasourceRequest(...args: any) { + const [requestType, apiPath, options = {}] = args; + try { + const PROJECT_ID = "PROJECT_ID"; + const data = await fetch( + `https://proxy.useparagon.com/projects/${PROJECT_ID}/sdk/proxy/${requestType}/${apiPath}`, + { + method: options.method || "GET", + body: options.body ? JSON.stringify(options.body) : undefined, + headers: { + Authorization: `Bearer ${generateJWT()}`, + "Content-Type": "application/json", + }, + }, + ).then((res) => res.json()); + console.log(data); + return data; + } catch (e) { + console.trace(e); + throw e; + } +} diff --git a/app/client/src/workers/Evaluation/fns/index.ts b/app/client/src/workers/Evaluation/fns/index.ts index a932688c11ca..70df0d838b0b 100644 --- a/app/client/src/workers/Evaluation/fns/index.ts +++ b/app/client/src/workers/Evaluation/fns/index.ts @@ -60,6 +60,7 @@ import { } from "./geolocationFns"; import { getFnWithGuards, isAsyncGuard } from "./utils/fnGuard"; import { isRunNClearFnQualifierEntity } from "ee/workers/Evaluation/fns/utils/isRunNClearFnQualifierEntity"; +import { datasourceRequest } from "./datasourceFns"; export const getPlatformFunctions = () => { return platformFns; @@ -172,6 +173,15 @@ const entityFns = [ isAsyncGuard, ]), }, + { + name: "request", + path: "appsmith.datasource.request", + qualifier: (entity: DataTreeEntity) => isAppsmithEntity(entity), + fn: () => + getFnWithGuards(datasourceRequest, "appsmith.datasource.request", [ + isAsyncGuard, + ]), + }, ]; export type ActionTriggerKeys = diff --git a/app/client/yarn.lock b/app/client/yarn.lock index da11c6eb1932..328f407c9ff7 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -12111,6 +12111,18 @@ __metadata: languageName: node linkType: hard +"@useparagon/connect@npm:^1.0.18": + version: 1.0.18 + resolution: "@useparagon/connect@npm:1.0.18" + dependencies: + hash.js: ^1.1.7 + jwt-decode: ^3.1.2 + react: ^17.0.2 + tslib: 2.3.1 + checksum: 968b3f3d824186b2335870d2c80117626698b6457253cfadb49fce073f2957e6b647470a89295b2fbc117a6b57d283722dd0f41283575895421722bb47b791b7 + languageName: node + linkType: hard + "@vitejs/plugin-react@npm:^4.3.1": version: 4.3.1 resolution: "@vitejs/plugin-react@npm:4.3.1" @@ -12958,6 +12970,7 @@ __metadata: "@uppy/url": ^1.5.16 "@uppy/utils": ^6.0.2 "@uppy/webcam": ^1.8.4 + "@useparagon/connect": ^1.0.18 "@welldone-software/why-did-you-render": ^4.2.5 acorn: 8.10.0 acorn-walk: 8.2.0 @@ -20208,7 +20221,7 @@ __metadata: languageName: node linkType: hard -"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3": +"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3, hash.js@npm:^1.1.7": version: 1.1.7 resolution: "hash.js@npm:1.1.7" dependencies: @@ -23358,6 +23371,13 @@ __metadata: languageName: node linkType: hard +"jwt-decode@npm:^3.1.2": + version: 3.1.2 + resolution: "jwt-decode@npm:3.1.2" + checksum: 20a4b072d44ce3479f42d0d2c8d3dabeb353081ba4982e40b83a779f2459a70be26441be6c160bfc8c3c6eadf9f6380a036fbb06ac5406b5674e35d8c4205eeb + languageName: node + linkType: hard + "kdbush@npm:^3.0.0": version: 3.0.0 resolution: "kdbush@npm:3.0.0" @@ -32739,6 +32759,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2.3.1": + version: 2.3.1 + resolution: "tslib@npm:2.3.1" + checksum: de17a98d4614481f7fcb5cd53ffc1aaf8654313be0291e1bfaee4b4bb31a20494b7d218ff2e15017883e8ea9626599b3b0e0229c18383ba9dce89da2adf15cb9 + languageName: node + linkType: hard + "tslib@npm:^1.13.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/InitUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/InitUtils.java index 932487650a61..585aa698b04e 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/InitUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/InitUtils.java @@ -5,13 +5,45 @@ import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DatasourceConfiguration; import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; @NoArgsConstructor +@Component public class InitUtils { + @Value("${appsmith.integration.provider.project.id}") + private String integrationProviderProjectId = "bd856e23-29d6-418f-9a5d-3c04d11c5fd9"; + public String initializeRequestUrl( ActionConfiguration actionConfiguration, DatasourceConfiguration datasourceConfiguration) { String path = (actionConfiguration.getPath() == null) ? "" : actionConfiguration.getPath(); + if ((datasourceConfiguration.getProperties().size() != 0) + && (datasourceConfiguration.getProperties().get(0).getKey().equals("integrationId"))) { + String apiPath = actionConfiguration + .getFormData() + .getOrDefault("apiPath", "") + .toString(); + String actionType = actionConfiguration + .getFormData() + .getOrDefault("actionType", "") + .toString(); + String workflowId = actionConfiguration + .getFormData() + .getOrDefault("workflowId", "") + .toString(); + if (actionType.equals("workflow")) { + return String.format( + "https://api.useparagon.com/projects/%s/sdk/triggers/%s", + integrationProviderProjectId, workflowId); + } else { + return String.format( + "https://proxy.useparagon.com/projects/%s/sdk/proxy/%s%s", + integrationProviderProjectId, + datasourceConfiguration.getProperties().get(1).getValue(), + apiPath); + } + } return datasourceConfiguration.getUrl().trim() + path.trim(); } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/RestAPIActivateUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/RestAPIActivateUtils.java index 88a94d2323f6..5ab62c35134d 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/RestAPIActivateUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/RestAPIActivateUtils.java @@ -16,13 +16,16 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.client.reactive.ClientHttpRequest; +import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.client.ClientResponse; @@ -38,6 +41,9 @@ import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; import java.time.Duration; import java.time.Instant; import java.util.Base64; @@ -49,7 +55,11 @@ import static org.springframework.util.CollectionUtils.isEmpty; @NoArgsConstructor +@Component public class RestAPIActivateUtils { + @Value("${appsmith.integration.provider.signing.key}") + private String integrationProviderSigningKey = + "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"; public static final String SIGNATURE_HEADER_NAME = "X-APPSMITH-SIGNATURE"; public static final String RESPONSE_DATA_TYPE = "X-APPSMITH-DATATYPE"; @@ -254,6 +264,35 @@ public WebClient.Builder getWebClientBuilder( return webClientBuilder; } + private PrivateKey getPrivateKey() throws Exception { + // Decode the Base64-encoded private key + byte[] keyBytes = Base64.getDecoder().decode(integrationProviderSigningKey); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + + // Generate an RSA private key from the key specification + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(keySpec); + } + + public String generateWorkspaceJWT(String workspaceId) throws Exception { + // Get the RSA private key + PrivateKey privateKey = getPrivateKey(); + + // Define the current time and expiration for the token + final Instant now = Instant.now(); + + // Build and sign the token with RS256 algorithm + final String token = Jwts.builder() + .setIssuer("Appsmith") // Token issuer + .setSubject(workspaceId) // Subject is the workspaceId + .setIssuedAt(new Date(now.toEpochMilli())) // Set issued at time + .setExpiration(new Date(now.plusSeconds(86400).toEpochMilli())) // Token expiration (1 day later) + .signWith(privateKey, SignatureAlgorithm.RS256) // Sign with RSA private key + .compact(); // Build the token + + return token; + } + protected void addSecretKey(WebClient.Builder webClientBuilder, DatasourceConfiguration datasourceConfiguration) throws AppsmithPluginException { // If users have chosen to share the Appsmith signature in the header, calculate and add that diff --git a/app/server/appsmith-interfaces/src/main/resources/application.properties b/app/server/appsmith-interfaces/src/main/resources/application.properties new file mode 100644 index 000000000000..d6cfedaa504c --- /dev/null +++ b/app/server/appsmith-interfaces/src/main/resources/application.properties @@ -0,0 +1,2 @@ +appsmith.integration.provider.signing.key=${APPSMITH_INTEGRATION_PROVIDER_SIGNING_KEY:} +appsmith.integration.provider.project.id=${APPSMITH_INTEGRATION_PROVIDER_PROJECT_ID:} diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java index 3844efa2bfbd..f01becec43c1 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java @@ -72,6 +72,24 @@ public Mono executeParameterized( DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { + // for paragon poc + if ((datasourceConfiguration.getProperties().size() != 0) + && (datasourceConfiguration.getProperties().get(0).getKey().equals("integrationId"))) { + Property bearerTokenHeader = new Property(); + bearerTokenHeader.setKey("Authorization"); + String generatedToken; + try { + generatedToken = restAPIActivateUtils.generateWorkspaceJWT(executeActionDTO.getWorkspaceId()); + } catch (Exception e) { + throw new RuntimeException(e); + } + bearerTokenHeader.setValue("Bearer " + generatedToken); + List headers = datasourceConfiguration.getHeaders() == null + ? new ArrayList<>() + : datasourceConfiguration.getHeaders(); + headers.add(bearerTokenHeader); + datasourceConfiguration.setHeaders(headers); + } log.debug(Thread.currentThread().getName() + ": executeParameterized() called for RestAPI plugin. Executing the API call."); final List properties = actionConfiguration.getPluginSpecifiedTemplates(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/WorkspaceControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/WorkspaceControllerCE.java index e64efd89923f..d41f55a2a34b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/WorkspaceControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/WorkspaceControllerCE.java @@ -7,6 +7,7 @@ import com.appsmith.server.dtos.PermissionGroupInfoDTO; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.dtos.UpdatePermissionGroupDTO; +import com.appsmith.server.dtos.WorkspaceTokenDTO; import com.appsmith.server.services.UserWorkspaceService; import com.appsmith.server.services.WorkspaceService; import com.fasterxml.jackson.annotation.JsonView; @@ -113,4 +114,12 @@ public Mono>> workspacesForHome() { .getUserWorkspacesByRecentlyUsedOrder() .map(workspaces -> new ResponseDTO<>(HttpStatus.OK.value(), workspaces, null)); } + + @JsonView(Views.Public.class) + @GetMapping("/{workspaceId}/token") + public Mono> generateWorkspaceToken(@PathVariable String workspaceId) + throws Exception { + return service.generateWorkspaceToken(workspaceId) + .map(workspaceTokenDTO -> new ResponseDTO<>(HttpStatus.OK.value(), workspaceTokenDTO, null)); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/WorkspaceTokenDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/WorkspaceTokenDTO.java new file mode 100644 index 000000000000..e414368714b0 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/WorkspaceTokenDTO.java @@ -0,0 +1,12 @@ +package com.appsmith.server.dtos; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class WorkspaceTokenDTO { + String workspaceId; + String token; + String projectId; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCE.java index 40ce39ce0298..a29b5c4ccd3a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCE.java @@ -4,6 +4,7 @@ import com.appsmith.server.domains.User; import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.PermissionGroupInfoDTO; +import com.appsmith.server.dtos.WorkspaceTokenDTO; import com.appsmith.server.services.CrudService; import org.springframework.http.codec.multipart.Part; import reactor.core.publisher.Flux; @@ -43,4 +44,6 @@ public interface WorkspaceServiceCE extends CrudService { Mono archiveById(String s); Mono getDefaultEnvironmentId(String workspaceId, AclPermission aclPermission); + + Mono generateWorkspaceToken(String workspaceId) throws Exception; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCEImpl.java index 10f08224596e..113e29d1f81d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCEImpl.java @@ -14,6 +14,7 @@ import com.appsmith.server.dtos.Permission; import com.appsmith.server.dtos.PermissionGroupInfoDTO; import com.appsmith.server.dtos.WorkspacePluginStatus; +import com.appsmith.server.dtos.WorkspaceTokenDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.TextUtils; @@ -30,10 +31,13 @@ import com.appsmith.server.solutions.PermissionGroupPermission; import com.appsmith.server.solutions.PolicySolution; import com.appsmith.server.solutions.WorkspacePermission; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Sort; import org.springframework.http.codec.multipart.Part; import org.springframework.stereotype.Service; @@ -41,7 +45,13 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.time.Instant; import java.util.Arrays; +import java.util.Base64; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Objects; @@ -81,6 +91,12 @@ public class WorkspaceServiceCEImpl extends BaseService getAll(AclPermission permission) { return repository.findAll(permission); } + + private PrivateKey getPrivateKey() throws Exception { + // Decode the Base64-encoded private key + byte[] keyBytes = Base64.getDecoder().decode(integrationProviderSigningKey); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + + // Generate an RSA private key from the key specification + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(keySpec); + } + + private String generateWorkspaceJWT(String workspaceId) throws Exception { + // Get the RSA private key + PrivateKey privateKey = getPrivateKey(); + + // Define the current time and expiration for the token + final Instant now = Instant.now(); + + // Build and sign the token with RS256 algorithm + final String token = Jwts.builder() + .setIssuer("Appsmith") // Token issuer + .setSubject(workspaceId) // Subject is the workspaceId + .setIssuedAt(new Date(now.toEpochMilli())) // Set issued at time + .setExpiration(new Date(now.plusSeconds(86400).toEpochMilli())) // Token expiration (1 day later) + .signWith(privateKey, SignatureAlgorithm.RS256) // Sign with RSA private key + .compact(); // Build the token + + return token; + } + + @Override + public Mono generateWorkspaceToken(String workspaceId) throws Exception { + String jwtToken = generateWorkspaceJWT(workspaceId); + WorkspaceTokenDTO workspaceTokenDTO = new WorkspaceTokenDTO(); + workspaceTokenDTO.setToken(jwtToken); + workspaceTokenDTO.setWorkspaceId(workspaceId); + workspaceTokenDTO.setProjectId(integrationProviderProjectId); + return Mono.just(workspaceTokenDTO); + } } diff --git a/app/server/appsmith-server/src/main/resources/application.properties b/app/server/appsmith-server/src/main/resources/application.properties index e1794aa238a3..099ca8d9aecc 100644 --- a/app/server/appsmith-server/src/main/resources/application.properties +++ b/app/server/appsmith-server/src/main/resources/application.properties @@ -130,3 +130,6 @@ appsmith.index.lock.file.time=${APPSMITH_INDEX_LOCK_FILE_TIME:300} springdoc.api-docs.path=/v3/docs springdoc.swagger-ui.path=/v3/swagger + +appsmith.integration.provider.signing.key=${APPSMITH_INTEGRATION_PROVIDER_SIGNING_KEY:} +appsmith.integration.provider.project.id=${APPSMITH_INTEGRATION_PROVIDER_PROJECT_ID:}