From dfda43de4ddfdb8720c396576a6043b08505c6f5 Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:10:01 +0530 Subject: [PATCH 01/18] github-env: update environment variables --- .env.example | 2 ++ backend/environment.d.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.env.example b/.env.example index 6b8639a748..0f5b095df6 100644 --- a/.env.example +++ b/.env.example @@ -38,12 +38,14 @@ CLIENT_ID_HEROKU= CLIENT_ID_VERCEL= CLIENT_ID_NETLIFY= CLIENT_ID_GITHUB= +CLIENT_ID_GITHUB_ENVIRONMENT= CLIENT_ID_GITLAB= CLIENT_ID_BITBUCKET= CLIENT_SECRET_HEROKU= CLIENT_SECRET_VERCEL= CLIENT_SECRET_NETLIFY= CLIENT_SECRET_GITHUB= +CLIENT_SECRET_GITHUB_ENVIRONMENT= CLIENT_SECRET_GITLAB= CLIENT_SECRET_BITBUCKET= CLIENT_SLUG_VERCEL= diff --git a/backend/environment.d.ts b/backend/environment.d.ts index 25f56dcc69..1fd4eccee1 100644 --- a/backend/environment.d.ts +++ b/backend/environment.d.ts @@ -21,11 +21,13 @@ declare global { CLIENT_ID_VERCEL: string; CLIENT_ID_NETLIFY: string; CLIENT_ID_GITHUB: string; + CLIENT_ID_GITHUB_ENVIRONMENT: string; CLIENT_ID_GITLAB: string; CLIENT_SECRET_HEROKU: string; CLIENT_SECRET_VERCEL: string; CLIENT_SECRET_NETLIFY: string; CLIENT_SECRET_GITHUB: string; + CLIENT_SECRET_GITHUB_ENVIRONMENT: string; CLIENT_SECRET_GITLAB: string; CLIENT_SLUG_VERCEL: string; POSTHOG_HOST: string; From 84ebf2085abaa1dd9f9da3358dd0017a6cd69f6a Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:12:05 +0530 Subject: [PATCH 02/18] github-env: add oauth exchange --- backend/src/integrations/exchange.ts | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 37382e79e3..c69e6a7f56 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -7,6 +7,7 @@ import { INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GCP_TOKEN_URL, INTEGRATION_GITHUB, + INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITHUB_TOKEN_URL, INTEGRATION_GITLAB, INTEGRATION_GITLAB_TOKEN_URL, @@ -22,6 +23,7 @@ import { getClientIdBitBucket, getClientIdGCPSecretManager, getClientIdGitHub, + getClientIdGitHubEnvironment, getClientIdGitLab, getClientIdNetlify, getClientIdVercel, @@ -29,6 +31,7 @@ import { getClientSecretBitBucket, getClientSecretGCPSecretManager, getClientSecretGitHub, + getClientSecretGitHubEnvironment, getClientSecretGitLab, getClientSecretHeroku, getClientSecretNetlify, @@ -157,6 +160,11 @@ const exchangeCode = async ({ code, }); break; + case INTEGRATION_GITHUB_ENVIRONMENT: + obj = await exchangeCodeGithubEnvironment({ + code, + }); + break; case INTEGRATION_GITLAB: obj = await exchangeCodeGitlab({ code, @@ -381,6 +389,39 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => { }; }; +/** + * Return [accessToken], [accessExpiresAt], and [refreshToken] for Github Environments + * code-token exchange + * @param {Object} obj1 + * @param {Object} obj1.code - code for code-token exchange + * @returns {Object} obj2 + * @returns {String} obj2.accessToken - access token for Github Environments API + * @returns {String} obj2.refreshToken - refresh token for Github Environments API + * @returns {Date} obj2.accessExpiresAt - date of expiration for access token + */ +const exchangeCodeGithubEnvironment = async ({ code }: { code: string }) => { + const res: ExchangeCodeGithubResponse = ( + await standardRequest.get(INTEGRATION_GITHUB_TOKEN_URL, { + params: { + client_id: await getClientIdGitHubEnvironment(), + client_secret: await getClientSecretGitHubEnvironment(), + code: code, + redirect_uri: `${await getSiteURL()}/integrations/github-environment/oauth2/callback`, + }, + headers: { + Accept: "application/json", + "Accept-Encoding": "application/json", + }, + }) + ).data; + + return { + accessToken: res.access_token, + refreshToken: null, + accessExpiresAt: null, + }; +}; + /** * Return [accessToken], [accessExpiresAt], and [refreshToken] for Gitlab * code-token exchange From 9c5f5695487f2885f632dea778b24251b2a39ff3 Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:15:32 +0530 Subject: [PATCH 03/18] github-env: add constants --- backend/src/models/integration/integration.ts | 3 +++ .../src/models/integrationAuth/integrationAuth.ts | 3 +++ backend/src/variables/integration.ts | 13 +++++++++++++ frontend/public/data/frequentConstants.ts | 1 + 4 files changed, 20 insertions(+) diff --git a/backend/src/models/integration/integration.ts b/backend/src/models/integration/integration.ts index eaaadec92b..b4f16ba1f2 100644 --- a/backend/src/models/integration/integration.ts +++ b/backend/src/models/integration/integration.ts @@ -13,6 +13,7 @@ import { INTEGRATION_FLYIO, INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GITHUB, + INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASURA_CLOUD, @@ -58,6 +59,7 @@ export interface IIntegration { | "vercel" | "netlify" | "github" + | "github-environment" | "gitlab" | "render" | "railway" @@ -167,6 +169,7 @@ const integrationSchema = new Schema( INTEGRATION_VERCEL, INTEGRATION_NETLIFY, INTEGRATION_GITHUB, + INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_RENDER, INTEGRATION_RAILWAY, diff --git a/backend/src/models/integrationAuth/integrationAuth.ts b/backend/src/models/integrationAuth/integrationAuth.ts index da1e572689..10442c731b 100644 --- a/backend/src/models/integrationAuth/integrationAuth.ts +++ b/backend/src/models/integrationAuth/integrationAuth.ts @@ -15,6 +15,7 @@ import { INTEGRATION_FLYIO, INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GITHUB, + INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASURA_CLOUD, @@ -42,6 +43,7 @@ export interface IIntegrationAuth extends Document { | "vercel" | "netlify" | "github" + | "github-environment" | "gitlab" | "render" | "railway" @@ -103,6 +105,7 @@ const integrationAuthSchema = new Schema( INTEGRATION_VERCEL, INTEGRATION_NETLIFY, INTEGRATION_GITHUB, + INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_RENDER, INTEGRATION_RAILWAY, diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index 28848dbb69..f9f41f168c 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -3,6 +3,7 @@ import { getClientIdBitBucket, getClientIdGCPSecretManager, getClientIdGitHub, + getClientIdGitHubEnvironment, getClientIdGitLab, getClientIdHeroku, getClientIdNetlify, @@ -18,6 +19,7 @@ export const INTEGRATION_HEROKU = "heroku"; export const INTEGRATION_VERCEL = "vercel"; export const INTEGRATION_NETLIFY = "netlify"; export const INTEGRATION_GITHUB = "github"; +export const INTEGRATION_GITHUB_ENVIRONMENT = "github-environment"; export const INTEGRATION_GITLAB = "gitlab"; export const INTEGRATION_RENDER = "render"; export const INTEGRATION_RAILWAY = "railway"; @@ -47,6 +49,7 @@ export const INTEGRATION_SET = new Set([ INTEGRATION_VERCEL, INTEGRATION_NETLIFY, INTEGRATION_GITHUB, + INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_RENDER, INTEGRATION_FLYIO, @@ -90,6 +93,7 @@ export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com"; export const GITLAB_URL = "https://gitlab.com"; export const INTEGRATION_GITLAB_API_URL = `${GITLAB_URL}/api`; export const INTEGRATION_GITHUB_API_URL = "https://api.github.com"; +export const INTEGRATION_GITHUB_ENVIRONMENT_API_URL = "https://api.github.com"; export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com"; export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com"; export const INTEGRATION_RENDER_API_URL = "https://api.render.com"; @@ -157,6 +161,15 @@ export const getIntegrationOptions = async () => { clientId: await getClientIdGitHub(), docsLink: "" }, + { + name: "GitHub Environment", + slug: "github-environment", + image: "GitHub.png", + isAvailable: true, + type: "oauth", + clientId: await getClientIdGitHubEnvironment(), + docsLink: "" + }, { name: "Render", slug: "render", diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index 451890ef98..9f8358552a 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -10,6 +10,7 @@ const integrationSlugNameMapping: Mapping = { vercel: "Vercel", netlify: "Netlify", github: "GitHub", + "github-environment": "GitHub-Environment", gitlab: "GitLab", render: "Render", "laravel-forge": "Laravel Forge", From b497423cacab5c1610c4d9653841e5f81cfc2e4c Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:17:14 +0530 Subject: [PATCH 04/18] github-env: add API endpoint to get repositories --- backend/src/config/index.ts | 4 + .../v1/integrationAuthController.ts | 73 +++++++++++++++++++ backend/src/routes/v1/integrationAuth.ts | 8 ++ backend/src/validation/integrationAuth.ts | 6 ++ 4 files changed, 91 insertions(+) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index a535c3f542..a956982689 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -49,6 +49,8 @@ export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; +export const getClientIdGitHubEnvironment = async () => + (await client.getSecret("CLIENT_ID_GITHUB_ENVIRONMENT")).secretValue; export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; export const getClientIdBitBucket = async () => @@ -65,6 +67,8 @@ export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; +export const getClientSecretGitHubEnvironment = async () => + (await client.getSecret("CLIENT_SECRET_GITHUB_ENVIRONMENT")).secretValue; export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; export const getClientSecretBitBucket = async () => diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index d04ead63f1..cb440b4579 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -29,6 +29,7 @@ import { } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { getIntegrationAuthAccessHelper } from "../../helpers"; +import { Octokit } from "@octokit/rest"; /*** * Return integration authorization with id [integrationAuthId] @@ -1048,6 +1049,78 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R }); }; +/** + * Return list of repositories for github environment integration + * @param req + * @param res + * @returns + */ +export const getIntegrationAuthGithubEnvironmentRepositories = async (req: Request, res: Response) => { + interface GitHubApp { + id: string; + name: string; + permissions: { + admin: boolean; + }; + owner: { + login: string; + }; + } + + const { + params: { integrationAuthId } + } = await validateRequest(reqValidator.GetIntegrationAuthGitHubEnvironmentRepositoriesV1, req); + + const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ + integrationAuthId: new Types.ObjectId(integrationAuthId) + }); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.Integrations + ); + + const octokit = new Octokit({ + auth: accessToken + }); + + const getAllRepos = async () => { + let repos: GitHubApp[] = []; + let page = 1; + const per_page = 100; + let hasMore = true; + + while (hasMore) { + const response = await octokit.request( + "GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}", + { + per_page, + page + } + ); + + if (response.data.length > 0) { + repos = repos.concat(response.data); + page++; + } else { + hasMore = false; + } + } + + return repos; + }; + + const repos = await getAllRepos(); + return res.status(200).send({ + repos: repos.filter((repo: GitHubApp) => repo.permissions.admin === true) + }); +}; + /** * Return list of secret groups for Northflank project with id [appId] * @param req diff --git a/backend/src/routes/v1/integrationAuth.ts b/backend/src/routes/v1/integrationAuth.ts index 68d0433b3f..321c93ba54 100644 --- a/backend/src/routes/v1/integrationAuth.ts +++ b/backend/src/routes/v1/integrationAuth.ts @@ -140,6 +140,14 @@ router.get( integrationAuthController.getIntegrationAuthBitBucketWorkspaces ); +router.get( + "/:integrationAuthId/github-environment/repositories", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT] + }), + integrationAuthController.getIntegrationAuthGithubEnvironmentRepositories +); + router.get( "/:integrationAuthId/northflank/secret-groups", requireAuth({ diff --git a/backend/src/validation/integrationAuth.ts b/backend/src/validation/integrationAuth.ts index 3eb51d8332..89ed365a05 100644 --- a/backend/src/validation/integrationAuth.ts +++ b/backend/src/validation/integrationAuth.ts @@ -192,6 +192,12 @@ export const GetIntegrationAuthNorthflankSecretGroupsV1 = z.object({ }) }); +export const GetIntegrationAuthGitHubEnvironmentRepositoriesV1 = z.object({ + params: z.object({ + integrationAuthId: z.string().trim() + }) +}); + export const DeleteIntegrationAuthV1 = z.object({ params: z.object({ integrationAuthId: z.string().trim() From c89a9aa2548a5cfdff27ebc4ff98347fdab8ae8b Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:17:50 +0530 Subject: [PATCH 05/18] github-env: update spec.json for get repositories --- backend/spec.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/backend/spec.json b/backend/spec.json index 1f9c0a4983..e3d96def19 100644 --- a/backend/spec.json +++ b/backend/spec.json @@ -2554,6 +2554,26 @@ } } }, + "/api/v1/integration-auth/{integrationAuthId}/github-environment/repositories": { + "get": { + "description": "", + "parameters": [ + { + "name": "integrationAuthId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/api/v1/folders/": { "post": { "summary": "Create folder", From f0df577702e2d05a78ede8a69eb8dadadc7ad519 Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:18:35 +0530 Subject: [PATCH 06/18] github-env: update apps.ts --- backend/src/integrations/apps.ts | 61 ++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index 00196b3d0e..403545b53a 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -25,6 +25,7 @@ import { INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME, INTEGRATION_GCP_SERVICE_USAGE_URL, INTEGRATION_GITHUB, + INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_GITLAB_API_URL, INTEGRATION_HASURA_CLOUD, @@ -122,6 +123,12 @@ const getApps = async ({ accessToken }); break; + case INTEGRATION_GITHUB_ENVIRONMENT: + apps = await getAppsGithubEnvironment({ + accessToken, + workspaceSlug + }); + break; case INTEGRATION_GITLAB: apps = await getAppsGitlab({ integrationAuth, @@ -504,6 +511,60 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => { return apps; }; +/** + * Return list of repositories for Github integration + * @param {Object} obj + * @param {String} obj.accessToken - access token for Github API + * @param {String} obj.workspaceSlug - Workspace identifier for fetching BitBucket repositories + * @returns {Object[]} apps - names of Github sites + * @returns {String} apps.name - name of Github site + */ +const getAppsGithubEnvironment = async ({ + accessToken, + workspaceSlug +}: { + accessToken: string, + workspaceSlug?: string +}) => { + if (!workspaceSlug) { + return []; + } + const octokit = new Octokit({ + auth: accessToken + }); + + const { data } = await octokit.request('GET /user', { + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + const { login: username } = data; + try { + const {data} = await octokit.request('GET /repos/{owner}/{repo}/environments', { + owner: username, + repo: workspaceSlug, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + const {environments} = data; + let apps: any = []; + if(environments) { + apps = environments + .map((a: any) => { + return { + appId: String(a.id), + name: a.name + }; + }); + } + + return apps; + } catch(e) { + return [] + } +}; + /** * Return list of services for Render integration * @param {Object} obj From 2c82bb3f3ef49142a4e69d5c2a36e5839d31f77b Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:19:02 +0530 Subject: [PATCH 07/18] github-env: update sync.ts --- backend/src/integrations/sync.ts | 121 +++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 615e3114c8..5c43f80abd 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -31,6 +31,7 @@ import { INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GCP_SECRET_MANAGER_URL, INTEGRATION_GITHUB, + INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_GITLAB_API_URL, INTEGRATION_HASHICORP_VAULT, @@ -167,6 +168,14 @@ const syncSecrets = async ({ appendices }); break; + case INTEGRATION_GITHUB_ENVIRONMENT: + await syncSecretsGitHubEnvironment({ + integration, + secrets, + accessToken, + appendices + }); + break; case INTEGRATION_GITLAB: await syncSecretsGitLab({ integrationAuth, @@ -1472,6 +1481,118 @@ const syncSecretsGitHub = async ({ }); }; +/** + * Sync/push [secrets] to GitHub repo with name [integration.app] + * @param {Object} obj + * @param {IIntegration} obj.integration - integration details + * @param {IIntegrationAuth} obj.integrationAuth - integration auth details + * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) + * @param {String} obj.accessToken - access token for GitHub integration + */ +const syncSecretsGitHubEnvironment = async ({ + integration, + secrets, + accessToken, + appendices +}: { + integration: IIntegration; + secrets: Record; + accessToken: string; + appendices?: { prefix: string; suffix: string }; +}) => { + interface GitHubEnvKey { + key_id: string; + key: string; + } + interface GitHubSecret { + name: string; + created_at: string; + updated_at: string; + } + + interface GitHubSecretRes { + [index: string]: GitHubSecret; + } + + const octokit = new Octokit({ + auth: accessToken + }); + const repository_id = Number(integration.targetEnvironmentId) + const environment_name = integration.app + const envPublicKey: GitHubEnvKey = ( + await octokit.request('GET /repositories/{repository_id}/environments/{environment_name}/secrets/public-key', { + repository_id, + environment_name + }) + ).data + + // Get local copy of encrypted secrets + let encryptedSecrets: GitHubSecretRes = ( + await octokit.request('GET /repositories/{repository_id}/environments/{environment_name}/secrets', { + repository_id, + environment_name + })).data.secrets.reduce( + (obj: any, secret: any) => ({ + ...obj, + [secret.name]: secret + }), + {} + ) + + encryptedSecrets = Object.keys(encryptedSecrets).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] = encryptedSecrets[key]; + } + return result; + }, + {} + ); + + Object.keys(encryptedSecrets).map(async (key) => { + if (!(key in secrets)) { + await octokit.request('DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}', { + repository_id, + environment_name, + secret_name: key, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + } + }); + + Object.keys(secrets).map((key) => { + sodium.ready.then(async () => { + // convert secret & base64 key to Uint8Array. + const binkey = sodium.from_base64(envPublicKey.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); + + await octokit.request('PUT /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}', { + repository_id, + environment_name, + secret_name: key, + encrypted_value: encryptedSecret, + key_id: envPublicKey.key_id + }) + }); + }); +}; + /** * Sync/push [secrets] to Render service with id [integration.appId] * @param {Object} obj From eaba4f7e5ab7f29b1c12b1ea6635a0c46f4a5c94 Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:21:57 +0530 Subject: [PATCH 08/18] github-env: add hook for getting repositories --- .../src/hooks/api/integrationAuth/index.tsx | 1 + .../src/hooks/api/integrationAuth/queries.tsx | 18 ++++++++++++++++++ .../src/hooks/api/integrationAuth/types.ts | 11 +++++++++++ 3 files changed, 30 insertions(+) diff --git a/frontend/src/hooks/api/integrationAuth/index.tsx b/frontend/src/hooks/api/integrationAuth/index.tsx index f5065919fe..20c259788e 100644 --- a/frontend/src/hooks/api/integrationAuth/index.tsx +++ b/frontend/src/hooks/api/integrationAuth/index.tsx @@ -3,6 +3,7 @@ export { useDeleteIntegrationAuth, useGetIntegrationAuthApps, useGetIntegrationAuthBitBucketWorkspaces, + useGetIntegrationAuthGitHubRepositories, useGetIntegrationAuthById, useGetIntegrationAuthChecklyGroups, useGetIntegrationAuthNorthflankSecretGroups, diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index 7e5d3d7112..e2783bfd33 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -10,6 +10,7 @@ import { Environment, IntegrationAuth, NorthflankSecretGroup, + GithubRepository, Org, Project, Service, @@ -80,6 +81,8 @@ const integrationAuthKeys = { }) => [{ integrationAuthId, appId }, "integrationAuthRailwayServices"] as const, getIntegrationAuthBitBucketWorkspaces: (integrationAuthId: string) => [{ integrationAuthId }, "integrationAuthBitbucketWorkspaces"] as const, + getIntegrationAuthGitHubRepositories: (integrationAuthId: string) => + [{ integrationAuthId }, "integrationAuthGithubRepositories"] as const, getIntegrationAuthNorthflankSecretGroups: ({ integrationAuthId, appId @@ -339,6 +342,13 @@ const fetchIntegrationAuthBitBucketWorkspaces = async (integrationAuthId: string return workspaces; }; +const fetchIntegrationAuthGitHubRepositories = async (integrationAuthId: string) => { + const { data: { repos } } = await apiRequest.get<{ workspaces: GithubRepository[] }>( + `/api/v1/integration-auth/${integrationAuthId}/github-environment/repositories` + ); + return repos; +}; + const fetchIntegrationAuthNorthflankSecretGroups = async ({ integrationAuthId, appId @@ -587,6 +597,14 @@ export const useGetIntegrationAuthBitBucketWorkspaces = (integrationAuthId: stri }); }; +export const useGetIntegrationAuthGitHubRepositories = (integrationAuthId: string) => { + return useQuery({ + queryKey: integrationAuthKeys.getIntegrationAuthGitHubRepositories(integrationAuthId), + queryFn: () => fetchIntegrationAuthGitHubRepositories(integrationAuthId), + enabled: true + }); +}; + export const useGetIntegrationAuthNorthflankSecretGroups = ({ integrationAuthId, appId diff --git a/frontend/src/hooks/api/integrationAuth/types.ts b/frontend/src/hooks/api/integrationAuth/types.ts index 5af8a3773b..8bce5774a3 100644 --- a/frontend/src/hooks/api/integrationAuth/types.ts +++ b/frontend/src/hooks/api/integrationAuth/types.ts @@ -57,6 +57,17 @@ export type BitBucketWorkspace = { slug: string; } +export type GithubRepository = { + id: string; + name: string; + permissions: { + admin: boolean; + }; + owner: { + login: string; + }; +} + export type NorthflankSecretGroup = { name: string; groupId: string; From bf69061679ce445384eb8222c4595efa8b1f5379 Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:25:13 +0530 Subject: [PATCH 09/18] github-env: add oauth logic --- .../github-environment/oauth2/callback.tsx | 38 +++++++++++++++++++ .../IntegrationPage.utils.tsx | 3 ++ 2 files changed, 41 insertions(+) create mode 100644 frontend/src/pages/integrations/github-environment/oauth2/callback.tsx diff --git a/frontend/src/pages/integrations/github-environment/oauth2/callback.tsx b/frontend/src/pages/integrations/github-environment/oauth2/callback.tsx new file mode 100644 index 0000000000..253e80eafe --- /dev/null +++ b/frontend/src/pages/integrations/github-environment/oauth2/callback.tsx @@ -0,0 +1,38 @@ +import { useEffect } from "react"; +import { useRouter } from "next/router"; +import queryString from "query-string"; + +import { + useAuthorizeIntegration +} from "@app/hooks/api"; + +export default function GitHubEnvironmentOAuth2CallbackPage() { + const router = useRouter(); + const { mutateAsync } = useAuthorizeIntegration(); + + const { code, state } = queryString.parse(router.asPath.split("?")[1]); + + useEffect(() => { + (async () => { + try { + // validate state + if (state !== localStorage.getItem("latestCSRFToken")) return; + localStorage.removeItem("latestCSRFToken"); + + const integrationAuth = await mutateAsync({ + workspaceId: localStorage.getItem("projectData.id") as string, + code: code as string, + integration: "github-environment" + }); + + router.push(`/integrations/github-environment/create?integrationAuthId=${integrationAuth._id}`); + } catch (err) { + console.error(err); + } + })(); + }, []); + + return
; +} + +GitHubEnvironmentOAuth2CallbackPage.requireAuth = true; diff --git a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx index bb517950a1..4ee84dac2a 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx @@ -62,6 +62,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => 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}`; break; + case "github-environment": + link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/integrations/github-environment/oauth2/callback&state=${state}`; + break; case "gitlab": link = `${window.location.origin}/integrations/gitlab/authorize`; break; From 6179241ee1a4eb527b8194e49ae5856c94cb5bce Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:25:37 +0530 Subject: [PATCH 10/18] github-env: add frontend changes --- .../github-environment/create.tsx | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 frontend/src/pages/integrations/github-environment/create.tsx diff --git a/frontend/src/pages/integrations/github-environment/create.tsx b/frontend/src/pages/integrations/github-environment/create.tsx new file mode 100644 index 0000000000..22f16b2109 --- /dev/null +++ b/frontend/src/pages/integrations/github-environment/create.tsx @@ -0,0 +1,214 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import queryString from "query-string"; + +import { + useCreateIntegration +} from "@app/hooks/api"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "../../../components/v2"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthGitHubRepositories, + useGetIntegrationAuthById, +} from "../../../hooks/api/integrationAuth"; +import { useGetWorkspaceById } from "../../../hooks/api/workspace"; + +export default function GitHubEnvironmentCreateIntegrationPage() { + const router = useRouter(); + const { mutateAsync } = useCreateIntegration(); + + const [targetRepositoryId, setTargetRepositoryId] = useState(""); + const [targetRepositoryName, setTargetRepositoryName] = useState(""); + const [targetEnvironmentId, setTargetEnvironmentId] = useState(""); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [isLoading, setIsLoading] = useState(false); + + const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); + const { data: targetRepositories } = useGetIntegrationAuthGitHubRepositories((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "", + workspaceSlug: targetRepositories?.find(repo => repo.id === targetRepositoryId)?.name ?? "none" + }); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetEnvironmentId(integrationAuthApps[0].appId as string); + } else { + setTargetEnvironmentId("none"); + } + } + }, [integrationAuthApps]); + + // useEffect(() => { + // if (targetRepositories) { + // if (targetRepositories.length > 0) { + // setTargetRepositoryName(targetRepositories[0].name); + // } else { + // setTargetRepositoryName("none") + // } + // } + // }, [targetRepositoryId]); + + useEffect(() => { + if (targetRepositories) { + if (targetRepositories.length > 0) { + setTargetRepositoryId(targetRepositories[0].id); + setTargetRepositoryName(targetRepositories[0].name); + } else { + setTargetRepositoryId("none"); + setTargetRepositoryName("none") + } + } + }, [targetRepositories]); + + const handleButtonClick = async () => { + try { + setIsLoading(true); + + if (!integrationAuth?._id) return; + + // const targetApp = integrationAuthApps?.find( + // (integrationAuthApp) => integrationAuthApp.appId === targetAppId + // ); + const targetEnvironment = integrationAuthApps?.find( + (app) => app.appId === targetEnvironmentId + ); + + const targetRepository = targetRepositories?.find( + (repository) => repository.id === targetRepositoryId + ); + + + if (!targetRepository || !targetEnvironment) return; + await mutateAsync({ + integrationAuthId: integrationAuth?._id, + isActive: true, + app: targetEnvironment.name, + appId: String(targetEnvironment.appId), + sourceEnvironment: selectedSourceEnvironment, + targetEnvironment: targetRepository.name, + targetEnvironmentId: String(targetRepository.id), + secretPath + }); + + setIsLoading(false); + + router.push(`/integrations/${localStorage.getItem("projectData.id")}`); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetRepositories ? ( +
+ + GitHub Integration + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + + + + +
+ ) : ( +
+ ); +} + +GitHubEnvironmentCreateIntegrationPage.requireAuth = true; From b21eb46369ca72167800b6bdac9745a43caaf187 Mon Sep 17 00:00:00 2001 From: Shreyas Singh Date: Sun, 26 Nov 2023 16:36:57 +0530 Subject: [PATCH 11/18] github-env: update title --- frontend/src/pages/integrations/github-environment/create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/integrations/github-environment/create.tsx b/frontend/src/pages/integrations/github-environment/create.tsx index 22f16b2109..f7cfe1ea62 100644 --- a/frontend/src/pages/integrations/github-environment/create.tsx +++ b/frontend/src/pages/integrations/github-environment/create.tsx @@ -126,7 +126,7 @@ export default function GitHubEnvironmentCreateIntegrationPage() { targetRepositories ? (
- GitHub Integration + GitHub Environment Integration setTargetRepositoryId(val)} + className="w-full border border-mineshaft-500" + isDisabled={targetRepositories.length === 0} + > + {targetRepositories.length > 0 ? ( + targetRepositories.map((targetRepository) => ( + + {targetRepository.name} + + )) + ) : ( + + No Repositories found + + )} + + + + + + + ) + } + @@ -232,6 +355,23 @@ export default function GitHubCreateIntegrationPage() { animate={{ opacity: 1, translateX: 0 }} exit={{ opacity: 0, translateX: 30 }} > + + + +
)} - {((integration.integration === "checkly") || (integration.integration === "github")) && ( + {((integration.integration === "checkly")) && ( <> {integration.targetService && (
@@ -188,6 +188,24 @@ export const IntegrationsSection = ({
)} + {((integration.integration === "github" )) && ( + <> + {integration.targetEnvironment && ( +
+ +
+ {integration.targetEnvironment} +
+
+ )} +
+ +
+ {integration?.metadata?.secretSuffix || "-"} +
+
+ + )}
Date: Sat, 2 Dec 2023 14:23:39 +0530 Subject: [PATCH 16/18] gh-env-secrets: Remove definition and references for Github environments --- .env.example | 2 - backend/environment.d.ts | 2 - backend/src/config/index.ts | 4 - backend/src/integrations/exchange.ts | 41 ---- backend/src/models/integration/integration.ts | 3 - .../models/integrationAuth/integrationAuth.ts | 3 - backend/src/variables/integration.ts | 13 -- frontend/public/data/frequentConstants.ts | 1 - .../github-environment/create.tsx | 197 ------------------ .../github-environment/oauth2/callback.tsx | 38 ---- .../IntegrationPage.utils.tsx | 3 - 11 files changed, 307 deletions(-) delete mode 100644 frontend/src/pages/integrations/github-environment/create.tsx delete mode 100644 frontend/src/pages/integrations/github-environment/oauth2/callback.tsx diff --git a/.env.example b/.env.example index 0f5b095df6..6b8639a748 100644 --- a/.env.example +++ b/.env.example @@ -38,14 +38,12 @@ CLIENT_ID_HEROKU= CLIENT_ID_VERCEL= CLIENT_ID_NETLIFY= CLIENT_ID_GITHUB= -CLIENT_ID_GITHUB_ENVIRONMENT= CLIENT_ID_GITLAB= CLIENT_ID_BITBUCKET= CLIENT_SECRET_HEROKU= CLIENT_SECRET_VERCEL= CLIENT_SECRET_NETLIFY= CLIENT_SECRET_GITHUB= -CLIENT_SECRET_GITHUB_ENVIRONMENT= CLIENT_SECRET_GITLAB= CLIENT_SECRET_BITBUCKET= CLIENT_SLUG_VERCEL= diff --git a/backend/environment.d.ts b/backend/environment.d.ts index 1fd4eccee1..25f56dcc69 100644 --- a/backend/environment.d.ts +++ b/backend/environment.d.ts @@ -21,13 +21,11 @@ declare global { CLIENT_ID_VERCEL: string; CLIENT_ID_NETLIFY: string; CLIENT_ID_GITHUB: string; - CLIENT_ID_GITHUB_ENVIRONMENT: string; CLIENT_ID_GITLAB: string; CLIENT_SECRET_HEROKU: string; CLIENT_SECRET_VERCEL: string; CLIENT_SECRET_NETLIFY: string; CLIENT_SECRET_GITHUB: string; - CLIENT_SECRET_GITHUB_ENVIRONMENT: string; CLIENT_SECRET_GITLAB: string; CLIENT_SLUG_VERCEL: string; POSTHOG_HOST: string; diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index a956982689..a535c3f542 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -49,8 +49,6 @@ export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; -export const getClientIdGitHubEnvironment = async () => - (await client.getSecret("CLIENT_ID_GITHUB_ENVIRONMENT")).secretValue; export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; export const getClientIdBitBucket = async () => @@ -67,8 +65,6 @@ export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; -export const getClientSecretGitHubEnvironment = async () => - (await client.getSecret("CLIENT_SECRET_GITHUB_ENVIRONMENT")).secretValue; export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; export const getClientSecretBitBucket = async () => diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index c69e6a7f56..37382e79e3 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -7,7 +7,6 @@ import { INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GCP_TOKEN_URL, INTEGRATION_GITHUB, - INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITHUB_TOKEN_URL, INTEGRATION_GITLAB, INTEGRATION_GITLAB_TOKEN_URL, @@ -23,7 +22,6 @@ import { getClientIdBitBucket, getClientIdGCPSecretManager, getClientIdGitHub, - getClientIdGitHubEnvironment, getClientIdGitLab, getClientIdNetlify, getClientIdVercel, @@ -31,7 +29,6 @@ import { getClientSecretBitBucket, getClientSecretGCPSecretManager, getClientSecretGitHub, - getClientSecretGitHubEnvironment, getClientSecretGitLab, getClientSecretHeroku, getClientSecretNetlify, @@ -160,11 +157,6 @@ const exchangeCode = async ({ code, }); break; - case INTEGRATION_GITHUB_ENVIRONMENT: - obj = await exchangeCodeGithubEnvironment({ - code, - }); - break; case INTEGRATION_GITLAB: obj = await exchangeCodeGitlab({ code, @@ -389,39 +381,6 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => { }; }; -/** - * Return [accessToken], [accessExpiresAt], and [refreshToken] for Github Environments - * code-token exchange - * @param {Object} obj1 - * @param {Object} obj1.code - code for code-token exchange - * @returns {Object} obj2 - * @returns {String} obj2.accessToken - access token for Github Environments API - * @returns {String} obj2.refreshToken - refresh token for Github Environments API - * @returns {Date} obj2.accessExpiresAt - date of expiration for access token - */ -const exchangeCodeGithubEnvironment = async ({ code }: { code: string }) => { - const res: ExchangeCodeGithubResponse = ( - await standardRequest.get(INTEGRATION_GITHUB_TOKEN_URL, { - params: { - client_id: await getClientIdGitHubEnvironment(), - client_secret: await getClientSecretGitHubEnvironment(), - code: code, - redirect_uri: `${await getSiteURL()}/integrations/github-environment/oauth2/callback`, - }, - headers: { - Accept: "application/json", - "Accept-Encoding": "application/json", - }, - }) - ).data; - - return { - accessToken: res.access_token, - refreshToken: null, - accessExpiresAt: null, - }; -}; - /** * Return [accessToken], [accessExpiresAt], and [refreshToken] for Gitlab * code-token exchange diff --git a/backend/src/models/integration/integration.ts b/backend/src/models/integration/integration.ts index b4f16ba1f2..eaaadec92b 100644 --- a/backend/src/models/integration/integration.ts +++ b/backend/src/models/integration/integration.ts @@ -13,7 +13,6 @@ import { INTEGRATION_FLYIO, INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GITHUB, - INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASURA_CLOUD, @@ -59,7 +58,6 @@ export interface IIntegration { | "vercel" | "netlify" | "github" - | "github-environment" | "gitlab" | "render" | "railway" @@ -169,7 +167,6 @@ const integrationSchema = new Schema( INTEGRATION_VERCEL, INTEGRATION_NETLIFY, INTEGRATION_GITHUB, - INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_RENDER, INTEGRATION_RAILWAY, diff --git a/backend/src/models/integrationAuth/integrationAuth.ts b/backend/src/models/integrationAuth/integrationAuth.ts index 10442c731b..da1e572689 100644 --- a/backend/src/models/integrationAuth/integrationAuth.ts +++ b/backend/src/models/integrationAuth/integrationAuth.ts @@ -15,7 +15,6 @@ import { INTEGRATION_FLYIO, INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GITHUB, - INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASURA_CLOUD, @@ -43,7 +42,6 @@ export interface IIntegrationAuth extends Document { | "vercel" | "netlify" | "github" - | "github-environment" | "gitlab" | "render" | "railway" @@ -105,7 +103,6 @@ const integrationAuthSchema = new Schema( INTEGRATION_VERCEL, INTEGRATION_NETLIFY, INTEGRATION_GITHUB, - INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_RENDER, INTEGRATION_RAILWAY, diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index f9f41f168c..28848dbb69 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -3,7 +3,6 @@ import { getClientIdBitBucket, getClientIdGCPSecretManager, getClientIdGitHub, - getClientIdGitHubEnvironment, getClientIdGitLab, getClientIdHeroku, getClientIdNetlify, @@ -19,7 +18,6 @@ export const INTEGRATION_HEROKU = "heroku"; export const INTEGRATION_VERCEL = "vercel"; export const INTEGRATION_NETLIFY = "netlify"; export const INTEGRATION_GITHUB = "github"; -export const INTEGRATION_GITHUB_ENVIRONMENT = "github-environment"; export const INTEGRATION_GITLAB = "gitlab"; export const INTEGRATION_RENDER = "render"; export const INTEGRATION_RAILWAY = "railway"; @@ -49,7 +47,6 @@ export const INTEGRATION_SET = new Set([ INTEGRATION_VERCEL, INTEGRATION_NETLIFY, INTEGRATION_GITHUB, - INTEGRATION_GITHUB_ENVIRONMENT, INTEGRATION_GITLAB, INTEGRATION_RENDER, INTEGRATION_FLYIO, @@ -93,7 +90,6 @@ export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com"; export const GITLAB_URL = "https://gitlab.com"; export const INTEGRATION_GITLAB_API_URL = `${GITLAB_URL}/api`; export const INTEGRATION_GITHUB_API_URL = "https://api.github.com"; -export const INTEGRATION_GITHUB_ENVIRONMENT_API_URL = "https://api.github.com"; export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com"; export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com"; export const INTEGRATION_RENDER_API_URL = "https://api.render.com"; @@ -161,15 +157,6 @@ export const getIntegrationOptions = async () => { clientId: await getClientIdGitHub(), docsLink: "" }, - { - name: "GitHub Environment", - slug: "github-environment", - image: "GitHub.png", - isAvailable: true, - type: "oauth", - clientId: await getClientIdGitHubEnvironment(), - docsLink: "" - }, { name: "Render", slug: "render", diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index 58bf95f6fa..451890ef98 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -10,7 +10,6 @@ const integrationSlugNameMapping: Mapping = { vercel: "Vercel", netlify: "Netlify", github: "GitHub", - "github-environment": "GitHub Environment", gitlab: "GitLab", render: "Render", "laravel-forge": "Laravel Forge", diff --git a/frontend/src/pages/integrations/github-environment/create.tsx b/frontend/src/pages/integrations/github-environment/create.tsx deleted file mode 100644 index 391362a8a8..0000000000 --- a/frontend/src/pages/integrations/github-environment/create.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { useEffect, useState } from "react"; -import { useRouter } from "next/router"; -import queryString from "query-string"; - -import { - useCreateIntegration -} from "@app/hooks/api"; - -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem -} from "../../../components/v2"; -import { - useGetIntegrationAuthApps, - useGetIntegrationAuthById, - useGetIntegrationAuthGitHubRepositories, -} from "../../../hooks/api/integrationAuth"; -import { useGetWorkspaceById } from "../../../hooks/api/workspace"; - -export default function GitHubEnvironmentCreateIntegrationPage() { - const router = useRouter(); - const { mutateAsync } = useCreateIntegration(); - - const [targetRepositoryId, setTargetRepositoryId] = useState(""); - const [targetEnvironmentId, setTargetEnvironmentId] = useState(""); - - const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); - const [secretPath, setSecretPath] = useState("/"); - const [isLoading, setIsLoading] = useState(false); - - const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); - const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); - const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); - const { data: targetRepositories } = useGetIntegrationAuthGitHubRepositories((integrationAuthId as string) ?? ""); - const { data: integrationAuthApps } = useGetIntegrationAuthApps({ - integrationAuthId: (integrationAuthId as string) ?? "", - workspaceSlug: targetRepositories?.find(repo => repo.id === targetRepositoryId)?.name ?? "none" - }); - - useEffect(() => { - if (workspace) { - setSelectedSourceEnvironment(workspace.environments[0].slug); - } - }, [workspace]); - - useEffect(() => { - if (integrationAuthApps) { - if (integrationAuthApps.length > 0) { - setTargetEnvironmentId(integrationAuthApps[0].appId as string); - } else { - setTargetEnvironmentId("none"); - } - } - }, [integrationAuthApps]); - - useEffect(() => { - if (targetRepositories) { - if (targetRepositories.length > 0) { - setTargetRepositoryId(targetRepositories[0].id); - } else { - setTargetRepositoryId("none"); - } - } - }, [targetRepositories]); - - const handleButtonClick = async () => { - try { - setIsLoading(true); - - if (!integrationAuth?._id) return; - - const targetEnvironment = integrationAuthApps?.find( - (app) => app.appId === targetEnvironmentId - ); - - const targetRepository = targetRepositories?.find( - (repository) => repository.id === targetRepositoryId - ); - - if (!targetRepository || !targetEnvironment) return; - await mutateAsync({ - integrationAuthId: integrationAuth?._id, - isActive: true, - app: targetEnvironment.name, - appId: String(targetEnvironment.appId), - sourceEnvironment: selectedSourceEnvironment, - targetEnvironment: targetRepository.name, - targetEnvironmentId: String(targetRepository.id), - secretPath - }); - - setIsLoading(false); - - router.push(`/integrations/${localStorage.getItem("projectData.id")}`); - } catch (err) { - console.error(err); - } - }; - - return integrationAuth && - workspace && - selectedSourceEnvironment && - integrationAuthApps && - targetRepositories ? ( -
- - GitHub Environment Integration - - - - - setSecretPath(evt.target.value)} - placeholder="Provide a path, default is /" - /> - - - - - - - - - -
- ) : ( -
- ); -} - -GitHubEnvironmentCreateIntegrationPage.requireAuth = true; diff --git a/frontend/src/pages/integrations/github-environment/oauth2/callback.tsx b/frontend/src/pages/integrations/github-environment/oauth2/callback.tsx deleted file mode 100644 index 253e80eafe..0000000000 --- a/frontend/src/pages/integrations/github-environment/oauth2/callback.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect } from "react"; -import { useRouter } from "next/router"; -import queryString from "query-string"; - -import { - useAuthorizeIntegration -} from "@app/hooks/api"; - -export default function GitHubEnvironmentOAuth2CallbackPage() { - const router = useRouter(); - const { mutateAsync } = useAuthorizeIntegration(); - - const { code, state } = queryString.parse(router.asPath.split("?")[1]); - - useEffect(() => { - (async () => { - try { - // validate state - if (state !== localStorage.getItem("latestCSRFToken")) return; - localStorage.removeItem("latestCSRFToken"); - - const integrationAuth = await mutateAsync({ - workspaceId: localStorage.getItem("projectData.id") as string, - code: code as string, - integration: "github-environment" - }); - - router.push(`/integrations/github-environment/create?integrationAuthId=${integrationAuth._id}`); - } catch (err) { - console.error(err); - } - })(); - }, []); - - return
; -} - -GitHubEnvironmentOAuth2CallbackPage.requireAuth = true; diff --git a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx index 4ee84dac2a..bb517950a1 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx @@ -62,9 +62,6 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => 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}`; break; - case "github-environment": - link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/integrations/github-environment/oauth2/callback&state=${state}`; - break; case "gitlab": link = `${window.location.origin}/integrations/gitlab/authorize`; break; From 549a549d178efeeb9575aaa873924374d6994393 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Sat, 2 Dec 2023 14:59:30 +0530 Subject: [PATCH 17/18] gh-env-secrets: update type --- backend/src/integrations/apps.ts | 2 +- frontend/src/pages/integrations/github/create.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index 0773bc24d9..372caec036 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -465,7 +465,7 @@ const getAppsGithub = async ({ accessToken, workspaceSlug }: { accessToken: stri auth: accessToken }); - if(workspaceSlug) { + if(workspaceSlug && workspaceSlug !== "none") { // get github environments if workflowSlug is set const { data } = await octokit.request("GET /user", { headers: { diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index 1845a335bc..21c509af6d 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -61,7 +61,7 @@ export default function GitHubCreateIntegrationPage() { const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); const { data: targetRepositories } = useGetIntegrationAuthGitHubRepositories((integrationAuthId as string) ?? ""); - const [workspaceSlug, setWorkspaceSlug] = useState(); + const [workspaceSlug, setWorkspaceSlug] = useState("none"); const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "", workspaceSlug @@ -113,7 +113,7 @@ export default function GitHubCreateIntegrationPage() { useEffect(() => { if(githubIntegration === "github-repo-secrets") { - setWorkspaceSlug(null) + setWorkspaceSlug("none") } else { setWorkspaceSlug(targetRepositories?.find(repo => repo.id === targetRepositoryId)?.name ?? "none") } @@ -129,7 +129,7 @@ export default function GitHubCreateIntegrationPage() { (integrationAuthApp) => targetAppIds.includes(String(integrationAuthApp.appId)) ); - if(githubIntegration === "github-repo-secrets") { + if(githubIntegration === "github-repo-secrets" && targetApps) { await Promise.all( targetApps.map(async (targetApp) => { await mutateAsync({ From c1c706097e3d089431af25914330896398be02db Mon Sep 17 00:00:00 2001 From: Shreyas Date: Wed, 10 Jan 2024 20:16:33 +0530 Subject: [PATCH 18/18] Update comment --- backend/src/integrations/apps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index 372caec036..633a7cb585 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -445,7 +445,7 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => { * Return list of repositories for Github integration * @param {Object} obj * @param {String} obj.accessToken - access token for Github API - * @param {String} obj.workspaceSlug - Workspace identifier for fetching BitBucket repositories + * @param {String} obj.workspaceSlug - Workspace identifier for fetching Github repositories * @returns {Object[]} apps - names of Github sites * @returns {String} apps.name - name of Github site */