From 5f99e58674147b7eeb1dba262152233a1882afc1 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 15 Mar 2024 18:45:55 -0700 Subject: [PATCH] Update github integration org/envs support --- .../integration-auth-service.ts | 1 + .../integration-sync-secret.ts | 210 ++++++++++-------- .../src/hooks/api/integrationAuth/queries.tsx | 2 - .../src/pages/integrations/github/create.tsx | 13 +- .../IntegrationPage.utils.tsx | 2 +- 5 files changed, 125 insertions(+), 103 deletions(-) diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 8f6b766d1..35ff27a9a 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -410,6 +410,7 @@ export const integrationAuthServiceFactory = ({ } }); if (!data) return []; + return data.map(({ login: name, id: orgId }) => ({ name, orgId: String(orgId) })); }; diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index 7ac5813ed..c7e39fff3 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -1124,10 +1124,6 @@ const syncSecretsGitHub = async ({ selected_repositories_url?: string | undefined; } - interface GitHubSecretRes { - [index: string]: GitHubSecret; - } - const octokit = new Octokit({ auth: accessToken }); @@ -1152,7 +1148,7 @@ const syncSecretsGitHub = async ({ const { data } = await octokit.request( "GET /repositories/{repository_id}/environments/{environment_name}/secrets/public-key", { - repository_id: Number(integration.targetServiceId), + repository_id: Number(integration.appId), environment_name: integration.targetEnvironmentId as string } ); @@ -1173,24 +1169,24 @@ const syncSecretsGitHub = async ({ let encryptedSecrets: GitHubSecret[]; switch (integration.scope) { - case GithubScope.Org: + case GithubScope.Org: { encryptedSecrets = ( await octokit.request("GET /orgs/{org}/actions/secrets", { org: integration.owner as string }) ).data.secrets; break; - - case GithubScope.Env: + } + case GithubScope.Env: { encryptedSecrets = ( await octokit.request("GET /repositories/{repository_id}/environments/{environment_name}/secrets", { - repository_id: Number(integration.targetServiceId), + repository_id: Number(integration.appId), environment_name: integration.targetEnvironmentId as string }) ).data.secrets; break; - - default: + } + default: { encryptedSecrets = ( await octokit.request("GET /repos/{owner}/{repo}/actions/secrets", { owner: integration.owner as string, @@ -1198,111 +1194,139 @@ const syncSecretsGitHub = async ({ }) ).data.secrets; break; + } } - let encryptedSecretsMap: GitHubSecretRes = encryptedSecrets.reduce( - (obj, secret) => ({ - ...obj, - [secret.name]: secret - }), - {} - ); - - // filter out secrets based on prefix or suffix - encryptedSecretsMap = Object.keys(encryptedSecretsMap).reduce( - ( - result: { - [key: string]: GitHubSecret; - }, - key - ) => { - if ( - (appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && - (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true) - ) { - result[key] = encryptedSecretsMap[key]; + for await (const encryptedSecret of encryptedSecrets) { + if ( + !(encryptedSecret.name in secrets) && + !(appendices?.prefix !== undefined && !encryptedSecret.name.startsWith(appendices?.prefix)) && + !(appendices?.suffix !== undefined && !encryptedSecret.name.endsWith(appendices?.suffix)) + ) { + switch (integration.scope) { + case GithubScope.Org: { + await octokit.request("DELETE /orgs/{org}/actions/secrets/{secret_name}", { + org: integration.owner as string, + secret_name: encryptedSecret.name + }); + break; + } + case GithubScope.Env: { + await octokit.request( + "DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}", + { + repository_id: Number(integration.appId), + environment_name: integration.targetEnvironmentId as string, + secret_name: encryptedSecret.name + } + ); + break; + } + default: { + await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", { + owner: integration.owner as string, + repo: integration.app as string, + secret_name: encryptedSecret.name + }); + break; + } } - return result; - }, - {} - ); + } + } - await Promise.all( - Object.keys(encryptedSecretsMap).map(async (key) => { - if (key in secrets) return; + await sodium.ready.then(async () => { + for await (const key of Object.keys(secrets)) { + // convert secret & base64 key to Uint8Array. + const binkey = sodium.from_base64(repoPublicKey.key, sodium.base64_variants.ORIGINAL); + const binsec = sodium.from_string(secrets[key].value); + + // encrypt secret using libsodium + const encBytes = sodium.crypto_box_seal(binsec, binkey); + + // convert encrypted Uint8Array to base64 + const encryptedSecret = sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL); switch (integration.scope) { case GithubScope.Org: - return octokit.request("DELETE /orgs/{org}/actions/secrets/{secret_name}", { + await octokit.request("PUT /orgs/{org}/actions/secrets/{secret_name}", { org: integration.owner as string, - secret_name: key + secret_name: key, + visibility: "all", + encrypted_value: encryptedSecret, + key_id: repoPublicKey.key_id }); - + break; case GithubScope.Env: - return octokit.request( - "DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}", + await octokit.request( + "PUT /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}", { - repository_id: Number(integration.targetServiceId), + repository_id: Number(integration.appId), environment_name: integration.targetEnvironmentId as string, - secret_name: key + secret_name: key, + encrypted_value: encryptedSecret, + key_id: repoPublicKey.key_id } ); + break; default: - return octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", { + await octokit.request("PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}", { owner: integration.owner as string, repo: integration.app as string, - secret_name: key + secret_name: key, + encrypted_value: encryptedSecret, + key_id: repoPublicKey.key_id }); + break; } - }) - ); + } + }); - await Promise.all( - Object.keys(secrets).map((key) => { - // let encryptedSecret; - return sodium.ready.then(async () => { - // convert secret & base64 key to Uint8Array. - const binkey = sodium.from_base64(repoPublicKey.key, sodium.base64_variants.ORIGINAL); - const binsec = sodium.from_string(secrets[key].value); + // for await (const key of Object.keys(secrets)) { + // sodium.ready.then(async () => { + // // convert secret & base64 key to Uint8Array. + // const binkey = sodium.from_base64(repoPublicKey.key, sodium.base64_variants.ORIGINAL); + // const binsec = sodium.from_string(secrets[key].value); - // encrypt secret using libsodium - const encBytes = sodium.crypto_box_seal(binsec, binkey); + // // encrypt secret using libsodium + // const encBytes = sodium.crypto_box_seal(binsec, binkey); - // convert encrypted Uint8Array to base64 - const encryptedSecret = sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL); + // // convert encrypted Uint8Array to base64 + // const encryptedSecret = sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL); - switch (integration.scope) { - case GithubScope.Org: - return octokit.request("PUT /orgs/{org}/actions/secrets/{secret_name}", { - org: integration.owner as string, - secret_name: key, - visibility: "all", - encrypted_value: encryptedSecret, - key_id: repoPublicKey.key_id - }); - case GithubScope.Env: - return octokit.request( - "PUT /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}", - { - repository_id: Number(integration.targetServiceId), - environment_name: integration.targetEnvironmentId as string, - secret_name: key, - encrypted_value: encryptedSecret, - key_id: repoPublicKey.key_id - } - ); - default: - return octokit.request("PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}", { - owner: integration.owner as string, - repo: integration.app as string, - secret_name: key, - encrypted_value: encryptedSecret, - key_id: repoPublicKey.key_id - }); - } - }); - }) - ); + // switch (integration.scope) { + // case GithubScope.Org: + // await octokit.request("PUT /orgs/{org}/actions/secrets/{secret_name}", { + // org: integration.owner as string, + // secret_name: key, + // visibility: "all", + // encrypted_value: encryptedSecret, + // key_id: repoPublicKey.key_id + // }); + // break; + // case GithubScope.Env: + // await octokit.request( + // "PUT /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}", + // { + // repository_id: Number(integration.appId), + // environment_name: integration.targetEnvironmentId as string, + // secret_name: key, + // encrypted_value: encryptedSecret, + // key_id: repoPublicKey.key_id + // } + // ); + // break; + // default: + // await octokit.request("PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}", { + // owner: integration.owner as string, + // repo: integration.app as string, + // secret_name: key, + // encrypted_value: encryptedSecret, + // key_id: repoPublicKey.key_id + // }); + // break; + // } + // }); + // } }; /** diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index 62ef07b12..fda7e322d 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -331,8 +331,6 @@ const fetchIntegrationAuthHerokuPipelines = async ({ integrationAuthId }: { `/api/v1/integration-auth/${integrationAuthId}/heroku/pipelines` ); - console.log(99999, pipelines) - return pipelines; }; diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index 65b0295f8..f81770441 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -133,7 +133,7 @@ export default function GitHubCreateIntegrationPage() { repoName, repoOwner ); - + const [isLoading, setIsLoading] = useState(false); useEffect(() => { @@ -205,9 +205,8 @@ export default function GitHubCreateIntegrationPage() { sourceEnvironment: data.selectedSourceEnvironment, scope: data.scope, app: repoName, + appId: data.repoId, owner: repoOwner, - targetService: "Repository", - targetServiceId: data.repoId, // github repo id is needed for sync secret targetEnvironmentId: data.envId, metadata: { secretSuffix: data.secretSuffix @@ -238,7 +237,7 @@ export default function GitHubCreateIntegrationPage() { }; return integrationAuth && workspace && integrationAuthApps ? ( -
+
Set Up GitHub Integration @@ -337,9 +336,9 @@ export default function GitHubCreateIntegrationPage() { onValueChange={onChange} className="w-full border border-mineshaft-500" > - Github Repositories - Github Organization - Github Environment + Organization + Repository + Repository Environment )} diff --git a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx index bb517950a..10ef21e27 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx @@ -60,7 +60,7 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => link = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/integrations/netlify/oauth2/callback`; break; case "github": - link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/integrations/github/oauth2/callback&state=${state}`; + link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo,admin:org&redirect_uri=${window.location.origin}/integrations/github/oauth2/callback&state=${state}`; break; case "gitlab": link = `${window.location.origin}/integrations/gitlab/authorize`;