mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-07 22:53:55 -05:00
feat(integration): add integration with BitBucket
This commit is contained in:
@@ -47,11 +47,13 @@ CLIENT_ID_VERCEL=
|
||||
CLIENT_ID_NETLIFY=
|
||||
CLIENT_ID_GITHUB=
|
||||
CLIENT_ID_GITLAB=
|
||||
CLIENT_ID_BITBUCKET=
|
||||
CLIENT_SECRET_HEROKU=
|
||||
CLIENT_SECRET_VERCEL=
|
||||
CLIENT_SECRET_NETLIFY=
|
||||
CLIENT_SECRET_GITHUB=
|
||||
CLIENT_SECRET_GITLAB=
|
||||
CLIENT_SECRET_BITBUCKET=
|
||||
CLIENT_SLUG_VERCEL=
|
||||
|
||||
# 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 getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).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 getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).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 getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).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 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";
|
||||
|
||||
@@ -7,6 +7,7 @@ import { IntegrationService } from "../../services";
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_BITBUCKET_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
@@ -141,12 +142,14 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
||||
*/
|
||||
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||
const teamId = req.query.teamId as string;
|
||||
const workspaceSlug = req.query.workspaceSlug as string;
|
||||
|
||||
const apps = await getApps({
|
||||
integrationAuth: req.integrationAuth,
|
||||
accessToken: req.accessToken,
|
||||
accessId: req.accessId,
|
||||
...(teamId && { teamId })
|
||||
...(teamId && { teamId }),
|
||||
...(workspaceSlug && { workspaceSlug })
|
||||
});
|
||||
|
||||
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]
|
||||
* @param req
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_API_URL,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_CHECKLY_API_URL,
|
||||
INTEGRATION_CIRCLECI,
|
||||
@@ -54,11 +56,13 @@ const getApps = async ({
|
||||
accessToken,
|
||||
accessId,
|
||||
teamId,
|
||||
workspaceSlug,
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
accessId?: string;
|
||||
teamId?: string;
|
||||
workspaceSlug?: string;
|
||||
}) => {
|
||||
let apps: App[] = [];
|
||||
switch (integrationAuth.integration) {
|
||||
@@ -145,6 +149,11 @@ const getApps = async ({
|
||||
accountId: accessId
|
||||
})
|
||||
break;
|
||||
case INTEGRATION_BITBUCKET:
|
||||
apps = await getAppsBitBucket({
|
||||
accessToken,
|
||||
workspaceSlug
|
||||
})
|
||||
}
|
||||
|
||||
return apps;
|
||||
@@ -721,4 +730,78 @@ const getAppsCloudflarePages = async ({
|
||||
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 };
|
||||
|
||||
@@ -2,6 +2,8 @@ import { standardRequest } from "../config/request";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITHUB_TOKEN_URL,
|
||||
INTEGRATION_GITLAB,
|
||||
@@ -15,11 +17,13 @@ import {
|
||||
} from "../variables";
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGitHub,
|
||||
getClientIdGitLab,
|
||||
getClientIdNetlify,
|
||||
getClientIdVercel,
|
||||
getClientSecretAzure,
|
||||
getClientSecretBitBucket,
|
||||
getClientSecretGitHub,
|
||||
getClientSecretGitLab,
|
||||
getClientSecretHeroku,
|
||||
@@ -78,6 +82,15 @@ interface ExchangeCodeGitlabResponse {
|
||||
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
|
||||
* code-token exchange for integration named [integration]
|
||||
@@ -129,6 +142,11 @@ const exchangeCode = async ({
|
||||
obj = await exchangeCodeGitlab({
|
||||
code,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_BITBUCKET:
|
||||
obj = await exchangeCodeBitBucket({
|
||||
code,
|
||||
})
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
@@ -2,6 +2,8 @@ import { standardRequest } from "../config/request";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HEROKU,
|
||||
} from "../variables";
|
||||
@@ -13,8 +15,10 @@ import {
|
||||
import { IntegrationService } from "../services";
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGitLab,
|
||||
getClientSecretAzure,
|
||||
getClientSecretBitBucket,
|
||||
getClientSecretGitLab,
|
||||
getClientSecretHeroku,
|
||||
getSiteURL,
|
||||
@@ -46,6 +50,15 @@ interface RefreshTokenGitLabResponse {
|
||||
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
|
||||
* named [integration]
|
||||
@@ -83,6 +96,11 @@ const exchangeRefresh = async ({
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_BITBUCKET:
|
||||
tokenDetails = await exchangeRefreshBitBucket({
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
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 };
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_API_URL,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_CHECKLY_API_URL,
|
||||
INTEGRATION_CIRCLECI,
|
||||
@@ -202,7 +204,14 @@ const syncSecrets = async ({
|
||||
accessToken
|
||||
});
|
||||
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 };
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
@@ -54,7 +55,8 @@ export interface IIntegration {
|
||||
| "supabase"
|
||||
| "checkly"
|
||||
| "hashicorp-vault"
|
||||
| "cloudflare-pages";
|
||||
| "cloudflare-pages"
|
||||
| "bitbucket";
|
||||
integrationAuth: Types.ObjectId;
|
||||
}
|
||||
|
||||
@@ -144,6 +146,7 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_BITBUCKET,
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_FLYIO,
|
||||
@@ -25,7 +26,25 @@ import {
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: 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;
|
||||
accountId: string;
|
||||
url: string;
|
||||
@@ -71,6 +90,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_BITBUCKET
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
|
||||
@@ -81,6 +81,7 @@ router.get(
|
||||
}),
|
||||
param("integrationAuthId"),
|
||||
query("teamId"),
|
||||
query("workspaceSlug"),
|
||||
validateRequest,
|
||||
integrationAuthController.getIntegrationAuthApps
|
||||
);
|
||||
@@ -141,6 +142,19 @@ router.get(
|
||||
integrationAuthController.getIntegrationAuthRailwayServices
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/bitbucket/workspaces",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireIntegrationAuthorizationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
}),
|
||||
param("integrationAuthId"),
|
||||
validateRequest,
|
||||
integrationAuthController.getIntegrationAuthBitBucketWorkspaces
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:integrationAuthId",
|
||||
requireAuth({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGitHub,
|
||||
getClientIdGitLab,
|
||||
getClientIdHeroku,
|
||||
@@ -26,6 +27,7 @@ export const INTEGRATION_SUPABASE = "supabase";
|
||||
export const INTEGRATION_CHECKLY = "checkly";
|
||||
export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault";
|
||||
export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
|
||||
export const INTEGRATION_BITBUCKET = "bitbucket";
|
||||
export const INTEGRATION_SET = new Set([
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
@@ -41,7 +43,8 @@ export const INTEGRATION_SET = new Set([
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_BITBUCKET
|
||||
]);
|
||||
|
||||
// integration types
|
||||
@@ -56,6 +59,7 @@ export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/toke
|
||||
export const INTEGRATION_GITHUB_TOKEN_URL =
|
||||
"https://github.com/login/oauth/access_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
|
||||
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_CHECKLY_API_URL = "https://api.checklyhq.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 () => {
|
||||
const INTEGRATION_OPTIONS = [
|
||||
@@ -245,6 +250,15 @@ export const getIntegrationOptions = async () => {
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "BitBucket",
|
||||
slug: "bitbucket",
|
||||
image: "BitBucket.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdBitBucket(),
|
||||
docsLink: ""
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
BIN
docs/images/integrations-bitbucket-auth.png
Normal file
BIN
docs/images/integrations-bitbucket-auth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/images/integrations-bitbucket.png
Normal file
BIN
docs/images/integrations-bitbucket.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
29
docs/integrations/cicd/bitbucket.mdx
Normal file
29
docs/integrations/cicd/bitbucket.mdx
Normal file
@@ -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 Secret Manager](/integrations/cloud/aws-secret-manager) | 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 |
|
||||
| [GitLab](/integrations/cicd/gitlab) | CI/CD | Available |
|
||||
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Available |
|
||||
|
||||
@@ -220,7 +220,8 @@
|
||||
"integrations/cicd/githubactions",
|
||||
"integrations/cicd/gitlab",
|
||||
"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>
|
||||
OAuth2 slug for Vercel integration
|
||||
</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 title="Auth Integrations">
|
||||
To integrate with external auth providers, provide value for the related keys
|
||||
|
||||
@@ -3,25 +3,26 @@ interface Mapping {
|
||||
}
|
||||
|
||||
const integrationSlugNameMapping: Mapping = {
|
||||
'azure-key-vault': 'Azure Key Vault',
|
||||
'aws-parameter-store': 'AWS Parameter Store',
|
||||
'aws-secret-manager': 'AWS Secret Manager',
|
||||
'heroku': 'Heroku',
|
||||
'vercel': 'Vercel',
|
||||
'netlify': 'Netlify',
|
||||
'github': 'GitHub',
|
||||
'gitlab': 'GitLab',
|
||||
'render': 'Render',
|
||||
'laravel-forge': "Laravel Forge",
|
||||
'railway': 'Railway',
|
||||
'flyio': 'Fly.io',
|
||||
'circleci': 'CircleCI',
|
||||
'travisci': 'TravisCI',
|
||||
'supabase': 'Supabase',
|
||||
'checkly': 'Checkly',
|
||||
'hashicorp-vault': 'Vault',
|
||||
'cloudflare-pages': 'Cloudflare Pages'
|
||||
}
|
||||
"azure-key-vault": "Azure Key Vault",
|
||||
"aws-parameter-store": "AWS Parameter Store",
|
||||
"aws-secret-manager": "AWS Secret Manager",
|
||||
heroku: "Heroku",
|
||||
vercel: "Vercel",
|
||||
netlify: "Netlify",
|
||||
github: "GitHub",
|
||||
gitlab: "GitLab",
|
||||
render: "Render",
|
||||
"laravel-forge": "Laravel Forge",
|
||||
railway: "Railway",
|
||||
flyio: "Fly.io",
|
||||
circleci: "CircleCI",
|
||||
travisci: "TravisCI",
|
||||
supabase: "Supabase",
|
||||
checkly: "Checkly",
|
||||
"hashicorp-vault": "Vault",
|
||||
"cloudflare-pages": "Cloudflare Pages",
|
||||
bitbucket: "BitBucket"
|
||||
};
|
||||
|
||||
const envMapping: Mapping = {
|
||||
Development: "dev",
|
||||
|
||||
BIN
frontend/public/images/integrations/BitBucket.png
Normal file
BIN
frontend/public/images/integrations/BitBucket.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
@@ -1,9 +1,10 @@
|
||||
export {
|
||||
useDeleteIntegrationAuth,
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthBitBucketWorkspaces,
|
||||
useGetIntegrationAuthById,
|
||||
useGetIntegrationAuthRailwayEnvironments,
|
||||
useGetIntegrationAuthRailwayServices,
|
||||
useGetIntegrationAuthTeams,
|
||||
useGetIntegrationAuthVercelBranches
|
||||
useGetIntegrationAuthVercelBranches,
|
||||
} from "./queries";
|
||||
|
||||
@@ -3,13 +3,13 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
import { App, Environment, IntegrationAuth, Service, Team } from "./types";
|
||||
import { App, BitBucketWorkspace, Environment, IntegrationAuth, Service, Team } from "./types";
|
||||
|
||||
const integrationAuthKeys = {
|
||||
getIntegrationAuthById: (integrationAuthId: string) =>
|
||||
[{ integrationAuthId }, "integrationAuth"] as const,
|
||||
getIntegrationAuthApps: (integrationAuthId: string, teamId?: string) =>
|
||||
[{ integrationAuthId, teamId }, "integrationAuthApps"] as const,
|
||||
getIntegrationAuthApps: (integrationAuthId: string, teamId?: string, workspaceSlug?: string) =>
|
||||
[{ integrationAuthId, teamId, workspaceSlug }, "integrationAuthApps"] as const,
|
||||
getIntegrationAuthTeams: (integrationAuthId: string) =>
|
||||
[{ integrationAuthId }, "integrationAuthTeams"] as const,
|
||||
getIntegrationAuthVercelBranches: ({
|
||||
@@ -32,7 +32,9 @@ const integrationAuthKeys = {
|
||||
}: {
|
||||
integrationAuthId: 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) => {
|
||||
@@ -44,12 +46,22 @@ const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
||||
|
||||
const fetchIntegrationAuthApps = async ({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
teamId,
|
||||
workspaceSlug
|
||||
}: {
|
||||
integrationAuthId: 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[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/apps`,
|
||||
{ params: searchParams }
|
||||
@@ -127,6 +139,13 @@ const fetchIntegrationAuthRailwayServices = async ({
|
||||
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) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId),
|
||||
@@ -137,17 +156,20 @@ export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
||||
|
||||
export const useGetIntegrationAuthApps = ({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
teamId,
|
||||
workspaceSlug,
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
teamId?: string;
|
||||
workspaceSlug?: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId),
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId, workspaceSlug),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthApps({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
teamId,
|
||||
workspaceSlug
|
||||
}),
|
||||
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 = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
@@ -29,3 +29,9 @@ export type Service = {
|
||||
name: string;
|
||||
serviceId: string;
|
||||
};
|
||||
|
||||
export type BitBucketWorkspace = {
|
||||
uuid: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
198
frontend/src/pages/integrations/bitbucket/create.tsx
Normal file
198
frontend/src/pages/integrations/bitbucket/create.tsx
Normal file
@@ -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":
|
||||
link = `${window.location.origin}/integrations/cloudflare-pages/authorize`;
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ export const IntegrationsSection = ({
|
||||
{(integration.integration === "vercel" ||
|
||||
integration.integration === "netlify" ||
|
||||
integration.integration === "railway" ||
|
||||
integration.integration === "gitlab") && (
|
||||
integration.integration === "gitlab" ||
|
||||
integration.integration === "bitbucket") && (
|
||||
<div className="ml-4 flex flex-col">
|
||||
<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">
|
||||
|
||||
Reference in New Issue
Block a user