App connection updates

This commit is contained in:
x032205
2025-07-03 00:00:50 -04:00
parent 62ad82f7b1
commit ae35a863bc
10 changed files with 127 additions and 22 deletions

View File

@@ -2632,6 +2632,9 @@ export const SecretScanningDataSources = {
CONFIG: { CONFIG: {
GITHUB: { GITHUB: {
includeRepos: 'The repositories to include when scanning. Defaults to all repositories (["*"]).' includeRepos: 'The repositories to include when scanning. Defaults to all repositories (["*"]).'
},
BITBUCKET: {
includeRepos: 'The repositories to include when scanning. Defaults to all repositories (["*"]).'
} }
} }
}; };

View File

@@ -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 { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { import {
CreateBitBucketConnectionSchema, CreateBitBucketConnectionSchema,
SanitizedBitBucketConnectionSchema, SanitizedBitBucketConnectionSchema,
UpdateBitBucketConnectionSchema UpdateBitBucketConnectionSchema
} from "@app/services/app-connection/bitbucket"; } from "@app/services/app-connection/bitbucket";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
@@ -15,4 +20,32 @@ export const registerBitBucketConnectionRouter = async (server: FastifyZodProvid
createSchema: CreateBitBucketConnectionSchema, createSchema: CreateBitBucketConnectionSchema,
updateSchema: UpdateBitBucketConnectionSchema 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 };
}
});
}; };

View File

@@ -3,9 +3,10 @@ import { AxiosError } from "axios";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; 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 { BitBucketConnectionMethod } from "./bitbucket-connection-enums";
import { TBitBucketConnectionConfig } from "./bitbucket-connection-types"; import { TBitBucketConnection, TBitBucketConnectionConfig, TBitBucketRepo } from "./bitbucket-connection-types";
export const getBitBucketConnectionListItem = () => { export const getBitBucketConnectionListItem = () => {
return { return {
@@ -15,16 +16,16 @@ export const getBitBucketConnectionListItem = () => {
}; };
}; };
export const validateBitBucketConnectionCredentials = async (config: TBitBucketConnectionConfig) => { export const getBitBucketUser = async ({ email, apiToken }: { email: string; apiToken: string }) => {
const { email, apiToken } = config.credentials;
try { 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: { headers: {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
Accept: "application/json" Accept: "application/json"
} }
}); });
return data;
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof AxiosError) { if (error instanceof AxiosError) {
throw new BadRequestError({ throw new BadRequestError({
@@ -35,6 +36,26 @@ export const validateBitBucketConnectionCredentials = async (config: TBitBucketC
message: "Unable to validate connection: verify credentials" message: "Unable to validate connection: verify credentials"
}); });
} }
};
export const validateBitBucketConnectionCredentials = async (config: TBitBucketConnectionConfig) => {
await getBitBucketUser(config.credentials);
return 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;
};

View File

@@ -1,7 +1,7 @@
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../app-connection-enums";
// import { listBitBucketVaults } from "./bitbucket-connection-fns"; import { listBitBucketRepositories } from "./bitbucket-connection-fns";
import { TBitBucketConnection } from "./bitbucket-connection-types"; import { TBitBucketConnection } from "./bitbucket-connection-types";
type TGetAppConnectionFunc = ( type TGetAppConnectionFunc = (
@@ -10,6 +10,17 @@ type TGetAppConnectionFunc = (
actor: OrgServiceActor actor: OrgServiceActor
) => Promise<TBitBucketConnection>; ) => Promise<TBitBucketConnection>;
export const bitBucketConnectionService = (_getAppConnection: TGetAppConnectionFunc) => { export const bitBucketConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
return {}; 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
};
}; };

View File

@@ -24,15 +24,7 @@ export type TBitBucketConnectionConfig = DiscriminativePick<
orgId: string; orgId: string;
}; };
export type TBitBucketVault = { export type TBitBucketRepo = {
id: string; full_name: string; // workspace-slug/repo-slug
name: string; slug: string;
type: string;
items: number;
attributeVersion: number;
contentVersion: number;
createdAt: string;
updatedAt: string;
}; };

View File

@@ -39,10 +39,10 @@ import {
VercelConnectionMethod, VercelConnectionMethod,
WindmillConnectionMethod WindmillConnectionMethod
} from "@app/hooks/api/appConnections/types"; } 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 { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection"; import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-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< export const APP_CONNECTION_MAP: Record<
AppConnection, AppConnection,

View File

@@ -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<typeof bitBucketConnectionKeys.listRepos>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: bitBucketConnectionKeys.listRepos(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<TBitBucketConnectionListRepositoriesResponse>(
`/api/v1/app-connections/bitbucket/${connectionId}/repositories`
);
return data.repositories;
},
...options
});
};

View File

@@ -0,0 +1,8 @@
export type TBitBucketRepo = {
id: string;
name: string; // workspace-slug/repo-slug
};
export type TBitBucketConnectionListRepositoriesResponse = {
repositories: TBitBucketRepo[];
};

View File

@@ -38,6 +38,7 @@ export * from "./azure-app-configuration-connection";
export * from "./azure-client-secrets-connection"; export * from "./azure-client-secrets-connection";
export * from "./azure-devops-connection"; export * from "./azure-devops-connection";
export * from "./azure-key-vault-connection"; export * from "./azure-key-vault-connection";
export * from "./bitbucket-connection";
export * from "./camunda-connection"; export * from "./camunda-connection";
export * from "./cloudflare-connection"; export * from "./cloudflare-connection";
export * from "./databricks-connection"; export * from "./databricks-connection";
@@ -60,7 +61,6 @@ export * from "./teamcity-connection";
export * from "./terraform-cloud-connection"; export * from "./terraform-cloud-connection";
export * from "./vercel-connection"; export * from "./vercel-connection";
export * from "./windmill-connection"; export * from "./windmill-connection";
export * from "./bitbucket-connection";
export type TAppConnection = export type TAppConnection =
| TAwsConnection | TAwsConnection

View File

@@ -16,6 +16,7 @@ import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConn
import { AzureClientSecretsConnectionForm } from "./AzureClientSecretsConnectionForm"; import { AzureClientSecretsConnectionForm } from "./AzureClientSecretsConnectionForm";
import { AzureDevOpsConnectionForm } from "./AzureDevOpsConnectionForm"; import { AzureDevOpsConnectionForm } from "./AzureDevOpsConnectionForm";
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm"; import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
import { BitBucketConnectionForm } from "./BitBucketConnectionForm";
import { CamundaConnectionForm } from "./CamundaConnectionForm"; import { CamundaConnectionForm } from "./CamundaConnectionForm";
import { CloudflareConnectionForm } from "./CloudflareConnectionForm"; import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
import { DatabricksConnectionForm } from "./DatabricksConnectionForm"; import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
@@ -38,7 +39,6 @@ import { TeamCityConnectionForm } from "./TeamCityConnectionForm";
import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm"; import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm";
import { VercelConnectionForm } from "./VercelConnectionForm"; import { VercelConnectionForm } from "./VercelConnectionForm";
import { WindmillConnectionForm } from "./WindmillConnectionForm"; import { WindmillConnectionForm } from "./WindmillConnectionForm";
import { BitBucketConnectionForm } from "./BitBucketConnectionForm";
type FormProps = { type FormProps = {
onComplete: (appConnection: TAppConnection) => void; onComplete: (appConnection: TAppConnection) => void;