mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 23:28:25 +00:00
feat(integration): add integration with BitBucket
This commit is contained in:
@@ -47,11 +47,13 @@ CLIENT_ID_VERCEL=
|
|||||||
CLIENT_ID_NETLIFY=
|
CLIENT_ID_NETLIFY=
|
||||||
CLIENT_ID_GITHUB=
|
CLIENT_ID_GITHUB=
|
||||||
CLIENT_ID_GITLAB=
|
CLIENT_ID_GITLAB=
|
||||||
|
CLIENT_ID_BITBUCKET=
|
||||||
CLIENT_SECRET_HEROKU=
|
CLIENT_SECRET_HEROKU=
|
||||||
CLIENT_SECRET_VERCEL=
|
CLIENT_SECRET_VERCEL=
|
||||||
CLIENT_SECRET_NETLIFY=
|
CLIENT_SECRET_NETLIFY=
|
||||||
CLIENT_SECRET_GITHUB=
|
CLIENT_SECRET_GITHUB=
|
||||||
CLIENT_SECRET_GITLAB=
|
CLIENT_SECRET_GITLAB=
|
||||||
|
CLIENT_SECRET_BITBUCKET=
|
||||||
CLIENT_SLUG_VERCEL=
|
CLIENT_SLUG_VERCEL=
|
||||||
|
|
||||||
# Sentry (optional) for monitoring errors
|
# Sentry (optional) for monitoring errors
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID
|
|||||||
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
|
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
|
||||||
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
|
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
|
||||||
export const getClientIdGoogle = async () => (await client.getSecret("CLIENT_ID_GOOGLE")).secretValue;
|
export const getClientIdGoogle = async () => (await client.getSecret("CLIENT_ID_GOOGLE")).secretValue;
|
||||||
|
export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue;
|
||||||
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
|
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
|
||||||
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
|
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
|
||||||
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue;
|
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue;
|
||||||
@@ -44,6 +45,7 @@ export const getClientSecretNetlify = async () => (await client.getSecret("CLIEN
|
|||||||
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
|
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
|
||||||
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
|
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
|
||||||
export const getClientSecretGoogle = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE")).secretValue;
|
export const getClientSecretGoogle = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE")).secretValue;
|
||||||
|
export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue;
|
||||||
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
|
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
|
||||||
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
|
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
|
||||||
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
|
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { IntegrationService } from "../../services";
|
|||||||
import {
|
import {
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
|
INTEGRATION_BITBUCKET_API_URL,
|
||||||
INTEGRATION_RAILWAY_API_URL,
|
INTEGRATION_RAILWAY_API_URL,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
@@ -141,12 +142,14 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
|||||||
*/
|
*/
|
||||||
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||||
const teamId = req.query.teamId as string;
|
const teamId = req.query.teamId as string;
|
||||||
|
const workspaceSlug = req.query.workspaceSlug as string;
|
||||||
|
|
||||||
const apps = await getApps({
|
const apps = await getApps({
|
||||||
integrationAuth: req.integrationAuth,
|
integrationAuth: req.integrationAuth,
|
||||||
accessToken: req.accessToken,
|
accessToken: req.accessToken,
|
||||||
accessId: req.accessId,
|
accessId: req.accessId,
|
||||||
...(teamId && { teamId })
|
...(teamId && { teamId }),
|
||||||
|
...(workspaceSlug && { workspaceSlug })
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
@@ -382,6 +385,66 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of workspaces allowed for integration with integration authorization id [integrationAuthId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: Response) => {
|
||||||
|
|
||||||
|
interface WorkspaceResponse {
|
||||||
|
size: number;
|
||||||
|
page: number;
|
||||||
|
pageLen: number;
|
||||||
|
next: string;
|
||||||
|
previous: string;
|
||||||
|
values: Array<Workspace>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Workspace {
|
||||||
|
type: string;
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
is_private: boolean;
|
||||||
|
created_on: string;
|
||||||
|
updated_on: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaces: Workspace[] = [];
|
||||||
|
let hasNextPage = true;
|
||||||
|
let workspaceUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/workspaces`
|
||||||
|
|
||||||
|
while (hasNextPage) {
|
||||||
|
const { data }: { data: WorkspaceResponse } = await standardRequest.get(
|
||||||
|
workspaceUrl,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${req.accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data?.values.length > 0) {
|
||||||
|
data.values.forEach((workspace) => {
|
||||||
|
workspaces.push(workspace)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.next) {
|
||||||
|
workspaceUrl = data.next
|
||||||
|
} else {
|
||||||
|
hasNextPage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
workspaces
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete integration authorization with id [integrationAuthId]
|
* Delete integration authorization with id [integrationAuthId]
|
||||||
* @param req
|
* @param req
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_BITBUCKET_API_URL,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
INTEGRATION_CHECKLY_API_URL,
|
INTEGRATION_CHECKLY_API_URL,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
@@ -54,11 +56,13 @@ const getApps = async ({
|
|||||||
accessToken,
|
accessToken,
|
||||||
accessId,
|
accessId,
|
||||||
teamId,
|
teamId,
|
||||||
|
workspaceSlug,
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
accessId?: string;
|
accessId?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
|
workspaceSlug?: string;
|
||||||
}) => {
|
}) => {
|
||||||
let apps: App[] = [];
|
let apps: App[] = [];
|
||||||
switch (integrationAuth.integration) {
|
switch (integrationAuth.integration) {
|
||||||
@@ -145,6 +149,11 @@ const getApps = async ({
|
|||||||
accountId: accessId
|
accountId: accessId
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_BITBUCKET:
|
||||||
|
apps = await getAppsBitBucket({
|
||||||
|
accessToken,
|
||||||
|
workspaceSlug
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
@@ -721,4 +730,78 @@ const getAppsCloudflarePages = async ({
|
|||||||
return apps;
|
return apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of repositories for the BitBucket integration based on provided BitBucket workspace
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for BitBucket API
|
||||||
|
* @param {String} obj.workspaceSlug - Workspace identifier for fetching BitBucket repositories
|
||||||
|
* @returns {Object[]} apps - BitBucket repositories
|
||||||
|
* @returns {String} apps.name - name of BitBucket repository
|
||||||
|
*/
|
||||||
|
const getAppsBitBucket = async ({
|
||||||
|
accessToken,
|
||||||
|
workspaceSlug,
|
||||||
|
}: {
|
||||||
|
accessToken: string;
|
||||||
|
workspaceSlug?: string;
|
||||||
|
}) => {
|
||||||
|
interface RepositoriesResponse {
|
||||||
|
size: number;
|
||||||
|
page: number;
|
||||||
|
pageLen: number;
|
||||||
|
next: string;
|
||||||
|
previous: string;
|
||||||
|
values: Array<Repository>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Repository {
|
||||||
|
type: string;
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
is_private: boolean;
|
||||||
|
created_on: string;
|
||||||
|
updated_on: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!workspaceSlug) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const repositories: Repository[] = [];
|
||||||
|
let hasNextPage = true;
|
||||||
|
let repositoriesUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/repositories/${workspaceSlug}`
|
||||||
|
|
||||||
|
while (hasNextPage) {
|
||||||
|
const { data }: { data: RepositoriesResponse } = await standardRequest.get(
|
||||||
|
repositoriesUrl,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data?.values.length > 0) {
|
||||||
|
data.values.forEach((repository) => {
|
||||||
|
repositories.push(repository)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.next) {
|
||||||
|
repositoriesUrl = data.next
|
||||||
|
} else {
|
||||||
|
hasNextPage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apps = repositories.map((repository) => {
|
||||||
|
return {
|
||||||
|
name: repository.name,
|
||||||
|
appId: repository.uuid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return apps;
|
||||||
|
}
|
||||||
|
|
||||||
export { getApps };
|
export { getApps };
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { standardRequest } from "../config/request";
|
|||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
INTEGRATION_AZURE_TOKEN_URL,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITHUB_TOKEN_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
@@ -15,11 +17,13 @@ import {
|
|||||||
} from "../variables";
|
} from "../variables";
|
||||||
import {
|
import {
|
||||||
getClientIdAzure,
|
getClientIdAzure,
|
||||||
|
getClientIdBitBucket,
|
||||||
getClientIdGitHub,
|
getClientIdGitHub,
|
||||||
getClientIdGitLab,
|
getClientIdGitLab,
|
||||||
getClientIdNetlify,
|
getClientIdNetlify,
|
||||||
getClientIdVercel,
|
getClientIdVercel,
|
||||||
getClientSecretAzure,
|
getClientSecretAzure,
|
||||||
|
getClientSecretBitBucket,
|
||||||
getClientSecretGitHub,
|
getClientSecretGitHub,
|
||||||
getClientSecretGitLab,
|
getClientSecretGitLab,
|
||||||
getClientSecretHeroku,
|
getClientSecretHeroku,
|
||||||
@@ -78,6 +82,15 @@ interface ExchangeCodeGitlabResponse {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExchangeCodeBitBucketResponse {
|
||||||
|
access_token: string;
|
||||||
|
token_type: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
scopes: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2
|
||||||
* code-token exchange for integration named [integration]
|
* code-token exchange for integration named [integration]
|
||||||
@@ -129,6 +142,11 @@ const exchangeCode = async ({
|
|||||||
obj = await exchangeCodeGitlab({
|
obj = await exchangeCodeGitlab({
|
||||||
code,
|
code,
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
|
case INTEGRATION_BITBUCKET:
|
||||||
|
obj = await exchangeCodeBitBucket({
|
||||||
|
code,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
@@ -347,4 +365,43 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for BitBucket
|
||||||
|
* code-token exchange
|
||||||
|
* @param {Object} obj1
|
||||||
|
* @param {Object} obj1.code - code for code-token exchange
|
||||||
|
* @returns {Object} obj2
|
||||||
|
* @returns {String} obj2.accessToken - access token for BitBucket API
|
||||||
|
* @returns {String} obj2.refreshToken - refresh token for BitBucket API
|
||||||
|
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||||
|
*/
|
||||||
|
const exchangeCodeBitBucket = async ({ code }: { code: string }) => {
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
const res: ExchangeCodeBitBucketResponse = (
|
||||||
|
await standardRequest.post(
|
||||||
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
code: code,
|
||||||
|
client_id: await getClientIdBitBucket(),
|
||||||
|
client_secret: await getClientSecretBitBucket(),
|
||||||
|
redirect_uri: `${await getSiteURL()}/integrations/bitbucket/oauth2/callback`,
|
||||||
|
} as any),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: res.access_token,
|
||||||
|
refreshToken: res.refresh_token,
|
||||||
|
accessExpiresAt,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export { exchangeCode };
|
export { exchangeCode };
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { standardRequest } from "../config/request";
|
|||||||
import { IIntegrationAuth } from "../models";
|
import { IIntegrationAuth } from "../models";
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
@@ -13,8 +15,10 @@ import {
|
|||||||
import { IntegrationService } from "../services";
|
import { IntegrationService } from "../services";
|
||||||
import {
|
import {
|
||||||
getClientIdAzure,
|
getClientIdAzure,
|
||||||
|
getClientIdBitBucket,
|
||||||
getClientIdGitLab,
|
getClientIdGitLab,
|
||||||
getClientSecretAzure,
|
getClientSecretAzure,
|
||||||
|
getClientSecretBitBucket,
|
||||||
getClientSecretGitLab,
|
getClientSecretGitLab,
|
||||||
getClientSecretHeroku,
|
getClientSecretHeroku,
|
||||||
getSiteURL,
|
getSiteURL,
|
||||||
@@ -46,6 +50,15 @@ interface RefreshTokenGitLabResponse {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RefreshTokenBitBucketResponse {
|
||||||
|
access_token: string;
|
||||||
|
token_type: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
scopes: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new access token by exchanging refresh token [refreshToken] for integration
|
* Return new access token by exchanging refresh token [refreshToken] for integration
|
||||||
* named [integration]
|
* named [integration]
|
||||||
@@ -83,6 +96,11 @@ const exchangeRefresh = async ({
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_BITBUCKET:
|
||||||
|
tokenDetails = await exchangeRefreshBitBucket({
|
||||||
|
refreshToken,
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Failed to exchange token for incompatible integration");
|
throw new Error("Failed to exchange token for incompatible integration");
|
||||||
}
|
}
|
||||||
@@ -218,4 +236,46 @@ const exchangeRefreshGitLab = async ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||||
|
* BitBucket integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for BitBucket
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const exchangeRefreshBitBucket = async ({
|
||||||
|
refreshToken,
|
||||||
|
}: {
|
||||||
|
refreshToken: string;
|
||||||
|
}) => {
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
data: RefreshTokenBitBucketResponse;
|
||||||
|
} = await standardRequest.post(
|
||||||
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
client_id: await getClientIdBitBucket(),
|
||||||
|
client_secret: await getClientSecretBitBucket(),
|
||||||
|
redirect_uri: `${await getSiteURL()}/integrations/bitbucket/oauth2/callback`,
|
||||||
|
} as any),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessExpiresAt,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export { exchangeRefresh };
|
export { exchangeRefresh };
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_BITBUCKET_API_URL,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
INTEGRATION_CHECKLY_API_URL,
|
INTEGRATION_CHECKLY_API_URL,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
@@ -202,7 +204,14 @@ const syncSecrets = async ({
|
|||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_BITBUCKET:
|
||||||
|
await syncSecretsBitBucket({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1937,4 +1946,122 @@ const syncSecretsCloudflarePages = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync/push [secrets] to BitBucket repo with name [integration.app]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {IIntegration} obj.integration - integration details
|
||||||
|
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
|
||||||
|
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||||
|
* @param {String} obj.accessToken - access token for BitBucket integration
|
||||||
|
*/
|
||||||
|
const syncSecretsBitBucket = async ({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken,
|
||||||
|
}: {
|
||||||
|
integration: IIntegration;
|
||||||
|
secrets: any;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
interface VariablesResponse {
|
||||||
|
size: number;
|
||||||
|
page: number;
|
||||||
|
pageLen: number;
|
||||||
|
next: string;
|
||||||
|
previous: string;
|
||||||
|
values: Array<Variable>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Variable {
|
||||||
|
type: string;
|
||||||
|
uuid: string;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
secured: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSecrets: Variable[] = [];
|
||||||
|
const workspaceSlug = integration.targetEnvironmentId
|
||||||
|
const repoSlug = integration.appId
|
||||||
|
let hasNextPage = true;
|
||||||
|
let variablesUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/repositories/${workspaceSlug}/${repoSlug}/pipelines_config/variables`
|
||||||
|
|
||||||
|
// Fetch all repository variables
|
||||||
|
while (hasNextPage) {
|
||||||
|
const { data }: { data: VariablesResponse } = await standardRequest.get(
|
||||||
|
variablesUrl,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data?.values.length > 0) {
|
||||||
|
data.values.forEach((variable) => {
|
||||||
|
existingSecrets.push(variable)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.next) {
|
||||||
|
variablesUrl = data.next
|
||||||
|
} else {
|
||||||
|
hasNextPage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(secrets).forEach(async (key) => {
|
||||||
|
const existingSecret = existingSecrets.find((secret) => secret.key.toUpperCase() === key.toUpperCase());
|
||||||
|
if (existingSecret) {
|
||||||
|
// Update existing secrets
|
||||||
|
await standardRequest.put(
|
||||||
|
`${variablesUrl}/${existingSecret.uuid}`,
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
value: secrets[key],
|
||||||
|
secured: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Create new secrets
|
||||||
|
await standardRequest.post(
|
||||||
|
variablesUrl,
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
value: secrets[key],
|
||||||
|
secured: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete secrets
|
||||||
|
existingSecrets.forEach(async (existingSecret) => {
|
||||||
|
if (!(existingSecret.key in secrets) && existingSecret.secured) {
|
||||||
|
await standardRequest.delete(
|
||||||
|
`${variablesUrl}/${existingSecret.uuid}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export { syncSecrets };
|
export { syncSecrets };
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
@@ -54,7 +55,8 @@ export interface IIntegration {
|
|||||||
| "supabase"
|
| "supabase"
|
||||||
| "checkly"
|
| "checkly"
|
||||||
| "hashicorp-vault"
|
| "hashicorp-vault"
|
||||||
| "cloudflare-pages";
|
| "cloudflare-pages"
|
||||||
|
| "bitbucket";
|
||||||
integrationAuth: Types.ObjectId;
|
integrationAuth: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +146,7 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
],
|
],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
@@ -25,7 +26,25 @@ import {
|
|||||||
export interface IIntegrationAuth extends Document {
|
export interface IIntegrationAuth extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
integration: "heroku" | "vercel" | "netlify" | "github" | "gitlab" | "render" | "railway" | "flyio" | "azure-key-vault" | "laravel-forge" | "circleci" | "travisci" | "supabase" | "aws-parameter-store" | "aws-secret-manager" | "checkly" | "cloudflare-pages";
|
integration:
|
||||||
|
| "heroku"
|
||||||
|
| "vercel"
|
||||||
|
| "netlify"
|
||||||
|
| "github"
|
||||||
|
| "gitlab"
|
||||||
|
| "render"
|
||||||
|
| "railway"
|
||||||
|
| "flyio"
|
||||||
|
| "azure-key-vault"
|
||||||
|
| "laravel-forge"
|
||||||
|
| "circleci"
|
||||||
|
| "travisci"
|
||||||
|
| "supabase"
|
||||||
|
| "aws-parameter-store"
|
||||||
|
| "aws-secret-manager"
|
||||||
|
| "checkly"
|
||||||
|
| "cloudflare-pages"
|
||||||
|
| "bitbucket";
|
||||||
teamId: string;
|
teamId: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -71,6 +90,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
|||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_BITBUCKET
|
||||||
],
|
],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ router.get(
|
|||||||
}),
|
}),
|
||||||
param("integrationAuthId"),
|
param("integrationAuthId"),
|
||||||
query("teamId"),
|
query("teamId"),
|
||||||
|
query("workspaceSlug"),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
integrationAuthController.getIntegrationAuthApps
|
integrationAuthController.getIntegrationAuthApps
|
||||||
);
|
);
|
||||||
@@ -141,6 +142,19 @@ router.get(
|
|||||||
integrationAuthController.getIntegrationAuthRailwayServices
|
integrationAuthController.getIntegrationAuthRailwayServices
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:integrationAuthId/bitbucket/workspaces",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
|
}),
|
||||||
|
requireIntegrationAuthorizationAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
}),
|
||||||
|
param("integrationAuthId"),
|
||||||
|
validateRequest,
|
||||||
|
integrationAuthController.getIntegrationAuthBitBucketWorkspaces
|
||||||
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:integrationAuthId",
|
"/:integrationAuthId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
getClientIdAzure,
|
getClientIdAzure,
|
||||||
|
getClientIdBitBucket,
|
||||||
getClientIdGitHub,
|
getClientIdGitHub,
|
||||||
getClientIdGitLab,
|
getClientIdGitLab,
|
||||||
getClientIdHeroku,
|
getClientIdHeroku,
|
||||||
@@ -26,6 +27,7 @@ export const INTEGRATION_SUPABASE = "supabase";
|
|||||||
export const INTEGRATION_CHECKLY = "checkly";
|
export const INTEGRATION_CHECKLY = "checkly";
|
||||||
export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault";
|
export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault";
|
||||||
export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
|
export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
|
||||||
|
export const INTEGRATION_BITBUCKET = "bitbucket";
|
||||||
export const INTEGRATION_SET = new Set([
|
export const INTEGRATION_SET = new Set([
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
@@ -41,7 +43,8 @@ export const INTEGRATION_SET = new Set([
|
|||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_BITBUCKET
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// integration types
|
// integration types
|
||||||
@@ -56,6 +59,7 @@ export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/toke
|
|||||||
export const INTEGRATION_GITHUB_TOKEN_URL =
|
export const INTEGRATION_GITHUB_TOKEN_URL =
|
||||||
"https://github.com/login/oauth/access_token";
|
"https://github.com/login/oauth/access_token";
|
||||||
export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
|
export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
|
||||||
|
export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token"
|
||||||
|
|
||||||
// integration apps endpoints
|
// integration apps endpoints
|
||||||
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
||||||
@@ -71,6 +75,7 @@ export const INTEGRATION_SUPABASE_API_URL = "https://api.supabase.com";
|
|||||||
export const INTEGRATION_LARAVELFORGE_API_URL = "https://forge.laravel.com";
|
export const INTEGRATION_LARAVELFORGE_API_URL = "https://forge.laravel.com";
|
||||||
export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.com";
|
export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.com";
|
||||||
export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com";
|
export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com";
|
||||||
|
export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org/";
|
||||||
|
|
||||||
export const getIntegrationOptions = async () => {
|
export const getIntegrationOptions = async () => {
|
||||||
const INTEGRATION_OPTIONS = [
|
const INTEGRATION_OPTIONS = [
|
||||||
@@ -245,6 +250,15 @@ export const getIntegrationOptions = async () => {
|
|||||||
type: "pat",
|
type: "pat",
|
||||||
clientId: "",
|
clientId: "",
|
||||||
docsLink: ""
|
docsLink: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BitBucket",
|
||||||
|
slug: "bitbucket",
|
||||||
|
image: "BitBucket.png",
|
||||||
|
isAvailable: true,
|
||||||
|
type: "oauth",
|
||||||
|
clientId: await getClientIdBitBucket(),
|
||||||
|
docsLink: ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: "BitBucket"
|
||||||
|
description: "How to sync secrets from Infisical to BitBucket"
|
||||||
|
---
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
|
||||||
|
## Navigate to your project's integrations tab
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Authorize Infisical for BitBucket
|
||||||
|
|
||||||
|
Press on the BitBucket tile and grant Infisical access to your BitBucket account.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Info>
|
||||||
|
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
|
||||||
|
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
## Start integration
|
||||||
|
|
||||||
|
Select which Infisical environment secrets you want to sync to which BitBucket repo and press start integration to start syncing secrets to the repo.
|
||||||
|
|
||||||
|

|
||||||
@@ -28,6 +28,7 @@ Missing an integration? [Throw in a request](https://github.com/Infisical/infisi
|
|||||||
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
||||||
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||||
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
|
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
|
||||||
|
| [BitBucket](/integrations/cicd/bitbucket) | CI/CD | Available |
|
||||||
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
|
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
|
||||||
| [GitLab](/integrations/cicd/gitlab) | CI/CD | Available |
|
| [GitLab](/integrations/cicd/gitlab) | CI/CD | Available |
|
||||||
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Available |
|
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Available |
|
||||||
|
|||||||
+2
-1
@@ -220,7 +220,8 @@
|
|||||||
"integrations/cicd/githubactions",
|
"integrations/cicd/githubactions",
|
||||||
"integrations/cicd/gitlab",
|
"integrations/cicd/gitlab",
|
||||||
"integrations/cicd/circleci",
|
"integrations/cicd/circleci",
|
||||||
"integrations/cicd/travisci"
|
"integrations/cicd/travisci",
|
||||||
|
"integrations/cicd/bitbucket"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -107,6 +107,14 @@ Other environment variables are listed below to increase the functionality of yo
|
|||||||
<ParamField query="CLIENT_SLUG_VERCEL" type="string" default="none" optional>
|
<ParamField query="CLIENT_SLUG_VERCEL" type="string" default="none" optional>
|
||||||
OAuth2 slug for Vercel integration
|
OAuth2 slug for Vercel integration
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField query="CLIENT_ID_BITBUCKET" type="string" default="none" optional>
|
||||||
|
OAuth2 client ID for BitBucket integration
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField query="CLIENT_SECRET_BITBUCKET" type="string" default="none" optional>
|
||||||
|
OAuth2 client secret for BitBucket integration
|
||||||
|
</ParamField>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Auth Integrations">
|
<Tab title="Auth Integrations">
|
||||||
To integrate with external auth providers, provide value for the related keys
|
To integrate with external auth providers, provide value for the related keys
|
||||||
|
|||||||
@@ -3,25 +3,26 @@ interface Mapping {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const integrationSlugNameMapping: Mapping = {
|
const integrationSlugNameMapping: Mapping = {
|
||||||
'azure-key-vault': 'Azure Key Vault',
|
"azure-key-vault": "Azure Key Vault",
|
||||||
'aws-parameter-store': 'AWS Parameter Store',
|
"aws-parameter-store": "AWS Parameter Store",
|
||||||
'aws-secret-manager': 'AWS Secret Manager',
|
"aws-secret-manager": "AWS Secret Manager",
|
||||||
'heroku': 'Heroku',
|
heroku: "Heroku",
|
||||||
'vercel': 'Vercel',
|
vercel: "Vercel",
|
||||||
'netlify': 'Netlify',
|
netlify: "Netlify",
|
||||||
'github': 'GitHub',
|
github: "GitHub",
|
||||||
'gitlab': 'GitLab',
|
gitlab: "GitLab",
|
||||||
'render': 'Render',
|
render: "Render",
|
||||||
'laravel-forge': "Laravel Forge",
|
"laravel-forge": "Laravel Forge",
|
||||||
'railway': 'Railway',
|
railway: "Railway",
|
||||||
'flyio': 'Fly.io',
|
flyio: "Fly.io",
|
||||||
'circleci': 'CircleCI',
|
circleci: "CircleCI",
|
||||||
'travisci': 'TravisCI',
|
travisci: "TravisCI",
|
||||||
'supabase': 'Supabase',
|
supabase: "Supabase",
|
||||||
'checkly': 'Checkly',
|
checkly: "Checkly",
|
||||||
'hashicorp-vault': 'Vault',
|
"hashicorp-vault": "Vault",
|
||||||
'cloudflare-pages': 'Cloudflare Pages'
|
"cloudflare-pages": "Cloudflare Pages",
|
||||||
}
|
bitbucket: "BitBucket"
|
||||||
|
};
|
||||||
|
|
||||||
const envMapping: Mapping = {
|
const envMapping: Mapping = {
|
||||||
Development: "dev",
|
Development: "dev",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
@@ -1,9 +1,10 @@
|
|||||||
export {
|
export {
|
||||||
useDeleteIntegrationAuth,
|
useDeleteIntegrationAuth,
|
||||||
useGetIntegrationAuthApps,
|
useGetIntegrationAuthApps,
|
||||||
|
useGetIntegrationAuthBitBucketWorkspaces,
|
||||||
useGetIntegrationAuthById,
|
useGetIntegrationAuthById,
|
||||||
useGetIntegrationAuthRailwayEnvironments,
|
useGetIntegrationAuthRailwayEnvironments,
|
||||||
useGetIntegrationAuthRailwayServices,
|
useGetIntegrationAuthRailwayServices,
|
||||||
useGetIntegrationAuthTeams,
|
useGetIntegrationAuthTeams,
|
||||||
useGetIntegrationAuthVercelBranches
|
useGetIntegrationAuthVercelBranches,
|
||||||
} from "./queries";
|
} from "./queries";
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { workspaceKeys } from "../workspace/queries";
|
import { workspaceKeys } from "../workspace/queries";
|
||||||
import { App, Environment, IntegrationAuth, Service, Team } from "./types";
|
import { App, BitBucketWorkspace, Environment, IntegrationAuth, Service, Team } from "./types";
|
||||||
|
|
||||||
const integrationAuthKeys = {
|
const integrationAuthKeys = {
|
||||||
getIntegrationAuthById: (integrationAuthId: string) =>
|
getIntegrationAuthById: (integrationAuthId: string) =>
|
||||||
[{ integrationAuthId }, "integrationAuth"] as const,
|
[{ integrationAuthId }, "integrationAuth"] as const,
|
||||||
getIntegrationAuthApps: (integrationAuthId: string, teamId?: string) =>
|
getIntegrationAuthApps: (integrationAuthId: string, teamId?: string, workspaceSlug?: string) =>
|
||||||
[{ integrationAuthId, teamId }, "integrationAuthApps"] as const,
|
[{ integrationAuthId, teamId, workspaceSlug }, "integrationAuthApps"] as const,
|
||||||
getIntegrationAuthTeams: (integrationAuthId: string) =>
|
getIntegrationAuthTeams: (integrationAuthId: string) =>
|
||||||
[{ integrationAuthId }, "integrationAuthTeams"] as const,
|
[{ integrationAuthId }, "integrationAuthTeams"] as const,
|
||||||
getIntegrationAuthVercelBranches: ({
|
getIntegrationAuthVercelBranches: ({
|
||||||
@@ -32,7 +32,9 @@ const integrationAuthKeys = {
|
|||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
}) => [{ integrationAuthId, appId }, "integrationAuthRailwayServices"] as const
|
}) => [{ integrationAuthId, appId }, "integrationAuthRailwayServices"] as const,
|
||||||
|
getIntegrationAuthBitBucketWorkspaces: (integrationAuthId: string) =>
|
||||||
|
[{ integrationAuthId }, "integrationAuthTeams"] as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
||||||
@@ -44,12 +46,22 @@ const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
|||||||
|
|
||||||
const fetchIntegrationAuthApps = async ({
|
const fetchIntegrationAuthApps = async ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
teamId
|
teamId,
|
||||||
|
workspaceSlug
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
|
workspaceSlug?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const searchParams = new URLSearchParams(teamId ? { teamId } : undefined);
|
const params: Record<string, string> = {}
|
||||||
|
if (teamId) {
|
||||||
|
params.teamId = teamId
|
||||||
|
}
|
||||||
|
if (workspaceSlug) {
|
||||||
|
params.workspaceSlug = workspaceSlug
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(params);
|
||||||
const { data } = await apiRequest.get<{ apps: App[] }>(
|
const { data } = await apiRequest.get<{ apps: App[] }>(
|
||||||
`/api/v1/integration-auth/${integrationAuthId}/apps`,
|
`/api/v1/integration-auth/${integrationAuthId}/apps`,
|
||||||
{ params: searchParams }
|
{ params: searchParams }
|
||||||
@@ -127,6 +139,13 @@ const fetchIntegrationAuthRailwayServices = async ({
|
|||||||
return services;
|
return services;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchIntegrationAuthBitBucketWorkspaces = async (integrationAuthId: string) => {
|
||||||
|
const { data: { workspaces } } = await apiRequest.get<{ workspaces: BitBucketWorkspace[] }>(
|
||||||
|
`/api/v1/integration-auth/${integrationAuthId}/bitbucket/workspaces`
|
||||||
|
);
|
||||||
|
return workspaces;
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId),
|
queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId),
|
||||||
@@ -137,17 +156,20 @@ export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
|||||||
|
|
||||||
export const useGetIntegrationAuthApps = ({
|
export const useGetIntegrationAuthApps = ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
teamId
|
teamId,
|
||||||
|
workspaceSlug,
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
|
workspaceSlug?: string;
|
||||||
}) => {
|
}) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId),
|
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId, workspaceSlug),
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetchIntegrationAuthApps({
|
fetchIntegrationAuthApps({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
teamId
|
teamId,
|
||||||
|
workspaceSlug
|
||||||
}),
|
}),
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
@@ -224,6 +246,14 @@ export const useGetIntegrationAuthRailwayServices = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetIntegrationAuthBitBucketWorkspaces = (integrationAuthId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: integrationAuthKeys.getIntegrationAuthBitBucketWorkspaces(integrationAuthId),
|
||||||
|
queryFn: () => fetchIntegrationAuthBitBucketWorkspaces(integrationAuthId),
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useDeleteIntegrationAuth = () => {
|
export const useDeleteIntegrationAuth = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -29,3 +29,9 @@ export type Service = {
|
|||||||
name: string;
|
name: string;
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BitBucketWorkspace = {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import queryString from "query-string";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardTitle,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "../../../components/v2";
|
||||||
|
import {
|
||||||
|
useGetIntegrationAuthApps,
|
||||||
|
useGetIntegrationAuthBitBucketWorkspaces,
|
||||||
|
useGetIntegrationAuthById,
|
||||||
|
} from "../../../hooks/api/integrationAuth";
|
||||||
|
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||||
|
import createIntegration from "../../api/integrations/createIntegration";
|
||||||
|
|
||||||
|
export default function BitBucketCreateIntegrationPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [targetAppId, setTargetAppId] = useState("");
|
||||||
|
const [targetEnvironmentId, setTargetEnvironmentId] = useState("");
|
||||||
|
|
||||||
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||||
|
const [secretPath, setSecretPath] = useState("/");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
|
const { data: targetEnvironments } = useGetIntegrationAuthBitBucketWorkspaces((integrationAuthId as string) ?? "");
|
||||||
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||||
|
workspaceSlug: targetEnvironmentId
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (workspace) {
|
||||||
|
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||||
|
}
|
||||||
|
}, [workspace]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (integrationAuthApps) {
|
||||||
|
if (integrationAuthApps.length > 0) {
|
||||||
|
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||||
|
} else {
|
||||||
|
setTargetAppId("none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (targetEnvironments) {
|
||||||
|
if (targetEnvironments.length > 0) {
|
||||||
|
setTargetEnvironmentId(targetEnvironments[0].slug);
|
||||||
|
} else {
|
||||||
|
setTargetEnvironmentId("none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [targetEnvironments]);
|
||||||
|
|
||||||
|
const handleButtonClick = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
if (!integrationAuth?._id) return;
|
||||||
|
|
||||||
|
const targetApp = integrationAuthApps?.find(
|
||||||
|
(integrationAuthApp) => integrationAuthApp.appId === targetAppId
|
||||||
|
);
|
||||||
|
const targetEnvironment = targetEnvironments?.find(
|
||||||
|
(environment) => environment.slug === targetEnvironmentId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!targetApp || !targetApp.appId || !targetEnvironment) return;
|
||||||
|
|
||||||
|
await createIntegration({
|
||||||
|
integrationAuthId: integrationAuth?._id,
|
||||||
|
isActive: true,
|
||||||
|
app: targetApp.name,
|
||||||
|
appId: targetApp.appId,
|
||||||
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
|
targetEnvironment: targetEnvironment.name,
|
||||||
|
targetEnvironmentId: targetEnvironment.slug,
|
||||||
|
targetService: null,
|
||||||
|
targetServiceId: null,
|
||||||
|
owner: null,
|
||||||
|
path: null,
|
||||||
|
region: null,
|
||||||
|
secretPath
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
|
||||||
|
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return integrationAuth &&
|
||||||
|
workspace &&
|
||||||
|
selectedSourceEnvironment &&
|
||||||
|
integrationAuthApps &&
|
||||||
|
targetEnvironments ? (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<Card className="max-w-md rounded-md p-8">
|
||||||
|
<CardTitle className="text-center">BitBucket Integration</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="mt-4">
|
||||||
|
<Select
|
||||||
|
value={selectedSourceEnvironment}
|
||||||
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
>
|
||||||
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
|
<SelectItem
|
||||||
|
value={sourceEnvironment.slug}
|
||||||
|
key={`source-environment-${sourceEnvironment.slug}`}
|
||||||
|
>
|
||||||
|
{sourceEnvironment.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl label="Secrets Path">
|
||||||
|
<Input
|
||||||
|
value={secretPath}
|
||||||
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
|
placeholder="Provide a path, default is /"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl label="BitBucket Workspace">
|
||||||
|
<Select
|
||||||
|
value={targetEnvironmentId}
|
||||||
|
onValueChange={(val) => setTargetEnvironmentId(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
isDisabled={targetEnvironments.length === 0}
|
||||||
|
>
|
||||||
|
{targetEnvironments.length > 0 ? (
|
||||||
|
targetEnvironments.map((targetEnvironment) => (
|
||||||
|
<SelectItem
|
||||||
|
value={targetEnvironment.slug as string}
|
||||||
|
key={`target-environment-${targetEnvironment.slug as string}`}
|
||||||
|
>
|
||||||
|
{targetEnvironment.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-environment-none">
|
||||||
|
No workspaces found
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl label="BitBucket Repo">
|
||||||
|
<Select
|
||||||
|
value={targetAppId}
|
||||||
|
onValueChange={(val) => setTargetAppId(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
>
|
||||||
|
{integrationAuthApps.length > 0 ? (
|
||||||
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
|
<SelectItem
|
||||||
|
value={integrationAuthApp.appId as string}
|
||||||
|
key={`target-app-${integrationAuthApp.appId as string}`}
|
||||||
|
>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No repositories found
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
color="mineshaft"
|
||||||
|
className="mt-4"
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
|
>
|
||||||
|
Create Integration
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BitBucketCreateIntegrationPage.requireAuth = true;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import queryString from "query-string";
|
||||||
|
|
||||||
|
import AuthorizeIntegration from "../../../api/integrations/authorizeIntegration";
|
||||||
|
|
||||||
|
export default function BitBucketOAuth2CallbackPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { code, state } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
// validate state
|
||||||
|
if (state !== localStorage.getItem("latestCSRFToken")) return;
|
||||||
|
localStorage.removeItem("latestCSRFToken");
|
||||||
|
|
||||||
|
const integrationAuth = await AuthorizeIntegration({
|
||||||
|
workspaceId: localStorage.getItem("projectData.id") as string,
|
||||||
|
code: code as string,
|
||||||
|
integration: "bitbucket"
|
||||||
|
});
|
||||||
|
|
||||||
|
router.push(`/integrations/bitbucket/create?integrationAuthId=${integrationAuth._id}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitBucketOAuth2CallbackPage.requireAuth = true;
|
||||||
@@ -92,6 +92,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
|||||||
case "cloudflare-pages":
|
case "cloudflare-pages":
|
||||||
link = `${window.location.origin}/integrations/cloudflare-pages/authorize`;
|
link = `${window.location.origin}/integrations/cloudflare-pages/authorize`;
|
||||||
break;
|
break;
|
||||||
|
case "bitbucket":
|
||||||
|
link = `https://bitbucket.org/site/oauth2/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/bitbucket/oauth2/callback&state=${state}`;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -106,7 +106,8 @@ export const IntegrationsSection = ({
|
|||||||
{(integration.integration === "vercel" ||
|
{(integration.integration === "vercel" ||
|
||||||
integration.integration === "netlify" ||
|
integration.integration === "netlify" ||
|
||||||
integration.integration === "railway" ||
|
integration.integration === "railway" ||
|
||||||
integration.integration === "gitlab") && (
|
integration.integration === "gitlab" ||
|
||||||
|
integration.integration === "bitbucket") && (
|
||||||
<div className="ml-4 flex flex-col">
|
<div className="ml-4 flex flex-col">
|
||||||
<FormLabel label="Target Environment" />
|
<FormLabel label="Target Environment" />
|
||||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||||
|
|||||||
Reference in New Issue
Block a user