feat: added camunda app connection

This commit is contained in:
Sheen Capadngan
2025-04-08 21:57:24 +08:00
parent 1da2896bb0
commit 479d6445a7
22 changed files with 528 additions and 15 deletions
@@ -0,0 +1,3 @@
export enum CamundaConnectionMethod {
ClientCredentials = "client-credentials"
}
@@ -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<TCamundaConnection["credentials"], "clientId" | "clientSecret">) => {
const { data } = await request.post<TAuthorizeCamundaConnection>(
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<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
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`
});
}
};
@@ -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()
});
@@ -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<TCamundaConnection>;
const listCamundaClusters = async (
appConnection: TCamundaConnection,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const accessToken = await getCamundaConnectionAccessToken(appConnection, appConnectionDAL, kmsService);
const { data } = await request.get<TCamundaListClustersResponse>(`${IntegrationUrls.CAMUNDA_API_URL}/clusters`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
});
return data ?? [];
};
export const camundaConnectionService = (
getAppConnection: TGetAppConnectionFunc,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
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
};
};
@@ -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<typeof CamundaConnectionSchema>;
export type TCamundaConnectionInput = z.infer<typeof CreateCamundaConnectionSchema> & {
app: AppConnection.Camunda;
};
export type TValidateCamundaConnectionCredentialsSchema = typeof ValidateCamundaConnectionCredentialsSchema;
export type TCamundaConnectionConfig = DiscriminativePick<TCamundaConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};
export type TAuthorizeCamundaConnection = {
access_token: string;
scope: string;
token_type: string;
expires_in: number;
};
export type TCamundaListClustersResponse = { uuid: string; name: string }[];
@@ -0,0 +1,4 @@
export * from "./camunda-connection-enums";
export * from "./camunda-connection-fns";
export * from "./camunda-connection-schema";
export * from "./camunda-connection-types";