mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 07:26:45 +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 { logger } from "@app/lib/logger";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
|
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 { AppConnection } from "../app-connection-enums";
|
||||||
import { GitHubConnectionMethod } from "./github-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>(
|
export const requestWithGitHubGateway = async <T>(
|
||||||
appConnection: { gatewayId?: string | null },
|
appConnection: { gatewayId?: string | null },
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||||
@@ -141,7 +157,7 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
|||||||
|
|
||||||
const token =
|
const token =
|
||||||
method === GitHubConnectionMethod.OAuth ? credentials.accessToken : await getGitHubAppAuthToken(appConnection);
|
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 results: T[] = [];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
@@ -355,7 +371,7 @@ export const validateGitHubConnectionCredentials = async (
|
|||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
}>(config, gatewayService, {
|
}>(config, gatewayService, {
|
||||||
url: IntegrationUrls.GITHUB_USER_INSTALLATIONS.replace("api.github.com", `api.${host}`),
|
url: `https://${await getGitHubInstanceApiUrl(config)}/user/installations`,
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
Authorization: `Bearer ${tokenResp.data.access_token}`,
|
Authorization: `Bearer ${tokenResp.data.access_token}`,
|
||||||
|
|||||||
@@ -10,26 +10,59 @@ import {
|
|||||||
|
|
||||||
import { GitHubConnectionMethod } from "./github-connection-enums";
|
import { GitHubConnectionMethod } from "./github-connection-enums";
|
||||||
|
|
||||||
export const GitHubConnectionOAuthInputCredentialsSchema = z.object({
|
export const GitHubConnectionOAuthInputCredentialsSchema = z.union([
|
||||||
code: z.string().trim().min(1, "OAuth code required"),
|
z.object({
|
||||||
host: z.string().trim().optional()
|
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({
|
export const GitHubConnectionAppInputCredentialsSchema = z.union([
|
||||||
code: z.string().trim().min(1, "GitHub App code required"),
|
z.object({
|
||||||
installationId: z.string().min(1, "GitHub App Installation ID required"),
|
code: z.string().trim().min(1, "GitHub App code required"),
|
||||||
host: z.string().trim().optional()
|
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({
|
export const GitHubConnectionOAuthOutputCredentialsSchema = z.union([
|
||||||
accessToken: z.string(),
|
z.object({
|
||||||
host: z.string().trim().optional()
|
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({
|
export const GitHubConnectionAppOutputCredentialsSchema = z.union([
|
||||||
installationId: z.string(),
|
z.object({
|
||||||
host: z.string().trim().optional()
|
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", [
|
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
z.object({
|
z.object({
|
||||||
@@ -84,11 +117,17 @@ export const GitHubConnectionSchema = z.intersection(
|
|||||||
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
|
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
|
||||||
BaseGitHubConnectionSchema.extend({
|
BaseGitHubConnectionSchema.extend({
|
||||||
method: z.literal(GitHubConnectionMethod.App),
|
method: z.literal(GitHubConnectionMethod.App),
|
||||||
credentials: GitHubConnectionAppOutputCredentialsSchema.pick({})
|
credentials: z.object({
|
||||||
|
instanceType: z.string().optional(),
|
||||||
|
host: z.string().optional()
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
BaseGitHubConnectionSchema.extend({
|
BaseGitHubConnectionSchema.extend({
|
||||||
method: z.literal(GitHubConnectionMethod.OAuth),
|
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 { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import {
|
import {
|
||||||
getGitHubAppAuthToken,
|
getGitHubAppAuthToken,
|
||||||
|
getGitHubInstanceApiUrl,
|
||||||
GitHubConnectionMethod,
|
GitHubConnectionMethod,
|
||||||
makePaginatedGitHubRequest,
|
makePaginatedGitHubRequest,
|
||||||
requestWithGitHubGateway
|
requestWithGitHubGateway
|
||||||
@@ -73,7 +74,7 @@ const getPublicKey = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, {
|
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, {
|
||||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
@@ -111,7 +112,7 @@ const deleteSecret = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
await requestWithGitHubGateway(connection, gatewayService, {
|
await requestWithGitHubGateway(connection, gatewayService, {
|
||||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
@@ -157,7 +158,7 @@ const putSecret = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
await requestWithGitHubGateway(connection, gatewayService, {
|
await requestWithGitHubGateway(connection, gatewayService, {
|
||||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
|
|||||||
method: GitHubConnectionMethod.OAuth;
|
method: GitHubConnectionMethod.OAuth;
|
||||||
credentials: {
|
credentials: {
|
||||||
code: string;
|
code: string;
|
||||||
|
instanceType?: "cloud" | "server";
|
||||||
host?: string;
|
host?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -19,6 +20,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
|
|||||||
credentials: {
|
credentials: {
|
||||||
code: string;
|
code: string;
|
||||||
installationId: string;
|
installationId: string;
|
||||||
|
instanceType?: "cloud" | "server";
|
||||||
host?: string;
|
host?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-32
@@ -48,9 +48,16 @@ const formSchema = genericAppConnectionFieldsSchema.extend({
|
|||||||
app: z.literal(AppConnection.GitHub),
|
app: z.literal(AppConnection.GitHub),
|
||||||
method: z.nativeEnum(GitHubConnectionMethod),
|
method: z.nativeEnum(GitHubConnectionMethod),
|
||||||
credentials: z
|
credentials: z
|
||||||
.object({
|
.union([
|
||||||
host: z.string().optional()
|
z.object({
|
||||||
})
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().min(1, "Required")
|
||||||
|
})
|
||||||
|
])
|
||||||
.optional()
|
.optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,7 +77,10 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
|||||||
defaultValues: appConnection ?? {
|
defaultValues: appConnection ?? {
|
||||||
app: AppConnection.GitHub,
|
app: AppConnection.GitHub,
|
||||||
method: GitHubConnectionMethod.App,
|
method: GitHubConnectionMethod.App,
|
||||||
gatewayId: null
|
gatewayId: null,
|
||||||
|
credentials: {
|
||||||
|
instanceType: "cloud"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,6 +88,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
watch,
|
watch,
|
||||||
|
setValue,
|
||||||
formState: { isSubmitting, isDirty }
|
formState: { isSubmitting, isDirty }
|
||||||
} = form;
|
} = form;
|
||||||
|
|
||||||
@@ -85,6 +96,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
|||||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||||
|
|
||||||
const selectedMethod = watch("method");
|
const selectedMethod = watch("method");
|
||||||
|
const instanceType = watch("credentials.instanceType");
|
||||||
|
|
||||||
const onSubmit = (formData: FormData) => {
|
const onSubmit = (formData: FormData) => {
|
||||||
setIsRedirecting(true);
|
setIsRedirecting(true);
|
||||||
@@ -103,7 +115,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
|||||||
switch (formData.method) {
|
switch (formData.method) {
|
||||||
case GitHubConnectionMethod.App:
|
case GitHubConnectionMethod.App:
|
||||||
window.location.assign(
|
window.location.assign(
|
||||||
`${githubHost}/apps/${appClientSlug}/installations/new?state=${state}`
|
`${githubHost}/${formData.credentials?.instanceType === "server" ? "github-apps" : "apps"}/${appClientSlug}/installations/new?state=${state}`
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case GitHubConnectionMethod.OAuth:
|
case GitHubConnectionMethod.OAuth:
|
||||||
@@ -175,13 +187,54 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{subscription.gateway && (
|
<Accordion type="single" collapsible className="w-full">
|
||||||
<Accordion type="single" collapsible className="w-full">
|
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
||||||
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
<div className="order-1 ml-3">GitHub Enterprise Options</div>
|
||||||
<div className="order-1 ml-3">GitHub Enterprise Options</div>
|
</AccordionTrigger>
|
||||||
</AccordionTrigger>
|
<AccordionContent childrenClassName="px-0">
|
||||||
<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
|
<OrgPermissionCan
|
||||||
I={OrgGatewayPermissionActions.AttachGateways}
|
I={OrgGatewayPermissionActions.AttachGateways}
|
||||||
a={OrgPermissionSubjects.Gateway}
|
a={OrgPermissionSubjects.Gateway}
|
||||||
@@ -190,7 +243,6 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="gatewayId"
|
name="gatewayId"
|
||||||
defaultValue=""
|
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
@@ -231,25 +283,10 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
<Controller
|
)}
|
||||||
name="credentials.host"
|
</AccordionContent>
|
||||||
control={control}
|
</AccordionItem>
|
||||||
shouldUnregister
|
</Accordion>
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error?.message)}
|
|
||||||
label="Hostname"
|
|
||||||
isOptional
|
|
||||||
>
|
|
||||||
<Input {...field} placeholder="github.com" />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
)}
|
|
||||||
<div className="mt-8 flex items-center">
|
<div className="mt-8 flex items-center">
|
||||||
<Button
|
<Button
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
|
|||||||
@@ -408,6 +408,7 @@ export const OAuthCallbackPage = () => {
|
|||||||
credentials: {
|
credentials: {
|
||||||
code: code as string,
|
code: code as string,
|
||||||
installationId: installationId as string,
|
installationId: installationId as string,
|
||||||
|
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||||
...(credentials?.host && { host: credentials.host })
|
...(credentials?.host && { host: credentials.host })
|
||||||
},
|
},
|
||||||
gatewayId
|
gatewayId
|
||||||
@@ -416,6 +417,7 @@ export const OAuthCallbackPage = () => {
|
|||||||
connectionId,
|
connectionId,
|
||||||
credentials: {
|
credentials: {
|
||||||
code: code as string,
|
code: code as string,
|
||||||
|
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||||
...(credentials?.host && { host: credentials.host })
|
...(credentials?.host && { host: credentials.host })
|
||||||
},
|
},
|
||||||
gatewayId
|
gatewayId
|
||||||
@@ -431,6 +433,7 @@ export const OAuthCallbackPage = () => {
|
|||||||
method: GitHubConnectionMethod.App,
|
method: GitHubConnectionMethod.App,
|
||||||
credentials: {
|
credentials: {
|
||||||
code: code as string,
|
code: code as string,
|
||||||
|
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||||
installationId: installationId as string,
|
installationId: installationId as string,
|
||||||
...(credentials?.host && { host: credentials.host })
|
...(credentials?.host && { host: credentials.host })
|
||||||
},
|
},
|
||||||
@@ -440,6 +443,7 @@ export const OAuthCallbackPage = () => {
|
|||||||
method: GitHubConnectionMethod.OAuth,
|
method: GitHubConnectionMethod.OAuth,
|
||||||
credentials: {
|
credentials: {
|
||||||
code: code as string,
|
code: code as string,
|
||||||
|
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||||
...(credentials?.host && { host: credentials.host })
|
...(credentials?.host && { host: credentials.host })
|
||||||
},
|
},
|
||||||
gatewayId
|
gatewayId
|
||||||
|
|||||||
Reference in New Issue
Block a user