From 1fa1d0a15a3a6b6bdbde5db6a291e8a4d4eea2d2 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Sat, 21 Jun 2025 02:23:20 +0800 Subject: [PATCH 1/6] misc: add self-serve for github connection setup --- ...tance-github-app-connection-credentials.ts | 91 +++++++ backend/src/db/schemas/super-admin.ts | 7 +- backend/src/server/routes/index.ts | 6 + backend/src/server/routes/v1/admin-router.ts | 19 +- .../github/github-connection-fns.ts | 25 +- .../super-admin/super-admin-service.ts | 111 ++++++++- .../services/super-admin/super-admin-types.ts | 19 ++ docs/integrations/app-connections/github.mdx | 20 +- frontend/src/hooks/api/admin/types.ts | 12 + .../components/GitHubAppConnectionForm.tsx | 222 ++++++++++++++++++ .../components/IntegrationsPageForm.tsx | 5 +- .../MicrosoftTeamsIntegrationForm.tsx | 2 +- .../components/SlackIntegrationForm.tsx | 2 +- 13 files changed, 522 insertions(+), 19 deletions(-) create mode 100644 backend/src/db/migrations/20250620144939_add-instance-github-app-connection-credentials.ts create mode 100644 frontend/src/pages/admin/IntegrationsPage/components/GitHubAppConnectionForm.tsx diff --git a/backend/src/db/migrations/20250620144939_add-instance-github-app-connection-credentials.ts b/backend/src/db/migrations/20250620144939_add-instance-github-app-connection-credentials.ts new file mode 100644 index 000000000..0da41b2e1 --- /dev/null +++ b/backend/src/db/migrations/20250620144939_add-instance-github-app-connection-credentials.ts @@ -0,0 +1,91 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasEncryptedGithubAppConnectionClientIdColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionClientId" + ); + const hasEncryptedGithubAppConnectionClientSecretColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionClientSecret" + ); + + const hasEncryptedGithubAppConnectionSlugColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionSlug" + ); + + const hasEncryptedGithubAppConnectionAppIdColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionId" + ); + + const hasEncryptedGithubAppConnectionAppPrivateKeyColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionPrivateKey" + ); + + await knex.schema.alterTable(TableName.SuperAdmin, (t) => { + if (!hasEncryptedGithubAppConnectionClientIdColumn) { + t.binary("encryptedGitHubAppConnectionClientId").nullable(); + } + if (!hasEncryptedGithubAppConnectionClientSecretColumn) { + t.binary("encryptedGitHubAppConnectionClientSecret").nullable(); + } + if (!hasEncryptedGithubAppConnectionSlugColumn) { + t.binary("encryptedGitHubAppConnectionSlug").nullable(); + } + if (!hasEncryptedGithubAppConnectionAppIdColumn) { + t.binary("encryptedGitHubAppConnectionId").nullable(); + } + if (!hasEncryptedGithubAppConnectionAppPrivateKeyColumn) { + t.binary("encryptedGitHubAppConnectionPrivateKey").nullable(); + } + }); +} + +export async function down(knex: Knex): Promise { + const hasEncryptedGithubAppConnectionClientIdColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionClientId" + ); + const hasEncryptedGithubAppConnectionClientSecretColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionClientSecret" + ); + + const hasEncryptedGithubAppConnectionSlugColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionSlug" + ); + + const hasEncryptedGithubAppConnectionAppIdColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionId" + ); + + const hasEncryptedGithubAppConnectionAppPrivateKeyColumn = await knex.schema.hasColumn( + TableName.SuperAdmin, + "encryptedGitHubAppConnectionPrivateKey" + ); + + await knex.schema.alterTable(TableName.SuperAdmin, (t) => { + if (hasEncryptedGithubAppConnectionClientIdColumn) { + t.dropColumn("encryptedGitHubAppConnectionClientId"); + } + if (hasEncryptedGithubAppConnectionClientSecretColumn) { + t.dropColumn("encryptedGitHubAppConnectionClientSecret"); + } + if (hasEncryptedGithubAppConnectionSlugColumn) { + t.dropColumn("encryptedGitHubAppConnectionSlug"); + } + if (hasEncryptedGithubAppConnectionAppIdColumn) { + t.dropColumn("encryptedGitHubAppConnectionId"); + } + if (hasEncryptedGithubAppConnectionAppPrivateKeyColumn) { + t.dropColumn("encryptedGitHubAppConnectionPrivateKey"); + } + }); +} diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index ec35042ad..de4975b20 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -29,7 +29,12 @@ export const SuperAdminSchema = z.object({ adminIdentityIds: z.string().array().nullable().optional(), encryptedMicrosoftTeamsAppId: zodBuffer.nullable().optional(), encryptedMicrosoftTeamsClientSecret: zodBuffer.nullable().optional(), - encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional() + encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional(), + encryptedGitHubAppConnectionClientId: zodBuffer.nullable().optional(), + encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(), + encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(), + encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(), + encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional() }); export type TSuperAdmin = z.infer; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 262e8f373..e3a91e238 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -2020,10 +2020,16 @@ export const registerRoutes = async ( if (licenseSyncJob) { cronJobs.push(licenseSyncJob); } + const microsoftTeamsSyncJob = await microsoftTeamsService.initializeBackgroundSync(); if (microsoftTeamsSyncJob) { cronJobs.push(microsoftTeamsSyncJob); } + + const adminIntegrationsSyncJob = await superAdminService.initializeAdminIntegrationConfigSync(); + if (adminIntegrationsSyncJob) { + cronJobs.push(adminIntegrationsSyncJob); + } } server.decorate("store", { diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index 0bade9904..f01f1722c 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -37,7 +37,12 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { encryptedSlackClientSecret: true, encryptedMicrosoftTeamsAppId: true, encryptedMicrosoftTeamsClientSecret: true, - encryptedMicrosoftTeamsBotId: true + encryptedMicrosoftTeamsBotId: true, + encryptedGitHubAppConnectionClientId: true, + encryptedGitHubAppConnectionClientSecret: true, + encryptedGitHubAppConnectionSlug: true, + encryptedGitHubAppConnectionId: true, + encryptedGitHubAppConnectionPrivateKey: true }).extend({ isMigrationModeOn: z.boolean(), defaultAuthOrgSlug: z.string().nullable(), @@ -87,6 +92,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { microsoftTeamsAppId: z.string().optional(), microsoftTeamsClientSecret: z.string().optional(), microsoftTeamsBotId: z.string().optional(), + gitHubAppConnectionClientId: z.string().optional(), + gitHubAppConnectionClientSecret: z.string().optional(), + gitHubAppConnectionSlug: z.string().optional(), + gitHubAppConnectionId: z.string().optional(), + gitHubAppConnectionPrivateKey: z.string().optional(), authConsentContent: z .string() .trim() @@ -348,6 +358,13 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { appId: z.string(), clientSecret: z.string(), botId: z.string() + }), + gitHubAppConnection: z.object({ + clientId: z.string(), + clientSecret: z.string(), + appSlug: z.string(), + appId: z.string(), + privateKey: z.string() }) }) } diff --git a/backend/src/services/app-connection/github/github-connection-fns.ts b/backend/src/services/app-connection/github/github-connection-fns.ts index 6ec675c0f..fd03fae4e 100644 --- a/backend/src/services/app-connection/github/github-connection-fns.ts +++ b/backend/src/services/app-connection/github/github-connection-fns.ts @@ -7,6 +7,7 @@ import { request } from "@app/lib/config/request"; import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors"; import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; +import { getSyncedAdminIntegrationsConfig } from "@app/services/super-admin/super-admin-service"; import { AppConnection } from "../app-connection-enums"; import { GitHubConnectionMethod } from "./github-connection-enums"; @@ -14,13 +15,14 @@ import { TGitHubConnection, TGitHubConnectionConfig } from "./github-connection- export const getGitHubConnectionListItem = () => { const { INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_APP_SLUG } = getConfig(); + const { gitHubAppConnection } = getSyncedAdminIntegrationsConfig(); return { name: "GitHub" as const, app: AppConnection.GitHub as const, methods: Object.values(GitHubConnectionMethod) as [GitHubConnectionMethod.App, GitHubConnectionMethod.OAuth], oauthClientId: INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, - appClientSlug: INF_APP_CONNECTION_GITHUB_APP_SLUG + appClientSlug: gitHubAppConnection.appSlug || INF_APP_CONNECTION_GITHUB_APP_SLUG }; }; @@ -30,23 +32,24 @@ export const getGitHubClient = (appConnection: TGitHubConnection) => { const { method, credentials } = appConnection; let client: Octokit; + const { gitHubAppConnection } = getSyncedAdminIntegrationsConfig(); + + const appId = gitHubAppConnection.appId || appCfg.INF_APP_CONNECTION_GITHUB_APP_ID; + const appPrivateKey = gitHubAppConnection.privateKey || appCfg.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY; switch (method) { case GitHubConnectionMethod.App: - if (!appCfg.INF_APP_CONNECTION_GITHUB_APP_ID || !appCfg.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY) { + if (!appId || !appPrivateKey) { throw new InternalServerError({ - message: `GitHub ${getAppConnectionMethodName(method).replace( - "GitHub", - "" - )} environment variables have not been configured` + message: `GitHub ${getAppConnectionMethodName(method).replace("GitHub", "")} has 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, + appId, + privateKey: appPrivateKey, installationId: credentials.installationId } }); @@ -154,6 +157,8 @@ type TokenRespData = { export const validateGitHubConnectionCredentials = async (config: TGitHubConnectionConfig) => { const { credentials, method } = config; + const { gitHubAppConnection } = getSyncedAdminIntegrationsConfig(); + const { INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET, @@ -165,8 +170,8 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect const { clientId, clientSecret } = method === GitHubConnectionMethod.App ? { - clientId: INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID, - clientSecret: INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET + clientId: gitHubAppConnection.clientId || INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID, + clientSecret: gitHubAppConnection.clientSecret || INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET } : // oauth { diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index ff319796e..052f4335a 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -1,4 +1,5 @@ import bcrypt from "bcrypt"; +import { CronJob } from "cron"; import jwt from "jsonwebtoken"; import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; @@ -8,6 +9,7 @@ import { getConfig } from "@app/lib/config/env"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { logger } from "@app/lib/logger"; import { TIdentityDALFactory } from "@app/services/identity/identity-dal"; import { TAuthLoginFactory } from "../auth/auth-login-service"; @@ -35,6 +37,7 @@ import { TAdminBootstrapInstanceDTO, TAdminGetIdentitiesDTO, TAdminGetUsersDTO, + TAdminIntegrationConfig, TAdminSignUpDTO, TGetOrganizationsDTO } from "./super-admin-types"; @@ -70,6 +73,31 @@ export let getServerCfg: () => Promise< } >; +let adminIntegrationsConfig: TAdminIntegrationConfig = { + slack: { + clientSecret: "", + clientId: "" + }, + microsoftTeams: { + appId: "", + clientSecret: "", + botId: "" + }, + gitHubAppConnection: { + clientId: "", + clientSecret: "", + appSlug: "", + appId: "", + privateKey: "" + } +}; + +Object.freeze(adminIntegrationsConfig); + +export const getSyncedAdminIntegrationsConfig = () => { + return adminIntegrationsConfig; +}; + const ADMIN_CONFIG_KEY = "infisical-admin-cfg"; const ADMIN_CONFIG_KEY_EXP = 60; // 60s export const ADMIN_CONFIG_DB_UUID = "00000000-0000-0000-0000-000000000000"; @@ -145,6 +173,11 @@ export const superAdminServiceFactory = ({ microsoftTeamsAppId?: string; microsoftTeamsClientSecret?: string; microsoftTeamsBotId?: string; + gitHubAppConnectionClientId?: string; + gitHubAppConnectionClientSecret?: string; + gitHubAppConnectionSlug?: string; + gitHubAppConnectionId?: string; + gitHubAppConnectionPrivateKey?: string; }, userId: string ) => { @@ -236,6 +269,37 @@ export const superAdminServiceFactory = ({ updatedData.microsoftTeamsBotId = undefined; microsoftTeamsSettingsUpdated = true; } + + if (data.gitHubAppConnectionClientId !== undefined) { + const encryptedClientId = encryptWithRoot(Buffer.from(data.gitHubAppConnectionClientId)); + updatedData.encryptedGitHubAppConnectionClientId = encryptedClientId; + updatedData.gitHubAppConnectionClientId = undefined; + } + + if (data.gitHubAppConnectionClientSecret !== undefined) { + const encryptedClientSecret = encryptWithRoot(Buffer.from(data.gitHubAppConnectionClientSecret)); + updatedData.encryptedGitHubAppConnectionClientSecret = encryptedClientSecret; + updatedData.gitHubAppConnectionClientSecret = undefined; + } + + if (data.gitHubAppConnectionSlug !== undefined) { + const encryptedAppSlug = encryptWithRoot(Buffer.from(data.gitHubAppConnectionSlug)); + updatedData.encryptedGitHubAppConnectionSlug = encryptedAppSlug; + updatedData.gitHubAppConnectionSlug = undefined; + } + + if (data.gitHubAppConnectionId !== undefined) { + const encryptedAppId = encryptWithRoot(Buffer.from(data.gitHubAppConnectionId)); + updatedData.encryptedGitHubAppConnectionId = encryptedAppId; + updatedData.gitHubAppConnectionId = undefined; + } + + if (data.gitHubAppConnectionPrivateKey !== undefined) { + const encryptedAppPrivateKey = encryptWithRoot(Buffer.from(data.gitHubAppConnectionPrivateKey)); + updatedData.encryptedGitHubAppConnectionPrivateKey = encryptedAppPrivateKey; + updatedData.gitHubAppConnectionPrivateKey = undefined; + } + const updatedServerCfg = await serverCfgDAL.updateById(ADMIN_CONFIG_DB_UUID, updatedData); await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg)); @@ -617,6 +681,24 @@ export const superAdminServiceFactory = ({ ? decrypt(serverCfg.encryptedMicrosoftTeamsBotId).toString() : ""; + const gitHubAppConnectionClientId = serverCfg.encryptedGitHubAppConnectionClientId + ? decrypt(serverCfg.encryptedGitHubAppConnectionClientId).toString() + : ""; + const gitHubAppConnectionClientSecret = serverCfg.encryptedGitHubAppConnectionClientSecret + ? decrypt(serverCfg.encryptedGitHubAppConnectionClientSecret).toString() + : ""; + + const gitHubAppConnectionAppSlug = serverCfg.encryptedGitHubAppConnectionSlug + ? decrypt(serverCfg.encryptedGitHubAppConnectionSlug).toString() + : ""; + + const gitHubAppConnectionAppId = serverCfg.encryptedGitHubAppConnectionId + ? decrypt(serverCfg.encryptedGitHubAppConnectionId).toString() + : ""; + const gitHubAppConnectionAppPrivateKey = serverCfg.encryptedGitHubAppConnectionPrivateKey + ? decrypt(serverCfg.encryptedGitHubAppConnectionPrivateKey).toString() + : ""; + return { slack: { clientSecret: slackClientSecret, @@ -626,6 +708,13 @@ export const superAdminServiceFactory = ({ appId: microsoftAppId, clientSecret: microsoftClientSecret, botId: microsoftBotId + }, + gitHubAppConnection: { + clientId: gitHubAppConnectionClientId, + clientSecret: gitHubAppConnectionClientSecret, + appSlug: gitHubAppConnectionAppSlug, + appId: gitHubAppConnectionAppId, + privateKey: gitHubAppConnectionAppPrivateKey } }; }; @@ -696,6 +785,25 @@ export const superAdminServiceFactory = ({ return (await keyStore.getItem("invalidating-cache")) !== null; }; + const $syncAdminIntegrationConfig = async () => { + const config = await getAdminIntegrationsConfig(); + Object.freeze(config); + adminIntegrationsConfig = config; + }; + + const initializeAdminIntegrationConfigSync = async () => { + logger.info("Setting up background sync process for admin integrations config"); + + // initial sync upon startup + await $syncAdminIntegrationConfig(); + + // sync admin integrations config every 5 minutes + const job = new CronJob("*/5 * * * *", $syncAdminIntegrationConfig); + job.start(); + + return job; + }; + return { initServerCfg, updateServerCfg, @@ -714,6 +822,7 @@ export const superAdminServiceFactory = ({ checkIfInvalidatingCache, getOrganizations, deleteOrganization, - deleteOrganizationMembership + deleteOrganizationMembership, + initializeAdminIntegrationConfigSync }; }; diff --git a/backend/src/services/super-admin/super-admin-types.ts b/backend/src/services/super-admin/super-admin-types.ts index 22803a650..205c59f2c 100644 --- a/backend/src/services/super-admin/super-admin-types.ts +++ b/backend/src/services/super-admin/super-admin-types.ts @@ -55,3 +55,22 @@ export enum CacheType { ALL = "all", SECRETS = "secrets" } + +export type TAdminIntegrationConfig = { + slack: { + clientSecret: string; + clientId: string; + }; + microsoftTeams: { + appId: string; + clientSecret: string; + botId: string; + }; + gitHubAppConnection: { + clientId: string; + clientSecret: string; + appSlug: string; + appId: string; + privateKey: string; + }; +}; diff --git a/docs/integrations/app-connections/github.mdx b/docs/integrations/app-connections/github.mdx index 2f8caffed..8f4283ae9 100644 --- a/docs/integrations/app-connections/github.mdx +++ b/docs/integrations/app-connections/github.mdx @@ -53,7 +53,22 @@ Infisical supports two methods for connecting to GitHub. Obtain the necessary Github application credentials. This would be the application slug, client ID, app ID, client secret, and private key. ![integrations github app credentials](/images/integrations/github/app/self-hosted-github-app-credentials.png) - Back in your Infisical instance, add the five new environment variables for the credentials of your GitHub application: + Back in your Infisical instance, you can configure the GitHub App credentials in one of two ways: + + **Option 1: Server Admin Panel (Recommended)** + + Navigate to the server admin panel > **Integrations** > **GitHub App** and enter the GitHub application credentials: + ![integrations github app admin panel](/images/integrations/github/app/self-hosted-github-app-admin-panel.png) + + - **Client ID**: The Client ID of your GitHub application + - **Client Secret**: The Client Secret of your GitHub application + - **App Slug**: The Slug of your GitHub application (found in the URL) + - **App ID**: The App ID of your GitHub application + - **Private Key**: The Private Key of your GitHub application + + **Option 2: Environment Variables** + + Alternatively, you can add the new environment variables for the credentials of your GitHub application: - `INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID`: The **Client ID** of your GitHub application. - `INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET`: The **Client Secret** of your GitHub application. @@ -61,7 +76,7 @@ Infisical supports two methods for connecting to GitHub. - `INF_APP_CONNECTION_GITHUB_APP_ID`: The **App ID** of your GitHub application. - `INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY`: The **Private Key** of your GitHub application. - Once added, restart your Infisical instance and use the GitHub integration via app authentication. + Once configured, you can use the GitHub integration via app authentication. If you configured the credentials using environment variables, restart your Infisical instance for the changes to take effect. If you configured them through the server admin panel, allow approximately 5 minutes for the changes to propagate. @@ -158,4 +173,5 @@ Infisical supports two methods for connecting to GitHub. + diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index 149014d85..c5d92b9da 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -56,6 +56,11 @@ export type TUpdateServerConfigDTO = { microsoftTeamsAppId?: string; microsoftTeamsClientSecret?: string; microsoftTeamsBotId?: string; + gitHubAppConnectionClientId?: string; + gitHubAppConnectionClientSecret?: string; + gitHubAppConnectionSlug?: string; + gitHubAppConnectionId?: string; + gitHubAppConnectionPrivateKey?: string; } & Partial; export type TCreateAdminUserDTO = { @@ -100,6 +105,13 @@ export type AdminIntegrationsConfig = { clientSecret: string; botId: string; }; + gitHubAppConnection: { + clientId: string; + clientSecret: string; + appSlug: string; + appId: string; + privateKey: string; + }; }; export type TGetServerRootKmsEncryptionDetails = { diff --git a/frontend/src/pages/admin/IntegrationsPage/components/GitHubAppConnectionForm.tsx b/frontend/src/pages/admin/IntegrationsPage/components/GitHubAppConnectionForm.tsx new file mode 100644 index 000000000..30af87db3 --- /dev/null +++ b/frontend/src/pages/admin/IntegrationsPage/components/GitHubAppConnectionForm.tsx @@ -0,0 +1,222 @@ +import { useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { FaGithub } from "react-icons/fa"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Button, + FormControl, + Input, + TextArea +} from "@app/components/v2"; +import { useToggle } from "@app/hooks"; +import { useUpdateServerConfig } from "@app/hooks/api"; +import { AdminIntegrationsConfig } from "@app/hooks/api/admin/types"; + +const gitHubAppFormSchema = z.object({ + clientId: z.string(), + clientSecret: z.string(), + appSlug: z.string(), + appId: z.string(), + privateKey: z.string() +}); + +type TGitHubAppConnectionForm = z.infer; + +type Props = { + adminIntegrationsConfig?: AdminIntegrationsConfig; +}; + +export const GitHubAppConnectionForm = ({ adminIntegrationsConfig }: Props) => { + const { mutateAsync: updateAdminServerConfig } = useUpdateServerConfig(); + const [isGitHubAppClientSecretFocused, setIsGitHubAppClientSecretFocused] = useToggle(); + const { + control, + handleSubmit, + setValue, + formState: { isSubmitting, isDirty } + } = useForm({ + resolver: zodResolver(gitHubAppFormSchema) + }); + + const onSubmit = async (data: TGitHubAppConnectionForm) => { + await updateAdminServerConfig({ + gitHubAppConnectionClientId: data.clientId, + gitHubAppConnectionClientSecret: data.clientSecret, + gitHubAppConnectionSlug: data.appSlug, + gitHubAppConnectionId: data.appId, + gitHubAppConnectionPrivateKey: data.privateKey + }); + + createNotification({ + text: "Updated GitHub app connection configuration. It can take up to 5 minutes to take effect.", + type: "success" + }); + }; + + useEffect(() => { + if (adminIntegrationsConfig) { + setValue("clientId", adminIntegrationsConfig.gitHubAppConnection.clientId); + setValue("clientSecret", adminIntegrationsConfig.gitHubAppConnection.clientSecret); + setValue("appSlug", adminIntegrationsConfig.gitHubAppConnection.appSlug); + setValue("appId", adminIntegrationsConfig.gitHubAppConnection.appId); + setValue("privateKey", adminIntegrationsConfig.gitHubAppConnection.privateKey); + } + }, [adminIntegrationsConfig]); + + return ( +
+ + + +
+ +
GitHub App
+
+
+ +
+
+ Step 1: Create and configure GitHub App. Please refer to the documentation below for + more information. +
+ +
+ Step 2: Configure your instance-wide settings to enable GitHub App connections. Copy + the credentials from your GitHub App's settings page. +
+ ( + + field.onChange(e.target.value)} + /> + + )} + /> + ( + + setIsGitHubAppClientSecretFocused.on()} + onBlur={() => setIsGitHubAppClientSecretFocused.off()} + onChange={(e) => field.onChange(e.target.value)} + /> + + )} + /> + + ( + + field.onChange(e.target.value)} + /> + + )} + /> + + ( + + field.onChange(e.target.value)} + /> + + )} + /> + + ( + +