feat(app-connections): GitHub Enterprise Server support

This commit is contained in:
x032205
2025-07-31 20:20:24 -04:00
parent 52f773c647
commit e88fdc957e
6 changed files with 156 additions and 57 deletions

View File

@@ -12,7 +12,6 @@ import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
import { logger } from "@app/lib/logger";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { AppConnection } from "../app-connection-enums";
import { GitHubConnectionMethod } from "./github-connection-enums";
@@ -30,6 +29,23 @@ export const getGitHubConnectionListItem = () => {
};
};
export const getGitHubInstanceApiUrl = async (config: {
credentials: Pick<TGitHubConnectionConfig["credentials"], "host" | "instanceType">;
}) => {
const host = config.credentials.host || "github.com";
await blockLocalAndPrivateIpAddresses(host);
let apiBase: string;
if (config.credentials.instanceType === "server") {
apiBase = `${host}/api/v3`;
} else {
apiBase = `api.${host}`;
}
return apiBase;
};
export const requestWithGitHubGateway = async <T>(
appConnection: { gatewayId?: string | null },
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
@@ -141,7 +157,7 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
const token =
method === GitHubConnectionMethod.OAuth ? credentials.accessToken : await getGitHubAppAuthToken(appConnection);
let url: string | null = `https://api.${credentials.host || "github.com"}${path}`;
let url: string | null = `https://${await getGitHubInstanceApiUrl(appConnection)}${path}`;
let results: T[] = [];
let i = 0;
@@ -355,7 +371,7 @@ export const validateGitHubConnectionCredentials = async (
};
}[];
}>(config, gatewayService, {
url: IntegrationUrls.GITHUB_USER_INSTALLATIONS.replace("api.github.com", `api.${host}`),
url: `https://${await getGitHubInstanceApiUrl(config)}/user/installations`,
headers: {
Accept: "application/json",
Authorization: `Bearer ${tokenResp.data.access_token}`,

View File

@@ -10,26 +10,59 @@ import {
import { GitHubConnectionMethod } from "./github-connection-enums";
export const GitHubConnectionOAuthInputCredentialsSchema = z.object({
code: z.string().trim().min(1, "OAuth code required"),
host: z.string().trim().optional()
});
export const GitHubConnectionOAuthInputCredentialsSchema = z.union([
z.object({
code: z.string().trim().min(1, "OAuth code required"),
instanceType: z.literal("server"),
host: z.string().trim().min(1, "Host is required for server instance type")
}),
z.object({
code: z.string().trim().min(1, "OAuth code required"),
instanceType: z.literal("cloud").optional(),
host: z.string().trim().optional()
})
]);
export const GitHubConnectionAppInputCredentialsSchema = z.object({
code: z.string().trim().min(1, "GitHub App code required"),
installationId: z.string().min(1, "GitHub App Installation ID required"),
host: z.string().trim().optional()
});
export const GitHubConnectionAppInputCredentialsSchema = z.union([
z.object({
code: z.string().trim().min(1, "GitHub App code required"),
installationId: z.string().min(1, "GitHub App Installation ID required"),
instanceType: z.literal("server"),
host: z.string().trim().min(1, "Host is required for server instance type")
}),
z.object({
code: z.string().trim().min(1, "GitHub App code required"),
installationId: z.string().min(1, "GitHub App Installation ID required"),
instanceType: z.literal("cloud").optional(),
host: z.string().trim().optional()
})
]);
export const GitHubConnectionOAuthOutputCredentialsSchema = z.object({
accessToken: z.string(),
host: z.string().trim().optional()
});
export const GitHubConnectionOAuthOutputCredentialsSchema = z.union([
z.object({
accessToken: z.string(),
instanceType: z.literal("server"),
host: z.string().trim().min(1)
}),
z.object({
accessToken: z.string(),
instanceType: z.literal("cloud").optional(),
host: z.string().trim().optional()
})
]);
export const GitHubConnectionAppOutputCredentialsSchema = z.object({
installationId: z.string(),
host: z.string().trim().optional()
});
export const GitHubConnectionAppOutputCredentialsSchema = z.union([
z.object({
installationId: z.string(),
instanceType: z.literal("server"),
host: z.string().trim().min(1)
}),
z.object({
installationId: z.string(),
instanceType: z.literal("cloud").optional(),
host: z.string().trim().optional()
})
]);
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
@@ -84,11 +117,17 @@ export const GitHubConnectionSchema = z.intersection(
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
BaseGitHubConnectionSchema.extend({
method: z.literal(GitHubConnectionMethod.App),
credentials: GitHubConnectionAppOutputCredentialsSchema.pick({})
credentials: z.object({
instanceType: z.string().optional(),
host: z.string().optional()
})
}),
BaseGitHubConnectionSchema.extend({
method: z.literal(GitHubConnectionMethod.OAuth),
credentials: GitHubConnectionOAuthOutputCredentialsSchema.pick({})
credentials: z.object({
instanceType: z.string().optional(),
host: z.string().optional()
})
})
]);

View File

@@ -3,6 +3,7 @@ import sodium from "libsodium-wrappers";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import {
getGitHubAppAuthToken,
getGitHubInstanceApiUrl,
GitHubConnectionMethod,
makePaginatedGitHubRequest,
requestWithGitHubGateway
@@ -73,7 +74,7 @@ const getPublicKey = async (
}
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, {
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
method: "GET",
headers: {
Accept: "application/vnd.github+json",
@@ -111,7 +112,7 @@ const deleteSecret = async (
}
await requestWithGitHubGateway(connection, gatewayService, {
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
method: "DELETE",
headers: {
Accept: "application/vnd.github+json",
@@ -157,7 +158,7 @@ const putSecret = async (
}
await requestWithGitHubGateway(connection, gatewayService, {
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
method: "PUT",
headers: {
Accept: "application/vnd.github+json",