feature: secret sync base + aws parameter store & github

This commit is contained in:
Scott Wilson
2025-01-15 21:52:50 -08:00
parent b440e918ac
commit 473efa91f0
214 changed files with 10212 additions and 623 deletions
@@ -2,3 +2,50 @@ export enum AppConnection {
GitHub = "github",
AWS = "aws"
}
export enum AWSRegion {
// US
US_EAST_1 = "us-east-1", // N. Virginia
US_EAST_2 = "us-east-2", // Ohio
US_WEST_1 = "us-west-1", // N. California
US_WEST_2 = "us-west-2", // Oregon
// GovCloud
US_GOV_EAST_1 = "us-gov-east-1", // US-East
US_GOV_WEST_1 = "us-gov-west-1", // US-West
// Africa
AF_SOUTH_1 = "af-south-1", // Cape Town
// Asia Pacific
AP_EAST_1 = "ap-east-1", // Hong Kong
AP_SOUTH_1 = "ap-south-1", // Mumbai
AP_SOUTH_2 = "ap-south-2", // Hyderabad
AP_NORTHEAST_1 = "ap-northeast-1", // Tokyo
AP_NORTHEAST_2 = "ap-northeast-2", // Seoul
AP_NORTHEAST_3 = "ap-northeast-3", // Osaka
AP_SOUTHEAST_1 = "ap-southeast-1", // Singapore
AP_SOUTHEAST_2 = "ap-southeast-2", // Sydney
AP_SOUTHEAST_3 = "ap-southeast-3", // Jakarta
AP_SOUTHEAST_4 = "ap-southeast-4", // Melbourne
// Canada
CA_CENTRAL_1 = "ca-central-1", // Central
// Europe
EU_CENTRAL_1 = "eu-central-1", // Frankfurt
EU_CENTRAL_2 = "eu-central-2", // Zurich
EU_WEST_1 = "eu-west-1", // Ireland
EU_WEST_2 = "eu-west-2", // London
EU_WEST_3 = "eu-west-3", // Paris
EU_SOUTH_1 = "eu-south-1", // Milan
EU_SOUTH_2 = "eu-south-2", // Spain
EU_NORTH_1 = "eu-north-1", // Stockholm
// Middle East
ME_SOUTH_1 = "me-south-1", // Bahrain
ME_CENTRAL_1 = "me-central-1", // UAE
// South America
SA_EAST_1 = "sa-east-1" // Sao Paulo
}
@@ -1,3 +1,4 @@
import { TAppConnections } from "@app/db/schemas/app-connections";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { TAppConnectionServiceFactoryDep } from "@app/services/app-connection/app-connection-service";
import { TAppConnection, TAppConnectionConfig } from "@app/services/app-connection/app-connection-types";
@@ -64,9 +65,8 @@ export const validateAppConnectionCredentials = async (
): Promise<TAppConnection["credentials"]> => {
const { app } = appConnection;
switch (app) {
case AppConnection.AWS: {
case AppConnection.AWS:
return validateAwsConnectionCredentials(appConnection);
}
case AppConnection.GitHub:
return validateGitHubConnectionCredentials(appConnection);
default:
@@ -90,3 +90,17 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
throw new Error(`Unhandled App Connection Method: ${method}`);
}
};
export const decryptAppConnection = async (
appConnection: TAppConnections,
kmsService: TAppConnectionServiceFactoryDep["kmsService"]
) => {
return {
...appConnection,
credentials: await decryptAppConnectionCredentials({
encryptedCredentials: appConnection.encryptedCredentials,
orgId: appConnection.orgId,
kmsService
})
} as TAppConnection;
};
@@ -1,13 +1,12 @@
import { ForbiddenError } from "@casl/ability";
import { ForbiddenError, subject } from "@casl/ability";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
import { DiscriminativePick, OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
decryptAppConnectionCredentials,
decryptAppConnection,
encryptAppConnectionCredentials,
getAppConnectionMethodName,
listAppConnectionOptions,
@@ -23,6 +22,7 @@ import {
} from "@app/services/app-connection/app-connection-types";
import { ValidateAwsConnectionCredentialsSchema } from "@app/services/app-connection/aws";
import { ValidateGitHubConnectionCredentialsSchema } from "@app/services/app-connection/github";
import { githubConnectionService } from "@app/services/app-connection/github/github-connection-service";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TAppConnectionDALFactory } from "./app-connection-dal";
@@ -31,7 +31,6 @@ export type TAppConnectionServiceFactoryDep = {
appConnectionDAL: TAppConnectionDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; // TODO: remove once launched
};
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
@@ -44,19 +43,9 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
export const appConnectionServiceFactory = ({
appConnectionDAL,
permissionService,
kmsService,
licenseService
kmsService
}: TAppConnectionServiceFactoryDep) => {
// app connections are disabled for public until launch
const checkAppServicesAvailability = async (orgId: string) => {
const subscription = await licenseService.getPlan(orgId);
if (!subscription.appConnections) throw new BadRequestError({ message: "App Connections are not available yet." });
};
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
await checkAppServicesAvailability(actor.orgId);
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
@@ -65,7 +54,10 @@ export const appConnectionServiceFactory = ({
actor.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read,
OrgPermissionSubjects.AppConnections
);
const appConnections = await appConnectionDAL.find(
app
@@ -78,24 +70,11 @@ export const appConnectionServiceFactory = ({
return Promise.all(
appConnections
.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()))
.map(async ({ encryptedCredentials, ...connection }) => {
const credentials = await decryptAppConnectionCredentials({
encryptedCredentials,
kmsService,
orgId: connection.orgId
});
return {
...connection,
credentials
} as TAppConnection;
})
.map((appConnection) => decryptAppConnection(appConnection, kmsService))
);
};
const findAppConnectionById = async (app: AppConnection, connectionId: string, actor: OrgServiceActor) => {
await checkAppServicesAvailability(actor.orgId);
const appConnection = await appConnectionDAL.findById(connectionId);
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
@@ -108,24 +87,18 @@ export const appConnectionServiceFactory = ({
appConnection.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read,
OrgPermissionSubjects.AppConnections
);
if (appConnection.app !== app)
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
return {
...appConnection,
credentials: await decryptAppConnectionCredentials({
encryptedCredentials: appConnection.encryptedCredentials,
orgId: appConnection.orgId,
kmsService
})
} as TAppConnection;
return decryptAppConnection(appConnection, kmsService);
};
const findAppConnectionByName = async (app: AppConnection, connectionName: string, actor: OrgServiceActor) => {
await checkAppServicesAvailability(actor.orgId);
const appConnection = await appConnectionDAL.findOne({ name: connectionName, orgId: actor.orgId });
if (!appConnection)
@@ -139,27 +112,21 @@ export const appConnectionServiceFactory = ({
appConnection.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read,
OrgPermissionSubjects.AppConnections
);
if (appConnection.app !== app)
throw new BadRequestError({ message: `App Connection with name ${connectionName} is not for App "${app}"` });
return {
...appConnection,
credentials: await decryptAppConnectionCredentials({
encryptedCredentials: appConnection.encryptedCredentials,
orgId: appConnection.orgId,
kmsService
})
} as TAppConnection;
return decryptAppConnection(appConnection, kmsService);
};
const createAppConnection = async (
{ method, app, credentials, ...params }: TCreateAppConnectionDTO,
actor: OrgServiceActor
) => {
await checkAppServicesAvailability(actor.orgId);
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
@@ -168,7 +135,10 @@ export const appConnectionServiceFactory = ({
actor.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.AppConnections);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Create,
OrgPermissionSubjects.AppConnections
);
const appConnection = await appConnectionDAL.transaction(async (tx) => {
const isConflictingName = Boolean(
@@ -216,15 +186,13 @@ export const appConnectionServiceFactory = ({
};
});
return appConnection;
return appConnection as TAppConnection;
};
const updateAppConnection = async (
{ connectionId, credentials, ...params }: TUpdateAppConnectionDTO,
actor: OrgServiceActor
) => {
await checkAppServicesAvailability(actor.orgId);
const appConnection = await appConnectionDAL.findById(connectionId);
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
@@ -237,7 +205,10 @@ export const appConnectionServiceFactory = ({
appConnection.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.AppConnections);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Edit,
OrgPermissionSubjects.AppConnections
);
const updatedAppConnection = await appConnectionDAL.transaction(async (tx) => {
if (params.name && appConnection.name !== params.name) {
@@ -304,19 +275,10 @@ export const appConnectionServiceFactory = ({
return updatedConnection;
});
return {
...updatedAppConnection,
credentials: await decryptAppConnectionCredentials({
encryptedCredentials: updatedAppConnection.encryptedCredentials,
orgId: updatedAppConnection.orgId,
kmsService
})
} as TAppConnection;
return decryptAppConnection(updatedAppConnection, kmsService);
};
const deleteAppConnection = async (app: AppConnection, connectionId: string, actor: OrgServiceActor) => {
await checkAppServicesAvailability(actor.orgId);
const appConnection = await appConnectionDAL.findById(connectionId);
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
@@ -329,23 +291,85 @@ export const appConnectionServiceFactory = ({
appConnection.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.AppConnections);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Delete,
OrgPermissionSubjects.AppConnections
);
if (appConnection.app !== app)
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
// TODO: specify delete error message if due to existing dependencies
// TODO (scott): add option to delete all dependencies
const deletedAppConnection = await appConnectionDAL.deleteById(connectionId);
try {
const deletedAppConnection = await appConnectionDAL.deleteById(connectionId);
return {
...deletedAppConnection,
credentials: await decryptAppConnectionCredentials({
encryptedCredentials: deletedAppConnection.encryptedCredentials,
orgId: deletedAppConnection.orgId,
kmsService
})
} as TAppConnection;
return await decryptAppConnection(deletedAppConnection, kmsService);
} catch (err) {
if (err instanceof DatabaseError && (err.error as { code: string })?.code === "23503") {
throw new BadRequestError({
message:
"Cannot delete App Connection with existing connections. Remove all existing connections and try again."
});
}
throw err;
}
};
const connectAppConnectionById = async <T extends TAppConnection>(
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => {
const appConnection = await appConnectionDAL.findById(connectionId);
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
appConnection.orgId,
actor.authMethod,
actor.orgId
);
ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionAppConnectionActions.Connect,
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
);
if (appConnection.app !== app)
throw new BadRequestError({
message: `${
APP_CONNECTION_NAME_MAP[appConnection.app as AppConnection]
} Connection with ID ${connectionId} cannot be used to connect to ${APP_CONNECTION_NAME_MAP[app]}`
});
const connection = await decryptAppConnection(appConnection, kmsService);
return connection as T;
};
const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor) => {
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const appConnections = await appConnectionDAL.find({ app, orgId: actor.orgId });
const availableConnections = appConnections.filter((connection) =>
orgPermission.can(
OrgPermissionAppConnectionActions.Connect,
subject(OrgPermissionSubjects.AppConnections, { connectionId: connection.id })
)
);
return availableConnections as Omit<TAppConnection, "credentials">[];
};
return {
@@ -355,6 +379,9 @@ export const appConnectionServiceFactory = ({
findAppConnectionByName,
createAppConnection,
updateAppConnection,
deleteAppConnection
deleteAppConnection,
connectAppConnectionById,
listAvailableAppConnectionsForUser,
github: githubConnectionService(connectAppConnectionById)
};
};
@@ -4,7 +4,7 @@ import { randomUUID } from "crypto";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
import { AwsConnectionMethod } from "./aws-connection-enums";
import { TAwsConnectionConfig } from "./aws-connection-types";
@@ -20,7 +20,7 @@ export const getAwsAppConnectionListItem = () => {
};
};
export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig, region = "us-east-1") => {
export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig, region = AWSRegion.US_EAST_1) => {
const appCfg = getConfig();
let accessKeyId: string;
@@ -38,11 +38,11 @@ export const AwsConnectionSchema = z.intersection(
export const SanitizedAwsConnectionSchema = z.discriminatedUnion("method", [
BaseAwsConnectionSchema.extend({
method: z.literal(AwsConnectionMethod.AssumeRole),
credentials: AwsConnectionAssumeRoleCredentialsSchema.omit({ roleArn: true })
credentials: AwsConnectionAssumeRoleCredentialsSchema.pick({})
}),
BaseAwsConnectionSchema.extend({
method: z.literal(AwsConnectionMethod.AccessKey),
credentials: AwsConnectionAccessTokenCredentialsSchema.omit({ secretAccessKey: true })
credentials: AwsConnectionAccessTokenCredentialsSchema.pick({ accessKeyId: true })
})
]);
@@ -75,7 +75,7 @@ export const UpdateAwsConnectionSchema = z
export const AwsConnectionListItemSchema = z.object({
name: z.literal("AWS"),
app: z.literal(AppConnection.AWS),
// the below is preferable but currently breaks mintlify
// the below is preferable but currently breaks with our zod to json schema parser
// methods: z.tuple([z.literal(AwsConnectionMethod.AssumeRole), z.literal(AwsConnectionMethod.AccessKey)]),
methods: z.nativeEnum(AwsConnectionMethod).array(),
accessKeyId: z.string().optional()
@@ -1,3 +1,5 @@
import { createAppAuth } from "@octokit/auth-app";
import { Octokit } from "@octokit/rest";
import { AxiosResponse } from "axios";
import { getConfig } from "@app/lib/config/env";
@@ -8,7 +10,7 @@ import { IntegrationUrls } from "@app/services/integration-auth/integration-list
import { AppConnection } from "../app-connection-enums";
import { GitHubConnectionMethod } from "./github-connection-enums";
import { TGitHubConnectionConfig } from "./github-connection-types";
import { TGitHubConnection, TGitHubConnectionConfig } from "./github-connection-types";
export const getGitHubConnectionListItem = () => {
const { INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_APP_SLUG } = getConfig();
@@ -22,10 +24,131 @@ export const getGitHubConnectionListItem = () => {
};
};
export const getGitHubClient = (appConnection: TGitHubConnection) => {
const appCfg = getConfig();
const { method, credentials } = appConnection;
let client: Octokit;
switch (method) {
case GitHubConnectionMethod.App:
if (!appCfg.INF_APP_CONNECTION_GITHUB_APP_ID || !appCfg.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY) {
throw new InternalServerError({
message: `GitHub ${getAppConnectionMethodName(method).replace(
"GitHub",
""
)} environment variables have not been configured`
});
}
client = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: appCfg.INF_APP_CONNECTION_GITHUB_APP_ID,
privateKey: appCfg.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY,
installationId: credentials.installationId
}
});
break;
case GitHubConnectionMethod.OAuth:
client = new Octokit({
auth: credentials.accessToken
});
break;
default:
throw new InternalServerError({
message: `Unhandled GitHub connection method: ${method as GitHubConnectionMethod}`
});
}
return client;
};
type GitHubOrganization = {
login: string;
id: number;
};
type GitHubRepository = {
id: number;
name: string;
owner: GitHubOrganization;
};
export const getGitHubRepositories = async (appConnection: TGitHubConnection) => {
const client = getGitHubClient(appConnection);
let repositories: GitHubRepository[];
switch (appConnection.method) {
case GitHubConnectionMethod.App:
repositories = await client.paginate("GET /installation/repositories");
break;
case GitHubConnectionMethod.OAuth:
default:
repositories = (await client.paginate("GET /user/repos")).filter((repo) => repo.permissions?.admin);
break;
}
return repositories;
};
export const getGitHubOrganizations = async (appConnection: TGitHubConnection) => {
const client = getGitHubClient(appConnection);
let organizations: GitHubOrganization[];
switch (appConnection.method) {
case GitHubConnectionMethod.App: {
const installationRepositories = await client.paginate("GET /installation/repositories");
const organizationMap: Record<string, GitHubOrganization> = {};
installationRepositories.forEach((repo) => {
if (repo.owner.type === "Organization") {
organizationMap[repo.owner.id] = repo.owner;
}
});
organizations = Object.values(organizationMap);
break;
}
case GitHubConnectionMethod.OAuth:
default:
organizations = await client.paginate("GET /user/orgs");
break;
}
return organizations;
};
export const getGitHubEnvironments = async (appConnection: TGitHubConnection, owner: string, repo: string) => {
const client = getGitHubClient(appConnection);
try {
const environments = await client.paginate("GET /repos/{owner}/{repo}/environments", {
owner,
repo
});
return environments;
} catch (e) {
// repo doesn't have envs
if ((e as { status: number }).status === 404) {
return [];
}
throw e;
}
};
type TokenRespData = {
access_token: string;
scope: string;
token_type: string;
error?: string;
};
export const validateGitHubConnectionCredentials = async (config: TGitHubConnectionConfig) => {
@@ -53,7 +176,10 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect
if (!clientId || !clientSecret) {
throw new InternalServerError({
message: `GitHub ${getAppConnectionMethodName(method)} environment variables have not been configured`
message: `GitHub ${getAppConnectionMethodName(method).replace(
"GitHub",
""
)} environment variables have not been configured`
});
}
@@ -65,7 +191,7 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect
client_id: clientId,
client_secret: clientSecret,
code: credentials.code,
redirect_uri: `${SITE_URL}/app-connections/github/oauth/callback`
redirect_uri: `${SITE_URL}/organization/app-connections/github/oauth/callback`
},
headers: {
Accept: "application/json",
@@ -90,6 +216,8 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect
id: number;
account: {
login: string;
type: string;
id: number;
};
}[];
}>(IntegrationUrls.GITHUB_USER_INSTALLATIONS, {
@@ -111,10 +239,13 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect
}
}
if (!tokenResp.data.access_token) {
throw new InternalServerError({ message: `Missing access token: ${tokenResp.data.error}` });
}
switch (method) {
case GitHubConnectionMethod.App:
return {
// access token not needed for GitHub App
installationId: credentials.installationId
};
case GitHubConnectionMethod.OAuth:
@@ -57,7 +57,7 @@ export const UpdateGitHubConnectionSchema = z
const BaseGitHubConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.GitHub) });
export const GitHubAppConnectionSchema = z.intersection(
export const GitHubConnectionSchema = z.intersection(
BaseGitHubConnectionSchema,
z.discriminatedUnion("method", [
z.object({
@@ -74,19 +74,19 @@ export const GitHubAppConnectionSchema = z.intersection(
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
BaseGitHubConnectionSchema.extend({
method: z.literal(GitHubConnectionMethod.App),
credentials: GitHubConnectionAppOutputCredentialsSchema.omit({ installationId: true })
credentials: GitHubConnectionAppOutputCredentialsSchema.pick({})
}),
BaseGitHubConnectionSchema.extend({
method: z.literal(GitHubConnectionMethod.OAuth),
credentials: GitHubConnectionOAuthOutputCredentialsSchema.omit({ accessToken: true })
credentials: GitHubConnectionOAuthOutputCredentialsSchema.pick({})
})
]);
export const GitHubConnectionListItemSchema = z.object({
name: z.literal("GitHub"),
app: z.literal(AppConnection.GitHub),
// the below is preferable but currently breaks mintlify
// methods: z.tuple([z.literal(GitHubConnectionMethod.GitHubApp), z.literal(GitHubConnectionMethod.OAuth)]),
// the below is preferable but currently breaks with our zod to json schema parser
// methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]),
methods: z.nativeEnum(GitHubConnectionMethod).array(),
oauthClientId: z.string().optional(),
appClientSlug: z.string().optional()
@@ -0,0 +1,55 @@
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
getGitHubEnvironments,
getGitHubOrganizations,
getGitHubRepositories
} from "@app/services/app-connection/github/github-connection-fns";
import { TGitHubConnection } from "@app/services/app-connection/github/github-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TGitHubConnection>;
type TListGitHubEnvironmentsDTO = {
connectionId: string;
repo: string;
owner: string;
};
export const githubConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listRepositories = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor);
const repositories = await getGitHubRepositories(appConnection);
return repositories;
};
const listOrganizations = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor);
const organizations = await getGitHubOrganizations(appConnection);
return organizations;
};
const listEnvironments = async (
{ connectionId, repo, owner }: TListGitHubEnvironmentsDTO,
actor: OrgServiceActor
) => {
const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor);
const environments = await getGitHubEnvironments(appConnection, owner, repo);
return environments;
};
return {
listRepositories,
listOrganizations,
listEnvironments
};
};
@@ -5,11 +5,11 @@ import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateGitHubConnectionSchema,
GitHubAppConnectionSchema,
GitHubConnectionSchema,
ValidateGitHubConnectionCredentialsSchema
} from "./github-connection-schemas";
export type TGitHubConnection = z.infer<typeof GitHubAppConnectionSchema>;
export type TGitHubConnection = z.infer<typeof GitHubConnectionSchema>;
export type TGitHubConnectionInput = z.infer<typeof CreateGitHubConnectionSchema> & {
app: AppConnection.GitHub;