mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 09:30:09 +00:00
Merge branch 'heads/main' into daniel/fips-initative
This commit is contained in:
@@ -28,6 +28,7 @@ export enum AppConnection {
|
||||
Flyio = "flyio",
|
||||
GitLab = "gitlab",
|
||||
Cloudflare = "cloudflare",
|
||||
Bitbucket = "bitbucket",
|
||||
Zabbix = "zabbix"
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ import {
|
||||
getAzureKeyVaultConnectionListItem,
|
||||
validateAzureKeyVaultConnectionCredentials
|
||||
} from "./azure-key-vault";
|
||||
import {
|
||||
BitbucketConnectionMethod,
|
||||
getBitbucketConnectionListItem,
|
||||
validateBitbucketConnectionCredentials
|
||||
} from "./bitbucket";
|
||||
import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda";
|
||||
import { CloudflareConnectionMethod } from "./cloudflare/cloudflare-connection-enum";
|
||||
import {
|
||||
@@ -138,6 +143,7 @@ export const listAppConnectionOptions = () => {
|
||||
getFlyioConnectionListItem(),
|
||||
getGitLabConnectionListItem(),
|
||||
getCloudflareConnectionListItem(),
|
||||
getBitbucketConnectionListItem(),
|
||||
getZabbixConnectionListItem()
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
@@ -219,6 +225,7 @@ export const validateAppConnectionCredentials = async (
|
||||
[AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.GitLab]: validateGitLabConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Zabbix]: validateZabbixConnectionCredentials as TAppConnectionCredentialsValidator
|
||||
};
|
||||
|
||||
@@ -256,6 +263,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
||||
case VercelConnectionMethod.ApiToken:
|
||||
case OnePassConnectionMethod.ApiToken:
|
||||
case CloudflareConnectionMethod.APIToken:
|
||||
case BitbucketConnectionMethod.ApiToken:
|
||||
case ZabbixConnectionMethod.ApiToken:
|
||||
return "API Token";
|
||||
case PostgresConnectionMethod.UsernameAndPassword:
|
||||
@@ -337,6 +345,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
||||
[AppConnection.Flyio]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.GitLab]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Cloudflare]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Zabbix]: platformManagedCredentialsNotSupported
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
||||
[AppConnection.Flyio]: "Fly.io",
|
||||
[AppConnection.GitLab]: "GitLab",
|
||||
[AppConnection.Cloudflare]: "Cloudflare",
|
||||
[AppConnection.Bitbucket]: "Bitbucket",
|
||||
[AppConnection.Zabbix]: "Zabbix"
|
||||
};
|
||||
|
||||
@@ -63,5 +64,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
||||
[AppConnection.Flyio]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.GitLab]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Cloudflare]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Zabbix]: AppConnectionPlanType.Regular
|
||||
};
|
||||
|
||||
@@ -45,6 +45,8 @@ import { azureClientSecretsConnectionService } from "./azure-client-secrets/azur
|
||||
import { ValidateAzureDevOpsConnectionCredentialsSchema } from "./azure-devops/azure-devops-schemas";
|
||||
import { azureDevOpsConnectionService } from "./azure-devops/azure-devops-service";
|
||||
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
|
||||
import { ValidateBitbucketConnectionCredentialsSchema } from "./bitbucket";
|
||||
import { bitbucketConnectionService } from "./bitbucket/bitbucket-connection-service";
|
||||
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
|
||||
import { camundaConnectionService } from "./camunda/camunda-connection-service";
|
||||
import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/cloudflare-connection-schema";
|
||||
@@ -122,6 +124,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
||||
[AppConnection.Flyio]: ValidateFlyioConnectionCredentialsSchema,
|
||||
[AppConnection.GitLab]: ValidateGitLabConnectionCredentialsSchema,
|
||||
[AppConnection.Cloudflare]: ValidateCloudflareConnectionCredentialsSchema,
|
||||
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
|
||||
[AppConnection.Zabbix]: ValidateZabbixConnectionCredentialsSchema
|
||||
};
|
||||
|
||||
@@ -533,6 +536,7 @@ export const appConnectionServiceFactory = ({
|
||||
flyio: flyioConnectionService(connectAppConnectionById),
|
||||
gitlab: gitlabConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||
cloudflare: cloudflareConnectionService(connectAppConnectionById),
|
||||
bitbucket: bitbucketConnectionService(connectAppConnectionById),
|
||||
zabbix: zabbixConnectionService(connectAppConnectionById)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -56,6 +56,12 @@ import {
|
||||
TAzureKeyVaultConnectionInput,
|
||||
TValidateAzureKeyVaultConnectionCredentialsSchema
|
||||
} from "./azure-key-vault";
|
||||
import {
|
||||
TBitbucketConnection,
|
||||
TBitbucketConnectionConfig,
|
||||
TBitbucketConnectionInput,
|
||||
TValidateBitbucketConnectionCredentialsSchema
|
||||
} from "./bitbucket";
|
||||
import {
|
||||
TCamundaConnection,
|
||||
TCamundaConnectionConfig,
|
||||
@@ -202,6 +208,7 @@ export type TAppConnection = { id: string } & (
|
||||
| TFlyioConnection
|
||||
| TGitLabConnection
|
||||
| TCloudflareConnection
|
||||
| TBitbucketConnection
|
||||
| TZabbixConnection
|
||||
);
|
||||
|
||||
@@ -239,6 +246,7 @@ export type TAppConnectionInput = { id: string } & (
|
||||
| TFlyioConnectionInput
|
||||
| TGitLabConnectionInput
|
||||
| TCloudflareConnectionInput
|
||||
| TBitbucketConnectionInput
|
||||
| TZabbixConnectionInput
|
||||
);
|
||||
|
||||
@@ -284,6 +292,7 @@ export type TAppConnectionConfig =
|
||||
| TFlyioConnectionConfig
|
||||
| TGitLabConnectionConfig
|
||||
| TCloudflareConnectionConfig
|
||||
| TBitbucketConnectionConfig
|
||||
| TZabbixConnectionConfig;
|
||||
|
||||
export type TValidateAppConnectionCredentialsSchema =
|
||||
@@ -316,6 +325,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
||||
| TValidateFlyioConnectionCredentialsSchema
|
||||
| TValidateGitLabConnectionCredentialsSchema
|
||||
| TValidateCloudflareConnectionCredentialsSchema
|
||||
| TValidateBitbucketConnectionCredentialsSchema
|
||||
| TValidateZabbixConnectionCredentialsSchema;
|
||||
|
||||
export type TListAwsConnectionKmsKeys = {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum BitbucketConnectionMethod {
|
||||
ApiToken = "api-token"
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
|
||||
import { BitbucketConnectionMethod } from "./bitbucket-connection-enums";
|
||||
import {
|
||||
TBitbucketConnection,
|
||||
TBitbucketConnectionConfig,
|
||||
TBitbucketRepo,
|
||||
TBitbucketWorkspace
|
||||
} from "./bitbucket-connection-types";
|
||||
|
||||
export const getBitbucketConnectionListItem = () => {
|
||||
return {
|
||||
name: "Bitbucket" as const,
|
||||
app: AppConnection.Bitbucket as const,
|
||||
methods: Object.values(BitbucketConnectionMethod) as [BitbucketConnectionMethod.ApiToken]
|
||||
};
|
||||
};
|
||||
|
||||
export const getBitbucketUser = async ({ email, apiToken }: { email: string; apiToken: string }) => {
|
||||
try {
|
||||
const { data } = await request.get<{ username: string }>(`${IntegrationUrls.BITBUCKET_API_URL}/2.0/user`, {
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
message: "Unable to validate connection: verify credentials"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const validateBitbucketConnectionCredentials = async (config: TBitbucketConnectionConfig) => {
|
||||
await getBitbucketUser(config.credentials);
|
||||
return config.credentials;
|
||||
};
|
||||
|
||||
interface BitbucketWorkspacesResponse {
|
||||
values: TBitbucketWorkspace[];
|
||||
next?: string;
|
||||
}
|
||||
|
||||
export const listBitbucketWorkspaces = async (appConnection: TBitbucketConnection) => {
|
||||
const { email, apiToken } = appConnection.credentials;
|
||||
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
|
||||
Accept: "application/json"
|
||||
};
|
||||
|
||||
let allWorkspaces: TBitbucketWorkspace[] = [];
|
||||
let nextUrl: string | undefined = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/workspaces?pagelen=100`;
|
||||
let iterationCount = 0;
|
||||
|
||||
// Limit to 10 iterations, fetching at most 10 * 100 = 1000 workspaces
|
||||
while (nextUrl && iterationCount < 10) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data }: { data: BitbucketWorkspacesResponse } = await request.get<BitbucketWorkspacesResponse>(nextUrl, {
|
||||
headers
|
||||
});
|
||||
|
||||
allWorkspaces = allWorkspaces.concat(data.values.map((workspace) => ({ slug: workspace.slug })));
|
||||
nextUrl = data.next;
|
||||
iterationCount += 1;
|
||||
}
|
||||
|
||||
return allWorkspaces;
|
||||
};
|
||||
|
||||
interface BitbucketRepositoriesResponse {
|
||||
values: TBitbucketRepo[];
|
||||
next?: string;
|
||||
}
|
||||
|
||||
export const listBitbucketRepositories = async (appConnection: TBitbucketConnection, workspaceSlug: string) => {
|
||||
const { email, apiToken } = appConnection.credentials;
|
||||
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
|
||||
Accept: "application/json"
|
||||
};
|
||||
|
||||
let allRepos: TBitbucketRepo[] = [];
|
||||
let nextUrl: string | undefined =
|
||||
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspaceSlug)}?pagelen=100`;
|
||||
let iterationCount = 0;
|
||||
|
||||
// Limit to 10 iterations, fetching at most 10 * 100 = 1000 repositories
|
||||
while (nextUrl && iterationCount < 10) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data }: { data: BitbucketRepositoriesResponse } = await request.get<BitbucketRepositoriesResponse>(
|
||||
nextUrl,
|
||||
{
|
||||
headers
|
||||
}
|
||||
);
|
||||
|
||||
allRepos = allRepos.concat(data.values);
|
||||
nextUrl = data.next;
|
||||
iterationCount += 1;
|
||||
}
|
||||
|
||||
return allRepos;
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import z from "zod";
|
||||
|
||||
import { AppConnections } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
BaseAppConnectionSchema,
|
||||
GenericCreateAppConnectionFieldsSchema,
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { BitbucketConnectionMethod } from "./bitbucket-connection-enums";
|
||||
|
||||
export const BitbucketConnectionAccessTokenCredentialsSchema = z.object({
|
||||
apiToken: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "API Token required")
|
||||
.max(255)
|
||||
.describe(AppConnections.CREDENTIALS.BITBUCKET.apiToken),
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.trim()
|
||||
.min(1, "Email required")
|
||||
.max(255)
|
||||
.describe(AppConnections.CREDENTIALS.BITBUCKET.email)
|
||||
});
|
||||
|
||||
const BaseBitbucketConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Bitbucket) });
|
||||
|
||||
export const BitbucketConnectionSchema = BaseBitbucketConnectionSchema.extend({
|
||||
method: z.literal(BitbucketConnectionMethod.ApiToken),
|
||||
credentials: BitbucketConnectionAccessTokenCredentialsSchema
|
||||
});
|
||||
|
||||
export const SanitizedBitbucketConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseBitbucketConnectionSchema.extend({
|
||||
method: z.literal(BitbucketConnectionMethod.ApiToken),
|
||||
credentials: BitbucketConnectionAccessTokenCredentialsSchema.pick({
|
||||
email: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateBitbucketConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z
|
||||
.literal(BitbucketConnectionMethod.ApiToken)
|
||||
.describe(AppConnections.CREATE(AppConnection.Bitbucket).method),
|
||||
credentials: BitbucketConnectionAccessTokenCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.Bitbucket).credentials
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
export const CreateBitbucketConnectionSchema = ValidateBitbucketConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.Bitbucket)
|
||||
);
|
||||
|
||||
export const UpdateBitbucketConnectionSchema = z
|
||||
.object({
|
||||
credentials: BitbucketConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.Bitbucket).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Bitbucket));
|
||||
|
||||
export const BitbucketConnectionListItemSchema = z.object({
|
||||
name: z.literal("Bitbucket"),
|
||||
app: z.literal(AppConnection.Bitbucket),
|
||||
methods: z.nativeEnum(BitbucketConnectionMethod).array()
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { listBitbucketRepositories, listBitbucketWorkspaces } from "./bitbucket-connection-fns";
|
||||
import { TBitbucketConnection, TGetBitbucketRepositoriesDTO } from "./bitbucket-connection-types";
|
||||
|
||||
type TGetAppConnectionFunc = (
|
||||
app: AppConnection,
|
||||
connectionId: string,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<TBitbucketConnection>;
|
||||
|
||||
export const bitbucketConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||
const listWorkspaces = async (connectionId: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await getAppConnection(AppConnection.Bitbucket, connectionId, actor);
|
||||
const workspaces = await listBitbucketWorkspaces(appConnection);
|
||||
return workspaces;
|
||||
};
|
||||
|
||||
const listRepositories = async (
|
||||
{ connectionId, workspaceSlug }: TGetBitbucketRepositoriesDTO,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const appConnection = await getAppConnection(AppConnection.Bitbucket, connectionId, actor);
|
||||
const repositories = await listBitbucketRepositories(appConnection, workspaceSlug);
|
||||
return repositories;
|
||||
};
|
||||
|
||||
return {
|
||||
listWorkspaces,
|
||||
listRepositories
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import z from "zod";
|
||||
|
||||
import { DiscriminativePick } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import {
|
||||
BitbucketConnectionSchema,
|
||||
CreateBitbucketConnectionSchema,
|
||||
ValidateBitbucketConnectionCredentialsSchema
|
||||
} from "./bitbucket-connection-schemas";
|
||||
|
||||
export type TBitbucketConnection = z.infer<typeof BitbucketConnectionSchema>;
|
||||
|
||||
export type TBitbucketConnectionInput = z.infer<typeof CreateBitbucketConnectionSchema> & {
|
||||
app: AppConnection.Bitbucket;
|
||||
};
|
||||
|
||||
export type TValidateBitbucketConnectionCredentialsSchema = typeof ValidateBitbucketConnectionCredentialsSchema;
|
||||
|
||||
export type TBitbucketConnectionConfig = DiscriminativePick<
|
||||
TBitbucketConnectionInput,
|
||||
"method" | "app" | "credentials"
|
||||
> & {
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type TGetBitbucketRepositoriesDTO = {
|
||||
connectionId: string;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export type TBitbucketWorkspace = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type TBitbucketRepo = {
|
||||
uuid: string;
|
||||
full_name: string; // workspace-slug/repo-slug
|
||||
slug: string;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./bitbucket-connection-enums";
|
||||
export * from "./bitbucket-connection-fns";
|
||||
export * from "./bitbucket-connection-schemas";
|
||||
export * from "./bitbucket-connection-types";
|
||||
@@ -814,9 +814,9 @@ const getAppsCloudflareWorkers = async ({ accessToken, accountId }: { accessToke
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of repositories for the BitBucket integration based on provided BitBucket workspace
|
||||
* Return list of repositories for the Bitbucket integration based on provided Bitbucket workspace
|
||||
*/
|
||||
const getAppsBitBucket = async ({ accessToken, workspaceSlug }: { accessToken: string; workspaceSlug?: string }) => {
|
||||
const getAppsBitbucket = async ({ accessToken, workspaceSlug }: { accessToken: string; workspaceSlug?: string }) => {
|
||||
interface RepositoriesResponse {
|
||||
size: number;
|
||||
page: number;
|
||||
@@ -1302,7 +1302,7 @@ export const getApps = async ({
|
||||
});
|
||||
|
||||
case Integrations.BITBUCKET:
|
||||
return getAppsBitBucket({
|
||||
return getAppsBitbucket({
|
||||
accessToken,
|
||||
workspaceSlug
|
||||
});
|
||||
|
||||
@@ -342,7 +342,7 @@ export const getIntegrationOptions = async () => {
|
||||
{
|
||||
name: "Bitbucket",
|
||||
slug: "bitbucket",
|
||||
image: "BitBucket.png",
|
||||
image: "Bitbucket.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: appCfg.CLIENT_ID_BITBUCKET,
|
||||
|
||||
@@ -3924,9 +3924,9 @@ const syncSecretsCloudflareWorkers = async ({
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to BitBucket repo with name [integration.app]
|
||||
* Sync/push [secrets] to Bitbucket repo with name [integration.app]
|
||||
*/
|
||||
const syncSecretsBitBucket = async ({
|
||||
const syncSecretsBitbucket = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
@@ -4835,7 +4835,7 @@ export const syncIntegrationSecrets = async ({
|
||||
});
|
||||
break;
|
||||
case Integrations.BITBUCKET:
|
||||
await syncSecretsBitBucket({
|
||||
await syncSecretsBitbucket({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
|
||||
@@ -64,7 +64,7 @@ type ExchangeCodeGitlabResponse = {
|
||||
created_at: number;
|
||||
};
|
||||
|
||||
type ExchangeCodeBitBucketResponse = {
|
||||
type ExchangeCodeBitbucketResponse = {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
@@ -392,10 +392,10 @@ const exchangeCodeGitlab = async ({ code, url }: { code: string; url?: string })
|
||||
};
|
||||
|
||||
/**
|
||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for BitBucket
|
||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Bitbucket
|
||||
* code-token exchange
|
||||
*/
|
||||
const exchangeCodeBitBucket = async ({ code }: { code: string }) => {
|
||||
const exchangeCodeBitbucket = async ({ code }: { code: string }) => {
|
||||
const accessExpiresAt = new Date();
|
||||
const appCfg = getConfig();
|
||||
if (!appCfg.CLIENT_SECRET_BITBUCKET || !appCfg.CLIENT_ID_BITBUCKET) {
|
||||
@@ -403,7 +403,7 @@ const exchangeCodeBitBucket = async ({ code }: { code: string }) => {
|
||||
}
|
||||
|
||||
const res = (
|
||||
await request.post<ExchangeCodeBitBucketResponse>(
|
||||
await request.post<ExchangeCodeBitbucketResponse>(
|
||||
IntegrationUrls.BITBUCKET_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
@@ -490,7 +490,7 @@ export const exchangeCode = async ({
|
||||
url
|
||||
});
|
||||
case Integrations.BITBUCKET:
|
||||
return exchangeCodeBitBucket({
|
||||
return exchangeCodeBitbucket({
|
||||
code
|
||||
});
|
||||
default:
|
||||
@@ -524,7 +524,7 @@ type RefreshTokenGitLabResponse = {
|
||||
created_at: number;
|
||||
};
|
||||
|
||||
type RefreshTokenBitBucketResponse = {
|
||||
type RefreshTokenBitbucketResponse = {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
@@ -653,9 +653,9 @@ const exchangeRefreshGitLab = async ({ refreshToken, url }: { url?: string | nul
|
||||
|
||||
/**
|
||||
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||
* BitBucket integration
|
||||
* Bitbucket integration
|
||||
*/
|
||||
const exchangeRefreshBitBucket = async ({ refreshToken }: { refreshToken: string }) => {
|
||||
const exchangeRefreshBitbucket = async ({ refreshToken }: { refreshToken: string }) => {
|
||||
const accessExpiresAt = new Date();
|
||||
const appCfg = getConfig();
|
||||
if (!appCfg.CLIENT_SECRET_BITBUCKET || !appCfg.CLIENT_ID_BITBUCKET) {
|
||||
@@ -664,7 +664,7 @@ const exchangeRefreshBitBucket = async ({ refreshToken }: { refreshToken: string
|
||||
const {
|
||||
data
|
||||
}: {
|
||||
data: RefreshTokenBitBucketResponse;
|
||||
data: RefreshTokenBitbucketResponse;
|
||||
} = await request.post(
|
||||
IntegrationUrls.BITBUCKET_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
@@ -794,7 +794,7 @@ export const exchangeRefresh = async (
|
||||
url
|
||||
});
|
||||
case Integrations.BITBUCKET:
|
||||
return exchangeRefreshBitBucket({
|
||||
return exchangeRefreshBitbucket({
|
||||
refreshToken
|
||||
});
|
||||
case Integrations.GCP_SECRET_MANAGER:
|
||||
|
||||
@@ -122,8 +122,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
|
||||
.orWhere((qb) => {
|
||||
// lastInvitedAt is older than 1 week ago AND createdAt is younger than 1 month ago
|
||||
void qb
|
||||
.where(`${TableName.OrgMembership}.lastInvitedAt`, "<", oneMonthAgo)
|
||||
.where(`${TableName.OrgMembership}.createdAt`, ">", oneWeekAgo);
|
||||
.where(`${TableName.OrgMembership}.lastInvitedAt`, "<", oneWeekAgo)
|
||||
.where(`${TableName.OrgMembership}.createdAt`, ">", oneMonthAgo);
|
||||
});
|
||||
|
||||
return memberships;
|
||||
@@ -135,9 +135,22 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateLastInvitedAtByIds = async (membershipIds: string[]) => {
|
||||
try {
|
||||
if (membershipIds.length === 0) return;
|
||||
await db(TableName.OrgMembership).whereIn("id", membershipIds).update({ lastInvitedAt: new Date() });
|
||||
} catch (error) {
|
||||
throw new DatabaseError({
|
||||
error,
|
||||
name: "Update last invited at by ids"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...orgMembershipOrm,
|
||||
findOrgMembershipById,
|
||||
findRecentInvitedMemberships
|
||||
findRecentInvitedMemberships,
|
||||
updateLastInvitedAtByIds
|
||||
};
|
||||
};
|
||||
|
||||
@@ -107,7 +107,12 @@ type TOrgServiceFactoryDep = {
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey" | "create">;
|
||||
orgMembershipDAL: Pick<
|
||||
TOrgMembershipDALFactory,
|
||||
"findOrgMembershipById" | "findOne" | "findById" | "findRecentInvitedMemberships" | "updateById"
|
||||
| "findOrgMembershipById"
|
||||
| "findOne"
|
||||
| "findById"
|
||||
| "findRecentInvitedMemberships"
|
||||
| "updateById"
|
||||
| "updateLastInvitedAtByIds"
|
||||
>;
|
||||
incidentContactDAL: TIncidentContactsDALFactory;
|
||||
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne">;
|
||||
@@ -762,6 +767,10 @@ export const orgServiceFactory = ({
|
||||
}
|
||||
});
|
||||
|
||||
await orgMembershipDAL.updateById(inviteeOrgMembership.id, {
|
||||
lastInvitedAt: new Date()
|
||||
});
|
||||
|
||||
return { signupToken: undefined };
|
||||
};
|
||||
|
||||
@@ -1435,6 +1444,7 @@ export const orgServiceFactory = ({
|
||||
const appCfg = getConfig();
|
||||
|
||||
const orgCache: Record<string, { name: string; id: string } | undefined> = {};
|
||||
const notifiedUsers: string[] = [];
|
||||
|
||||
await Promise.all(
|
||||
invitedUsers.map(async (invitedUser) => {
|
||||
@@ -1465,13 +1475,12 @@ export const orgServiceFactory = ({
|
||||
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
||||
}
|
||||
});
|
||||
notifiedUsers.push(invitedUser.id);
|
||||
}
|
||||
|
||||
await orgMembershipDAL.updateById(invitedUser.id, {
|
||||
lastInvitedAt: new Date()
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
await orgMembershipDAL.updateLastInvitedAtByIds(notifiedUsers);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -71,6 +71,15 @@ export const telemetryQueueServiceFactory = ({
|
||||
QueueName.TelemetryInstanceStats // just a job id
|
||||
);
|
||||
|
||||
if (postHog) {
|
||||
await queueService.queue(QueueName.TelemetryInstanceStats, QueueJobs.TelemetryInstanceStats, undefined, {
|
||||
jobId: QueueName.TelemetryInstanceStats,
|
||||
repeat: { pattern: "0 0 * * *", utc: true }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const startAggregatedEventsJob = async () => {
|
||||
// clear previous aggregated events job
|
||||
await queueService.stopRepeatableJob(
|
||||
QueueName.TelemetryAggregatedEvents,
|
||||
@@ -80,11 +89,6 @@ export const telemetryQueueServiceFactory = ({
|
||||
);
|
||||
|
||||
if (postHog) {
|
||||
await queueService.queue(QueueName.TelemetryInstanceStats, QueueJobs.TelemetryInstanceStats, undefined, {
|
||||
jobId: QueueName.TelemetryInstanceStats,
|
||||
repeat: { pattern: "0 0 * * *", utc: true }
|
||||
});
|
||||
|
||||
// Start aggregated events job (runs every five minutes)
|
||||
await queueService.queue(QueueName.TelemetryAggregatedEvents, QueueJobs.TelemetryAggregatedEvents, undefined, {
|
||||
jobId: QueueName.TelemetryAggregatedEvents,
|
||||
@@ -102,6 +106,7 @@ export const telemetryQueueServiceFactory = ({
|
||||
});
|
||||
|
||||
return {
|
||||
startTelemetryCheck
|
||||
startTelemetryCheck,
|
||||
startAggregatedEventsJob
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ export const TELEMETRY_SECRET_PROCESSED_KEY = "telemetry-secret-processed";
|
||||
export const TELEMETRY_SECRET_OPERATIONS_KEY = "telemetry-secret-operations";
|
||||
|
||||
export const POSTHOG_AGGREGATED_EVENTS = [PostHogEventTypes.SecretPulled];
|
||||
const TELEMETRY_AGGREGATED_KEY_EXP = 900; // 15mins
|
||||
const TELEMETRY_AGGREGATED_KEY_EXP = 600; // 10mins
|
||||
|
||||
// Bucket configuration
|
||||
const TELEMETRY_BUCKET_COUNT = 30;
|
||||
@@ -102,13 +102,6 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme
|
||||
const instanceType = licenseService.getInstanceType();
|
||||
// capture posthog only when its cloud or signup event happens in self-hosted
|
||||
if (instanceType === InstanceType.Cloud || event.event === PostHogEventTypes.UserSignedUp) {
|
||||
if (event.organizationId) {
|
||||
try {
|
||||
postHog.groupIdentify({ groupType: "organization", groupKey: event.organizationId });
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to identify PostHog organization");
|
||||
}
|
||||
}
|
||||
if (POSTHOG_AGGREGATED_EVENTS.includes(event.event)) {
|
||||
const eventKey = createTelemetryEventKey(event.event, event.distinctId);
|
||||
await keyStore.setItemWithExpiry(
|
||||
@@ -122,6 +115,13 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme
|
||||
})
|
||||
);
|
||||
} else {
|
||||
if (event.organizationId) {
|
||||
try {
|
||||
postHog.groupIdentify({ groupType: "organization", groupKey: event.organizationId });
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to identify PostHog organization");
|
||||
}
|
||||
}
|
||||
postHog.capture({
|
||||
event: event.event,
|
||||
distinctId: event.distinctId,
|
||||
|
||||
Reference in New Issue
Block a user