mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(app-connections): GitHub Enterprise Server support
This commit is contained in:
@@ -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}`,
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -11,6 +11,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
|
||||
method: GitHubConnectionMethod.OAuth;
|
||||
credentials: {
|
||||
code: string;
|
||||
instanceType?: "cloud" | "server";
|
||||
host?: string;
|
||||
};
|
||||
}
|
||||
@@ -19,6 +20,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
|
||||
credentials: {
|
||||
code: string;
|
||||
installationId: string;
|
||||
instanceType?: "cloud" | "server";
|
||||
host?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,9 +48,16 @@ const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.GitHub),
|
||||
method: z.nativeEnum(GitHubConnectionMethod),
|
||||
credentials: z
|
||||
.object({
|
||||
host: z.string().optional()
|
||||
})
|
||||
.union([
|
||||
z.object({
|
||||
instanceType: z.literal("cloud").optional(),
|
||||
host: z.string().optional()
|
||||
}),
|
||||
z.object({
|
||||
instanceType: z.literal("server"),
|
||||
host: z.string().min(1, "Required")
|
||||
})
|
||||
])
|
||||
.optional()
|
||||
});
|
||||
|
||||
@@ -70,7 +77,10 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.GitHub,
|
||||
method: GitHubConnectionMethod.App,
|
||||
gatewayId: null
|
||||
gatewayId: null,
|
||||
credentials: {
|
||||
instanceType: "cloud"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -78,6 +88,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
@@ -85,6 +96,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
const selectedMethod = watch("method");
|
||||
const instanceType = watch("credentials.instanceType");
|
||||
|
||||
const onSubmit = (formData: FormData) => {
|
||||
setIsRedirecting(true);
|
||||
@@ -103,7 +115,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
switch (formData.method) {
|
||||
case GitHubConnectionMethod.App:
|
||||
window.location.assign(
|
||||
`${githubHost}/apps/${appClientSlug}/installations/new?state=${state}`
|
||||
`${githubHost}/${formData.credentials?.instanceType === "server" ? "github-apps" : "apps"}/${appClientSlug}/installations/new?state=${state}`
|
||||
);
|
||||
break;
|
||||
case GitHubConnectionMethod.OAuth:
|
||||
@@ -175,13 +187,54 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{subscription.gateway && (
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||
<div className="order-1 ml-3">GitHub Enterprise Options</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent childrenClassName="px-0">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||
<div className="order-1 ml-3">GitHub Enterprise Options</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent childrenClassName="px-0">
|
||||
<Controller
|
||||
name="credentials.instanceType"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl label="Instance Type">
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(e) => {
|
||||
field.onChange(e);
|
||||
if (e === "cloud") {
|
||||
setValue("gatewayId", null);
|
||||
}
|
||||
}}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
placeholder="Enterprise Cloud"
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value="cloud">Enterprise Cloud</SelectItem>
|
||||
<SelectItem value="server">Enterprise Server</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="credentials.host"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Instance Hostname"
|
||||
isOptional={instanceType === "cloud"}
|
||||
isRequired={instanceType === "server"}
|
||||
>
|
||||
<Input {...field} placeholder="github.com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{subscription.gateway && instanceType === "server" && (
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
@@ -190,7 +243,6 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
@@ -231,25 +283,10 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<Controller
|
||||
name="credentials.host"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Hostname"
|
||||
isOptional
|
||||
>
|
||||
<Input {...field} placeholder="github.com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
||||
@@ -408,6 +408,7 @@ export const OAuthCallbackPage = () => {
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string,
|
||||
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
@@ -416,6 +417,7 @@ export const OAuthCallbackPage = () => {
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
@@ -431,6 +433,7 @@ export const OAuthCallbackPage = () => {
|
||||
method: GitHubConnectionMethod.App,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||
installationId: installationId as string,
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
@@ -440,6 +443,7 @@ export const OAuthCallbackPage = () => {
|
||||
method: GitHubConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
|
||||
Reference in New Issue
Block a user