diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index 2cb5d6db9d..1024cae691 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -12,6 +12,10 @@ import { AzureKeyVaultConnectionListItemSchema, SanitizedAzureKeyVaultConnectionSchema } from "@app/services/app-connection/azure-key-vault"; +import { + CamundaConnectionListItemSchema, + SanitizedCamundaConnectionSchema +} from "@app/services/app-connection/camunda"; import { DatabricksConnectionListItemSchema, SanitizedDatabricksConnectionSchema @@ -39,7 +43,8 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedDatabricksConnectionSchema.options, ...SanitizedHumanitecConnectionSchema.options, ...SanitizedPostgresConnectionSchema.options, - ...SanitizedMsSqlConnectionSchema.options + ...SanitizedMsSqlConnectionSchema.options, + ...SanitizedCamundaConnectionSchema.options ]); const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ @@ -51,7 +56,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ DatabricksConnectionListItemSchema, HumanitecConnectionListItemSchema, PostgresConnectionListItemSchema, - MsSqlConnectionListItemSchema + MsSqlConnectionListItemSchema, + CamundaConnectionListItemSchema ]); export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/server/routes/v1/app-connection-routers/camunda-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/camunda-connection-router.ts new file mode 100644 index 0000000000..7da0b7e5f2 --- /dev/null +++ b/backend/src/server/routes/v1/app-connection-routers/camunda-connection-router.ts @@ -0,0 +1,51 @@ +import { z } from "zod"; + +import { readLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + CreateCamundaConnectionSchema, + SanitizedCamundaConnectionSchema, + UpdateCamundaConnectionSchema +} from "@app/services/app-connection/camunda"; +import { AuthMode } from "@app/services/auth/auth-type"; + +import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; + +export const registerCamundaConnectionRouter = async (server: FastifyZodProvider) => { + registerAppConnectionEndpoints({ + app: AppConnection.Camunda, + server, + sanitizedResponseSchema: SanitizedCamundaConnectionSchema, + createSchema: CreateCamundaConnectionSchema, + updateSchema: UpdateCamundaConnectionSchema + }); + + // The below endpoints are not exposed and for Infisical App use + + server.route({ + method: "GET", + url: `/:connectionId/clusters`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z.object({ + clusters: z.object({ uuid: z.string(), name: z.string() }).array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + + const clusters = await server.services.appConnection.camunda.listClusters(connectionId, req.permission); + + return { clusters }; + } + }); +}; diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 906ffaee9b..d1831c0a51 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -3,6 +3,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums import { registerAwsConnectionRouter } from "./aws-connection-router"; import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router"; import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router"; +import { registerCamundaConnectionRouter } from "./camunda-connection-router"; import { registerDatabricksConnectionRouter } from "./databricks-connection-router"; import { registerGcpConnectionRouter } from "./gcp-connection-router"; import { registerGitHubConnectionRouter } from "./github-connection-router"; @@ -22,5 +23,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { getDatabricksConnectionListItem(), getHumanitecConnectionListItem(), getPostgresConnectionListItem(), - getMsSqlConnectionListItem() + getMsSqlConnectionListItem(), + getCamundaConnectionListItem() ].sort((a, b) => a.name.localeCompare(b.name)); }; @@ -108,7 +110,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record return "Service Account Impersonation"; case DatabricksConnectionMethod.ServicePrincipal: return "Service Principal"; + case CamundaConnectionMethod.ClientCredentials: + return "Client Credentials"; case HumanitecConnectionMethod.ApiToken: return "API Token"; case PostgresConnectionMethod.UsernameAndPassword: @@ -175,5 +180,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.AzureAppConfiguration]: platformManagedCredentialsNotSupported, [AppConnection.Humanitec]: platformManagedCredentialsNotSupported, [AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, - [AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform + [AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, + [AppConnection.Camunda]: platformManagedCredentialsNotSupported }; diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 978f3bfd7f..9572e37c1f 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -31,6 +31,8 @@ import { ValidateAwsConnectionCredentialsSchema } from "./aws"; import { awsConnectionService } from "./aws/aws-connection-service"; import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration"; import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault"; +import { ValidateCamundaConnectionCredentialsSchema } from "./camunda"; +import { camundaConnectionService } from "./camunda/camunda-connection-service"; import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks"; import { databricksConnectionService } from "./databricks/databricks-connection-service"; import { ValidateGcpConnectionCredentialsSchema } from "./gcp"; @@ -59,7 +61,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record>>; @@ -78,6 +85,7 @@ export type TAppConnectionInput = { id: string } & ( | THumanitecConnectionInput | TPostgresConnectionInput | TMsSqlConnectionInput + | TCamundaConnectionInput ); export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput; @@ -99,7 +107,8 @@ export type TAppConnectionConfig = | TAzureAppConfigurationConnectionConfig | TDatabricksConnectionConfig | THumanitecConnectionConfig - | TSqlConnectionConfig; + | TSqlConnectionConfig + | TCamundaConnectionConfig; export type TValidateAppConnectionCredentialsSchema = | TValidateAwsConnectionCredentialsSchema @@ -110,7 +119,8 @@ export type TValidateAppConnectionCredentialsSchema = | TValidateDatabricksConnectionCredentialsSchema | TValidateHumanitecConnectionCredentialsSchema | TValidatePostgresConnectionCredentialsSchema - | TValidateMsSqlConnectionCredentialsSchema; + | TValidateMsSqlConnectionCredentialsSchema + | TValidateCamundaConnectionCredentialsSchema; export type TListAwsConnectionKmsKeys = { connectionId: string; diff --git a/backend/src/services/app-connection/camunda/camunda-connection-enums.ts b/backend/src/services/app-connection/camunda/camunda-connection-enums.ts new file mode 100644 index 0000000000..ea1ea0aaf5 --- /dev/null +++ b/backend/src/services/app-connection/camunda/camunda-connection-enums.ts @@ -0,0 +1,3 @@ +export enum CamundaConnectionMethod { + ClientCredentials = "client-credentials" +} diff --git a/backend/src/services/app-connection/camunda/camunda-connection-fns.ts b/backend/src/services/app-connection/camunda/camunda-connection-fns.ts new file mode 100644 index 0000000000..da6ba53f2b --- /dev/null +++ b/backend/src/services/app-connection/camunda/camunda-connection-fns.ts @@ -0,0 +1,88 @@ +import { request } from "@app/lib/config/request"; +import { BadRequestError } from "@app/lib/errors"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; + +import { TAppConnectionDALFactory } from "../app-connection-dal"; +import { CamundaConnectionMethod } from "./camunda-connection-enums"; +import { TAuthorizeCamundaConnection, TCamundaConnection, TCamundaConnectionConfig } from "./camunda-connection-types"; + +export const getCamundaConnectionListItem = () => { + return { + name: "Camunda" as const, + app: AppConnection.Camunda as const, + methods: Object.values(CamundaConnectionMethod) as [CamundaConnectionMethod.ClientCredentials] + }; +}; + +const authorizeCamundaConnection = async ({ + clientId, + clientSecret +}: Pick) => { + const { data } = await request.post( + IntegrationUrls.CAMUNDA_TOKEN_URL, + { + grant_type: "client_credentials", + client_id: clientId, + client_secret: clientSecret, + audience: "api.cloud.camunda.io" + }, + { + headers: { + "Content-Type": "application/json" + } + } + ); + + return { accessToken: data.access_token, expiresAt: data.expires_in * 1000 + Date.now() }; +}; + +export const getCamundaConnectionAccessToken = async ( + { id, orgId, credentials }: TCamundaConnection, + appConnectionDAL: Pick, + kmsService: Pick +) => { + const { clientSecret, clientId, accessToken, expiresAt } = credentials; + + // get new token if less than 10 minutes from expiry + if (Date.now() < expiresAt - 10_000) { + return accessToken; + } + + const authData = await authorizeCamundaConnection({ clientId, clientSecret }); + + const updatedCredentials: TCamundaConnection["credentials"] = { + ...credentials, + ...authData + }; + + const encryptedCredentials = await encryptAppConnectionCredentials({ + credentials: updatedCredentials, + orgId, + kmsService + }); + + await appConnectionDAL.updateById(id, { encryptedCredentials }); + + return authData.accessToken; +}; + +export const validateCamundaConnectionCredentials = async (appConnection: TCamundaConnectionConfig) => { + const { credentials } = appConnection; + + try { + const { accessToken, expiresAt } = await authorizeCamundaConnection(appConnection.credentials); + + return { + ...credentials, + accessToken, + expiresAt + }; + } catch (e: unknown) { + throw new BadRequestError({ + message: `Unable to validate connection: verify credentials` + }); + } +}; diff --git a/backend/src/services/app-connection/camunda/camunda-connection-schema.ts b/backend/src/services/app-connection/camunda/camunda-connection-schema.ts new file mode 100644 index 0000000000..593cd4d495 --- /dev/null +++ b/backend/src/services/app-connection/camunda/camunda-connection-schema.ts @@ -0,0 +1,73 @@ +import { z } from "zod"; + +import { AppConnections } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + BaseAppConnectionSchema, + GenericCreateAppConnectionFieldsSchema, + GenericUpdateAppConnectionFieldsSchema +} from "@app/services/app-connection/app-connection-schemas"; + +import { CamundaConnectionMethod } from "./camunda-connection-enums"; + +const BaseCamundaConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Camunda) }); + +export const CamundaConnectionClientCredentialsInputCredentialsSchema = z.object({ + clientId: z.string().trim().min(1, "Client ID required"), + clientSecret: z.string().trim().min(1, "Client Secret required") +}); + +export const CamundaConnectionClientCredentialsOutputCredentialsSchema = z + .object({ + accessToken: z.string(), + expiresAt: z.number() + }) + .merge(CamundaConnectionClientCredentialsInputCredentialsSchema); + +export const CamundaConnectionSchema = z.intersection( + BaseCamundaConnectionSchema, + z.discriminatedUnion("method", [ + z.object({ + method: z.literal(CamundaConnectionMethod.ClientCredentials), + credentials: CamundaConnectionClientCredentialsOutputCredentialsSchema + }) + ]) +); + +export const SanitizedCamundaConnectionSchema = z.discriminatedUnion("method", [ + BaseCamundaConnectionSchema.extend({ + method: z.literal(CamundaConnectionMethod.ClientCredentials), + credentials: CamundaConnectionClientCredentialsOutputCredentialsSchema.pick({ + clientId: true + }) + }) +]); + +export const ValidateCamundaConnectionCredentialsSchema = z.discriminatedUnion("method", [ + z.object({ + method: z + .literal(CamundaConnectionMethod.ClientCredentials) + .describe(AppConnections.CREATE(AppConnection.Camunda).method), + credentials: CamundaConnectionClientCredentialsInputCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.Camunda).credentials + ) + }) +]); + +export const CreateCamundaConnectionSchema = ValidateCamundaConnectionCredentialsSchema.and( + GenericCreateAppConnectionFieldsSchema(AppConnection.Camunda) +); + +export const UpdateCamundaConnectionSchema = z + .object({ + credentials: CamundaConnectionClientCredentialsInputCredentialsSchema.optional().describe( + AppConnections.UPDATE(AppConnection.Camunda).credentials + ) + }) + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Camunda)); + +export const CamundaConnectionListItemSchema = z.object({ + name: z.literal("Camunda"), + app: z.literal(AppConnection.Camunda), + methods: z.nativeEnum(CamundaConnectionMethod).array() +}); diff --git a/backend/src/services/app-connection/camunda/camunda-connection-service.ts b/backend/src/services/app-connection/camunda/camunda-connection-service.ts new file mode 100644 index 0000000000..28b3882faf --- /dev/null +++ b/backend/src/services/app-connection/camunda/camunda-connection-service.ts @@ -0,0 +1,50 @@ +import { request } from "@app/lib/config/request"; +import { OrgServiceActor } from "@app/lib/types"; +import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; + +import { getCamundaConnectionAccessToken } from "./camunda-connection-fns"; +import { TCamundaConnection, TCamundaListClustersResponse } from "./camunda-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +const listCamundaClusters = async ( + appConnection: TCamundaConnection, + appConnectionDAL: Pick, + kmsService: Pick +) => { + const accessToken = await getCamundaConnectionAccessToken(appConnection, appConnectionDAL, kmsService); + + const { data } = await request.get(`${IntegrationUrls.CAMUNDA_API_URL}/clusters`, { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json" + } + }); + + return data ?? []; +}; + +export const camundaConnectionService = ( + getAppConnection: TGetAppConnectionFunc, + appConnectionDAL: Pick, + kmsService: Pick +) => { + const listClusters = async (connectionId: string, actor: OrgServiceActor) => { + const appConnection = await getAppConnection(AppConnection.Camunda, connectionId, actor); + + const clusters = await listCamundaClusters(appConnection, appConnectionDAL, kmsService); + + return clusters; + }; + + return { + listClusters + }; +}; diff --git a/backend/src/services/app-connection/camunda/camunda-connection-types.ts b/backend/src/services/app-connection/camunda/camunda-connection-types.ts new file mode 100644 index 0000000000..d59a8c7ce4 --- /dev/null +++ b/backend/src/services/app-connection/camunda/camunda-connection-types.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; + +import { DiscriminativePick } from "@app/lib/types"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +import { + CamundaConnectionSchema, + CreateCamundaConnectionSchema, + ValidateCamundaConnectionCredentialsSchema +} from "./camunda-connection-schema"; + +export type TCamundaConnection = z.infer; + +export type TCamundaConnectionInput = z.infer & { + app: AppConnection.Camunda; +}; + +export type TValidateCamundaConnectionCredentialsSchema = typeof ValidateCamundaConnectionCredentialsSchema; + +export type TCamundaConnectionConfig = DiscriminativePick & { + orgId: string; +}; + +export type TAuthorizeCamundaConnection = { + access_token: string; + scope: string; + token_type: string; + expires_in: number; +}; + +export type TCamundaListClustersResponse = { uuid: string; name: string }[]; diff --git a/backend/src/services/app-connection/camunda/index.ts b/backend/src/services/app-connection/camunda/index.ts new file mode 100644 index 0000000000..4458717248 --- /dev/null +++ b/backend/src/services/app-connection/camunda/index.ts @@ -0,0 +1,4 @@ +export * from "./camunda-connection-enums"; +export * from "./camunda-connection-fns"; +export * from "./camunda-connection-schema"; +export * from "./camunda-connection-types"; diff --git a/backend/src/services/integration-auth/integration-list.ts b/backend/src/services/integration-auth/integration-list.ts index d6c4507519..7bd33d86ba 100644 --- a/backend/src/services/integration-auth/integration-list.ts +++ b/backend/src/services/integration-auth/integration-list.ts @@ -63,6 +63,7 @@ export enum IntegrationUrls { GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token", GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token", BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token", + CAMUNDA_TOKEN_URL = "https://login.cloud.camunda.io/oauth/token", // integration apps endpoints GCP_API_URL = "https://cloudresourcemanager.googleapis.com", @@ -94,6 +95,7 @@ export enum IntegrationUrls { HASURA_CLOUD_API_URL = "https://data.pro.hasura.io/v1/graphql", AZURE_DEVOPS_API_URL = "https://dev.azure.com", HUMANITEC_API_URL = "https://api.humanitec.io", + CAMUNDA_API_URL = "https://api.cloud.camunda.io", GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com", GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`, diff --git a/frontend/public/images/integrations/Camunda.png b/frontend/public/images/integrations/Camunda.png new file mode 100644 index 0000000000..a3bb215b34 Binary files /dev/null and b/frontend/public/images/integrations/Camunda.png differ diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index d75b426b7b..a28bc454bf 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -6,6 +6,7 @@ import { AwsConnectionMethod, AzureAppConfigurationConnectionMethod, AzureKeyVaultConnectionMethod, + CamundaConnectionMethod, DatabricksConnectionMethod, GcpConnectionMethod, GitHubConnectionMethod, @@ -30,7 +31,8 @@ export const APP_CONNECTION_MAP: Record { @@ -49,6 +51,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) return { name: "Service Account Impersonation", icon: faUser }; case DatabricksConnectionMethod.ServicePrincipal: return { name: "Service Principal", icon: faUser }; + case CamundaConnectionMethod.ClientCredentials: + return { name: "Client Credentials", icon: faKey }; case HumanitecConnectionMethod.ApiToken: return { name: "API Token", icon: faKey }; case PostgresConnectionMethod.UsernameAndPassword: diff --git a/frontend/src/hooks/api/appConnections/enums.ts b/frontend/src/hooks/api/appConnections/enums.ts index 6891e7e2c3..19381e56c3 100644 --- a/frontend/src/hooks/api/appConnections/enums.ts +++ b/frontend/src/hooks/api/appConnections/enums.ts @@ -7,5 +7,6 @@ export enum AppConnection { Databricks = "databricks", Humanitec = "humanitec", Postgres = "postgres", - MsSql = "mssql" + MsSql = "mssql", + Camunda = "camunda" } diff --git a/frontend/src/hooks/api/appConnections/types/app-options.ts b/frontend/src/hooks/api/appConnections/types/app-options.ts index d3d376e6eb..409b735543 100644 --- a/frontend/src/hooks/api/appConnections/types/app-options.ts +++ b/frontend/src/hooks/api/appConnections/types/app-options.ts @@ -47,6 +47,10 @@ export type TMsSqlConnectionOption = TAppConnectionOptionBase & { app: AppConnection.MsSql; }; +export type TCamundaConnectionOption = TAppConnectionOptionBase & { + app: AppConnection.Camunda; +}; + export type TAppConnectionOption = | TAwsConnectionOption | TGitHubConnectionOption @@ -56,7 +60,8 @@ export type TAppConnectionOption = | TDatabricksConnectionOption | THumanitecConnectionOption | TPostgresConnectionOption - | TMsSqlConnectionOption; + | TMsSqlConnectionOption + | TCamundaConnectionOption; export type TAppConnectionOptionMap = { [AppConnection.AWS]: TAwsConnectionOption; @@ -68,4 +73,5 @@ export type TAppConnectionOptionMap = { [AppConnection.Humanitec]: THumanitecConnectionOption; [AppConnection.Postgres]: TPostgresConnectionOption; [AppConnection.MsSql]: TMsSqlConnectionOption; + [AppConnection.Camunda]: TCamundaConnectionOption; }; diff --git a/frontend/src/hooks/api/appConnections/types/camunda-connection.ts b/frontend/src/hooks/api/appConnections/types/camunda-connection.ts new file mode 100644 index 0000000000..06b6f5b787 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/types/camunda-connection.ts @@ -0,0 +1,14 @@ +import { AppConnection } from "../enums"; +import { TRootAppConnection } from "./root-connection"; + +export enum CamundaConnectionMethod { + ClientCredentials = "client-credentials" +} + +export type TCamundaConnection = TRootAppConnection & { app: AppConnection.Camunda } & { + method: CamundaConnectionMethod.ClientCredentials; + credentials: { + clientId: string; + clientSecret: string; + }; +}; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index a241e21a11..05cef80fb4 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -3,6 +3,7 @@ import { TAppConnectionOption } from "./app-options"; import { TAwsConnection } from "./aws-connection"; import { TAzureAppConfigurationConnection } from "./azure-app-configuration-connection"; import { TAzureKeyVaultConnection } from "./azure-key-vault-connection"; +import { TCamundaConnection } from "./camunda-connection"; import { TDatabricksConnection } from "./databricks-connection"; import { TGcpConnection } from "./gcp-connection"; import { TGitHubConnection } from "./github-connection"; @@ -13,6 +14,7 @@ import { TPostgresConnection } from "./postgres-connection"; export * from "./aws-connection"; export * from "./azure-app-configuration-connection"; export * from "./azure-key-vault-connection"; +export * from "./camunda-connection"; export * from "./databricks-connection"; export * from "./gcp-connection"; export * from "./github-connection"; @@ -29,7 +31,8 @@ export type TAppConnection = | TDatabricksConnection | THumanitecConnection | TPostgresConnection - | TMsSqlConnection; + | TMsSqlConnection + | TCamundaConnection; export type TAvailableAppConnection = Pick; @@ -66,4 +69,5 @@ export type TAppConnectionMap = { [AppConnection.Humanitec]: THumanitecConnection; [AppConnection.Postgres]: TPostgresConnection; [AppConnection.MsSql]: TMsSqlConnection; + [AppConnection.Camunda]: TCamundaConnection; }; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index 6a57bef5a8..3d142b0ad3 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -12,6 +12,7 @@ import { AppConnectionHeader } from "../AppConnectionHeader"; import { AwsConnectionForm } from "./AwsConnectionForm"; import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConnectionForm"; import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm"; +import { CamundaConnectionForm } from "./CamundaConnectionForm"; import { DatabricksConnectionForm } from "./DatabricksConnectionForm"; import { GcpConnectionForm } from "./GcpConnectionForm"; import { GitHubConnectionForm } from "./GitHubConnectionForm"; @@ -74,6 +75,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { return ; case AppConnection.MsSql: return ; + case AppConnection.Camunda: + return ; default: throw new Error(`Unhandled App ${app}`); } @@ -128,6 +131,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.MsSql: return ; + case AppConnection.Camunda: + return ; default: throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`); } diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/CamundaConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/CamundaConnectionForm.tsx new file mode 100644 index 0000000000..5adb9c7139 --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/CamundaConnectionForm.tsx @@ -0,0 +1,148 @@ +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { + Button, + FormControl, + Input, + ModalClose, + SecretInput, + Select, + SelectItem +} from "@app/components/v2"; +import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { CamundaConnectionMethod, TCamundaConnection } from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { + genericAppConnectionFieldsSchema, + GenericAppConnectionsFields +} from "./GenericAppConnectionFields"; + +type Props = { + appConnection?: TCamundaConnection; + onSubmit: (formData: FormData) => Promise; +}; + +const rootSchema = genericAppConnectionFieldsSchema.extend({ + app: z.literal(AppConnection.Camunda) +}); + +const formSchema = z.discriminatedUnion("method", [ + rootSchema.extend({ + method: z.literal(CamundaConnectionMethod.ClientCredentials), + credentials: z.object({ + clientId: z.string().trim().min(1, "Client ID required"), + clientSecret: z.string().trim().min(1, "Client secret required") + }) + }) +]); + +type FormData = z.infer; + +export const CamundaConnectionForm = ({ appConnection, onSubmit }: Props) => { + const isUpdate = Boolean(appConnection); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: appConnection ?? { + app: AppConnection.Camunda, + method: CamundaConnectionMethod.ClientCredentials + } + }); + + const { + handleSubmit, + control, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
+ {!isUpdate && } + ( + + + + )} + /> + ( + + + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+ + + + +
+ +
+ ); +};