From ae35a863bc6b3f19a70df38eb301500eec24951a Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 3 Jul 2025 00:00:50 -0400 Subject: [PATCH] App connection updates --- backend/src/lib/api-docs/constants.ts | 3 ++ .../bitbucket-connection-router.ts | 33 +++++++++++++++++ .../bitbucket/bitbucket-connection-fns.ts | 31 +++++++++++++--- .../bitbucket/bitbucket-connection-service.ts | 17 +++++++-- .../bitbucket/bitbucket-connection-types.ts | 14 ++----- frontend/src/helpers/appConnections.ts | 2 +- .../api/appConnections/bitbucket/queries.tsx | 37 +++++++++++++++++++ .../api/appConnections/bitbucket/types.ts | 8 ++++ .../hooks/api/appConnections/types/index.ts | 2 +- .../AppConnectionForm/AppConnectionForm.tsx | 2 +- 10 files changed, 127 insertions(+), 22 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index b457eafb3..e5ed91a26 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2632,6 +2632,9 @@ export const SecretScanningDataSources = { CONFIG: { GITHUB: { includeRepos: 'The repositories to include when scanning. Defaults to all repositories (["*"]).' + }, + BITBUCKET: { + includeRepos: 'The repositories to include when scanning. Defaults to all repositories (["*"]).' } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/bitbucket-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/bitbucket-connection-router.ts index bcef795c1..4f4271273 100644 --- a/backend/src/server/routes/v1/app-connection-routers/bitbucket-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/bitbucket-connection-router.ts @@ -1,9 +1,14 @@ +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 { CreateBitBucketConnectionSchema, SanitizedBitBucketConnectionSchema, UpdateBitBucketConnectionSchema } from "@app/services/app-connection/bitbucket"; +import { AuthMode } from "@app/services/auth/auth-type"; import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; @@ -15,4 +20,32 @@ export const registerBitBucketConnectionRouter = async (server: FastifyZodProvid createSchema: CreateBitBucketConnectionSchema, updateSchema: UpdateBitBucketConnectionSchema }); + + // The below endpoints are not exposed and for Infisical App use + + server.route({ + method: "GET", + url: `/:connectionId/repositories`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z.object({ + repositories: z.object({ id: z.string(), name: z.string() }).array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + + const repositories = await server.services.appConnection.bitbucket.listRepositories(connectionId, req.permission); + + return { repositories }; + } + }); }; diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-fns.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-fns.ts index 45ce2c0b5..11e447443 100644 --- a/backend/src/services/app-connection/bitbucket/bitbucket-connection-fns.ts +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-fns.ts @@ -3,9 +3,10 @@ import { AxiosError } from "axios"; import { request } from "@app/lib/config/request"; import { BadRequestError } from "@app/lib/errors"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { BitBucketConnectionMethod } from "./bitbucket-connection-enums"; -import { TBitBucketConnectionConfig } from "./bitbucket-connection-types"; +import { TBitBucketConnection, TBitBucketConnectionConfig, TBitBucketRepo } from "./bitbucket-connection-types"; export const getBitBucketConnectionListItem = () => { return { @@ -15,16 +16,16 @@ export const getBitBucketConnectionListItem = () => { }; }; -export const validateBitBucketConnectionCredentials = async (config: TBitBucketConnectionConfig) => { - const { email, apiToken } = config.credentials; - +export const getBitBucketUser = async ({ email, apiToken }: { email: string; apiToken: string }) => { try { - await request.get("https://api.bitbucket.org/2.0/user", { + const { data } = await request.get<{ username: string }>(`${IntegrationUrls.BITBUCKET_API_URL}/2.0/user`, { headers: { Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Accept: "application/json" } }); + + return data; } catch (error: unknown) { if (error instanceof AxiosError) { throw new BadRequestError({ @@ -35,6 +36,26 @@ export const validateBitBucketConnectionCredentials = async (config: TBitBucketC message: "Unable to validate connection: verify credentials" }); } +}; +export const validateBitBucketConnectionCredentials = async (config: TBitBucketConnectionConfig) => { + await getBitBucketUser(config.credentials); return config.credentials; }; + +export const listBitBucketRepositories = async (appConnection: TBitBucketConnection) => { + const { email, apiToken } = appConnection.credentials; + + // TODO(andrey): Support pagination for cases where a token has access to over 100 repos + const { data } = await request.get<{ values: TBitBucketRepo[] }>( + `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories?role=member&pagelen=100`, + { + headers: { + Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, + Accept: "application/json" + } + } + ); + + return data.values; +}; diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-service.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-service.ts index 16d3edea4..5b9c049f7 100644 --- a/backend/src/services/app-connection/bitbucket/bitbucket-connection-service.ts +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-service.ts @@ -1,7 +1,7 @@ import { OrgServiceActor } from "@app/lib/types"; import { AppConnection } from "../app-connection-enums"; -// import { listBitBucketVaults } from "./bitbucket-connection-fns"; +import { listBitBucketRepositories } from "./bitbucket-connection-fns"; import { TBitBucketConnection } from "./bitbucket-connection-types"; type TGetAppConnectionFunc = ( @@ -10,6 +10,17 @@ type TGetAppConnectionFunc = ( actor: OrgServiceActor ) => Promise; -export const bitBucketConnectionService = (_getAppConnection: TGetAppConnectionFunc) => { - return {}; +export const bitBucketConnectionService = (getAppConnection: TGetAppConnectionFunc) => { + const listRepositories = async (connectionId: string, actor: OrgServiceActor) => { + const appConnection = await getAppConnection(AppConnection.BitBucket, connectionId, actor); + + const repositories = await listBitBucketRepositories(appConnection); + + // TODO(andrey): May need to change from slug to ID or something + return repositories.map((repo) => ({ id: repo.slug, name: repo.full_name })); + }; + + return { + listRepositories + }; }; diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-types.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-types.ts index a59fdb397..41fdaef29 100644 --- a/backend/src/services/app-connection/bitbucket/bitbucket-connection-types.ts +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-types.ts @@ -24,15 +24,7 @@ export type TBitBucketConnectionConfig = DiscriminativePick< orgId: string; }; -export type TBitBucketVault = { - id: string; - name: string; - type: string; - items: number; - - attributeVersion: number; - contentVersion: number; - - createdAt: string; - updatedAt: string; +export type TBitBucketRepo = { + full_name: string; // workspace-slug/repo-slug + slug: string; }; diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index 972247bf4..8ed28f4ab 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -39,10 +39,10 @@ import { VercelConnectionMethod, WindmillConnectionMethod } from "@app/hooks/api/appConnections/types"; +import { BitBucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection"; import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection"; import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection"; import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection"; -import { BitBucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection"; export const APP_CONNECTION_MAP: Record< AppConnection, diff --git a/frontend/src/hooks/api/appConnections/bitbucket/queries.tsx b/frontend/src/hooks/api/appConnections/bitbucket/queries.tsx index e69de29bb..6d29d0f64 100644 --- a/frontend/src/hooks/api/appConnections/bitbucket/queries.tsx +++ b/frontend/src/hooks/api/appConnections/bitbucket/queries.tsx @@ -0,0 +1,37 @@ +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { appConnectionKeys } from "../queries"; +import { TBitBucketConnectionListRepositoriesResponse, TBitBucketRepo } from "./types"; + +const bitBucketConnectionKeys = { + all: [...appConnectionKeys.all, "bitbucket"] as const, + listRepos: (connectionId: string) => + [...bitBucketConnectionKeys.all, "repos", connectionId] as const +}; + +export const useBitBucketConnectionListRepositories = ( + connectionId: string, + options?: Omit< + UseQueryOptions< + TBitBucketRepo[], + unknown, + TBitBucketRepo[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: bitBucketConnectionKeys.listRepos(connectionId), + queryFn: async () => { + const { data } = await apiRequest.get( + `/api/v1/app-connections/bitbucket/${connectionId}/repositories` + ); + + return data.repositories; + }, + ...options + }); +}; diff --git a/frontend/src/hooks/api/appConnections/bitbucket/types.ts b/frontend/src/hooks/api/appConnections/bitbucket/types.ts index e69de29bb..c4c02c098 100644 --- a/frontend/src/hooks/api/appConnections/bitbucket/types.ts +++ b/frontend/src/hooks/api/appConnections/bitbucket/types.ts @@ -0,0 +1,8 @@ +export type TBitBucketRepo = { + id: string; + name: string; // workspace-slug/repo-slug +}; + +export type TBitBucketConnectionListRepositoriesResponse = { + repositories: TBitBucketRepo[]; +}; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index 6c0da9b69..e42c067d8 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -38,6 +38,7 @@ export * from "./azure-app-configuration-connection"; export * from "./azure-client-secrets-connection"; export * from "./azure-devops-connection"; export * from "./azure-key-vault-connection"; +export * from "./bitbucket-connection"; export * from "./camunda-connection"; export * from "./cloudflare-connection"; export * from "./databricks-connection"; @@ -60,7 +61,6 @@ export * from "./teamcity-connection"; export * from "./terraform-cloud-connection"; export * from "./vercel-connection"; export * from "./windmill-connection"; -export * from "./bitbucket-connection"; export type TAppConnection = | TAwsConnection 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 92b23ce3e..37cd7a13a 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -16,6 +16,7 @@ import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConn import { AzureClientSecretsConnectionForm } from "./AzureClientSecretsConnectionForm"; import { AzureDevOpsConnectionForm } from "./AzureDevOpsConnectionForm"; import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm"; +import { BitBucketConnectionForm } from "./BitBucketConnectionForm"; import { CamundaConnectionForm } from "./CamundaConnectionForm"; import { CloudflareConnectionForm } from "./CloudflareConnectionForm"; import { DatabricksConnectionForm } from "./DatabricksConnectionForm"; @@ -38,7 +39,6 @@ import { TeamCityConnectionForm } from "./TeamCityConnectionForm"; import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm"; import { VercelConnectionForm } from "./VercelConnectionForm"; import { WindmillConnectionForm } from "./WindmillConnectionForm"; -import { BitBucketConnectionForm } from "./BitBucketConnectionForm"; type FormProps = { onComplete: (appConnection: TAppConnection) => void;