diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index 83fdf7de1..ec166df48 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -1,7 +1,7 @@ import { Request, Response } from "express"; import { Types } from "mongoose"; import { standardRequest } from "../../config/request"; -import { getApps, getTeams, getGroups, revokeAccess } from "../../integrations"; +import { getApps, getTeams, revokeAccess } from "../../integrations"; import { Bot, IntegrationAuth, Workspace } from "../../models"; import { EventType } from "../../ee/models"; import { IntegrationService } from "../../services"; @@ -10,6 +10,7 @@ import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_UTF8, INTEGRATION_BITBUCKET_API_URL, + INTEGRATION_CHECKLY_API_URL, INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_NORTHFLANK_API_URL, INTEGRATION_QOVERY_API_URL, @@ -207,40 +208,6 @@ export const saveIntegrationToken = async (req: Request, res: Response) => { }); }; -/** - * Return list of groups allowed for integration with integration authorization id [integrationAuthId] - * @param req - * @param res - * @returns - */ -export const getIntegrationAuthGroups = async (req: Request, res: Response) => { - const { - params: { integrationAuthId } - } = await validateRequest(reqValidator.GetIntegrationAuthGroupsV1, req); - - const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ - integrationAuthId: new ObjectId(integrationAuthId) - }); - - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Read, - ProjectPermissionSub.Integrations - ); - - const groups = await getGroups({ - integrationAuth: integrationAuth, - accessToken: accessToken - }); - - return res.status(200).send({ - groups - }); -}; - /** * Return list of applications allowed for integration with integration authorization id [integrationAuthId] * @param req @@ -378,6 +345,59 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon }); }; +/** + * Return list of Checkly groups for a specific user + * @param req + * @param res + */ +export const getIntegrationAuthChecklyGroups = async (req: Request, res: Response) => { + const { + params: { integrationAuthId }, + query: { accountId } + } = await validateRequest(reqValidator.GetIntegrationAuthChecklyGroupsV1, req); + + const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ + integrationAuthId: new Types.ObjectId(integrationAuthId) + }); + + const { permission } = await getUserProjectPermissions( + req.user._id, + integrationAuth.workspace.toString() + ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.Integrations + ); + + interface ChecklyGroup { + id: number; + name: string; + } + + if (accountId && accountId !== "") { + const { data }: { data: ChecklyGroup[] } = ( + await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/check-groups`, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json", + "X-Checkly-Account": accountId + } + }) + ); + + return res.status(200).send({ + groups: data.map((g: ChecklyGroup) => ({ + name: g.name, + groupId: g.id, + })) + }); + } + + return res.status(200).send({ + groups: [] + }); +} + /** * Return list of Qovery Orgs for a specific user * @param req diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index e1aaf08e2..e67136275 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -911,7 +911,7 @@ const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => { }; /** - * Return list of projects for the Checkly integration + * Return list of accounts for the Checkly integration * @param {Object} obj * @param {String} obj.accessToken - api key for the Checkly API * @returns {Object[]} apps - Сheckly accounts diff --git a/backend/src/integrations/groups.ts b/backend/src/integrations/groups.ts deleted file mode 100644 index c69a21bbd..000000000 --- a/backend/src/integrations/groups.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - IIntegrationAuth, -} from "../models"; -import { - INTEGRATION_CHECKLY, - INTEGRATION_CHECKLY_API_URL, -} from "../variables"; -import { standardRequest } from "../config/request"; - -interface Group { - name: string; - groupId: string; -} - -/** - * Return list of groups for checkly integration authorization [integrationAuth] - * @param {Object} obj - * @param {String} obj.integrationAuth - integration authorization to get groups - * @param {String} obj.accessToken - access token for integration authorization - * @returns {Object[]} groups - groups for integration authorization - * @returns {String} groups.name - name of group - * @returns {String} groups.groupId - id of group -*/ -const getGroups = async ({ - integrationAuth, - accessToken, -}: { - integrationAuth: IIntegrationAuth; - accessToken: string; -}) => { - - let groups: Group[] = []; - - switch (integrationAuth.integration) { - case INTEGRATION_CHECKLY: - groups = await getGroupsCheckly({ - accessToken, - }); - break; - } - - return groups; -} - -/** - * Return list of groups for Checkly integration - * @param {Object} obj - * @param {String} obj.accessToken - access token for Checkly API - * @returns {Object[]} groups - list of groups in Checkly - * @returns {String} groups.name - name of group - * @returns {String} groups.groupId - id of group -*/ -const getGroupsCheckly = async ({ - accessToken, -}: { - accessToken: string; -}) => { - - let groups: Group[] = []; - - // case: fetch account id - const { data } = await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/accounts`, { - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: "application/json" - } - }); - - const accountId = data.map((a: any) => { - return { - id: a.id, - }; - }); - - // case: fetch list of groups in Checkly - const res = accountId.length > 0 && ( - await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/check-groups`, { - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: "application/json", - "X-Checkly-Account": accountId[0].id, - } - }) - ).data; - - groups = res.map((g: any) => ({ - name: g.name, - groupId: g.id, - })); - - return groups; -} - -export { - getGroups, -} \ No newline at end of file diff --git a/backend/src/integrations/index.ts b/backend/src/integrations/index.ts index e5b6ad0ea..e1bf23ba1 100644 --- a/backend/src/integrations/index.ts +++ b/backend/src/integrations/index.ts @@ -2,7 +2,6 @@ import { exchangeCode } from "./exchange"; import { exchangeRefresh } from "./refresh"; import { getApps } from "./apps"; import { getTeams } from "./teams"; -import { getGroups } from "./groups"; import { revokeAccess } from "./revoke"; export { @@ -10,6 +9,5 @@ export { exchangeRefresh, getApps, getTeams, - getGroups, revokeAccess, } \ No newline at end of file diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index f4a076320..1670651b2 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -2104,7 +2104,7 @@ const syncSecretsSupabase = async ({ }; /** - * Sync/push [secrets] to Checkly app + * Sync/push [secrets] to Checkly app/group * @param {Object} obj * @param {IIntegration} obj.integration - integration details * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) @@ -2123,6 +2123,8 @@ const syncSecretsCheckly = async ({ }) => { if (integration.targetServiceId) { + // sync secrets to checkly group envars + let getGroupSecretsRes = ( await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/check-groups/${integration.targetServiceId}`, { headers: { @@ -2162,48 +2164,22 @@ const syncSecretsCheckly = async ({ value: secrets[key].value })); - // add secrets - for await (const key of Object.keys(secrets)) { - - if (!(key in getGroupSecretsRes)) { - // case: secret does not exist in checkly group - // -> add secret - - await standardRequest.put( - `${INTEGRATION_CHECKLY_API_URL}/v1/check-groups/${integration.targetServiceId}`, - { - environmentVariables: groupEnvironmentVariables - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: "application/json", - "X-Checkly-Account": integration.appId - } - } - ); - } else { - // case: secret exists in checkly group - // -> update/set secret - - if (secrets[key] !== getGroupSecretsRes[key]) { - await standardRequest.put( - `${INTEGRATION_CHECKLY_API_URL}/v1/check-groups/${integration.targetServiceId}`, - { - environmentVariables: groupEnvironmentVariables - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: "application/json", - "X-Checkly-Account": integration.appId - } - } - ); + await standardRequest.put( + `${INTEGRATION_CHECKLY_API_URL}/v1/check-groups/${integration.targetServiceId}`, + { + environmentVariables: groupEnvironmentVariables + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json", + "X-Checkly-Account": integration.appId } } - } + ); } else { + // sync secrets to checkly global envars + let getSecretsRes = ( await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, { headers: { diff --git a/backend/src/routes/v1/integrationAuth.ts b/backend/src/routes/v1/integrationAuth.ts index d5d81cf13..68d0433b3 100644 --- a/backend/src/routes/v1/integrationAuth.ts +++ b/backend/src/routes/v1/integrationAuth.ts @@ -52,14 +52,6 @@ router.get( integrationAuthController.getIntegrationAuthTeams ); -router.get( - "/:integrationAuthId/groups", - requireAuth({ - acceptedAuthModes: [AuthMode.JWT] - }), - integrationAuthController.getIntegrationAuthGroups -); - router.get( "/:integrationAuthId/vercel/branches", requireAuth({ @@ -68,6 +60,14 @@ router.get( integrationAuthController.getIntegrationAuthVercelBranches ); +router.get( + "/:integrationAuthId/checkly/groups", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT] + }), + integrationAuthController.getIntegrationAuthChecklyGroups +); + router.get( "/:integrationAuthId/qovery/orgs", requireAuth({ diff --git a/backend/src/services/RedisService.ts b/backend/src/services/RedisService.ts index 23f945bb7..218234a1a 100644 --- a/backend/src/services/RedisService.ts +++ b/backend/src/services/RedisService.ts @@ -1,11 +1,12 @@ import Redis, { Redis as TRedis } from "ioredis"; +import { logger } from "../utils/logging"; let redisClient: TRedis | null; if (process.env.REDIS_URL) { redisClient = new Redis(process.env.REDIS_URL as string); } else { - console.warn("Redis URL not set, skipping Redis initialization."); + logger.warn("Redis URL not set, skipping Redis initialization."); redisClient = null; } diff --git a/backend/src/utils/setup/index.ts b/backend/src/utils/setup/index.ts index 6625bff7b..62c7272f3 100644 --- a/backend/src/utils/setup/index.ts +++ b/backend/src/utils/setup/index.ts @@ -11,7 +11,6 @@ import { backfillBots, backfillEncryptionMetadata, backfillIntegration, - backfillPermission, backfillSecretBlindIndexData, backfillSecretFolders, backfillSecretVersions, @@ -28,6 +27,7 @@ import { } from "./reencryptData"; import { getMongoURL, getNodeEnv, getRedisUrl, getSentryDSN } from "../../config"; import { initializePassport } from "../auth"; +import { logger } from "../logging"; /** * Prepare Infisical upon startup. This includes tasks like: @@ -41,7 +41,7 @@ import { initializePassport } from "../auth"; */ export const setup = async () => { if ((await getRedisUrl()) === undefined || (await getRedisUrl()) === "") { - console.error( + logger.error( "WARNING: Redis is not yet configured. Infisical may not function as expected without it." ); } diff --git a/backend/src/validation/integrationAuth.ts b/backend/src/validation/integrationAuth.ts index 9f3798ad6..3eb51d833 100644 --- a/backend/src/validation/integrationAuth.ts +++ b/backend/src/validation/integrationAuth.ts @@ -117,9 +117,12 @@ export const GetIntegrationAuthVercelBranchesV1 = z.object({ }) }); -export const GetIntegrationAuthGroupsV1 = z.object({ +export const GetIntegrationAuthChecklyGroupsV1 = z.object({ params: z.object({ integrationAuthId: z.string().trim() + }), + query: z.object({ + accountId: z.string().trim() }) }); diff --git a/frontend/src/hooks/api/integrationAuth/index.tsx b/frontend/src/hooks/api/integrationAuth/index.tsx index 2a9147ffe..f5065919f 100644 --- a/frontend/src/hooks/api/integrationAuth/index.tsx +++ b/frontend/src/hooks/api/integrationAuth/index.tsx @@ -4,12 +4,12 @@ export { useGetIntegrationAuthApps, useGetIntegrationAuthBitBucketWorkspaces, useGetIntegrationAuthById, + useGetIntegrationAuthChecklyGroups, useGetIntegrationAuthNorthflankSecretGroups, useGetIntegrationAuthRailwayEnvironments, useGetIntegrationAuthRailwayServices, useGetIntegrationAuthTeamCityBuildConfigs, useGetIntegrationAuthTeams, useGetIntegrationAuthVercelBranches, - useSaveIntegrationAccessToken, - useGetIntegrationAuthGroups -} from "./queries"; + useSaveIntegrationAccessToken +} from "./queries"; \ No newline at end of file diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index 71ee4f535..7e5d3d711 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -6,6 +6,7 @@ import { workspaceKeys } from "../workspace/queries"; import { App, BitBucketWorkspace, + ChecklyGroup, Environment, IntegrationAuth, NorthflankSecretGroup, @@ -13,8 +14,8 @@ import { Project, Service, Team, - Group, - TeamCityBuildConfig} from "./types"; + TeamCityBuildConfig +} from "./types"; const integrationAuthKeys = { getIntegrationAuthById: (integrationAuthId: string) => @@ -23,8 +24,6 @@ const integrationAuthKeys = { [{ integrationAuthId, teamId, workspaceSlug }, "integrationAuthApps"] as const, getIntegrationAuthTeams: (integrationAuthId: string) => [{ integrationAuthId }, "integrationAuthTeams"] as const, - getIntegrationAuthGroups: (integrationAuthId: string) => - [{ integrationAuthId }, "integrationAuthGroups"] as const, getIntegrationAuthVercelBranches: ({ integrationAuthId, appId @@ -32,6 +31,14 @@ const integrationAuthKeys = { integrationAuthId: string; appId: string; }) => [{ integrationAuthId, appId }, "integrationAuthVercelBranches"] as const, + getIntegrationAuthChecklyGroups: ({ + integrationAuthId, + accountId + }: { + integrationAuthId: string; + accountId: string; + }) => + [{ integrationAuthId, accountId }, "integrationAuthChecklyGroups"] as const, getIntegrationAuthQoveryOrgs: (integrationAuthId: string) => [{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const, getIntegrationAuthQoveryProjects: ({ @@ -128,10 +135,22 @@ const fetchIntegrationAuthTeams = async (integrationAuthId: string) => { return data.teams; }; -const fetchIntegrationAuthGroups = async (integrationAuthId: string) => { - const { data } = await apiRequest.get<{ groups: Group[] }>( - `/api/v1/integration-auth/${integrationAuthId}/groups` +const fetchIntegrationAuthChecklyGroups = async ({ + integrationAuthId, + accountId +}: { + integrationAuthId: string; + accountId: string; +}) => { + const { data } = await apiRequest.get<{ groups: ChecklyGroup[] }>( + `/api/v1/integration-auth/${integrationAuthId}/checkly/groups`, + { + params: { + accountId + } + } ); + return data.groups; }; @@ -422,10 +441,22 @@ export const useGetIntegrationAuthVercelBranches = ({ }); }; -export const useGetIntegrationAuthGroups = (integrationAuthId: string) => { +export const useGetIntegrationAuthChecklyGroups = ({ + integrationAuthId, + accountId +}: { + integrationAuthId: string; + accountId: string; +}) => { return useQuery({ - queryKey: integrationAuthKeys.getIntegrationAuthGroups(integrationAuthId), - queryFn: () => fetchIntegrationAuthGroups(integrationAuthId), + queryKey: integrationAuthKeys.getIntegrationAuthChecklyGroups({ + integrationAuthId, + accountId + }), + queryFn: () => fetchIntegrationAuthChecklyGroups({ + integrationAuthId, + accountId + }), enabled: true }); }; diff --git a/frontend/src/hooks/api/integrationAuth/types.ts b/frontend/src/hooks/api/integrationAuth/types.ts index e3f9af247..5af8a3773 100644 --- a/frontend/src/hooks/api/integrationAuth/types.ts +++ b/frontend/src/hooks/api/integrationAuth/types.ts @@ -26,9 +26,9 @@ export type Environment = { environmentId: string; }; -export type Group = { +export type ChecklyGroup = { name: string; - groupId: string; + groupId: number; }; export type Container = { diff --git a/frontend/src/pages/integrations/checkly/create.tsx b/frontend/src/pages/integrations/checkly/create.tsx index 41122548b..e775dd5df 100644 --- a/frontend/src/pages/integrations/checkly/create.tsx +++ b/frontend/src/pages/integrations/checkly/create.tsx @@ -9,8 +9,6 @@ import { motion } from "framer-motion"; import queryString from "query-string"; import { - Alert, - AlertDescription, Button, Card, CardTitle, @@ -30,7 +28,7 @@ import { import { useGetIntegrationAuthApps, useGetIntegrationAuthById, - useGetIntegrationAuthGroups + useGetIntegrationAuthChecklyGroups } from "../../../hooks/api/integrationAuth"; import { useGetWorkspaceById } from "../../../hooks/api/workspace"; @@ -45,26 +43,24 @@ export default function ChecklyCreateIntegrationPage() { const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [secretSuffix, setSecretSuffix] = useState(""); + + const [targetAppId, setTargetAppId] = useState(""); + const [targetGroupId, setTargetGroupId] = useState(""); + + const [isLoading, setIsLoading] = useState(false); + const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "" }); - const { data: integrationAuthGroups, isLoading: isintegrationAuthGroupsLoading } = useGetIntegrationAuthGroups( - (integrationAuthId as string) ?? "" - ); - - const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); - const [secretPath, setSecretPath] = useState("/"); - const [secretSuffix, setSecretSuffix] = useState(""); - - const [targetApp, setTargetApp] = useState(""); - const [targetAppId, setTargetAppId] = useState(""); - - const [targetGroup, setTargetGroup] = useState(""); - const [targetGroupId, setTargetGroupId] = useState(""); - - const [isLoading, setIsLoading] = useState(false); + const { data: integrationAuthGroups, isLoading: isintegrationAuthGroupsLoading } = useGetIntegrationAuthChecklyGroups({ + integrationAuthId: (integrationAuthId as string) ?? "", + accountId: targetAppId + }); useEffect(() => { if (workspace) { @@ -73,41 +69,38 @@ export default function ChecklyCreateIntegrationPage() { }, [workspace]); useEffect(() => { - // TODO: handle case where apps can be empty if (integrationAuthApps) { if (integrationAuthApps.length > 0) { - setTargetApp(integrationAuthApps[0].name); - setTargetAppId(String(integrationAuthApps[0].appId)); + setTargetAppId(integrationAuthApps[0].appId as string); } else { - setTargetApp("none"); + setTargetAppId("none"); } } }, [integrationAuthApps]); - const handleValueChange = (val) => { - const selectedGroup = integrationAuthGroups.find(group => group.name === val); - if (selectedGroup) { - setTargetGroupId(selectedGroup.groupId); - } else { - setTargetGroupId(""); - } - setTargetGroup(val); - } - const handleButtonClick = async () => { try { if (!integrationAuth?._id) return; setIsLoading(true); + const targetApp = integrationAuthApps?.find( + (integrationAuthApp) => integrationAuthApp.appId === targetAppId + ); + const targetGroup = integrationAuthGroups?.find( + (group) => group.groupId === Number(targetGroupId) + ); + + if (!targetApp) return; + await mutateAsync({ integrationAuthId: integrationAuth?._id, isActive: true, - app: targetApp, - appId: targetAppId, + app: targetApp?.name, + appId: targetApp?.appId, sourceEnvironment: selectedSourceEnvironment, - targetService: targetGroup, - targetServiceId: targetGroupId.toString(), + targetService: targetGroup?.name, + targetServiceId: String(targetGroup?.groupId), secretPath, metadata: { secretSuffix @@ -127,7 +120,7 @@ export default function ChecklyCreateIntegrationPage() { selectedSourceEnvironment && integrationAuthApps && integrationAuthGroups && - targetApp ? ( + targetAppId ? (
Set Up Checkly Integration @@ -197,48 +190,18 @@ export default function ChecklyCreateIntegrationPage() { placeholder="Provide a path, default is /" /> - - - - - - By default environment variables are synced to the global level, select a group above to sync at the Group level. - - + + +